《Java 2D游戏编程入门》—— 2.4 相对鼠标移动

2.4 相对鼠标移动

对于图2.3所示的当前的鼠标输入类来说,有一个问题。首先,它看上去似乎挺明显,但是,只有在鼠标位于窗口之中的时候,程序才接受鼠标事件。一旦鼠标离开了窗口,鼠标指针的坐标位置在应用程序中就变得不再有效。更为糟糕的是,在全屏模式中,当鼠标到达屏幕边缘的时候,它会直接停下来,而不会继续注册事件。根据应用程序的需要,可能需要相对鼠标移动。好在,更新鼠标输入类以支持相对鼠标移动并不难。

RelativeMouseInput类位于javagames.util包中,构建于前面示例中的类的基础之上,并且添加了相对鼠标移动。为了实现这一点,用Robot类来将鼠标保持在窗口的中央。还有监听鼠标事件的Swing组件,可以计算窗口的中央位置,并且从相对窗口坐标转换为绝对屏幕坐标。如果鼠标光标总是位于窗口的中央,那么,它可能不会离开,并且窗口将总是接受鼠标事件。如下代码保持鼠标居中:

// RelativeMouseInput.java
private Point getComponentCenter() {
  int w = component.getWidth();
  int h = component.getHeight();
  return new Point( w / 2, h / 2 );
}
private void centerMouse() {
  if( robot != null && component.isShowing() ) {
    Point center = getComponentCenter();
    SwingUtilities.convertPointToScreen( center, component );
    robot.mouseMove( center.x, center.y );
  }
}```
只有在运行时才会计算窗口的中央位置,因此即使窗口改变了大小,鼠标将仍然位于中央。相对中央位置必须转换为绝对屏幕坐标。不管窗口位于桌面上的何处,窗口左上角的像素都是(0,0)。如果在桌面上移动窗口会改变左上角像素的值,那么,图形化编程将会变得非常困难。尽管使用相对像素值会使得绘制较为容易,但把鼠标定位到窗口的中央并没有考虑窗口的位置,也没有考虑到将鼠标光标放置到距离窗口很远时,它会停止接受鼠标事件。使用SwingUtilities类转换得到屏幕坐标,从而解决这一问题。

要意识到将鼠标重新居中很重要,因为当鼠标的新位置和当前位置相同时,要求Robot类重新把鼠标定位到相同的位置,而这并不会产生新的鼠标事件。如果这种行为有变化的话,相对鼠标类将总是产生鼠标事件,即便鼠标并没有移动。即便鼠标行为将来不发生变化,在请求重新居中之前,也应先检查当前位置和新的位置是否不同,这种做法将会解决这个问题。

给RelativeMouseInput类添加一个标志,以允许相对和绝对鼠标移动。一款游戏在某些时候可能既需要绝对鼠标模式,也需要相对鼠标模式,因此,这个类允许在运行时切换。mouseMoved()方法添加了新的代码。如果是在相对模式中,距离将计算为与中心点之间的差距,然后让鼠标光标重新居中。由于鼠标坐标和组件坐标都是相对值,因此不需要转换这些值。最后,在轮询方法的过程中,鼠标位置可以是相对的或绝对的。在轮询方法中,delta变量和所有其他变量一起重新设置。

package javagames.util;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class RelativeMouseInput
implements MouseListener, MouseMotionListener, MouseWheelListener {
  private static final int BUTTON_COUNT = 3;
  private Point mousePos;
  private Point currentPos;
  private boolean[] mouse;
  private int[] polled;
  private int notches;
  private int polledNotches;
  private int dx, dy;
  private Robot robot;
  private Component component;
  private boolean relative;
  public RelativeMouseInput( Component component ) {
    this.component = component;
    try {
      robot = new Robot();
    } catch( Exception e ) {
      // Handle exception [game specific]
      e.printStackTrace();
    }
    mousePos = new Point( 0, 0 );
    currentPos = new Point( 0, 0 );
    mouse = new boolean[ BUTTON_COUNT ];
    polled = new int[ BUTTON_COUNT ];
  }
  public synchronized void poll() {
    if( isRelative() ) {
      mousePos = new Point( dx, dy );
    } else {
      mousePos = new Point( currentPos );
    }
    dx = dy = 0;
    polledNotches = notches;
    notches = 0;
    for( int i = 0; i < mouse.length; ++i ) {
      if( mouse[i] ) {
        polled[i]++;
      } else {
        polled[i] = 0;
      }
    }
  }
  public boolean isRelative() {
    return relative;
  }
  public void setRelative( boolean relative ) {
    this.relative = relative;
    if( relative ) {
      centerMouse();
    }
  }
  public Point getPosition() {
    return mousePos;
  }
  public int getNotches() {
    return polledNotches;
  }
  public boolean buttonDown( int button ) {
    return polled[ button - 1 ] > 0;
  }
  public boolean buttonDownOnce( int button ) {
    return polled[ button - 1 ] == 1;
  }
  public synchronized void mousePressed( MouseEvent e ) {
    int button = e.getButton() - 1;
    if( button >= 0 && button < mouse.length ) {
      mouse[ button ] = true;
    }
  }
  public synchronized void mouseReleased( MouseEvent e ) {
    int button = e.getButton() - 1;
    if( button >= 0 && button < mouse.length ) {
      mouse[ button ] = false;
    }
  }
  public void mouseClicked( MouseEvent e ) {
    // Not needed
  }
  public synchronized void mouseEntered( MouseEvent e ) {
    mouseMoved( e );
  }
  public synchronized void mouseExited( MouseEvent e ) {
    mouseMoved( e );
  }
  public synchronized void mouseDragged( MouseEvent e ) {
    mouseMoved( e );
  }
  public synchronized void mouseMoved( MouseEvent e ) {
    if( isRelative() ) {
      Point p = e.getPoint();
      Point center = getComponentCenter();
      dx += p.x - center.x;
      dy += p.y - center.y;
      centerMouse();
    } else {
      currentPos = e.getPoint();
    }
  }
  public synchronized void mouseWheelMoved( MouseWheelEvent e ) {
    notches += e.getWheelRotation();
  }
  private Point getComponentCenter() {
    int w = component.getWidth();
    int h = component.getHeight();
    return new Point( w / 2, h / 2 );
  }
  private void centerMouse() {
    if( robot != null && component.isShowing() ) {
      Point center = getComponentCenter();
      SwingUtilities.convertPointToScreen( center, component );
      robot.mouseMove( center.x, center.y );
    }
  }
}`
RelativeMouseExample位于javagames.input包中,如图2.4所示,它针对鼠标输入类测试更新的新功能。

