Java游戏编程---人机交互和用户接口

第三章   人机交互和用户接口

 

AWT事件模型

正如前面提到的,AWT有自己的事件分配线程。这个线程分配来自于操作系统的各种事件,例如,点击鼠标、按下键盘键。

 

AWT是在哪里分配这些事件的呢?当某个组件发生一个事件时,AWT检查是否有该事件的listenerListener是一个对象,它接受来自另一个对象的事件。这里,事件来自于AWT事件分配线程。

 

不同的事件有不同的listener。例如,键盘输入事件有KeyListener接口。

 

下面的例子说明了键盘按键的事件模型:

1.        用户按下一个键

2.        操作系统向Java运行时发送键盘事件

3.        Java运行时将收到的事件放入AWT事件队列

4.        AWT事件分配线程将事件分配给KeyListener

5.        KeyListener收到键盘事件,完成键盘事件所要求的工作

 

所有Listener都是接口,因此任何对象都可以通过实现listener接口成为listener。还要注意到,同一个类型的事件可以有几个listener。例如,几个对象都在侦听鼠标事件。这个特点是有用的,但是你不必在你的代码中处理同类型事件多个listener的情况。

 

有一个方法可以捕获所有AWT事件。虽然这样做对实际的游戏没有用,但是,这对调试代码或弄清分配了什幺事件是有帮助的。下面的代码通过创建AWTEventListener捕获所有事件,并将事件输出到控制台:

Toolkit.getDefaultToolkit().addAWTEventListener(

    new AWTEventListener() {

        public void eventDispatched(AWTEvent event) {

            System.out.println(event);

        }

    }, -1);

 

要记住的是,不要在交付的游戏代码中使用上面的代码段;只在测试时使用这样的代码。

 

键盘输入

游戏中会用到许多键,例如,箭头键控制运动方向,Ctrl键发射武器。我们确实不打算处理文本输入这样的事件---这留给本章后面讨论的Swing组件处理。

 

要捕获键盘事件需要做两件事情:创建KeyListener和注册listener以便接收事件。要注册listener,只要调用接收键盘事件的组件的addKeyListener()方法。对游戏来说,这个组件就是全屏幕窗口:

Window window = screen.getFullScreenWindow();
window.addKeyListener(keyListener);

 

要创建KeyListener,只要创建一个实现了KeyListener接口的对象。KeyListener接口有三个方法:keyPressed()keyReleased()keyTyped()。接收“Typed”事件对游戏几乎没有什幺用处,因此,我们只讨论按下键和释放键事件。

 

这三个方法都以KeyEvent作为参数。KeyEvent对象使你可以检查按下或释放了什幺键,得到的是虚拟键码。虚拟键码是Java中与键盘键对应的代码,但是,它不同于字符。例如,虽然Qq是不同的字符,但是,它们有相同的虚拟键码。

 

