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

使用Graphics2D绘制的按钮悬停按下时渲染异常排查

问题描述

我正在为自制RPG游戏的菜单编写按钮组件,需求是当鼠标悬停在按钮上且按下时,按钮变为红色(c1为绿色,c2为红色)。但实际测试时按钮只会偶尔闪红,多数时候保持绿色,请问问题出在哪?

PushButton 代码

package rpg.menu;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import rpg.main.CatHero;

public class PushButton extends Button {
  public boolean hovering;
  public boolean isMouseDown;
  public PushButton(int x, int y, int width, int height, String message, int id,
      Color c1, Color c2) {
    super(x, y, width, height, message, id, c1, c2, true);
  }
  public void tick() {
    isMouseDown = CatHero.getCatHero().in.mouseDown;
    Point p = new Point(
        CatHero.getCatHero().in.mouseX, CatHero.getCatHero().in.mouseY);
    if (contains(p)) {
      hovering = true;
    } else if (!contains(p)) {
      hovering = false;
    }

    if (isMouseDown && hovering) {
      press();
    }
    CatHero.getCatHero().in.mouseDown = false;
  }
  public void render(Graphics2D g) {
    if (isMouseDown && hovering) {
      g.setColor(c2); // 按下时变红
    } else {
      g.setColor(c1); // 未按下或松开时变绿
    }

    g.fill(this);
    g.setColor(Color.BLACK);
    g.setFont(new Font("Arial", Font.PLAIN, (width / 5)));
    g.drawString(message, x, y + (height / 2));
  }
}

InputHandler 代码

package rpg.io;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

public class InputHandler implements KeyListener, MouseListener, MouseMotionListener {
    public boolean[] key = new boolean[68836];
    public boolean mouseDown = false;
    public int mouseX;
    public int mouseY;
    public boolean allowKeyboardInput = true;
    public boolean allowMouseInput = true;

    public InputHandler() {
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        // 按需处理鼠标点击
    }

    @Override
    public void mousePressed(MouseEvent e) {
        if (allowMouseInput) {
            mouseDown = true;
        }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        if (allowMouseInput) {
            mouseDown = false;
        }
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        // 按需处理鼠标进入
    }

    @Override
    public void mouseExited(MouseEvent e) {
        // 按需处理鼠标退出
    }

    @Override
    public void keyTyped(KeyEvent e) {
        // 按需处理按键输入
    }