如下的代码,通过将光标图像设置为在运行时创建的一个空的光标,从而关闭鼠标光标。

// RelativeMouseExample .java
private void disableCursor() {
  Toolkit tk = Toolkit.getDefaultToolkit();
  Image image = tk.createImage( "" );
  Point point = new Point( 0, 0 );
  String name = "CanBeAnything";
  Cursor cursor = tk.createCustomCursor( image, point, name );
  setCursor( cursor );
}```
这个示例的render()方法显示了帮助文本,计算了帧速率,并且在屏幕上绘制了矩形。在processInput()方法内部,空格将鼠标模式从绝对模式切换为相对模式。C键用来显示或隐藏鼠标光标。在当前的鼠标位置用来更新方块位置的时候,相对值添加到了该位置,而绝对值替代了之前的值。这段代码确保了方框不会离开屏幕,如果它在任何一个方向走得太远的话,都会折返其位置。

package javagames.input;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javagames.util.*;
import javax.swing.*;

public class RelativeMouseExample extends JFrame
          implements Runnable {
  private FrameRate frameRate;
  private BufferStrategy bs;
  private volatile boolean running;
  private Thread gameThread;
  private Canvas canvas;
  private RelativeMouseInput mouse;
  private KeyboardInput keyboard;
  private Point point = new Point( 0, 0 );
  private boolean disableCursor = false;
  public RelativeMouseExample() {
    frameRate = new FrameRate();
  }
  protected void createAndShowGUI() {
    canvas = new Canvas();
    canvas.setSize( 640, 480 );
    canvas.setBackground( Color.BLACK );
    canvas.setIgnoreRepaint( true );
    getContentPane().add( canvas );
    setTitle( "Relative Mouse Example" );
    setIgnoreRepaint( true );
    pack();
    // Add key listeners
    keyboard = new KeyboardInput();
    canvas.addKeyListener( keyboard );
    // Add mouse listeners
    // For full screen : mouse = new RelativeMouseInput( this );
    mouse = new RelativeMouseInput( canvas );
    canvas.addMouseListener( mouse );
    canvas.addMouseMotionListener( mouse );
    canvas.addMouseWheelListener( mouse );
    setVisible( true );
    canvas.createBufferStrategy( 2 );
    bs = canvas.getBufferStrategy();
    canvas.requestFocus();
    gameThread = new Thread( this );
    gameThread.start();
  }
  public void run() {
    running = true;
    frameRate.initialize();
    while( running ) {
      gameLoop();
    }
  }
  private void gameLoop() {
    processInput();
    renderFrame();
    sleep( 10L );
  }
  private void renderFrame() {
    do {
      do {
        Graphics g = null;
        try {
          g = bs.getDrawGraphics();
          g.clearRect( 0, 0, getWidth(), getHeight() );
          render( g );
        } finally {
          if( g != null ) {
            g.dispose();
          }
        }
      } while( bs.contentsRestored() );
      bs.show();
    } while( bs.contentsLost() );
  }
  private void sleep( long sleep ) {
    try {
      Thread.sleep( sleep );
    } catch( InterruptedException ex ) { }
  }
  private void processInput() {
    keyboard.poll();
    mouse.poll();
    Point p = mouse.getPosition();
    if( mouse.isRelative() ) {
      point.x += p.x;
      point.y += p.y;
    } else {
      point.x = p.x;
      point.y = p.y;
    }
    // Wrap rectangle around the screen
    if( point.x + 25 < 0 )
      point.x = canvas.getWidth() - 1;
    else if( point.x > canvas.getWidth() - 1 )
      point.x = -25;
    if( point.y + 25 < 0 )
      point.y = canvas.getHeight() - 1;
    else if( point.y > canvas.getHeight() - 1 )
      point.y = -25;
    // Toggle relative
    if( keyboard.keyDownOnce( KeyEvent.VK_SPACE ) ) {
      mouse.setRelative( !mouse.isRelative() );
    }
    // Toggle cursor
    if( keyboard.keyDownOnce( KeyEvent.VK_C ) ) {
      disableCursor = !disableCursor;
      if( disableCursor ) {
        disableCursor();
      } else {
        // setCoursor( Cursor.DEFAULT_CURSOR ) is deprecated
        setCursor( new Cursor( Cursor.DEFAULT_CURSOR ) );
      }
    }
  }
  private void render( Graphics g ) {
    g.setColor( Color.GREEN );
    frameRate.calculate();
    g.drawString( mouse.getPosition().toString(), 20, 20 );
    g.drawString( "Relative: " + mouse.isRelative(), 20, 35 );
    g.drawString( "Press Space to switch mouse modes", 20, 50 );
    g.drawString( "Press C to toggle cursor", 20, 65 );
    g.setColor( Color.WHITE );
    g.drawRect( point.x, point.y, 25, 25 );
  }
  private void disableCursor() {
    Toolkit tk = Toolkit.getDefaultToolkit();
    Image image = tk.createImage( "" );
    Point point = new Point( 0, 0 );
    String name = "CanBeAnything";
    Cursor cursor = tk.createCustomCursor( image, point, name );
    setCursor( cursor );
  }
  protected void onWindowClosing() {
    try {
      running = false;
      gameThread.join();
    } catch( InterruptedException e ) {
      e.printStackTrace();
    }
    System.exit( 0 );
  }
  public static void main( String[] args ) {
    final RelativeMouseExample app = new RelativeMouseExample();
    app.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent e ) {
        app.onWindowClosing();
      }
    });
    SwingUtilities.invokeLater( new Runnable() {
      public void run() {
        app.createAndShowGUI();
      }
    });
  }
}`

