交互界面与输入(1)

3 交互性与用户界面:本章介绍如何取得用户输入,即键盘与鼠标事件。还要介绍把输入集成到游戏中,并介绍如何用Swing实现用户界面。下面先看一个简单类来简化速测程序的实现,清单   3.1 GameCore 类就是起这个作用。它实现了一些常见的技术,如设置显示方式和运行动画循环等。这里只是扩展这个抽象类,并实现draw()与update()方法。
清单 3.1 GameCore.java
package com.brackeen.javagamebook.test;

import java.awt.*;
import javax.swing.ImageIcon;

import com.brackeen.javagamebook.graphics.ScreenManager;

/**
    Simple abstract class used for testing. Subclasses should
    implement the draw() method.
*/
public abstract class GameCore {

    protected static final int FONT_SIZE = 24;

    private static final DisplayMode POSSIBLE_MODES[] = {
        new DisplayMode(800, 600, 32, 0),
        new DisplayMode(800, 600, 24, 0),
        new DisplayMode(800, 600, 16, 0),
        new DisplayMode(640, 480, 32, 0),
        new DisplayMode(640, 480, 24, 0),
        new DisplayMode(640, 480, 16, 0)
    };

    private boolean isRunning;
    protected ScreenManager screen;


    /**
        Signals the game loop that it's time to quit
    */
    public void stop() {
        isRunning = false;
    }


    /**
        Calls init() and gameLoop()
    */
    public void run() {
        try {
            init();
            gameLoop();
        }
        finally {
            screen.restoreScreen();
        }
    }


    /**
        Sets full screen mode and initiates and objects.
    */
    public void init() {
        screen = new ScreenManager();
        DisplayMode displayMode =
            screen.findFirstCompatibleMode(POSSIBLE_MODES);
        screen.setFullScreen(displayMode);

        Window window = screen.getFullScreenWindow();
        window.setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));
        window.setBackground(Color.blue);
        window.setForeground(Color.white);

        isRunning = true;
    }


    public Image loadImage(String fileName) {
        return new ImageIcon(fileName).getImage();
    }


    /**
        Runs through the game loop until stop() is called.
    */
    public void gameLoop() {
        long startTime = System.currentTimeMillis();
        long currTime = startTime;

        while (isRunning) {
            long elapsedTime =
                System.currentTimeMillis() - currTime;
            currTime += elapsedTime;

            // update
            update(elapsedTime);

            // draw the screen
            Graphics2D g = screen.getGraphics();
            draw(g);
            g.dispose();
            screen.update();

            // take a nap
            try {
                Thread.sleep(20);
            }
            catch (InterruptedException ex) { }
        }
    }


    /**
        Updates the state of the game/animation based on the
        amount of elapsed time that has passed.
    */
    public void update(long elapsedTime) {
        // do nothing
    }


    /**
        Draws to the screen. Subclasses must override this
        method.
    */
    public abstract void draw(Graphics2D g);
}

