You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Graphics2D玩家跳跃后无法恢复原动作的技术问题

基于Java Graphics2D的游戏角色跳跃后无法恢复前置动作的解决方案

问题说明

同时按住D键(向右移动)和SPACE键(跳跃),松开SPACE键后,角色无法继续向右移动——当前按键处理逻辑在keyReleased中直接重置了移动状态,完全没考虑其他按键仍处于按压状态的情况。

代码问题分析

  • 单键状态覆盖:keyPressed中直接设置dx和动画,但没有维护当前所有按下的按键,多键按压时后触发的按键会覆盖之前的状态
  • 释放时盲目重置:keyReleased里直接把moving设为false、非SPACE键时把dx设为0,不管其他键是否还按着,导致松开跳跃键时,移动状态被错误清空
  • 动作记录逻辑错误:用ArrayList记录动作的思路不对,它只能记录操作顺序,无法实时反映当前的按键状态

解决方案:维护实时按键状态集合

核心思路是用一个集合存储当前所有按下的按键码,在每一帧的update中根据集合内的按键来计算角色状态,而不是在单次按键事件中直接修改状态。

具体修改步骤

  1. 新增集合存储当前按下的按键:private Set<Integer> pressedKeys = new HashSet<>();
  2. 新增跳跃触发标志位(避免按住SPACE连续跳):private boolean jumpTriggered = false;
  3. keyPressed时仅将按键码加入集合,不直接修改角色状态
  4. keyReleased时移除按键码,重置跳跃触发标志
  5. 在update方法中根据集合内的按键,计算移动方向、切换动画、处理跳跃

修改后的完整代码

import java.awt.Image;
import java.awt.event.KeyEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.ImageIcon;

public class Player2 {
    private boolean blocked = false;
    private int x, y, dx, dy;
    Image imagem;
    private int altura, largura;
    private boolean direita = false;
    private boolean esquerda = false;
    private final int GRAVITY = 1;
    private final int JUMP_FORCE = -20;
    private final int GROUND_Y = 750;
    private int yVel;
    boolean moving = false;

    // 新增:存储当前按下的按键集合
    private Set<Integer> pressedKeys = new HashSet<>();
    // 新增:跳跃触发标志,避免按住SPACE连续跳
    private boolean jumpTriggered = false;

    public Player2() {
        x = 100;
        y = GROUND_Y;
        yVel = 0;
    }

    public void update() {
        // 重置移动速度
        dx = 0;
        moving = false;

        // 处理移动按键
        if (pressedKeys.contains(KeyEvent.VK_D)) {
            dx = 5;
            direita = true;
            esquerda = false;
            moving = true;
            setRunRightImage();
        }
        if (pressedKeys.contains(KeyEvent.VK_A)) {
            dx = -5;
            direita = false;
            esquerda = true;
            moving = true;
            setRunLeftImage();
        }

        // 处理下蹲按键(仅在地面且未移动时)
        if (pressedKeys.contains(KeyEvent.VK_S) && y == GROUND_Y && !moving) {
            setCrouchImage();
            blocked = true;
        } else {
            blocked = false;
        }

        // 处理跳跃按键(仅在地面且未触发过跳跃)
        if (pressedKeys.contains(KeyEvent.VK_SPACE) && y == GROUND_Y && !jumpTriggered) {
            yVel = JUMP_FORCE;
            setJumpImage();
            jumpTriggered = true;
            moving = true;
        }

        // 如果没有任何移动动作且在地面,切换到 idle 动画
        if (!moving && y == GROUND_Y && !blocked) {
            setIdleImage();
        }

        // 应用重力和位置更新
        x += dx;
        yVel += GRAVITY;
        y += yVel;

        if (y >= GROUND_Y) {
            y = GROUND_Y;
            yVel = 0;
            // 回到地面后重置跳跃触发标志
            jumpTriggered = false;
        }
    }

    void keyPressed(KeyEvent botaoPressionado) {
        pressedKeys.add(botaoPressionado.getKeyCode());
        // 处理ESC菜单
        if (botaoPressionado.getKeyCode() == KeyEvent.VK_ESCAPE) {
            Menu menu = new Menu();
            menu.setVisible(true);
        }
    }

    public void keyReleased(KeyEvent botaoSoltado) {
        pressedKeys.remove(botaoSoltado.getKeyCode());
        // 松开SPACE时重置跳跃触发标志
        if (botaoSoltado.getKeyCode() == KeyEvent.VK_SPACE) {
            jumpTriggered = false;
        }
    }

    // 封装图片设置方法,避免重复代码
    private void setIdleImage() {
        ImageIcon referencia = new ImageIcon("res\\Colour2//player//120x80_gifs//__Idle.gif");
        imagem = referencia.getImage();
    }

    private void setRunRightImage() {
        ImageIcon referencia = new ImageIcon("res\\Colour2//player//120x80_gifs//__RunNormal.gif");
        imagem = referencia.getImage();
    }

    private void setRunLeftImage() {
        ImageIcon referencia = new ImageIcon("res\\Colour2//player//120x80_gifs//__RunBehind.gif");
        imagem = referencia.getImage();
    }

    private void setJumpImage() {
        ImageIcon referencia = new ImageIcon("res\\Colour2//player//120x80_gifs//__Jump.gif");
        imagem = referencia.getImage();
    }

    private void setCrouchImage() {
        ImageIcon referencia = new ImageIcon("res\\Colour2//player//120x80_gifs//__Crouch.gif");
        imagem = referencia.getImage();
    }

    void load() {
        setIdleImage();
        altura = imagem.getHeight(null);
        largura = imagem.getWidth(null);
    }

    // 原nadaAinda方法保留空实现以防报错
    private void nadaAinda() {}
}

关键优化点

  • 用HashSet实时维护当前按下的按键,彻底解决多键按压的状态丢失问题
  • 将状态计算逻辑统一放到update方法,每一帧都根据当前按键状态刷新,动作切换更准确
  • 新增跳跃触发标志,避免按住SPACE键时角色连续跳跃
  • 封装图片设置方法,减少代码重复,便于维护

内容的提问来源于stack exchange,提问作者XN 2X

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 00:44:54