【问题标题】:How to make smoother movement in my java code如何在我的 Java 代码中进行更流畅的移动
【发布时间】:2021-11-20 20:44:20
【问题描述】:

我试图让我的动作更顺畅。当我按下向上键时它会上升,然后当我按下左键时它会停止然后向左转。我想做到,所以当我按右时,它会立即做到,没有延迟。我怎么能用我的代码

                
        import java.awt.event.*;
        import javax.swing.*;
        import java.awt.*;
        import java.awt.Image;
        import javax.swing.*;
        
        
        class MyFrame extends JFrame implements KeyListener {
            JLabel label;
            
            MyFrame(){ 
             this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
             addKeyListener(this);
             ImageIcon imageIcon = new ImageIcon("C:\\Users\\jacob\\Downloads\\player.png");
             
             Image image = imageIcon.getImage();
             Image newimg = image.getScaledInstance(150, 150,  java.awt.Image.SCALE_SMOOTH);
             imageIcon = new ImageIcon(newimg);
             label = new JLabel(imageIcon);
             this.add(label);
             this.setSize(500, 500);
             this.setVisible(true);
         }
            @Override
            public void keyTyped(KeyEvent e) {
                // TODO Auto-generated method stub
                
            }
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_UP)
                    label.setLocation(label.getX(), label.getY()-10);
                else if (e.getKeyCode() == KeyEvent.VK_DOWN)
                    label.setLocation(label.getX(), label.getY()+10);
                else if (e.getKeyCode() == KeyEvent.VK_LEFT) 
                    label.setLocation(label.getX()-10, label.getY());
                else if (e.getKeyCode() == KeyEvent.VK_RIGHT) 
                    label.setLocation(label.getX()+10, label.getY());
                
            }
            @Override
            public void keyReleased(KeyEvent e) {
                // TODO Auto-generated method stub
                
            }
        }

【问题讨论】:

  • 你考虑过使用 Swing Timer 类吗?这可能是您更好的解决方案。
  • 尝试像 stackoverflow.com/questions/2623995/… 这样实现它,您将按键存储在一个哈希集中,所以它不能仅在事件之外起作用..
  • 不要使用 KeyListener。相反,您应该使用Key Bindings。请参阅:Motion Using the KeyboardKeyboardAnimation.java 会做你想做的事。

标签: java


【解决方案1】:

这里的主要问题是不同的操作系统具有不同的按键重复功能时间(重复按键传递的频率和加速程度)。

解决这个问题的方法是维护当前键是否关闭的状态,并在某些计时器线程中,如果状态为真,则执行操作。

例如,(在非常粗糙的 Java 中,我认为如果不修复就无法编译)你想要这样的东西

// mark that the key is down
public void keyPressed(KeyEvent e) {
    keyboardState[e.getKeyCode()] = true;
}

// mark that the key has been released
public void keyReleased(KeyEvent e) {
    keyboardState[e.getKeyCode()] = false;
}

// this function should be called at some repeatable interval, fast enough where it looks "smooth"
public void calledAtFixedInterval(int delta) {
    // we can query definitively if the key is currently down or up, rather than waiting on some event to be delivered at an arbitrary interval
    if (keyboardState[KeyEvent.VK_LEFT] == true) {
        // your logic here
    }
}

这种架构大致是大多数游戏引擎的工作方式——它们将使用事件驱动架构来处理用户输入,但只需维护一个巨大的地图,说明当前哪些键已关闭——这样就可以轻松快速地查询是否有键在渲染循环中被按下。

