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

2.3 处理鼠标输入

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

mouseClicked(MouseEvent e)
mouseEntered(MouseEvent e)
mouseExited(MouseEvent e)
mousePressed(MouseEvent e)
mouseReleased(MouseEvent e)```
这个接口中的两个方法,mouseEntered()和mouseExited(),负责处理鼠标光标移动。另外3个方法用于鼠标按键。就像键盘监听器一样,按下和释放方法会记录鼠标按钮的状态,而点击方法则会被忽略。为了确定按下了哪个鼠标按键,鼠标事件包含了如下的方法:

public int MouseEvent.getButton()`
这个方法所返回的值,映射到如下的常量:

MouseEvent.NOBUTTON = 0
MouseEvent.BUTTON1 = 1
MouseEvent.BUTTON2 = 2
MouseEvent.BUTTON3 = 3```
按键的编号是从1开始而不是从0开始的,0表示没有按键,在引用鼠标按键数组的时候,不需要将按键编号减1。除了这一点小小的差别,鼠标按键和键盘按键的处理方式相同,也包括buttonDown()和buttonDownOnce()方法。

还有其他的鼠标状态可供程序使用,例如鼠标指针的位置和状态。和MouseListener接口的mouseEntered()和mouseExited()方法一样,鼠标输入类也实现了MouseMotionListener接口。

mouseDragged(MouseEvent e)
mouseMoved(MouseEvent e)`
针对鼠标事件,如果鼠标进入或离开了组件监听,所有这4个方法(进入、退出、拖拽和移动)都会捕获鼠标的位置并通知程序。如下的方法会获取鼠标的当前位置:

public Point MouseEvent.getPoint()```
当轮询鼠标输入时,会复制这个值并使其可供游戏循环使用。当前的鼠标位置,对于如下的方法来说是可用的:

public Point SimpleMouseInput.getPosition()`
最后,为了监控鼠标滚轮的输入,输入类实现了MouseWheelListener:

mouseWheelMoved(MouseEvent e)```
如下的方法返回了鼠标滚轮的点击。如果这个数值是负值,表示滚轮已经从用户那里移走了。如果这个值是正的,表示滚轮已经朝着用户移动了。

public int MouseWheelEvent.getWheelRotation()`
这个值也保存在poll()方法中,并且可通过如下方法供游戏循环使用:

public int SimpleMouseInput.getNotches()```
SimpleMouseInput类的代码如下:

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

public class SimpleMouseInput
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;
  public SimpleMouseInput() {
    mousePos = new Point( 0, 0 );
    currentPos = new Point( 0, 0 );
    mouse = new boolean[ BUTTON_COUNT ];
    polled = new int[ BUTTON_COUNT ];
  }
  public synchronized void poll() {
    mousePos = new Point( currentPos );
    polledNotches = notches;
    notches = 0;
    for( int i = 0; i < mouse.length; ++i ) {
      if( mouse[i] ) {
        polled[i]++;
      } else {
        polled[i] = 0;
      }
    }
  }
  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 ) {
    currentPos = e.getPoint();
  }
  public synchronized void mouseWheelMoved( MouseWheelEvent e ) {
    notches += e.getWheelRotation();
  }
}`
SimpleMouseExample如图2.2所示,它位于javagames.input包中,为第一个示例添加了很多新的内容。这个示例以主动渲染示例代码为基础,添加了键盘和鼠标输入类;它不仅展示了所有3种类型的鼠标输入,而且也是使用Graphics对象绘制到屏幕的第一个示例。

首先需要注意的是,创建GUI时的方法调用。这些调用添加了KeyboardInput和Simple MouseInput作为组件的监听器。注意,尽管给JFrame和Canvas都添加了键盘,但是只给Canvas添加了鼠标。当应用程序初次启动时,如果没有给JFrame添加键盘事件的话,它不会处理键盘事件。一旦画布对象接受到焦点,它将会接受键盘输入,但在画布被选中之前,都只有JFrame接受键盘输入。在游戏循环中,添加了如下的方法调用:

public void processInput()```
在这个方法中,对键盘和鼠标都进行了轮询,以确保它们的数据可用。当初次按下鼠标按键时,会设置绘制标志。对于保持鼠标按键按下的每一帧,都会向该行的数据结构添加一个新的点。当按键释放的时候,该标志被清除,并且向列表添加一个空的对象,标志该行的结束。通过这种方式,该数据结构可以同时保存所有的行。

当按下C键的时候,该行从数据结构中清除。如果用户缺乏绘画技巧的话(就像我一样),这会允许他们重新开始。渲染方法也有新的代码加入。除了显示帧速率,还给出了使用该应用程序的说明,并把当前鼠标的位置显示为字符串。

鼠标滚轮用来选择一个颜色索引。如下的代码使用模除运算符和绝对值来保证索引是一个有效值,无论所获取的索引有多大或多小。

// SimpleMouseExample.java
colorIndex += mouse.getNotches();
Color color = COLORS[ Math.abs( colorIndex % COLORS.length ) ];
g.setColor( color );`
%操作符将会保证值在(–3, 3)之间。因为对一个负值进行模除运算,会得到0或者一个负值,所以使用绝对值来保证数组索引位于(0, size –1)之间。

