使用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;
这会导致:
- 鼠标按下时,
InputHandler把mouseDown设为true,但第一个tick执行后就被强制改成false - 后续帧的
tick中,isMouseDown拿到的都是false,按钮自然无法保持红色 - 只有当刚好在
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
相关产品推荐
相关产品推荐

