FXGL 04.动画 (可爱的乌贼娘)
我们在之前的文章中简单创建了一个png格式的图片展示的实体,这里我们要更换换这个实体的展示模式,让一个活灵活现的乌贼娘出现在我们的游戏世界中!
1.实体的展示过程
这样的一个实体创建过程我们并不陌生,如果需要将某个素材作为展示内容,我们只需要在创建中的view方法中加入其中。
Entity entity = FXGL.entityBuilder().with(new MoveComponent()).build();
那我们来探究下view方法的本质。(我并不太懂Kotlin)
但是还是找到了以下源码
fun view(node: Node) = this.also {
entity.viewComponent.addChild(node)
}
fun view(textureName: String) = this.also {
view(FXGL.texture(textureName))
}
Node 类是大多数javafx的形状图形的顶级抽象类,我们熟悉的shape,box等类都继承自该类。明显可以理解viewcomponent是实体的实际展示控制类。
那如果想要在程序运行时修改实体的动画内容,很容易就能想到需要做就是修改entity中的viewcomponent
2.从素材网站中下载素材
这里我推荐使用爱给网,免费且资源众多。
在其中可以下载需要的各种素材。
3.修改之前的实体展示
我这里下载了一张乌贼娘的行走图
首先对于实体静态工厂,我将其中的view部分移除
public static Entity createEntity(EntityType type){
switch (type) {
case PLANE -> {
Entity entity = FXGL.entityBuilder().with(new MoveComponent()).build();
entity.setType(EntityType.PLANE);
return entity;
}
default -> {
return null;
}
}
}
然后修改移动组件,为其增加动画内容
package com.dam.wonder.component;
import com.almasb.fxgl.core.math.Vec2;
import com.almasb.fxgl.entity.component.Component;
import com.almasb.fxgl.texture.AnimatedTexture;
import com.almasb.fxgl.texture.AnimationChannel;
import javafx.scene.image.Image;
import javafx.util.Duration;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MoveComponent extends Component {
private double speedX = 0d;
private double speedY = 0d;
/**
* Called after the component is added to entity.
*/
@Override
public void onAdded() {
entity.getViewComponent().addChild(texture);
}
private double maxSpeed = 4d;
private double aTime = 1d;
private boolean speedXAdd;
private boolean speedYAdd;
//这里增加一个面向属性,用来判断实体在各种速度下的面向问题
private int face = 1;
//动画频道 对应四个方向的动画
private final AnimationChannel up;
private final AnimationChannel down;
private final AnimationChannel right;
private final AnimationChannel left;
//动画素材
private final AnimatedTexture texture;
@Override
public void onUpdate(double tpf) {
// log.info("当前状态下 x速度为=[{}], Y速度为=[{}] x加速状态为=[{}] Y加速状态为 =[{}]",speedX,speedY,speedXAdd,speedYAdd);
int tempFace = face;
if (speedX != 0d) {
Vec2 dir = Vec2.fromAngle(entity.getRotation() - 360)
.mulLocal(speedX);
entity.translate(dir);
if (speedX>0) {
tempFace = 1;
}else {
tempFace = 2;
}
}
if (speedY != 0d) {
Vec2 dir = Vec2.fromAngle(entity.getRotation() - 90)
.mulLocal(speedY);
entity.translate(dir);
if (speedY > 0) {
tempFace = 3;
}else {
tempFace = 4;
}
}
if (!speedXAdd) {
slowDownSpeed(true);
}
if (!speedYAdd) {
slowDownSpeed(false);
}
if (tempFace != face) {
if (tempFace == 1) {
this.texture.loopAnimationChannel(left);
}else if (tempFace == 2){
this.texture.loopAnimationChannel(right);
}else if (tempFace == 3) {
this.texture.loopAnimationChannel(up);
}else {
this.texture.loopAnimationChannel(down);
}
face = tempFace;
}
}
public MoveComponent () {
//在构造方法中增加加载动画的方法
Image image = new Image("assets/textures/player.png");
//这里通过对图片的规划,每一行的动画个数,每个帧的宽度高度,整个动画的跨度时间,跨度几个动画
down = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 0, 3);
right = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 4, 7);
left = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 8, 11);
up = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 12, 15);
texture = new AnimatedTexture(up);
//播放动画
texture.loop();
}
public void up() {
changeSpeed(true,false);
}
public void left() {
changeSpeed(false,true);
}
public void right() {
changeSpeed(true,true);
}
public void down(){
changeSpeed(false,false);
}
public void stop() {
speedX = 0d;
speedY = 0d;
}
public void stopX() {
speedXAdd = false;
}
public void stopY() {
speedYAdd = false;
}
/**
* 改变移动速度 主动改变
* @param upOrDown
* @param xOrY
*/
private void changeSpeed(boolean upOrDown,boolean xOrY) {
if (xOrY) {
speedXAdd = true;
if (upOrDown) {
if (speedX < maxSpeed) {
speedX = speedX + (float)maxSpeed/(10*aTime) + 0.01;
}
}else {
if (speedX > -maxSpeed) {
speedX = speedX - (float)maxSpeed/(10f*aTime) - 0.01;
}
}
}else {
speedYAdd = true;
if (upOrDown) {
if (speedY < maxSpeed) {
speedY = speedY + (float)maxSpeed/(10f*aTime) + 0.01;
}
}else {
if (speedY > -maxSpeed) {
speedY = speedY - (float)maxSpeed/(10f*aTime) - 0.01;
}
}
}
// log.info("实体当前状态为 位置=[{}],速度Y=[{}],速度X =[{}]",entity.getPosition(),this.speedY,this.speedX);
}
/**
* 速度减少 被动减速
* @param xOrY
*/
private void slowDownSpeed(boolean xOrY) {
if (xOrY) {
if (speedX > 0.5) {
speedX = speedX - (float)speedX/10 -0.01;}
else if (speedX< -0.5){
speedX = speedX - (float)speedX/10 +0.01;
}else {
speedX = 0d;
}
}else {
if (speedY > 0.5) {
speedY = speedY - (float)speedY/10 -0.01;}
else if (speedY< -0.5){
speedY = speedY - (float)speedY/10 +0.01;
}else {
speedY = 0d;
}
}
}
public double getSpeedX() {
return speedX;
}
public void setSpeedX(double speedX) {
this.speedX = speedX;
}
public double getSpeedY() {
return speedY;
}
public void setSpeedY(double speedY) {
this.speedY = speedY;
}
public double getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(double maxSpeed) {
this.maxSpeed = maxSpeed;
}
public double getaTime() {
return aTime;
}
public void setaTime(double aTime) {
this.aTime = aTime;
}
}
简简单单的启动项目,一个乌贼娘跃然纸上啦!
当然,只到这一步我是不满足的,因为这个乌贼娘在速度为零的时候依然保持着移动动画。进行一点点小小的修改。
很容易想到,简单的添加四个全新的信道。
private final AnimationChannel up;
private final AnimationChannel upHold;
private final AnimationChannel down;
private final AnimationChannel downHold;
private final AnimationChannel right;
private final AnimationChannel rightHold;
private final AnimationChannel left;
private final AnimationChannel leftHold;
private final AnimatedTexture texture;
简单的初始化他们
down = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 0, 3);
downHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 1, 1);
right = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 4, 7);
rightHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 5, 5);
leftHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 9, 9);
left = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 8, 11);
up = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 12, 15);
upHold = new AnimationChannel(image, 4, 32, 38, Duration.seconds(1), 13, 13);
再简单的判断速度为零时修改为静止信道
@Override
public void onUpdate(double tpf) {
// log.info("当前状态下 x速度为=[{}], Y速度为=[{}] x加速状态为=[{}] Y加速状态为 =[{}]",speedX,speedY,speedXAdd,speedYAdd);
int tempFace = face;
if (speedX != 0d) {
Vec2 dir = Vec2.fromAngle(entity.getRotation() - 360)
.mulLocal(speedX);
entity.translate(dir);
if (speedX>0) {
tempFace = 1;
}else if (speedX<0){
tempFace = 2;
}
}else {
//转变面向
if (tempFace == 1) {
tempFace = 5;
}else if (tempFace == 2) {
tempFace = 6;
}
}
if (speedY != 0d) {
Vec2 dir = Vec2.fromAngle(entity.getRotation() - 90)
.mulLocal(speedY);
entity.translate(dir);
if (speedY > 0) {
tempFace = 3;
}else {
tempFace = 4;
}
}else {
if (tempFace == 3) {
tempFace = 7;
}else if (tempFace == 4) {
tempFace = 8;
}
}
if (!speedXAdd) {
slowDownSpeed(true);
}
if (!speedYAdd) {
slowDownSpeed(false);
}
if (tempFace != face) {
if (tempFace == 1) {
this.texture.loopAnimationChannel(left);
}else if (tempFace == 2){
this.texture.loopAnimationChannel(right);
}else if (tempFace == 3) {
this.texture.loopAnimationChannel(up);
}else if (tempFace == 4){
this.texture.loopAnimationChannel(down);
}else if (tempFace == 5) {
this.texture.loopAnimationChannel(leftHold);
}else if (tempFace == 6) {
this.texture.loopAnimationChannel(rightHold);
}else if (tempFace == 7){
this.texture.loopAnimationChannel(upHold);
}else if (tempFace == 8) {
this.texture.loopAnimationChannel(downHold);
}
face = tempFace;
}
}
非常容易啊,啪的一下,这个完整的动画过程就做完了。
当然除了动态素材动画信道的方法,还有一个使用的更多的动画建造器方式
(animationBuilder)
这个方法能自定义很多动画,例如各种变形动画,粒子特效等,也能设置回调用于控制动画。在官方wiki中有详细介绍,这里就不多赘述了。