3.1 AWT事件模型:每种事件都用不同类型的监听器,如键盘输入事件的监听器是KeyListener接口。下面是键盘单击事件在AWT事件模型中的步骤。(1)用户单击一个按键(2)操作系统向java运行环境发送一个健击事件。(3)java运行环境将键击事件发送到AWT事件队列。(4)AWT事件派生线程将键击事件发送到任何KeyListener.(5)KeyListener受到键击事件并对其进行处理。所有监听器都是接口,因此任何对象对可以成为监听器。另外同一事件可以有多个监听器。
3.2键盘输入:要捕获键击事件,就要做两个2件事件:生成KeyListener和将监听器注册成接受事件。若要注册监听器,则调用键击事件所在组件的addKeyListener()方法。对这里游戏而言,这个组件就是全屏窗口,注册监听器代码如下:
Window window=screen.getFullScreenWindow();
Window addKeyListener(keyListener);
若要生成KeyListener,只要实现KeyListener接口的对象,KeyListener接口包括三种方法:keyPressed(),keyReleased(),keyTyped().Typed事件在首次击键时发生,然后在重复击键时根据重复的速率发生。接收Typed事件对游戏毫无用处,因此主要介绍Pressed和Released事件。清单3.2的KeyListener接口,它只是在屏幕上显示键按下与放开事件还有可以按Escape键退出程序。
清单3.2 KeyTest.java
import java.awt.*;
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 KeyListner 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 KeyListner 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;
        }
    }
}
3.3 鼠标输入:可以取得鼠标三种不同类型的事件:鼠标单击事件,鼠标移动事件,鼠标轮滚动事件。每个鼠标事件都有自己的监听器,如MouseListener,MouseMotionListener和MouseWheelListener,不过监听器都取一个MouseEvent作为参数。对于鼠标的运动,用MouseMotionListener接口可以探测出2种运动事件:普通移动和拖动,其中拖动事件发生自阿按住鼠标并移动鼠标时。这两种运动都可以用MouseEvent类的getX()和getY()方法取得鼠标当前的位置。MouseWheelListener用MouseEvent的子类MouseWheelEvent,其getWheelRotation()方法检查鼠标运动量,负值表示向上滚动,正值表示向下滚动。下面介绍一个生成程序。清单3.3的MouseTest程序在鼠标所在位置画一个Hello World消息。单击时,它变成trail mode,并画出最近10个鼠标的位置,从而产生一个尾巴。同时鼠标轮会改变文本颜色。和前面一样,也可以按Escape键退出程序。
清单 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
    }
}
3.4通过鼠标移动的观看模式
如果运行像Quake III等第一人称视角的射击游戏时,就会使用鼠标观看特性,它使游戏者可以移动鼠标器达到对视野的观看,如向左移鼠标,游戏者则向左看,鼠标观看特性的妙处所在是可以随意移动鼠标,由于鼠标不受屏幕的限制,就可以一直移动鼠标,直到头晕或移出鼠标板为止。虽然Java API没有直接提供探测鼠标相对移动的方法,但可以换个角度想。首先保证鼠标不移出屏幕外,然后每次鼠标移动时,只要将鼠标移动到屏幕的中央。这样,鼠标就不会因为碰上屏幕边沿而停下来,即总是根据上一个位置来计算鼠标的移动量。下面就是实现具体步骤:
(1)鼠标从屏幕中央开始
(2)用户移动鼠标,就要计算鼠标的移动量。
(3)发一个事件,将鼠标移动到屏幕中央
(4)发现鼠标重新居中后,将忽略此事件。
可以使用Robot类移动鼠标位置,Robot类并不会派一个机器人到桌面上帮你移动鼠标,而是一个自动测试图形的应用程序。除了通过程序移动鼠标外,还有各种其他功能,如传真键击和进行屏幕捕获。移动鼠标的方法很简单,实现代码如下:robot.mouseMove(x,y);下面对Robot类做个测试。清单3.4 类实现了一个简单的背景纹理,可以用鼠标观看特性无限滚动 。可以按空格键关掉鼠标观看属性,还可以按Escape键退出程序。
清单 3.4 MouselookTest.java
import java.awt.*;
import java.awt.event.*;
import javax.swing.SwingUtilities;

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

