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();
}
});
}
}`