java 游戏2d视野_《Java 2D游戏编程入门》—— 2.4 相对鼠标移动

本节书摘来异步社区《Java 2D游戏编程入门》一书中的第2章,第2.4节,作者:【美】Timothy Wright(莱特),更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.4 相对鼠标移动

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

b1525f82c0ae88489ee7c8609a3efb49.png

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所示,它针对鼠标输入类测试更新的新功能。

c317cfc222d60173a565314bcf5661b2.png

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

// 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();

}

});

}

}`

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值