首页 > 解决方案 > 在迭代到下一个动画之前,循环不会等待动画完成

问题描述

在玩家移动一块石头(类似于 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();
}

标签: javajavafx

解决方案


ASequentialTransition可以由其他SequentialTransitions组成。对于您的代码,您可以创建一个“主”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);
    }

}

推荐阅读