在Java中捕获(trap)窗口中的鼠标光标

4
我正在寻找一种方法来捕获或限制鼠标在窗口中,就像鼠标被困在虚拟机窗口中一样,直到用户按下CTRL + ALT + DEL或以其他方式释放鼠标。如何在Java中实现这一点?全屏不是一个选项。
编辑:
以下是一些SSCCE代码。这段代码将限制你的鼠标在窗口中。要退出,您只需进入生成的框架并直接移动到关闭按钮。如果您注意到,当您的鼠标尝试离开时,它会自动返回到(0,0)处。我需要知道的是如何让它回到它退出的坐标。我尝试使用getX()和getY()代替(0,0),但机器人没有将鼠标返回到那里(我认为响应时间太慢)。我还让机器人将鼠标移回crosshair.x和crosshair.y,但这仍然允许鼠标在用户点击正确时逃逸。有什么想法吗?
主类:
import java.awt.AWTException;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferStrategy;
import java.awt.image.MemoryImageSource;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import javax.swing.JFrame;

public class Game extends JFrame implements MouseMotionListener, MouseListener{

    private int windowWidth = 640;
    private int windowHeight = 480;
        private Crosshair crosshair;

    public static void main(String[] args) {
        new Game();
    }

    public Game() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(windowWidth, windowHeight);
        this.setResizable(false);
        this.setLocation(0,0);
        this.setVisible(true);

        this.createBufferStrategy(2);
                addMouseMotionListener(this);
                addMouseListener(this);
        initGame();

        while(true) {
            long start = System.currentTimeMillis();
            gameLoop();
            while(System.currentTimeMillis()-start < 5) {
                            //empty while loop
            }
        }
    }

    private void initGame() {
            hideCursor();
            crosshair = new Crosshair (windowWidth/2, windowHeight/2);
    }

        private void gameLoop() {
            //game logic
            drawFrame();
        }

        private void drawFrame() {

            BufferStrategy bf = this.getBufferStrategy();
            Graphics g = (Graphics)bf.getDrawGraphics();
            try {
                g = bf.getDrawGraphics();
                Color darkBlue = new Color(0x010040);
                g.setColor(darkBlue);
                g.fillRect(0, 0, windowWidth, windowHeight);
                drawCrossHair(g);
            } finally {
                g.dispose();
            }
            bf.show();
            Toolkit.getDefaultToolkit().sync();
        }

        private void drawCrossHair(Graphics g){
            Color yellow = new Color (0xEDFF62);
            g.setColor(yellow);
            g.drawOval(crosshair.x, crosshair.y, 40, 40);

            g.fillArc(crosshair.x + 10, crosshair.y + 21 , 20, 20, -45, -90);
            g.fillArc(crosshair.x - 1, crosshair.y + 10, 20, 20, -135, -90);
            g.fillArc(crosshair.x + 10, crosshair.y - 1, 20, 20, -225, -90);
            g.fillArc(crosshair.x + 21, crosshair.y + 10, 20, 20, -315, -90);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
        //empty method
        }

        @Override
        public void mouseMoved(MouseEvent e) {
        crosshair.x = e.getX();
        crosshair.y = e.getY();
        }

        private void hideCursor() {
            int[] pixels = new int[16 * 16];
            Image image = Toolkit.getDefaultToolkit().createImage(new MemoryImageSource(16, 16, pixels, 0, 16));
            Cursor transparentCursor = Toolkit.getDefaultToolkit().createCustomCursor(image, new Point(0, 0), "invisiblecursor");
            getContentPane().setCursor(transparentCursor);
    }

        public void mouseExited(MouseEvent e) {
            System.out.println("Event: " + e);
            try {
                Robot robot = new Robot();
                robot.mouseMove(0, 0);// When I use (getX(),getY()) instead of (0,0) the robot will not move the mouse at all even though getX() and getY() are the coordinates I want the mouse to be moved to.  Also the mouse can still escape, even when crosshair.x and crosshair.y are used as the coordinates.  It seems that robot is too slow.
            }
            catch (AWTException ex) {
                ex.printStackTrace();
            }
        }

        public void mouseEntered(MouseEvent e){
        }

        public void mousePressed(MouseEvent e) {
        }

        public void mouseReleased(MouseEvent e) {
        }

        public void mouseClicked(MouseEvent e) {
        }
}

另一个类:

public class Crosshair{
        public int x;
    public int y;
    public Crosshair(int x, int y) {
        this.x = x;
        this.y = y;
        }
}

1
如果我是用户,这样做会真的很惹恼我。 - Lawrence Dol
我知道你的意思,但这是必要的,因为我已经设置了Hotas Cougar(一个操纵杆)来模拟鼠标移动,并且我编写了我的代码,使十字准心由鼠标移动控制。我计划加入一些功能,以便窗口可以以用户友好的方式“释放”鼠标。 - ubiquibacon
3个回答

5

我想你可以使用全局事件监听器来监听框架的鼠标进入事件。然后在鼠标退出事件上,我想你可以使用机器人来重置鼠标的位置,一旦它离开了框架。