【讨论】:

    【解决方案2】:

    我的猜测是可能会产生velocityX 和velocityY。 而且,如果你想要流畅的动作。你需要一个 tick 方法。

    Java游戏开发的基本tick方法:

    public synchronized void start() {
        thread = new Thread(this);
        thread.start();
        running = true;
    }
    
    public synchronized void stop() {
        try {
            thread.join();
        }catch(Exception e){
            e.printStackTrace();
        }
        }
    
    public void run() {
        long lastTime = System.nanoTime();
        double ns =1000000000 / 60.0;
        double delta = 0;
        long timer = System.currentTimeMillis();
        int ticks = 0;
        int frames = 0;
        while(running) {
            long now = System.nanoTime();
            delta += (now - lastTime) / ns;
            lastTime = now;
            while(delta >= 1) {
                tick();
                ticks++;
                delta--;
            }
            if(running) {
                render();
                frames++;
            }
            
            if(System.currentTimeMillis() - timer > 1000) {
                timer += 1000;
                System.out.println(ticks + " tps" + " | " + frames + " fps");
                frame.setTitle(name + " | " + ticks + " tps" + "  " + frames + " fps");
                frames = 0;
                ticks = 0;
                
                //meanings
                //ns = nanoseconds
                //amountOfTicks = ticks per second
                
            }
        }
        stop();
    }
    

    开始和停止方法是针对线程的。 你可以找到关于如何做tick方法的简单教程并完全理解它。

    当你的刻度正常工作时,你可以让你的 x 和 y += velX 和 velY,就像这样:

    public void tick() {
            y += velY;
            x += velX;
    }
    

    让玩家每次更新更新 x 和 y

    而 velX 和 velY 需要对您的 KeyInputs 做出反应,所以它们,你知道,有一个值,像这样:

    if(key == KeyEvent.VK_W) { tempObject.setVelY(-10); keyDown[0] = true; }
                    if(key == KeyEvent.VK_S) { tempObject.setVelY(10); keyDown[1] = 
    true; }
                    if(key == KeyEvent.VK_D) { tempObject.setVelX(10); keyDown[2] = 
    true; }
                    if(key == KeyEvent.VK_A) { tempObject.setVelX(-10); keyDown[3] = 
    true; }
    

    此代码,请勿复制粘贴。它有不同的方法,因为我将这段代码基于我不久前制作的游戏。所以方法不同。

    所以当你得到它时,你基本上就完成了。

    setVelY 和 setVelX 看起来像这样:

    public void setVelY(int velY){
        this.velY = velY;
    }
    
    public void setVelY(int velY){
        this.velX = velX;
    }
    

    所以,结论。 如果你想要更流畅的动画,你需要一个 tick 方法,所以 x 和 y 每秒更新一次。

    如果你复制粘贴,我的代码基本上没有任何作用,但它只是如何使动作平滑的概念。

    所以 x 和 y 需要每秒更新一次。制作 Runnable tick 方法是您唯一的方法。

    我真的希望这对你有一点帮助。

    祝你好运! :D

    【讨论】:

    • 谢谢。我应该将 tick 方法放在我的代码中的什么位置?
    • 可以把tick方法放到主类中。我喜欢让我的代码井井有条,所以我做的所有项目的 main 方法都称为 Game ,它只是为了让它更干净,但是 tick 方法可以插入到代码的任何部分,当然,不能插入另一个方法中。而且,别忘了,有tick方法的类需要实现Runnable,只是为了让run方法真正运行。而且我有我的代码结构,所以主tick方法位于类Game中,所以我基本上只是将所有具有tick方法的类调用到主tick方法。
    • (继续最后的评论)就像这样(记住这是主要的tick方法。) public void tick(){ example.tick(); example2.tick();所以在这里你可以看到,我调用了 example 和 example2 tick 方法。你可以查看我制作的游戏的开源代码,看看我是如何做到的,以便更好地了解我想要解释的内容。我很不擅长解释,所以实际的代码会比我更能为你服务。 github.com/Robatortas/P-O-N-G/tree/main/PONG/src/main 这是代码的链接。在那里你可以看到我是如何做到的。祝你今天过得愉快! :D
    • 您能做的最好的事情之一就是玩弄其他人的代码以更好地理解它! :D
    【解决方案3】:

    我现在常用的一个解决方案是将游戏动画线程与输入事件(鼠标和键盘)分开,采用松散耦合在两者之间进行通信。

    游戏循环可以由以 60 fps 运行的java.util.Timer 驱动。我使用这个速率是因为显示器以 60 fps 刷新是很常见的,因此以更快的速率运行会带来收益递减。

    有些人更喜欢使用javax.swing.Timer,但如果这是个好主意的话,请使用 IDK。 EDT(事件调度线程)可能会成为瓶颈。 (经典的 Java 游戏编程书籍:Killer Game Programming in Java 比较了两个计时器,而 util Timer 胜出。)可以说,ScheduledThreadPoolExecutor 比任何一个都更健壮,尽管它需要一个如果您还没有使用 Executors 进行多线程处理,那会有点学习曲线。 (我更喜欢使用JavaFX,所以我使用它的AnimationTimer。)

    保存游戏循环的类应该具有公共方法,其中触发的键盘或鼠标事件可以简单地设置变量或切换布尔值(或将变量添加到队列顶部)。键盘的公共输入法可以有一些额外的处理来比较传入的键事件和当前的键状态(例如,保持的键状态映射,如本线程其他地方所述)并为游戏循环线程设置一个字段阅读并采取行动。

    这种游戏程序的架构增加了大量的可预测性和控制性,并澄清了各个层的逻辑。它还充分利用了当今处理器的多线程功能。

    【讨论】:

    • Swing 组件或组件状态的更新应在 EDT 上完成。所以你应该使用javax.swing.Timer 来确保所有更新都在 EDT 上完成。
    • Goetz:Java Concurrency in Practice,第 192 页。“对于简单的、短时间运行的任务,整个操作可以留在事件线程中;对于运行时间较长的任务,应该卸载一些处理到另一个线程。”大多数游戏循环工作应该局限于模型和控制级别,而不是直接与 Swing 组件相关。在不涉及 Swing 组件的情况下,您真的希望额外的处理减慢 EDT 的速度吗?此外,同一页面(靠近顶部),有效异常:“重新绘制排队的方法”可从任何线程调用。
    • 应该在 EDT 上设置组件的位置。在 Swing 尝试绘制组件时,您不希望某些 Thread 更新位置的(远程)可能性。这就是您获得难以复制的随机结果的方式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 2018-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-17
    • 1970-01-01
    相关资源
    最近更新 更多