最后,绘制了线条。由于在数据结构中插入了空的值,我们添加了代码以确保没有点为空的时候才绘制线条。

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

public class SimpleMouseExample extends JFrame
         implements Runnable {
  private FrameRate frameRate;
  private BufferStrategy bs;
  private volatile boolean running;
  private Thread gameThread;
  private SimpleMouseInput mouse;
  private KeyboardInput keyboard;
  private ArrayList<Point> lines = new ArrayList<Point>();
  private boolean drawingLine;
  private Color[] COLORS = {
    Color.RED,
    Color.GREEN,
    Color.YELLOW,
    Color.BLUE
  };
  private int colorIndex;
  public SimpleMouseExample() {
    frameRate = new FrameRate();
  }
  protected void createAndShowGUI() {
    Canvas canvas = new Canvas();
    canvas.setSize( 640, 480 );
    canvas.setBackground( Color.BLACK );
    canvas.setIgnoreRepaint( true );
    getContentPane().add( canvas );
    setTitle( "Simple Mouse Example" );
    setIgnoreRepaint( true );
    pack();
    // Add key listeners
    keyboard = new KeyboardInput();
    canvas.addKeyListener( keyboard );
    // Add mouse listeners
    mouse = new SimpleMouseInput();
    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();
    if( keyboard.keyDownOnce( KeyEvent.VK_SPACE ) ) {
      System.out.println("VK_SPACE");
    }
    // if button is pressed for first time,
    // start drawing lines
    if( mouse.buttonDownOnce( MouseEvent.BUTTON1 ) ) {
      drawingLine = true;
    }
    // if the button is down, add line point
    if( mouse.buttonDown( MouseEvent.BUTTON1 ) ) {
      lines.add( mouse.getPosition() );
      // if the button is not down but we were drawing,
      // add a null to break up the lines
    } else if( drawingLine ) {
      lines.add( null );
      drawingLine = false;
    }
    // if ’C’ is down, clear the lines
    if( keyboard.keyDownOnce( KeyEvent.VK_C ) ) {
      lines.clear();
    }
  }
  private void render( Graphics g ) {
    colorIndex += mouse.getNotches();
    Color color = COLORS[ Math.abs( colorIndex % COLORS.length ) ];
    g.setColor( color );
    frameRate.calculate();
    g.drawString( frameRate.getFrameRate(), 30, 30 );
    g.drawString( "Use mouse to draw lines", 30, 45 );
    g.drawString( "Press C to clear lines", 30, 60 );
    g.drawString( "Mouse Wheel cycles colors", 30, 75 );
    g.drawString( mouse.getPosition().toString(), 30, 90 );
    for( int i = 0; i < lines.size() - 1; ++i ) {
    Point p1 = lines.get( i );
      Point p2 = lines.get( i + 1 );
      // Adding a null into the list is used
      // for breaking up the lines when
      // there are two or more lines
      // that are not connected
      if( !( p1 == null || p2 == null ) )
        g.drawLine( p1.x, p1.y, p2.x, p2.y );
    }
  }
  protected void onWindowClosing() {
    try {
      running = false;
      gameThread.join();
    } catch( InterruptedException e ) {
      e.printStackTrace();
    }
    System.exit( 0 );
  }
  public static void main( String[] args ) {
    final SimpleMouseExample app = new SimpleMouseExample();
    app.addWindowListener( new WindowAdapter() {
      public void windowClosing( WindowEvent e ) {
        app.onWindowClosing();
      }
    });
    SwingUtilities.invokeLater( new Runnable() {
      public void run() {
        app.createAndShowGUI();
      }
    });
  }
时间: 2024-10-02 07:31:14

《Java 2D游戏编程入门》—— 2.3 处理鼠标输入的相关文章

《Java 2D游戏编程入门》—— 第2章 输入

第2章 输入 如图2.1所示,输入对于视频游戏来说是非常重要的.游戏之所以与电影不同,在于游戏有输入,而这也是本章的主题.尽管很多人在游戏的外观上投入了较多的精力,但不管游戏外观看上去有多么壮观,如果操控不稳定.糟糕或很难的话,玩家会感到非常沮丧. 尽管如今有很多的输入设备可供使用,但本书只介绍最常见的两种:键盘和鼠标. 本文仅用于学习和交流目的,不代表异步社区观点.非商业转载请注明作译者.出处,并保留本文的原始链接.

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

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

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

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

《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