时间: 2025-01-01 01:11:54

《Java 2D游戏编程入门》—— 2.4 相对鼠标移动的相关文章

《Java 2D游戏编程入门》—— 导读

前言 多年前,当我第一次将软件开发作为专业工作的时候,有人请我编写一个applet.那时候,我对于Java语言知道得并不多.在整个上学期间,我很广泛地使用C++.我确实用Java编写过一些代码,但认为它太慢并且是C++的没落版. 同时,我购买和阅读了很多可以接触到的游戏编程图书.我通读了一本关于人工智能的书,其中包含很多不错的示例,但它们都是用C++和DirectX编写的.由于忙着学习Java以便在工作中使用,我认为将示例转换为由Java编写可能是学习这门语言的一种好办法. 毕竟,Java游戏编

《Java 2D游戏编程入门》—— 2.3 处理鼠标输入

2.3 处理鼠标输入 SimpleMouseInput类位于javagames.util包中,它和前面小节中开发的键盘输入类非常相似.处理鼠标按键的方式与处理键盘按键的方式相同.实现MouseListener接口的类包含了如下的方法: mouseClicked(MouseEvent e) mouseEntered(MouseEvent e) mouseExited(MouseEvent e) mousePressed(MouseEvent e) mouseReleased(MouseEvent

《Java 2D游戏编程入门》—— 8.1 创建一个多边形包装类

8.1 创建一个多边形包装类 制作一款2D太空飞船游戏时,首先要解决的大问题是,当飞船到达屏幕边缘时,会发生什么事情.一个解决方案是,将飞船保持在屏幕的中央,而移动其周围的环境.没有对加载文件的任何支持,也没有关卡编辑器的话,要做到这点似乎有点难.另一个选择是,当飞船到达屏幕边界的时候,将其弹回.这样做似乎显得很奇怪.第三种选择是,让飞船从一端到另一端折返.让我们采用第三种方法. 还有很多方法可以解决这一问题.一种方法是,让飞船完全离开屏幕,然后让其在另一端返回,如图8.1所示. 尝试这一想法似

《Java 2D游戏编程入门》—— 1.6 修改显示模式

1.6 修改显示模式 要创建全屏的应用程序,需要修改显示模式.DisplayModeTest如图1.4所示,它使用了Swing组件.这个示例也位于javagames.render包中.如果你还没有编写过任何的Swing程序,一些代码可能会看上去很陌生.有很多相关的图书和教程可供参考,并且如果要详细介绍Swing所提供的所有功能,需要比这本书更大的篇幅. 这个示例应用程序列出了所有可用的屏幕分辨率,这允许用户在全屏模式之间来回切换,以使用任何可用的显示模式.这个示例不仅展示了使用Swing组件编程

《Java 2D游戏编程入门》—— 2.1 处理键盘输入

2.1 处理键盘输入 在大多数应用程序中,软件都不需要处理键盘事件.当某些事情发生变化的时候,由任意的组件(如文本框)来处理输入并通知软件.但是,大多数计算机游戏使用键盘不是为了录入,而是为了游戏输入.根据游戏的不同,虽然可能会有录入,但键盘按键常用做方向键和发射激光武器.很多计算机游戏具有不同的输入配置,并且有些游戏甚至允许用户根据自己的意愿来设置按键.不管游戏如何使用键盘(并且它可能采用一种全新的方式来使用键盘,监听键盘事件的常用方法都不适用于游戏循环程序设计. Swing组件允许在实现了K

《Java 2D游戏编程入门》—— 1.7 全屏显示模式中的主动渲染

1.7 全屏显示模式中的主动渲染 位于javagames.render包中的FullScreenRenderingExample,包含了主动渲染框架和切换到全拼模式的显示模式代码:它创建了一个简单的全屏游戏框架.这个示例包含了前面各部分中展示的很多代码.此外还可以直接给JFrame设置背景颜色并且忽略重绘,以及设置setUndecorated()标志.由于在前面的示例中应用程序是从窗口模式切换到全屏模式的,因此没有设置该标志:但是当只使用全屏模式的时候,应该对JFrame进行该项设置. 保存当前

《Java 2D游戏编程入门》—— 8.5 原型Bullet类

8.5 原型Bullet类 PrototypeBullet代码位于javagames.prototype包中,这是一个最简单的原型游戏源代码.除了绘制圆以便可以调整屏幕大小外,没有其他任何值得讨论的内容. package javagames.prototype; import java.awt.*; import javagames.util.*; public class PrototypeBullet { private Vector2f velocity; private Vector2f

《Java 2D游戏编程入门》—— 8.7 编写原型游戏

8.7 编写原型游戏 原型游戏如图8.12所示,位于javagames.prototype包中,它使用了我们目前为止所见过的所有技术.尽管这只是一个原型,并且目前还没有成为一款完整的游戏,但我已经展示了足够的工具来让一些功能奏效.如果要等到最后再制作一款游戏,可能需要等太长的时间. 该原型游戏使用了我们在本章前面所介绍的如下的类. PolygonWrapper PrototypeShip PrototypeAsteroid PrototypeAsteroidFactory PrototypeBu

《Java 2D游戏编程入门》—— 2.2 键盘改进

2.2 键盘改进 尽管键盘输入类允许在游戏循环中访问键盘状态,但是实现起来还是有一些问题.首先,游戏循环代码执行的时候,如果键盘按键没有按下,将会错过keypress事件.尽管对于这些简单的示例来说,不太可能发生这种情况,但当应用程序变得更强大并且游戏循环需要更多的时间来处理代码时,游戏循环就可能变得太慢而导致错过了输入.现在,只需要知道这可能是一个问题就行了.我们将会在第11章中讨论确保事件不会被错过的一种解决方案. 第二个问题是,当按键第一次被按下的时候,很难进行测试.如果需要跟踪20个按键