    @Override
    public void keyPressed(KeyEvent e) {
        if (allowKeyboardInput) {
            key[e.getKeyCode()] = true;
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        if (allowKeyboardInput) {
            key[e.getKeyCode()] = false;
        }
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        // 按需处理鼠标拖拽
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        if (allowMouseInput) {
            mouseX = e.getX();
            mouseY = e.getY();
        }
    }
}

Button 基类代码

package rpg.menu;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;

import rpg.main.CatHero;

public class Button extends Rectangle{
public boolean isEnabled;
public String message;
public int id;
public Color c1;
public Color c2;
    public Button(int x, int y, int width, int height, String message, int id, Color c1, Color c2, boolean startAs) {
    super(x,y,width,height);
    this.message = message;
    this.id = id;
    isEnabled = startAs;
    this.c1 = c1;
    this.c2 = c2;
    }
    public void press() {
        CatHero.getCatHero().handleButton(id);
    }
    public void toggle() {
    isEnabled = !isEnabled;
    }
    public boolean touches(Rectangle r) {
        return r.intersects(this);
    }
}

主类 CatHero 代码

package rpg.main;

import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;

import javax.swing.JFrame;

import rpg.game.saveload.Settings;
import rpg.io.InputHandler;
import rpg.menu.PushButton;
import rpg.menu.ToggleButton;

public class CatHero extends Canvas implements Runnable {
public Thread thread;
public boolean isRunning = false;
public Graphics2D g;
public static final int CANVAS_WIDTH = 1920;
public static final int CANVAS_HEIGHT = 1080;
public static final int CANVAS_HEIGHT_TILE = 18;
public static final int CANVAS_WIDTH_TILE = 60;
public static CatHero game;
public InputHandler in;
public ToggleButton tb;
public PushButton pb;
public int gameDiffuculty;
public int gameState;
public boolean allowPause;
public Settings sett;
    public CatHero() {
        tb = new ToggleButton(0,0,50,50,"test",0,true,Color.GREEN,Color.RED);
         in = new InputHandler();
         pb = new PushButton(0,50,50,50,"say hello",1, Color.GREEN,Color.RED);
         sett = new Settings();
        addKeyListener(in);
        addMouseListener(in);
        addMouseMotionListener(in);
        in.allowKeyboardInput = true;
        in.allowMouseInput = true;
        allowPause = true;
        gameState = 0;
        /*
         * 0: 菜单
         * 1: 游戏中
         * 2: 暂停
         * 3: 战斗
         * 4: 背包
         */
    }
    @Override
    public void run() {
        while(isRunning) {
            tick();
            render();
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
    }
    private void start() {
        if(isRunning)
            return;
        isRunning = true;
        thread = new Thread(this);
        thread.start();
    }
    private void stop() {
        if(!isRunning)
            return;
        isRunning = false;
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
public void tick() {
    tb.tick();
    pb.tick();
    }
public void render() {
    BufferStrategy bs = getBufferStrategy();
    if(bs == null) {
        createBufferStrategy(2);
        return;
    }
    g = (Graphics2D) bs.getDrawGraphics();
    tb.render(g);
    pb.render(g);
g.dispose();
bs.show();
}
public static void main(String args[]) {
     game = new CatHero();
     JFrame frame = new JFrame("Cat Hero");
     frame.setSize(CANVAS_WIDTH,CANVAS_HEIGHT);
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     frame.setVisible(true);
     frame.add(game);
     game.start();
}
public void handleButton(int id) {
    if(id == 0) {
        sett.testDiffuculty = tb.isEnabled;
    }
    if(id == 1) {
        System.out.println(sett.testDiffuculty);
    }
}
public void loadSettings(Settings s) {
    
    
}
public static CatHero getCatHero() {
    return game;    
}
public void cutsceneModeToggle() {
in.allowMouseInput = !in.allowMouseInput;
in.allowKeyboardInput = !in.allowKeyboardInput;
}
}

问题原因与修复方案

核心问题:强制重置全局mouseDown状态

在PushButton.tick()方法的最后一行,你直接将全局的mouseDown设为false:

CatHero.getCatHero().in.mouseDown = false;

这会导致:

  1. 鼠标按下时,InputHandler把mouseDown设为true,但第一个tick执行后就被强制改成false
  2. 后续帧的tick中,isMouseDown拿到的都是false,按钮自然无法保持红色
  3. 只有当刚好在mousePressed事件触发后的那1帧检测到状态,才会闪一下红

修复后的PushButton代码

package rpg.menu;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.FontMetrics;
import rpg.main.CatHero;

public class PushButton extends Button {
  public boolean hovering;
  
  public PushButton(int x, int y, int width, int height, String message, int id, Color c1, Color c2) {
    super(x, y, width, height, message, id, c1, c2, true);
  }
  
  public void tick() {
    Point p = new Point(CatHero.getCatHero().in.mouseX, CatHero.getCatHero().in.mouseY);
    // 简化hovering判断逻辑
    hovering = contains(p);

    // 仅在鼠标按下且悬停时触发点击,避免重复触发可添加标记
    if (CatHero.getCatHero().in.mouseDown && hovering) {
      press();
      // 可选:如果要防止重复触发press,可给InputHandler加mouseHandled标记,而非直接重置mouseDown
      CatHero.getCatHero().in.mouseDown = false;
    }
  }
  
  public void render(Graphics2D g) {
    // 直接使用全局输入状态判断,去掉冗余的isMouseDown变量
    if (CatHero.getCatHero().in.mouseDown && hovering) {
      g.setColor(c2); // 按下时变红
    } else {
      g.setColor(c1); // 未按下或松开时变绿
    }

    g.fill(this);
    g.setColor(Color.BLACK);
    Font buttonFont = new Font("Arial", Font.PLAIN, width / 5);
    g.setFont(buttonFont);
    
    // 调整文本位置实现垂直居中
    FontMetrics metrics = g.getFontMetrics(buttonFont);
    int textY = y + (height / 2) + (metrics.getAscent() / 2);
    g.drawString(message, x, textY);
  }
}

额外优化建议

  • 移除PushButton中的isMouseDown成员变量,直接使用InputHandler的全局状态,减少冗余
  • 如果需要防止按钮重复触发press,建议给InputHandler添加mouseHandled标记,在tick中判断是否已处理,不要直接修改全局输入状态
  • 统一输入状态的管理逻辑,避免在组件类中直接修改全局输入变量

内容的提问来源于stack exchange,提问作者user15132300

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 00:15:55