/**
    A simple mouselook test. Using mouselook, the user can
    virtually move the mouse in any direction indefinitly.
    Without mouselook, the mouse stops when it hits the edge of
    the screen.
    <p>Mouselook works by recentering the mouse whenever it is
    moved, so it can always measure the relative mouse movement,
    and the mouse never hits the edge of the screen.
*/
public class MouselookTest extends GameCore
    implements MouseMotionListener, KeyListener
{

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

    private Image bgImage;
    private Robot robot;
    private Point mouseLocation;
    private Point centerLocation;
    private Point imageLocation;
    private boolean relativeMouseMode;
    private boolean isRecentering;

    public void init() {
        super.init();
        mouseLocation = new Point();
        centerLocation = new Point();
        imageLocation = new Point();

        relativeMouseMode = true;
        isRecentering = false;

        try {
            robot = new Robot();
            recenterMouse();
            mouseLocation.x = centerLocation.x;
            mouseLocation.y = centerLocation.y;
        }
        catch (AWTException ex) {
            System.out.println("Couldn't create Robot!");
        }
        Window window = screen.getFullScreenWindow();
        window.addMouseMotionListener(this);
        window.addKeyListener(this);
        bgImage = loadImage("../images/background.jpg");
    }


    public synchronized void draw(Graphics2D g) {

        int w = screen.getWidth();
        int h = screen.getHeight();

        // make sure position is correct
        imageLocation.x %= w;
        imageLocation.y %= screen.getHeight();
        if (imageLocation.x < 0) {
            imageLocation.x += w;
        }
        if (imageLocation.y < 0) {
            imageLocation.y += screen.getHeight();
        }

        // draw the image in four places to cover the screen
        int x = imageLocation.x;
        int y = imageLocation.y;
        g.drawImage(bgImage, x, y, null);
        g.drawImage(bgImage, x-w, y, null);
        g.drawImage(bgImage, x, y-h, null);
        g.drawImage(bgImage, x-w, y-h, null);

        // draw instructions
        g.setRenderingHint(
                RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.drawString("Press Space to change mouse modes.", 5,
            FONT_SIZE);
        g.drawString("Press Escape to exit.", 5, FONT_SIZE*2);
    }


    /**
        Uses the Robot class to try to postion the mouse in the
        center of the screen.
        <p>Note that use of the Robot class may not be available
        on all platforms.
    */
    private synchronized void recenterMouse() {
        Window window = screen.getFullScreenWindow();
        if (robot != null && window.isShowing()) {
            centerLocation.x = window.getWidth() / 2;
            centerLocation.y = window.getHeight() / 2;
            SwingUtilities.convertPointToScreen(centerLocation,
                window);
            isRecentering = true;
            robot.mouseMove(centerLocation.x, centerLocation.y);
        }
    }


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


    // from the MouseMotionListener interface
    public synchronized void mouseMoved(MouseEvent e) {
        // this event is from re-centering the mouse - ignore it
        if (isRecentering &&
            centerLocation.x == e.getX() &&
            centerLocation.y == e.getY())
        {
            isRecentering = false;
        }
        else {
            int dx = e.getX() - mouseLocation.x;
            int dy = e.getY() - mouseLocation.y;
            imageLocation.x += dx;
            imageLocation.y += dy;
            // recenter the mouse
            if (relativeMouseMode) {
                recenterMouse();
            }

        }

        mouseLocation.x = e.getX();
        mouseLocation.y = e.getY();

    }


    // from the KeyListener interface
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
            // exit the program
            stop();
        }
        else if (e.getKeyCode() == KeyEvent.VK_SPACE) {
            // change relative mouse mode
            relativeMouseMode = !relativeMouseMode;
        }
    }


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


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

}
隐藏光标:在MouselookTest程序中,注意到屏幕中的鼠标光标不太好看。居中时它可能会闪烁,可能在短时间看到鼠标移动,下面就要隐藏鼠标光标。好在java API 提供改变鼠标光标的方法,java中定义的鼠标包括:
CROSSHAIR_CURSOR 光标显示加号
DEFAULT_CURSOR   正常箭头光标
HAND_CURSOR      手形光标
TEXT_CURSOR       文本光标
WAIT_CURSOR  等待光标
为了完整起见,java ApI允许定制图形来生成自己的鼠标光标,所以只要生成空白图形的广标即可。为此调用Toolkit类的createCostomerCursor()方法,实现代码如下:
Cursor invisibleCursor=Toolkit.getDefaultToolkit().createCustomerCursor(Toolkit.getDefaultToolkit().getImage(""),new point(0,0),"invisible");
实际上,代码只是生成作废图像,但工具确将其解释为隐藏光标。若要改变游戏的光标可用setCursor方法,实现代码如下:
Window window=screen.getFullScreenWindow();
Window.setCursor(invisibleCursor);可以用Cursor的getPredefinedCursor()方法返回缺省光标。
Cursor normalCursor=Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)
3.5 创建输入管理器:    输入管理器具有下列功能
(1)处理所有键盘与鼠标事件,包括鼠标运动;
(2)保存事件,以便要需要时精确地查询,而不是在AWT事件派生线程中改变游戏状态;
(3)探测某些键的初击,探测其他键的连续按下;
(4)将键贴图为一般性游戏操作,将空格键贴图为跳动操作;
(5)动态改变键贴图,使用户可以配置键盘。清单3.5 的GameAction类很容易使用,其中isPressed()方法用于键击,getAmount()方法显示鼠标移动量。GameAction类还处理初击和正确击键行为。最后GameAction类提供了Input Manager调用的方法,如pressed()与release()方法。
清单3.5 GameAction.java
package com.brackeen.javagamebook.input;

/**
    The GameAction class is an abstract to a user-initiated
    action, like jumping or moving. GameActions can be mapped
    to keys or the mouse with the InputManager.
*/
public class GameAction {

    /**
        Normal behavior. The isPressed() method returns true
        as long as the key is held down.
    */
    public static final int NORMAL = 0;

    /**
        Initial press behavior. The isPressed() method returns
        true only after the key is first pressed, and not again
        until the key is released and pressed again.
    */
    public static final int DETECT_INITAL_PRESS_ONLY = 1;