所有虚拟键码都以VK_xxx的形式定义在KeyEvent中。例如,Q键的键码是KeyEvent.VK_Q。大部分键码都是可想而知的(例如,VK_ENTERVK_1,完整的虚拟键码可在Java API文档的KeyEvent类中查找到。

 

现在,让我们试一试。代码3.2中的KeyTest类是KeyListener接口的一个实现。它将按下的键和释放的键显示在屏幕上。按ESC键退出程序。

代码3.2 KeyTest.java
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.util.LinkedList;
 

import com.brackeen.javagamebook.graphics.*;
import com.brackeen.javagamebook.test.GameCore;
 

/**
    A simple keyboard test. Displays keys pressed and released to
    the screen. Useful for debugging key input, too.
*/
public class KeyTest extends GameCore implements KeyListener {
 

    public static void main(String[] args) {
        new KeyTest().run();
    }
 

    private LinkedList messages = new LinkedList();
 

    public void init() {
        super.init();
 

        Window window = screen.getFullScreenWindow();
 

        // allow input of the TAB key and other keys normally
        // used for focus traversal
        window.setFocusTraversalKeysEnabled(false);
 

        // register this object as a key listener for the window
        window.addKeyListener(this);
 

        addMessage("KeyInputTest. Press Escape to exit");
    }
 

 

    // a method from the KeyListener interface
    public void keyPressed(KeyEvent e) {
        int keyCode = e.getKeyCode();
 

        // exit the program
        if (keyCode == KeyEvent.VK_ESCAPE) {
             stop();
        }
        else {
            addMessage("Pressed: " +
                KeyEvent.getKeyText(keyCode));
 

            // make sure the key isn't processed for anything else
            e.consume();
        }
    }
 

 

    // a method from the KeyListener interface
    public void keyReleased(KeyEvent e) {
        int keyCode = e.getKeyCode();
        addMessage("Released: " + KeyEvent.getKeyText(keyCode));
 

        // make sure the key isn't processed for anything else
        e.consume();
    }
 

 

    // a method from the KeyListener interface
    public void keyTyped(KeyEvent e) {
        // this is called after the key is released - ignore it
        // make sure the key isn't processed for anything else
        e.consume();
    }
 

 

    /**
        Add a message to the list of messages.
    */
    public synchronized void addMessage(String message) {
        messages.add(message);
        if (messages.size() >= screen.getHeight() / FONT_SIZE) {
            messages.remove(0);
        }
    }
 

 

    /**
        Draw the list of messages
    */
    public synchronized void draw(Graphics2D g) {
 

        Window window = screen.getFullScreenWindow();
 

        g.setRenderingHint(
            RenderingHints.KEY_TEXT_ANTIALIASING,
            RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 

        // draw background
        g.setColor(window.getBackground());
        g.fillRect(0, 0, screen.getWidth(), screen.getHeight());
 

        // draw messages
        g.setColor(window.getForeground());
        int y = FONT_SIZE;
        for (int i=0; i<messages.size(); i++) {
            g.drawString((String)messages.get(i), 5, y);
            y+=FONT_SIZE;
        }

    }

}

 

你应当注意到了两点。第一,init()方法用到了下面一行代码:

window.setFocusTraversalKeysEnabled(false);

 

这行代码屏蔽了focus traversal 键。focus traversal 键是按下后改变键盘焦点的键。例如,在Web页面上,按Tab键可在表单的元素之间切换。Tab键事件被AWTfocus traversal代码掩盖,但是,这里我们想要接收到Tab键事件。调用这个方法就可以了。

 

如果你想知道有哪些focus traversal 键,就调用getFocusTraversalKeys()方法。

 

Tab键不是唯一会引起奇怪现象的键。Alt键也会引起问题。在大多数系统上,Alt键用于激活记忆键,所谓记忆键就是特定的用户接口的快捷键。例如Alt+F激活大多数带有菜单条应用的文件菜单。AWT会认为在Alt键后按下的键是记忆的而忽略了这个键。为了避免这种情况,在KeyTest中用下面的代码防止KeyListener中的KeyEvent按照缺省的方式处理:

e.consume();

 

这确保没有其它对象处理Alt键,因此,Alt键就象其它键一样处理。

 

KeyTest还有一个作用,就是用来测试不同系统上键的反应。“哇!你是说在不同的系统上键输入有可能不同。”是的,确实如此。

 

让我们以重复按键为例。当用户按住一个键时,操作系统发出多个事件。例如,在文本编辑器中,当你按住Q键,就一直输入Q。在有些系统上(例如Linux)这样的操作会产生按下键和释放键事件。在另一些系统上(例如Windows)只产生按下键事件,用户释放了键后才产生释放键事件。

 

还有一些其它的细小差别,例如,不同版本的Java虚拟机的键事件也会有点不同。

 

幸运的是差别不大。大多数时候你不需管它。

 

鼠标输入

键盘只不过是排列在一起的一组键,但是,鼠标就复杂多了。鼠标不仅有键(有一键、两键、三键或更多键鼠标),还可以移动,可能还有滚轮。

 

也就是说,你可能收到三种类型的鼠标事件:

l          点击鼠标按键

l          滑动鼠标

l          转动鼠标滚轮

 

点击鼠标按键与按下键盘键相同,但是没有重复键。鼠标的位置用屏幕的xy座标表示。鼠标的滚轮事件给出了滚轮转动了多少。

 

每一种鼠标事件都有自己的listenerMouseListenerMouseMotionListenerMouseWheelListener。它们都以MouseEvent作为参数。

 

KeyListener一样,MouseListener接口的方法可以检测鼠标键的按下、释放和点击(按下,接着释放)。我们在游戏中不考虑点击,就象我们不考虑KeyTyped事件一样,只考虑按下和释放。可以调用MouseEventgetButton()方法获得那个键被按下了或释放了。

 

MouseListener接口的方法还可以检测到鼠标进入或退出组件。因为我们所用的组件覆盖了整个屏幕,我们也不考虑这样的方法。

 

对于鼠标的移动,我们可以用MouseMotionListener接口检测到两种移动:通常的移动和拖动。当用户按住一个鼠标键同时移动鼠标时,就发生拖动事件。这两种移动都可以用MouseEventgetX()getY()方法获得当前的位置。

 

MouseWheelListener使用了MouseEvent的子类,MouseWheelEvent。它的getWheelRotation()方法检测鼠标滚轮转动了多少。负值表示向上转动,正值表示向下转动。

 

好了,已经介绍了鼠标输入基础。让我们编写一个程序试一试。

 

代码3.3中的MouseTest在鼠标所在的位置上画出“Hello World”。当点击鼠标时,画出最后10个鼠标的位置做成轨迹,鼠标改变到“轨迹模式”。转动鼠标的滚轮会改变文本的颜色。如前所述,按ESC键退出程序。

 

 

代码3.3 MouseTest.java
import java.awt.*;
import java.awt.event.*;
import java.util.LinkedList;
 

import com.brackeen.javagamebook.graphics.*;
import com.brackeen.javagamebook.test.GameCore;
 

/**
    A simple mouse test. Draws a "Hello World!" message at
    the location of the cursor. Click to change to "trail mode"
    to draw several messages. Use the mouse wheel (if available)
    to change colors.
*/
public class MouseTest extends GameCore implements KeyListener,
    MouseMotionListener, MouseListener, MouseWheelListener
 

{
 

    public static void main(String[] args) {
        new MouseTest().run();
    }
 

    private static final int TRAIL_SIZE = 10;
    private static final Color[] COLORS = {
        Color.white, Color.black, Color.yellow, Color.magenta
    };
 

    private LinkedList trailList;
    private boolean trailMode;
    private int colorIndex;
 

 

    public void init() {
        super.init();
        trailList = new LinkedList();
 

        Window window = screen.getFullScreenWindow();
        window.addMouseListener(this);
        window.addMouseMotionListener(this);
        window.addMouseWheelListener(this);
        window.addKeyListener(this);
    }
 

 

    public synchronized void draw(Graphics2D g) {
        int count = trailList.size();
 

        if (count > 1 && !trailMode) {
            count = 1;
        }
 

        Window window = screen.getFullScreenWindow();
 

        // draw background
        g.setColor(window.getBackground());
        g.fillRect(0, 0, screen.getWidth(), screen.getHeight());
 

        // draw instructions
        g.setRenderingHint(
                 RenderingHints.KEY_TEXT_ANTIALIASING,
                 RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setColor(window.getForeground());
        g.drawString("MouseTest. Press Escape to exit.", 5,
            FONT_SIZE);
 

        // draw mouse trail
        for (int i=0; i<count; i++) {
            Point p = (Point)trailList.get(i);
            g.drawString("Hello World!", p.x, p.y);
        }
    }
 

 

    // from the MouseListener interface
    public void mousePressed(MouseEvent e) {
        trailMode = !trailMode;
    }
 

 

    // from the MouseListener interface
    public void mouseReleased(MouseEvent e) {
        // do nothing
    }
 

 

    // from the MouseListener interface
    public void mouseClicked(MouseEvent e) {
        // called after mouse is released - ignore it
    }
 

 

    // from the MouseListener interface
    public void mouseEntered(MouseEvent e) {
        mouseMoved(e);
    }
 

 

    // from the MouseListener interface
    public void mouseExited(MouseEvent e) {
        mouseMoved(e);
    }
 

 

    // from the MouseMotionListener interface
    public void mouseDragged(MouseEvent e) {
        mouseMoved(e);
    }
 

 

    // from the MouseMotionListener interface
    public synchronized void mouseMoved(MouseEvent e) {
        Point p = new Point(e.getX(), e.getY());
        trailList.addFirst(p);
        while (trailList.size() > TRAIL_SIZE) {
            trailList.removeLast();
        }
    }
 

 

    // from the MouseWheelListener interface
    public void mouseWheelMoved(MouseWheelEvent e) {
        colorIndex = (colorIndex + e.getWheelRotation()) %
            COLORS.length;
 

        if (colorIndex < 0) {
            colorIndex+=COLORS.length;
        }
        Window window = screen.getFullScreenWindow();
        window.setForeground(COLORS[colorIndex]);
    }
 

 

    // from the KeyListener interface
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            // exit the program
            stop();
        }
    }
 

 

    // from the KeyListener interface
    public void keyReleased(KeyEvent e) {
        // do nothing
    }
 

 

    // from the KeyListener interface
    public void keyTyped(KeyEvent e) {
        // do nothing
    }
}
 

MouseTest的代码简单易懂,没有什幺需要解释的。当鼠标移动时,就向trailList中增加一个新的Point对象。Point对象含有xy的值。TrailList中最多可有10Point。如果开启了轨迹模式,draw()方法就在trailList的每个Point的位置画出“Hello World”。否则,“Hello World”就只画在第一个Point的位置。点击鼠标切换轨迹模式。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值