我目前正在使用JavaFX制作一个类型速度游戏,其中单词应该从顶部掉下来,用户必须在下降到底部之前尽可能快地输入它们。我已经准备好了游戏的基本设置。我唯一在纠结的是如何让单词从顶部下降并下降到底部(目前它们从底部到顶部传播(。而且我希望多个单词以它们之间的特定时间间隔(例如 30 毫秒(从顶部的随机位置(不是同一原点(落下。到目前为止,我拥有的代码:
public void showWords() throws InterruptedException
{
int missedWords = 0; // number of words the user failed to type
while (missedWords != 10)
{
dequedWord = queue.dequeue(); // the word that the Text object will contain
Text runWord = new Text(dequedWord);
wordsPane.getChildren().add(runWord); // the canvas in which the words will travel from top to bottom
double PaneHeight = wordsPane.getHeight();
//double PaneWidth = wordsPane.getWidth();
double runWordWidth = runWord.getLayoutBounds().getWidth();
KeyValue initKeyValue = new KeyValue(runWord.translateYProperty(), PaneHeight);
KeyFrame initFrame = new KeyFrame(Duration.ZERO, initKeyValue);
KeyValue endKeyValue = new KeyValue(runWord.translateYProperty(), -1.0 * runWordWidth);
KeyFrame endFrame = new KeyFrame(Duration.seconds(12), endKeyValue);
Timeline timeline = new Timeline(initFrame, endFrame);
timeline.setCycleCount(1);
timeline.play();
// add code to check whether user typed the word in the Text object
missedWords++;
}
}
我是动画新手,所以我对时间轴、键值和关键帧类了解不多。我尝试阅读 API 的文档,但对我没有多大帮助。任何帮助将不胜感激。谢谢:)
坐标系的y轴向下指向(这在计算机图形学中很常见(。这就是您的节点向错误方向移动的原因。此外,Timeline
似乎不太适合这里,因为您需要为每个单词运行一个Timeline
,并运行另一个Timeline
来添加新单词。
我建议改用AnimationTimer
,其中包含一个为每个帧调用的方法,它允许您根据时间更新位置、删除旧单词和添加新单词。
例:
@Override
public void start(Stage primaryStage) {
final Queue<String> words = new LinkedList<>(Arrays.asList(
"Hello",
"World",
"foo",
"bar"
));
final Pane wordsPane = new Pane();
wordsPane.setPrefSize(800, 400);
final long wordDelay = 500_000_000L; // 500 ms
final long fallDuration = 12_000_000_000L; // 12 s
AnimationTimer animation = new AnimationTimer() {
private long lastWordAdd = Long.MIN_VALUE; // never added a word before
private final Map<Text, Long> nodes = new LinkedHashMap<>();
private double nextX = 0;
private void assignXPosition(Text text) {
text.setTranslateX(nextX);
nextX += text.getBoundsInLocal().getWidth();
}
@Override
public void handle(long now) {
// updates & cleanup
long deletionLimit = now - fallDuration;
for (Iterator<Map.Entry<Text, Long>> iter = nodes.entrySet().iterator(); iter.hasNext();) {
Map.Entry<Text, Long> entry = iter.next();
final Text text = entry.getKey();
final long startTime = entry.getValue();
if (startTime < deletionLimit) {
// delete old word
iter.remove();
wordsPane.getChildren().remove(text);
} else {
// update existing word
double factor = ((double) (now - startTime)) / fallDuration;
Bounds bounds = text.getBoundsInLocal();
text.setTranslateY((wordsPane.getHeight() + bounds.getHeight()) * factor - bounds.getMaxY());
}
}
if (words.isEmpty()) {
if (nodes.isEmpty()) {
stop(); // end animation since there are no more words
}
} else if (lastWordAdd + wordDelay <= now) {
lastWordAdd = now;
// add new word
Text text = new Text(words.remove());
wordsPane.getChildren().add(text);
assignXPosition(text);
text.setTranslateY(-text.getBoundsInLocal().getMaxY());
nodes.put(text, now);
}
}
};
animation.start();
Scene scene = new Scene(wordsPane);
primaryStage.setScene(scene);
primaryStage.show();
}