    private static final int STATE_RELEASED = 0;
    private static final int STATE_PRESSED = 1;
    private static final int STATE_WAITING_FOR_RELEASE = 2;

    private String name;
    private int behavior;
    private int amount;
    private int state;

    /**
        Create a new GameAction with the NORMAL behavior.
    */
    public GameAction(String name) {
        this(name, NORMAL);
    }


    /**
        Create a new GameAction with the specified behavior.
    */
    public GameAction(String name, int behavior) {
        this.name = name;
        this.behavior = behavior;
        reset();
    }


    /**
        Gets the name of this GameAction.
    */
    public String getName() {
        return name;
    }


    /**
        Resets this GameAction so that it appears like it hasn't
        been pressed.
    */
    public void reset() {
        state = STATE_RELEASED;
        amount = 0;
    }


    /**
        Taps this GameAction. Same as calling press() followed
        by release().
    */
    public synchronized void tap() {
        press();
        release();
    }


    /**
        Signals that the key was pressed.
    */
    public synchronized void press() {
        press(1);
    }


    /**
        Signals that the key was pressed a specified number of
        times, or that the mouse move a spcified distance.
    */
    public synchronized void press(int amount) {
        if (state != STATE_WAITING_FOR_RELEASE) {
            this.amount+=amount;
            state = STATE_PRESSED;
        }

    }


    /**
        Signals that the key was released
    */
    public synchronized void release() {
        state = STATE_RELEASED;
    }


    /**
        Returns whether the key was pressed or not since last
        checked.
    */
    public synchronized boolean isPressed() {
        return (getAmount() != 0);
    }


    /**
        For keys, this is the number of times the key was
        pressed since it was last checked.
        For mouse movement, this is the distance moved.
    */
    public synchronized int getAmount() {
        int retVal = amount;
        if (retVal != 0) {
            if (state == STATE_RELEASED) {
                amount = 0;
            }
            else if (behavior == DETECT_INITAL_PRESS_ONLY) {
                state = STATE_WAITING_FOR_RELEASE;
                amount = 0;
            }
        }
        return retVal;
    }
}
最后,清单3.6生成InputManager类,InputManager对象具有上述输入实例中所有的代码,包括隐藏光标和相对鼠标移动。另外,它还包括将键盘与鼠标事件贴图到GameAction对象上。当按下键时,代码检测是否有贴图这个键的GameAction对象,有则调用GameAction类的press()方法。贴图是如何生成的?生成一个GameAction数组,数组中每个索引对应一个虚拟键码,大多数虚拟键码值小于600,因此GameAction数组的长度为600。鼠标事件的贴图也差不多,由于鼠标事件没有代码,因此InputManager中建立了伪鼠标代码。鼠标移动(上,下,左,右),鼠标轮滚动和鼠标键都有鼠标代码。InputManager类代码如下:
清单 3.6 InputManager.java
package com.brackeen.javagamebook.input;

import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.SwingUtilities;

