在键输入上使用 JavaFX 动画精灵

Animating a sprite with JavaFX on key input

我是 JavaFX 的新手,但对面向对象的 Java 有很好的理解。下面的程序是两个示例的组合,一个是动画和移动形状,另一个是在按下鼠标按钮时为对象设置动画。许多功能已根据我的需要被删除或更改。

我搜索了很多例子,但没有找到一个我完全理解的关于移动精灵和按键动画的例子press.In我的程序我确定我没有使用正确的classes 来创建游戏对象,即使经过一些调整我确信它可以工作。

我添加了一些 println 函数来测试动画。问题似乎是 walkSouth 动画中的 KeyFrame 部分不是 working/playing.

我的问题是:

  1. 我是否应该使用不同的 JavaFX classes 来创建 sprite-sheet 动画?
  2. 此代码是否可以轻松适应功能,以便我可以更好地了解 JavaFX 的工作原理。

这里是主要的class:

package testing;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import javafx.util.Duration;

public class Main extends Application {

    private enum UserAction{
        NONE,NORTH,SOUTH;
    }
    private static  int APP_W = 200;
    private static  int APP_H = 200;

    private Scene scene;

    private UserAction action = UserAction.NONE;

    private Timeline timeline = new Timeline();
    private boolean running = true;
    private int FPS = 60;

    private Parent createContent(){
        Pane root = new Pane();
        root.setPrefSize(APP_W,APP_H);

        Image cat_image = new Image("file:res/cata.png");
        GameObject obj = new GameObject(cat_image,12,8);
        obj.setTranslateX(100);
        obj.setTranslateY(100);

        KeyFrame frame = new KeyFrame(Duration.millis(1000/FPS), event -> {
            if(!running)
                return;

            switch(action){
                case NORTH:
                    obj.setTranslateY(obj.getTranslateY()-1);
                    break;

                case SOUTH:
                    obj.walkSouth();
                    obj.setTranslateY(obj.getTranslateY()+1);
                    break;
                case NONE:
                    obj.pauseAnimation();
                    break;
            }
        });

        timeline.getKeyFrames().add(frame);
        timeline.setCycleCount(Timeline.INDEFINITE);

        root.getChildren().add(obj);

        return root;
    }

    private void restartGame(){
        stopGame();
        startGame();
    }
    private void stopGame(){
        running = false;
        timeline.stop();
    }
    private void startGame(){
        timeline.play();
        running = true;
    }

    public void start(Stage primaryStage) throws Exception{
        scene = new Scene(createContent());

        scene.setOnKeyPressed(event -> {
            switch (event.getCode()) {
                case W:
                    action = UserAction.NORTH;
                    break;
                case S:
                    action = UserAction.SOUTH;
                    break;
            }
        });

        scene.setOnKeyReleased(event -> {
            switch (event.getCode()) {
                case W:
                    action = UserAction.NONE;
                    break;
                case S:
                    action = UserAction.NONE;
                    break;
            }
        });

        primaryStage.setTitle("Simple Animation");
        primaryStage.setScene(scene);
        primaryStage.show();
        startGame();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

这是游戏对象 class:

package testing;

import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.util.Duration;


/**
 * Created by matt on 26/02/17.
 */
public class GameObject extends Pane {

    ObjectImage objectImage;

    public GameObject( Image image, int columns, int rows){
        objectImage = new ObjectImage(image,columns,rows);
        getChildren().setAll(objectImage);
    }

    public void pauseAnimation(){
        getChildren().setAll(objectImage);
        objectImage.pauseAnimation();
    }

    public void walkSouth(){
        getChildren().setAll(objectImage);
        objectImage.walkSouth();
    }

}

class ObjectImage extends ImageView {

    private Rectangle2D[] clips;
    private double  width,height;
    private Timeline timeline = new Timeline();

    public ObjectImage(Image image,int columns,int rows){

        width = image.getWidth()/columns;
        height = image.getHeight()/rows;

        clips = new Rectangle2D[rows*columns];
        int count=0;
        for(int row =0;row < rows;row++ )
            for(int column = 0 ; column < columns; column++,count++)
                clips[count] = new Rectangle2D(width * column, height * row,width,height);

        setImage(image);
        setViewport(clips[0]);

    }

    public void pauseAnimation(){
        timeline.pause();
    }

    public void walkSouth(){
        System.out.println("walk south test");
        IntegerProperty count = new SimpleIntegerProperty(0);

        KeyFrame frame = new KeyFrame( Duration.millis(1000/5), event -> {
            if(count.get() < 2) count.set(count.get()+1);
            else count.set(0);
            setViewport(clips[count.get()]);
            System.out.println("frame test");
        });

        timeline.setCycleCount(timeline.INDEFINITE);
        timeline.getKeyFrames();
        timeline.play();
    }
}

This is the sprite-sheet image I'm working with

This is the outcome

正如评论所暗示的那样,您确实忘记了在 walkSouth 方法中添加框架。 (你还在walkSouth方法中设置了每个图片帧为200ms。你是想改变它吗?)这是改变后的代码:

public void walkSouth(){
    System.out.println("walk south test");
    IntegerProperty count = new SimpleIntegerProperty(0);

    KeyFrame frame = new KeyFrame( Duration.millis(1000/FPS), event -> {
        if(count.get() < 2) count.set(count.get()+1);
        else count.set(0);
        setViewport(clips[count.get()]);
    });

    timeline.setCycleCount(timeline.INDEFINITE);
    timeline.getKeyFrames().add(frame); //This was the offending line.
    timeline.play();
}

要回答您的第一个问题,是的,您可以使用 classes 的许多其他选项。您可以做的两个选择是使用 AnimationTimerTransition class。这是对两者的简要说明(带有代码示例)。

AnimationTimer 在渲染的每个周期或帧中被调用,我相信您可能想要这个:

public void walkSouth(){
    System.out.println("walk south test");
    IntegerProperty count = new SimpleIntegerProperty(0);

    AnimationTimer tmr = new AnimationTimer() {
        @Override
        public void handle(long nanoTime)
        {
            //nanoTime specifies the current time at the beginning of the frame in nano seconds.
            if(count.get() < 2) count.set(count.get()+1);
            else count.set(0);
            setViewport(clips[count.get()]);
        }

    };
    tmr.start();
    //call tmr.stop() to stop/ pause timer.
}

但是,如果您不希望每一帧都调用动画,则可以扩展 Transition。转换有一个 frac(小数)值,范围从 0 到 1,该值随时间增加。我不打算详细介绍,但我相信您可以在 api.

上查找更多信息
public void walkSouth(){
    System.out.println("walk south test");
    IntegerProperty count = new SimpleIntegerProperty(0);

    Transition trans = new Transition() {
        {
            setCycleDuration(Duration.millis(1000 / 60.0));
        }
        @Override
        public void interpolate(double frac)
        {
            if (frac != 1) 
                return;
            //End of one cycle.
            if(count.get() < 2) count.set(count.get()+1);
            else count.set(0);
            setViewport(clips[count.get()]);
        }

    };
    trans.setCycleCount(Animation.INDEFINITE);
    trans.playFromStart();
    //Use trans.pause to pause, trans.stop to stop.
}