本节书摘来异步社区《Java 2D游戏编程入门》一书中的第2章,第2.4节,作者:【美】Timothy Wright(莱特),更多章节内容可以访问云栖社区“异步社区”公众号查看。
对于图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 RelativeMouseInputimplements 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(); } }); }}`
相关资源:Java 游戏编程开发教程中文高清完整版PDF