好的Camick,我已经阅读了你的博客(非常好),并在mouseExited事件中设置了一个机器人。该机器人将鼠标移回到一组绝对坐标,但我希望机器人将鼠标移回到它退出的确切位置。我使用了getX()和getY(),但机器人不会将鼠标移回那里(我认为响应时间太慢了)。我还使用了我的准星坐标(crosshair.x和crosshair.y),但即使如此,如果用户在正确的时间点击,鼠标也可能逃脱。有什么想法吗? - ubiquibacon
你应该使用event.getX()和event.getY()。这些点是相对于框架的,而Robot需要使用相对于屏幕的位置。你可以使用SwingUtilities类中的方法来进行转换。并且你可能需要调整这些点,因为mouseExited事件不一定会在框架边界生成点,这取决于鼠标拖动的速度。 - camickr
关于鼠标逃逸并单击另一个组件,我不确定如何解决这个问题(尽管我无法复制它,我猜我太慢了)。无论如何,当我运行你的演示时,我的CPU跳到50%,因为你有一个无限循环。这显然不是构建游戏的方式。也许如果CPU没有被占用,机器人会更快地响应。游戏循环通常可以通过Swing计时器来控制,以便您不会占用CPU。我也不确定您从哪里获取代码。Swing是双缓冲的,因此不需要使用BufferStrategy。 - camickr
我修改了这个教程的代码:http://www.gamedeveloperskills.com/?p=52当我在我的电脑上运行代码时,我的CPU使用率只会跳到13%(但我有一台相当强大的机器)。 我已经尝试过e.getX和e.getY,但结果不太令人满意(因为如果我移动得很快,事件注册之前鼠标可能已经进入其他监视器之一,因此事件的坐标实际上在我所指的另一个监视器中)。 使用实际准心坐标(crosshair.x和crosshair.y)似乎给我带来了最好的结果,但鼠标仍然可能逃脱。 - ubiquibacon
看起来用鼠标事件控制由我的操纵杆驱动的准星不会按照我想要的方式工作。我想我必须回去尝试使用某种类型的操纵杆输入,除非你有更好的想法。提醒一下,我的操纵杆是Hotas Cougar(我现在可以从操纵杆生成几乎任何鼠标或键盘输入,只是让它像一个鼠标)。 - ubiquibacon
关于GameDeveloperSkill的代码,请查看下面我的回答。 - camickr

3

我不是游戏开发者,但根据我所看到的信息,链接中提供的代码对于一个简单的“乒乓球游戏”来说非常糟糕。我复制了代码并运行,使得球可以弹动。

在我的电脑上,由于浪费CPU时间来提供球移动的动画效果,无限循环导致CPU使用率达到50%。而将它改为休眠5毫秒的线程,CPU使用率降至约18%。

然后,我将代码更改为在Swing面板上进行球的动画效果,这样代码更易编写,CPU使用率降至1%,球的动画效果也更快。

这是我的最终版本。如果不将面板添加到框架中并再次调用drawFrame()方法,则应能够切换回使用缓冲策略。

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.image.BufferStrategy;

import javax.swing.*;

public class Game3 extends JFrame {

    /**
     * @author Georgi Khomeriki
     */

    private Ball ball;

    // added this
    private JPanel gamePanel;

    private int windowWidth = 800;
    private int windowHeight = 600;


    public static void main(String[] args) {
        new Game3();
    }

    public Game3() {
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setSize(windowWidth, windowHeight);
        this.setResizable(false);
        this.setLocation(100, 100);
        this.setVisible(true);

        this.createBufferStrategy(2);

        initGame3();
/*
        while(true) {
            long start = System.currentTimeMillis();
            gameLoop();
            while(System.currentTimeMillis()-start < 5) {
                 //do nothing
            }
        }
*/

        Thread thread = new Thread(new Runnable()
        {
            public void run()
            {
                while(true)
                {
                    gameLoop();
                    try { Thread.sleep(5); }
                    catch(Exception e) {}
                }
            }
        });
        thread.start();

    }

    private void initGame3() {
        // all you're game variables should be initialized here
        ball = new Ball(windowWidth/2, windowHeight/2, 5, 5);

        // added these lines

        gamePanel = new GamePanel();
        gamePanel.setBackground(Color.BLACK);
        add(gamePanel);
    }

    private void gameLoop() {
        // your game logic goes here

// move the ball
    ball.x = ball.x + ball.dx;
    ball.y = ball.y + ball.dy;

    // change the direction of the ball if it hits a wall
    if(ball.x <= 0 || ball.x >= windowWidth-40)
        ball.dx = -ball.dx;
    if(ball.y <= 0 || ball.y >= windowHeight-40)
        ball.dy = -ball.dy;


        // changed to following to use Swing instead of buffer strategy
//      drawFrame();
        gamePanel.repaint();
    }

    private void drawFrame() {
        // code for the drawing goes here
        BufferStrategy bf = this.getBufferStrategy();
        Graphics g = null;

        try {
            g = bf.getDrawGraphics();

            // clear the back buffer (just draw a big black rectangle over it)
            g.setColor(Color.BLACK);
            g.fillRect(0, 0, windowWidth, windowHeight);

drawBall(g);


        } finally {
            // It is best to dispose() a Graphics object when done with it.
            g.dispose();
        }

        // Shows the contents of the backbuffer on the screen.
        bf.show();

        //Tell the System to do the Drawing now, otherwise it can take a few extra ms until
        //Drawing is done which looks very jerky
        Toolkit.getDefaultToolkit().sync();
    }

    private void drawBall(Graphics g) {

        g.setColor(Color.GREEN);
        g.fillOval(ball.x, ball.y, 40, 40);
    }

    // added this

    class GamePanel extends JPanel
    {
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            drawBall(g);
        }
    }


    class Ball {

        public int x;
        public int y;
        public int dx;
        public int dy;

        public Ball(int x, int y, int dx, int dy) {
            this.x = x;
            this.y = y;
            this.dx = dx;
            this.dy = dy;
        }
    }



}

我还有其他事情要赶,但我一定会在本周晚些时候看一下。谢谢! - ubiquibacon

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接