如何为 JavaFX 编写 KeyListener
Posted
技术标签:
【中文标题】如何为 JavaFX 编写 KeyListener【英文标题】:How to write a KeyListener for JavaFX 【发布时间】:2015-07-09 20:10:22 【问题描述】:我想写一个小游戏,我可以使用 W、A、S、D 键。
我有一个getPosX()
和setPosX()
,但我不知道如何写一个KeyListener
,例如如果我按 D,则计算 setPosX(getPosX()+1)
。
我该怎么办?
【问题讨论】:
看看this answer。 【参考方案1】:来自JavaRanch Forum post。
在场景中添加按键和释放处理程序,并更新应用程序中记录的移动状态变量。一个动画计时器与 JavaFX 脉冲机制挂钩(默认情况下,该机制将被限制为每秒触发 60 次事件)——因此这是一种游戏“循环”。在计时器中检查移动状态变量并将它们的增量动作应用于角色位置 - 这实际上在屏幕上移动角色以响应按键。
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
/**
* Hold down an arrow key to have your hero move around the screen.
* Hold down the shift key to have the hero run.
*/
public class Runner extends Application
private static final double W = 600, H = 400;
private static final String HERO_IMAGE_LOC =
"http://icons.iconarchive.com/icons/raindropmemory/legendora/64/Hero-icon.png";
private Image heroImage;
private Node hero;
boolean running, goNorth, goSouth, goEast, goWest;
@Override
public void start(Stage stage) throws Exception
heroImage = new Image(HERO_IMAGE_LOC);
hero = new ImageView(heroImage);
Group dungeon = new Group(hero);
moveHeroTo(W / 2, H / 2);
Scene scene = new Scene(dungeon, W, H, Color.FORESTGREEN);
scene.setOnKeyPressed(new EventHandler<KeyEvent>()
@Override
public void handle(KeyEvent event)
switch (event.getCode())
case UP: goNorth = true; break;
case DOWN: goSouth = true; break;
case LEFT: goWest = true; break;
case RIGHT: goEast = true; break;
case SHIFT: running = true; break;
);
scene.setOnKeyReleased(new EventHandler<KeyEvent>()
@Override
public void handle(KeyEvent event)
switch (event.getCode())
case UP: goNorth = false; break;
case DOWN: goSouth = false; break;
case LEFT: goWest = false; break;
case RIGHT: goEast = false; break;
case SHIFT: running = false; break;
);
stage.setScene(scene);
stage.show();
AnimationTimer timer = new AnimationTimer()
@Override
public void handle(long now)
int dx = 0, dy = 0;
if (goNorth) dy -= 1;
if (goSouth) dy += 1;
if (goEast) dx += 1;
if (goWest) dx -= 1;
if (running) dx *= 3; dy *= 3;
moveHeroBy(dx, dy);
;
timer.start();
private void moveHeroBy(int dx, int dy)
if (dx == 0 && dy == 0) return;
final double cx = hero.getBoundsInLocal().getWidth() / 2;
final double cy = hero.getBoundsInLocal().getHeight() / 2;
double x = cx + hero.getLayoutX() + dx;
double y = cy + hero.getLayoutY() + dy;
moveHeroTo(x, y);
private void moveHeroTo(double x, double y)
final double cx = hero.getBoundsInLocal().getWidth() / 2;
final double cy = hero.getBoundsInLocal().getHeight() / 2;
if (x - cx >= 0 &&
x + cx <= W &&
y - cy >= 0 &&
y + cy <= H)
hero.relocate(x - cx, y - cy);
public static void main(String[] args) launch(args);
如果已经提供的信息足以满足您的目的,请忽略此答案的其余部分。
虽然上述解决方案足以回答这个问题,但如果有兴趣,可以在此演示突破游戏中找到更复杂的输入处理程序(具有更通用和分离的输入和更新处理逻辑):
Breakout input handler。来自示例突围游戏的通用输入处理程序示例:
class InputHandler implements EventHandler<KeyEvent>
final private Set<KeyCode> activeKeys = new HashSet<>();
@Override
public void handle(KeyEvent event)
if (KeyEvent.KEY_PRESSED.equals(event.getEventType()))
activeKeys.add(event.getCode());
else if (KeyEvent.KEY_RELEASED.equals(event.getEventType()))
activeKeys.remove(event.getCode());
public Set<KeyCode> getActiveKeys()
return Collections.unmodifiableSet(activeKeys);
虽然ObservableSet
具有适当的设置更改侦听器可以用于活动键集,但我使用了一个访问器,它返回一组不可修改的键,这些键在快照中及时处于活动状态,因为这就是我对这里感兴趣,而不是实时观察活动键集的变化。
通用输入处理程序用法示例:
Scene gameScene = createGameScene();
// register the input handler to the game scene.
InputHandler inputHandler = new InputHandler();
gameScene.setOnKeyPressed(inputHandler);
gameScene.setOnKeyReleased(inputHandler);
gameLoop = createGameLoop();
// . . .
private AnimationTimer createGameLoop()
return new AnimationTimer()
public void handle(long now)
update(now, inputHandler.getActiveKeys());
if (gameState.isGameOver())
this.stop();
;
public void update(long now, Set<KeyCode> activeKeys)
applyInputToPaddle(activeKeys);
// . . . rest of logic to update game state and view.
// The paddle is sprite implementation with
// an in-built velocity setting that is used to
// update its position for each frame.
//
// on user input, The paddle velocity changes
// to point in the correct predefined direction.
private void applyInputToPaddle(Set<KeyCode> activeKeys)
Point2D paddleVelocity = Point2D.ZERO;
if (activeKeys.contains(KeyCode.LEFT))
paddleVelocity = paddleVelocity.add(paddleLeftVelocity);
if (activeKeys.contains(KeyCode.RIGHT))
paddleVelocity = paddleVelocity.add(paddleRightVelocity);
gameState.getPaddle().setVelocity(paddleVelocity);
【讨论】:
在加载 fxml 时如何执行此操作。似乎不起作用,我是否必须使用注释@FXML以某种方式注册监听器? @FlyingSwissman 这个例子真的与 FXML 没有任何关系。在示例中,关键事件处理程序被添加到场景中,即使应用程序是基于 FXML 的,也不会在 FXML 中定义。如果您对事件处理程序和 FXML 仍有疑问,请通过 mcve 将它们作为新问题提出。【参考方案2】:Scene myScene = new Scene();
KeyCombination cntrlZ = new KeyCodeCombination(KeyCode.Z, KeyCodeCombination.CONTROL_DOWN);
myScene.setOnKeyPressed(new EventHandler<KeyEvent>()
@Override
public void handle(KeyEvent event)
if(contrlZ.match(event))
//Do something
);
【讨论】:
【参考方案3】:使用 JNativeHook: https://github.com/kwhat/jnativehook
<!-- https://mvnrepository.com/artifact/com.1stleg/jnativehook -->
<dependency>
<groupId>com.1stleg</groupId>
<artifactId>jnativehook</artifactId>
<version>2.1.0</version>
</dependency>
private void initKeyListener(Stage primaryStage)
/* Note: JNativeHook does *NOT* operate on the event dispatching thread.
* Because Swing components must be accessed on the event dispatching
* thread, you *MUST* wrap access to Swing components using the
* SwingUtilities.invokeLater() or EventQueue.invokeLater() methods.
*/
try
GlobalScreen.registerNativeHook();
catch (NativeHookException e1)
// TODO Auto-generated catch block
e1.printStackTrace();
GlobalScreen.addNativeKeyListener(new NativeKeyListener()
public void nativeKeyPressed(NativeKeyEvent e)
if ( (e.getModifiers() & NativeKeyEvent.CTRL_MASK) != 0
&& (e.getModifiers() & NativeKeyEvent.ALT_MASK) != 0
&& (e.getKeyCode() == NativeKeyEvent.VC_B))
logger.debug("key :Hide");
primaryStage.hide();
if ( (e.getModifiers() & NativeKeyEvent.CTRL_MASK) != 0
&& (e.getModifiers() & NativeKeyEvent.SHIFT_MASK) != 0
&& (e.getModifiers() & NativeKeyEvent.ALT_MASK) != 0
&& (e.getKeyCode() == NativeKeyEvent.VC_B))
logger.debug("key :Show");
primaryStage.show();
//System.out.println("Key Pressed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
public void nativeKeyReleased(NativeKeyEvent e)
//System.out.println("Key Released: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
public void nativeKeyTyped(NativeKeyEvent e)
//System.out.println("Key Typed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
);
/*
GlobalScreen.addNativeMouseListener(new NativeMouseListener()
@Override
public void nativeMouseReleased(NativeMouseEvent arg0)
// TODO Auto-generated method stub
System.out.println("MouseReleased()");
@Override
public void nativeMousePressed(NativeMouseEvent arg0)
// TODO Auto-generated method stub
System.out.println("MousePressed()");
@Override
public void nativeMouseClicked(NativeMouseEvent arg0)
// TODO Auto-generated method stub
System.out.println("MouseClicked()");
);
*/
【讨论】:
如果核心 javafx 完全可以解决 OP 的要求,为什么还要使用第三方库? 此实现用于通过按键显示和隐藏舞台。如果是隐藏的,那么一定是这样监听的。 啊,我明白了..但这与问题有什么关系?以上是关于如何为 JavaFX 编写 KeyListener的主要内容,如果未能解决你的问题,请参考以下文章