/**
    The InputManager manages input of key and mouse events.
    Events are mapped to GameActions.
*/
public class InputManager implements KeyListener, MouseListener,
    MouseMotionListener, MouseWheelListener
{
    /**
        An invisible cursor.
    */
    public static final Cursor INVISIBLE_CURSOR =
        Toolkit.getDefaultToolkit().createCustomCursor(
            Toolkit.getDefaultToolkit().getImage(""),
            new Point(0,0),
            "invisible");

    // mouse codes
    public static final int MOUSE_MOVE_LEFT = 0;
    public static final int MOUSE_MOVE_RIGHT = 1;
    public static final int MOUSE_MOVE_UP = 2;
    public static final int MOUSE_MOVE_DOWN = 3;
    public static final int MOUSE_WHEEL_UP = 4;
    public static final int MOUSE_WHEEL_DOWN = 5;
    public static final int MOUSE_BUTTON_1 = 6;
    public static final int MOUSE_BUTTON_2 = 7;
    public static final int MOUSE_BUTTON_3 = 8;

    private static final int NUM_MOUSE_CODES = 9;

    // key codes are defined in java.awt.KeyEvent.
    // most of the codes (except for some rare ones like
    // "alt graph") are less than 600.
    private static final int NUM_KEY_CODES = 600;

    private GameAction[] keyActions =
        new GameAction[NUM_KEY_CODES];
    private GameAction[] mouseActions =
        new GameAction[NUM_MOUSE_CODES];

    private Point mouseLocation;
    private Point centerLocation;
    private Component comp;
    private Robot robot;
    private boolean isRecentering;

    /**
        Creates a new InputManager that listens to input from the
        specified component.
    */
    public InputManager(Component comp) {
        this.comp = comp;
        mouseLocation = new Point();
        centerLocation = new Point();

        // register key and mouse listeners
        comp.addKeyListener(this);
        comp.addMouseListener(this);
        comp.addMouseMotionListener(this);
        comp.addMouseWheelListener(this);

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


    /**
        Sets the cursor on this InputManager's input component.
    */
    public void setCursor(Cursor cursor) {
        comp.setCursor(cursor);
    }


    /**
        Sets whether realtive mouse mode is on or not. For
        relative mouse mode, the mouse is "locked" in the center
        of the screen, and only the changed in mouse movement
        is measured. In normal mode, the mouse is free to move
        about the screen.
    */
    public void setRelativeMouseMode(boolean mode) {
        if (mode == isRelativeMouseMode()) {
            return;
        }

        if (mode) {
            try {
                robot = new Robot();
                recenterMouse();
            }
            catch (AWTException ex) {
                // couldn't create robot!
                robot = null;
            }
        }
        else {
            robot = null;
        }
    }


    /**
        Returns whether or not relative mouse mode is on.
    */
    public boolean isRelativeMouseMode() {
        return (robot != null);
    }


    /**
        Maps a GameAction to a specific key. The key codes are
        defined in java.awt.KeyEvent. If the key already has
        a GameAction mapped to it, the new GameAction overwrites
        it.
    */
    public void mapToKey(GameAction gameAction, int keyCode) {
        keyActions[keyCode] = gameAction;
    }


    /**
        Maps a GameAction to a specific mouse action. The mouse
        codes are defined herer in InputManager (MOUSE_MOVE_LEFT,
        MOUSE_BUTTON_1, etc). If the mouse action already has
        a GameAction mapped to it, the new GameAction overwrites
        it.
    */
    public void mapToMouse(GameAction gameAction,
        int mouseCode)
    {
        mouseActions[mouseCode] = gameAction;
    }


    /**
        Clears all mapped keys and mouse actions to this
        GameAction.
    */
    public void clearMap(GameAction gameAction) {
        for (int i=0; i<keyActions.length; i++) {
            if (keyActions[i] == gameAction) {
                keyActions[i] = null;
            }
        }

        for (int i=0; i<mouseActions.length; i++) {
            if (mouseActions[i] == gameAction) {
                mouseActions[i] = null;
            }
        }

        gameAction.reset();
    }


    /**
        Gets a List of names of the keys and mouse actions mapped
        to this GameAction. Each entry in the List is a String.
    */
    public List getMaps(GameAction gameCode) {
        ArrayList list = new ArrayList();

        for (int i=0; i<keyActions.length; i++) {
            if (keyActions[i] == gameCode) {
                list.add(getKeyName(i));
            }
        }

        for (int i=0; i<mouseActions.length; i++) {
            if (mouseActions[i] == gameCode) {
                list.add(getMouseName(i));
            }
        }
        return list;
    }


    /**
        Resets all GameActions so they appear like they haven't
        been pressed.
    */
    public void resetAllGameActions() {
        for (int i=0; i<keyActions.length; i++) {
            if (keyActions[i] != null) {
                keyActions[i].reset();
            }
        }

        for (int i=0; i<mouseActions.length; i++) {
            if (mouseActions[i] != null) {
                mouseActions[i].reset();
            }
        }
    }


    /**
        Gets the name of a key code.
    */
    public static String getKeyName(int keyCode) {
        return KeyEvent.getKeyText(keyCode);
    }


    /**
        Gets the name of a mouse code.
    */
    public static String getMouseName(int mouseCode) {
        switch (mouseCode) {
            case MOUSE_MOVE_LEFT: return "Mouse Left";
            case MOUSE_MOVE_RIGHT: return "Mouse Right";
            case MOUSE_MOVE_UP: return "Mouse Up";
            case MOUSE_MOVE_DOWN: return "Mouse Down";
            case MOUSE_WHEEL_UP: return "Mouse Wheel Up";
            case MOUSE_WHEEL_DOWN: return "Mouse Wheel Down";
            case MOUSE_BUTTON_1: return "Mouse Button 1";
            case MOUSE_BUTTON_2: return "Mouse Button 2";
            case MOUSE_BUTTON_3: return "Mouse Button 3";
            default: return "Unknown mouse code " + mouseCode;
        }
    }


    /**
        Gets the x position of the mouse.
    */
    public int getMouseX() {
        return mouseLocation.x;
    }


    /**
        Gets the y position of the mouse.
    */
    public int getMouseY() {
        return mouseLocation.y;
    }


    /**
        Uses the Robot class to try to postion the mouse in the
        center of the screen.
        <p>Note that use of the Robot class may not be available
        on all platforms.
    */
    private synchronized void recenterMouse() {
        if (robot != null && comp.isShowing()) {
            centerLocation.x = comp.getWidth() / 2;
            centerLocation.y = comp.getHeight() / 2;
            SwingUtilities.convertPointToScreen(centerLocation,
                comp);
            isRecentering = true;
            robot.mouseMove(centerLocation.x, centerLocation.y);
        }
    }


    private GameAction getKeyAction(KeyEvent e) {
        int keyCode = e.getKeyCode();
        if (keyCode < keyActions.length) {
            return keyActions[keyCode];
        }
        else {
            return null;
        }
    }

    /**
        Gets the mouse code for the button specified in this
        MouseEvent.
    */
    public static int getMouseButtonCode(MouseEvent e) {
         switch (e.getButton()) {
            case MouseEvent.BUTTON1:
                return MOUSE_BUTTON_1;
            case MouseEvent.BUTTON2:
                return MOUSE_BUTTON_2;
            case MouseEvent.BUTTON3:
                return MOUSE_BUTTON_3;
            default:
                return -1;
        }
    }


    private GameAction getMouseButtonAction(MouseEvent e) {
        int mouseCode = getMouseButtonCode(e);
        if (mouseCode != -1) {
             return mouseActions[mouseCode];
        }
        else {
             return null;
        }
    }


    // from the KeyListener interface
    public void keyPressed(KeyEvent e) {
        GameAction gameAction = getKeyAction(e);
        if (gameAction != null) {
            gameAction.press();
        }
        // make sure the key isn't processed for anything else
        e.consume();
    }


    // from the KeyListener interface
    public void keyReleased(KeyEvent e) {
        GameAction gameAction = getKeyAction(e);
        if (gameAction != null) {
            gameAction.release();
        }
        // make sure the key isn't processed for anything else
        e.consume();
    }


    // from the KeyListener interface
    public void keyTyped(KeyEvent e) {
        // make sure the key isn't processed for anything else
        e.consume();
    }


    // from the MouseListener interface
    public void mousePressed(MouseEvent e) {
        GameAction gameAction = getMouseButtonAction(e);
        if (gameAction != null) {
            gameAction.press();
        }
    }


    // from the MouseListener interface
    public void mouseReleased(MouseEvent e) {
        GameAction gameAction = getMouseButtonAction(e);
        if (gameAction != null) {
            gameAction.release();
        }
    }


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


    // 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) {
        // this event is from re-centering the mouse - ignore it
        if (isRecentering &&
            centerLocation.x == e.getX() &&
            centerLocation.y == e.getY())
        {
            isRecentering = false;
        }
        else {
            int dx = e.getX() - mouseLocation.x;
            int dy = e.getY() - mouseLocation.y;
            mouseHelper(MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, dx);
            mouseHelper(MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, dy);

            if (isRelativeMouseMode()) {
                recenterMouse();
            }
        }

        mouseLocation.x = e.getX();
        mouseLocation.y = e.getY();

    }


    // from the MouseWheelListener interface
    public void mouseWheelMoved(MouseWheelEvent e) {
        mouseHelper(MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN,
            e.getWheelRotation());
    }

    private void mouseHelper(int codeNeg, int codePos,
        int amount)
    {
        GameAction gameAction;
        if (amount < 0) {
            gameAction = mouseActions[codeNeg];
        }
        else {
            gameAction = mouseActions[codePos];
        }
        if (gameAction != null) {
            gameAction.press(Math.abs(amount));
            gameAction.release();
        }
    }

}
InputManager类还有一个特性,就是可以取得键和鼠标事件的名称。另外,InputManager类还有一个resetAllGameAction()方法,它复位当前按下的任何GameAction对象,适用于从主菜单切换到游戏的情形。如果不清除GameActions,则菜单中按空格键可能使游戏者在游戏中发生跳动。

  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值