java - 在迭代到下一个动画之前,循环不会等待动画完成
问题描述
在玩家移动一块石头(类似于 Candycrush 的游戏)之后,在逻辑中我收集有关玩家移动是否导致结构的信息,然后需要爆炸。当然,一旦结构通过移除结构元素并在上方掉落石头而爆炸,就会出现新的结构,也需要按顺序爆炸。
为此,我有一个 AnimationData 类,它有一个 ExplosionData 列表,它具有找到的结构的大小,由初始玩家移动引起。
我的代码只适用于一次爆炸,但如果有多次爆炸就会混乱。问题是循环不会等到爆炸动画完成后才继续迭代。
澄清:方法 updateGui,在 switchAnimation.setOnFinished 内循环
视觉上: 我录制的单个爆炸和多个爆炸的剪辑
public void updateGui(AnimationData aData) {
final int rowHeight = (int) (boardGPane.getHeight() / boardGPane.getRowConstraints().size());
Coords switchSourceCoords = aData.getSwitchSourceCoords();
Coords switchTargetCoords = aData.getSwitchTargetCoords();
// Apply player move
ParallelTransition switchAnimation = switchStones(switchSourceCoords, switchTargetCoords);
switchAnimation.play();
// Revert switch, if the move was invalid
if (aData.geteData().isEmpty()) {
switchAnimation.setOnFinished(event -> {
ParallelTransition switchBackAnimation = switchStones(switchSourceCoords, switchTargetCoords);
switchBackAnimation.play();
});
} else {
switchAnimation.setOnFinished(event -> {
// Animate explosions for every found Structure
for (ExplosionData eData : aData.geteData()) {
SequentialTransition explosionAnimation = new SequentialTransition();
// Coordinates of where the bonusStone appears
Coords bonusSource = eData.getBonusSourceCoords();
// Coordinates of where the bonusStone need to be repositioned
Coords bonusTarget = eData.getBonusTargetCoords();
// Remove all Structure elements and make Stones above drop to their target
// positions. Also translate them back to the same position for the animation
removeStructureAndReplaceIvs(eData, bonusTarget, bonusSource, rowHeight);
// This shall only proceed if the animation involves handeling a bonusStone
if (bonusSource != null && bonusTarget != null) {
int rowsToMove = bonusTarget.getRow() - bonusSource.getRow();
ImageView bonusIv = (ImageView) JavaFXGUI.getNodeFromGridPane(boardGPane, bonusTarget.getCol(), bonusTarget.getRow());
// BonusStone shall fade in at the source Position
explosionAnimation = bonusStoneFadeIn(explosionAnimation, rowsToMove, bonusIv, rowHeight);
// Translate to targetPosition, if sourcePosition is not equal to targetPosition
explosionAnimation = bonusStoneMoveToTargetCoords(explosionAnimation, rowsToMove, bonusIv, rowHeight);
}
// Make the Stone ImageViews translate from their origin position to their new target positions
explosionAnimation = dropAndFillUpEmptySpace(explosionAnimation, eData, bonusTarget, bonusSource, rowHeight);
explosionAnimation.play();
}
});
}
}
private void removeStructureAndReplaceIvs(ExplosionData eData,
Coords bonusTargetCoords,
Coords bonusSourceCoords,
final int rowHeight) {
// Removing the Structure and all stones above by deleting the ImageViews col by col
for (DropInfo info : eData.getExplosionInfo()) {
// Coordinates of the Structure element that is going to be removed in this col
int col = info.getCoords().getCol();
int row = info.getCoords().getRow();
// If a bonusStone will apear, the heightOffset gets reduced by one
int offset = getAppropiateOffset(bonusTargetCoords, info, col);
// Remove the Structure and all ImageViews above
removeImageViewsFromCells(col, row, row + 1);
List<String> stoneToken = info.getFallingStoneToken();
for (int r = row, i = 0; r >= 0; --r, ++i) {
// Fill up removed Cells with new ImageViews values
ImageView newIv = new ImageView(new Image(preImagePath + stoneToken.get(i) + ".png"));
// Place each iv to their target Coords
addImageViewToPane(newIv, col, r);
// Translate all non-bonusStones to the position they were placed before
if (ignoreBonusTargetCoordinates(bonusTargetCoords, bonusSourceCoords, r, col)) {
newIv.setTranslateY(-rowHeight * offset);
}
}
}
}
// If the removed Structure results to generate a bonusStone, make it fade in at source position
private SequentialTransition bonusStoneFadeIn(SequentialTransition explosionAnimation,
int sourceToTargetDiff,
ImageView bonusIv,
final int rowHeight) {
FadeTransition bonusFadeIn = new FadeTransition(Duration.seconds(1), bonusIv);
bonusFadeIn.setFromValue(0f);
bonusFadeIn.setToValue(1f);
// If the target Position is not the same, place it to target and translate to source position
if (sourceToTargetDiff > 0) {
bonusIv.setTranslateY(-rowHeight * sourceToTargetDiff);
}
explosionAnimation.getChildren().add(bonusFadeIn);
return explosionAnimation;
}
// If the bonusStone must be moved from source Coordinates to target Coordinates
private SequentialTransition bonusStoneMoveToTargetCoords(SequentialTransition explosionAnimation,
int sourceToTargetDiff,
ImageView bonusIv,
final int rowHeight) {
// Difference in row from bonusSourceCoordinates to bonusTargetCoordinates
if (sourceToTargetDiff > 0) {
TranslateTransition moveToTargetCoords = new TranslateTransition(Duration.seconds(1), bonusIv);
moveToTargetCoords.fromYProperty().set(-rowHeight * sourceToTargetDiff);
moveToTargetCoords.toYProperty().set(0);
explosionAnimation.getChildren().add(moveToTargetCoords);
}
return explosionAnimation;
}
private SequentialTransition dropAndFillUpEmptySpace(SequentialTransition explosionAnimation,
ExplosionData eData,
Coords bonusTargetCoords,
Coords bonusSourceCoords,
final int rowHeight) {
ParallelTransition animateDrop = new ParallelTransition();
for (int i = 0; i < eData.getExplosionInfo().size(); i++) {
// List of all stoneToken to create respective ImageViews for each col
List<DropInfo> allDropInfo = eData.getExplosionInfo();
int col = allDropInfo.get(i).getCoords().getCol();
int row = allDropInfo.get(i).getCoords().getRow();
// If a bonusStone will apear, the heightOffset gets reduced by one
int offset = getAppropiateOffset(bonusTargetCoords, allDropInfo.get(i), col);
for (int r = row; r >= 0; --r) {
// Drop all Stones above the removed Structure to fill up the empty space
// Ignore possible bonusStones since they are being animated seperately
if (ignoreBonusTargetCoordinates(bonusTargetCoords, bonusSourceCoords, r, col)) {
ImageView iv = (ImageView) JavaFXGUI.getNodeFromGridPane(boardGPane, col, r);
TranslateTransition tt = new TranslateTransition(Duration.millis(1500), iv);
tt.fromYProperty().set(-rowHeight * offset);
tt.toYProperty().set(0);
animateDrop.getChildren().add(tt);
}
}
}
explosionAnimation.getChildren().add(animateDrop);
return explosionAnimation;
}
private int getAppropiateOffset(Coords bonusTargetCoords, DropInfo dropInfo, int col) {
int bonusOffset = (bonusTargetCoords != null && col == bonusTargetCoords.getCol()) ? 1 : 0;
return dropInfo.getHeightOffset() - bonusOffset;
}
private boolean ignoreBonusTargetCoordinates(Coords bonusTargetCoords,
Coords bonusSourceCoords,
int row,
int col) {
return bonusSourceCoords == null
|| bonusTargetCoords != null && col != bonusTargetCoords.getCol()
|| bonusTargetCoords != null && row != bonusTargetCoords.getRow();
}
解决方案
ASequentialTransition
可以由其他SequentialTransition
s组成。对于您的代码,您可以创建一个“主”SequentialTransition
并在SequentialTransition
每次循环迭代时创建它for
。然后你会播放主转换。
switchAnimation.setOnFinished(event -> {
SequentialTransition masterAnimation = new SequentialTransition();
for (ExplosionData eData : aData.geteData()) {
SequentialTransition explosionAnimation = new SequentialTransition();
// ... configure the explosionAnimation ...
masterAnimation.getChildren().add(explosionAnimation); // add to masterAnimation
}
masterAnimation.play(); // play all the explosionAnimations in squential order
});
您的代码在继续循环的下一次迭代之前不等待动画完成的原因是因为Animation.play()
是“异步调用”。当您调用play()
动画时,动画会在后台使用一些内部时钟/计时器进行安排,并且该方法会立即返回。
Animation
从当前位置按 指示的方向 播放rate
。如果Animation
正在运行,则没有效果。当
rate > 0
(forward play)时,如果anAnimation
已经定位到最后,则不播放第一个循环,认为已经结束。如果 an位于开头,这也适用于后向 (rate < 0
) 循环。Animation
但是,如果动画有cycleCount > 1
,则将照常播放以下循环。当
Animation
到达结束时,动画停止并且播放头保持在结束处。
Animation
从结尾向后播放:
animation.setRate(negative rate);
animation.jumpTo(overall duration of animation);
animation.play();
笔记:
play()
是异步调用,Animation
可能不会立即启动。
(强调我的)
这是一个可行的例子。它需要一个Rectangle
并将其翻译到场景的每个角落。每个“平移到角落”都是一个单独的动画,它还涉及旋转Rectangle
和改变它的颜色。然后将所有“平移到角落”动画放入一个SequentialTransition
. 单击时禁用顶部的,并在主节点完成Button
后重新启用。SequentialTransition
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.ParallelTransition;
import javafx.animation.RotateTransition;
import javafx.animation.SequentialTransition;
import javafx.animation.Timeline;
import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Separator;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class Main extends Application {
private Button playBtn;
private StackPane groupParent;
private Rectangle rectangle;
@Override
public void start(Stage primaryStage) {
playBtn = new Button("Play Animation");
playBtn.setOnAction(ae -> {
ae.consume();
playBtn.setDisable(true);
playAnimation();
});
HBox btnBox = new HBox(playBtn);
btnBox.setAlignment(Pos.CENTER);
btnBox.setPadding(new Insets(8));
rectangle = new Rectangle(150, 100, Color.BLUE);
groupParent = new StackPane(new Group(rectangle));
groupParent.getChildren().get(0).setManaged(false);
VBox root = new VBox(btnBox, new Separator(), groupParent);
root.setMaxSize(600, 400);
root.setAlignment(Pos.CENTER);
VBox.setVgrow(groupParent, Priority.ALWAYS);
Scene scene = new Scene(root, 600, 400);
primaryStage.setScene(scene);
primaryStage.setTitle("Animation");
primaryStage.setResizable(false);
primaryStage.show();
}
private void playAnimation() {
double maxX = groupParent.getWidth() - rectangle.getWidth();
double maxY = groupParent.getHeight() - rectangle.getHeight();
ParallelTransition pt1 = createAnimation(-25, maxY - 25, 90, Color.FIREBRICK);
ParallelTransition pt2 = createAnimation(maxX, maxY, 180, Color.BLUE);
ParallelTransition pt3 = createAnimation(maxX + 25, 25, 270, Color.FIREBRICK);
ParallelTransition pt4 = createAnimation(0, 0, 360, Color.BLUE);
SequentialTransition st = new SequentialTransition(rectangle, pt1, pt2, pt3, pt4);
st.setOnFinished(ae -> {
ae.consume();
rectangle.setTranslateX(0);
rectangle.setTranslateY(0);
rectangle.setRotate(0);
playBtn.setDisable(false);
});
st.play();
}
private ParallelTransition createAnimation(double x, double y, double r, Color c) {
TranslateTransition tt = new TranslateTransition(Duration.seconds(1.0));
tt.setToX(x);
tt.setToY(y);
RotateTransition rt = new RotateTransition(Duration.seconds(1));
rt.setToAngle(r);
Timeline tl = new Timeline(new KeyFrame(Duration.seconds(1), new KeyValue(rectangle.fillProperty(), c)));
return new ParallelTransition(tt, rt, tl);
}
}
推荐阅读
- c# - 添加数据时发生未处理的异常
- spring-boot - JavaMail Spring boot 属性不识别属性
- android - 从 ARcore 会话获取 RGB 和深度图像时出现 MIssingGlContextApplication 异常
- html - CSS在文本附近写入元素标签
- python - Itterating through a list of objects from custom made class
- jmeter - 如何在jmeter中单击pop uo URL上的按钮而不记录脚本,以便将数据保存在应用程序中
- javascript - $ 不是函数(在 JQuery 中发生错误)
- python-3.x - Selenium - 动态改变ip
- java - 如何从写入的文件中获取数字
- android - 处理带有阴影偏移的拖放