我的世界1.20.1forge模组开发进阶教程——Geckolib动画实体(3)
注意:本章涉及大量的geckolib底层代码,补充讲解了上一节没讲的,如果看不懂请去学习Java
GeoEntity
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package software.bernie.geckolib.animatable;
import javax.annotation.Nullable;
import net.minecraft.world.entity.Entity;
import net.minecraftforge.network.PacketDistributor;
import software.bernie.geckolib.core.animatable.GeoAnimatable;
import software.bernie.geckolib.network.GeckoLibNetwork;
import software.bernie.geckolib.network.SerializableDataTicket;
import software.bernie.geckolib.network.packet.EntityAnimDataSyncPacket;
import software.bernie.geckolib.network.packet.EntityAnimTriggerPacket;
public interface GeoEntity extends GeoAnimatable {
@Nullable
default <D> D getAnimData(SerializableDataTicket<D> dataTicket) {
return (D)this.getAnimatableInstanceCache().getManagerForId((long)((Entity)this).getId()).getData(dataTicket);
}
default <D> void setAnimData(SerializableDataTicket<D> dataTicket, D data) {
Entity entity = (Entity)this;
if (entity.level().isClientSide()) {
this.getAnimatableInstanceCache().getManagerForId((long)entity.getId()).setData(dataTicket, data);
} else {
GeckoLibNetwork.send(new EntityAnimDataSyncPacket(entity.getId(), dataTicket, data), PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> entity));
}
}
default void triggerAnim(@Nullable String controllerName, String animName) {
Entity entity = (Entity)this;
if (entity.level().isClientSide()) {
this.getAnimatableInstanceCache().getManagerForId((long)entity.getId()).tryTriggerAnimation(controllerName, animName);
} else {
GeckoLibNetwork.send(new EntityAnimTriggerPacket(entity.getId(), controllerName, animName), PacketDistributor.TRACKING_ENTITY_AND_SELF.with(() -> entity));
}
}
default double getTick(Object entity) {
return (double)((Entity)entity).tickCount;
}
}
这段代码是 GeckoLib 库中用于处理实体动画的核心接口 GeoEntity
,它为 Minecraft 实体(如生物、玩家等)提供了动画控制、数据同步和网络通信的能力。以下是对代码的逐层解析及其在 GeckoLib 中的作用分析:
代码结构概览
- 接口定义
GeoEntity
继承自GeoAnimatable
,表明这是一个面向实体(Entity)的动画接口。 - 核心方法
包含getAnimData
、setAnimData
、triggerAnim
和getTick
四个方法,分别用于动画数据操作和生命周期管理。
核心方法解析
1. getAnimData(SerializableDataTicket<D> dataTicket)
• 作用
通过 SerializableDataTicket
从实体中获取特定类型的动画数据。
• 实现逻辑
• 通过 getAnimatableInstanceCache()
获取实体动画缓存。
• 使用 (long) ((Entity) this).getId()
获取实体唯一ID,定位动画管理器。
• 调用管理器中的 getData(dataTicket)
获取数据。
• 用途
用于客户端或服务端读取实体的动态动画数据(如自定义动画参数)。
2. setAnimData(SerializableDataTicket<D> dataTicket, D data)
• 作用
设置实体的动画数据,并自动处理网络同步。
• 实现逻辑
• 客户端:直接更新本地动画管理器的数据。
• 服务端:通过 GeckoLibNetwork
发送 EntityAnimDataSyncPacket
数据包,确保所有客户端同步更新。
• 用途
跨端(客户端-服务端)同步动画数据,例如同步实体的血量变化触发的动画参数。
3. triggerAnim(@Nullable String controllerName, String animName)
• 作用
触发实体播放特定名称的动画。
• 实现逻辑
• 客户端:直接调用本地动画管理器的 tryTriggerAnimation
。
• 服务端:发送 EntityAnimTriggerPacket
数据包通知客户端播放动画。
• 用途
在多人游戏中,服务端控制的实体(如 Boss 攻击动作)触发动画时,客户端能正确响应。
4. getTick(Object entity)
• 作用
返回实体的当前游戏刻(tickCount
),用于动画时间计算。
• 用途
为动画系统提供时间基准,确保动画更新与游戏逻辑同步。
在 GeckoLib 中的核心作用
-
动画数据管理
• 通过SerializableDataTicket
实现类型安全的动画数据存取,支持自定义数据类型(如浮点数、布尔值等)。
• 自动处理客户端与服务端之间的数据同步,避免手动编写网络代码。 -
网络通信
• 使用GeckoLibNetwork
发送标准化的动画数据包(EntityAnimDataSyncPacket
、EntityAnimTriggerPacket
)。
•PacketDistributor.TRACKING_ENTITY_AND_SELF
确保动画数据仅同步给关注该实体的客户端。 -
实体动画生命周期
• 依赖getTick
提供的时间基准,驱动动画逐帧更新。
• 通过GeoAnimatable
接口与 GeckoLib 动画核心(如动画控制器、状态机)交互。
设计模式与协作
• 客户端-服务端解耦
通过条件判断 entity.level().isClientSide()
分离本地操作与网络通信,确保单机和多人模式的一致性。
• 数据驱动动画
SerializableDataTicket
允许开发者定义自定义动画参数(如 ATTACKING
、MOVING
),实现数据驱动的动画逻辑。
• 与 AnimatableInstanceCache
协作
动画管理器通过实体 ID 唯一标识,支持同一实体类型多实例的独立动画控制。
典型使用场景
- 同步 Boss 攻击动画
服务端触发triggerAnim("controller", "attack")
,所有客户端播放攻击动画。 - 动态血量条动画
实体血量变化时,通过setAnimData
同步血量百分比到客户端,驱动材质动画。 - 客户端粒子效果触发
客户端通过getAnimData
读取SHOULD_PLAY_PARTICLES
参数,决定是否生成粒子。
总结
这段代码是 GeckoLib 实体动画系统的核心枢纽,实现了动画数据的存储、同步、触发和时间管理。通过标准化接口和网络通信机制,开发者可以专注于动画逻辑本身,而无需处理底层细节,极大简化了复杂实体动画的实现流程。
GeoAnimatable接口
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package software.bernie.geckolib.core.animatable;
import software.bernie.geckolib.core.animatable.instance.AnimatableInstanceCache;
import software.bernie.geckolib.core.animation.AnimatableManager;
public interface GeoAnimatable {
void registerControllers(AnimatableManager.ControllerRegistrar var1);
AnimatableInstanceCache getAnimatableInstanceCache();
default double getBoneResetTime() {
return (double)1.0F;
}
default boolean shouldPlayAnimsWhileGamePaused() {
return false;
}
double getTick(Object var1);
default AnimatableInstanceCache animatableCacheOverride() {
return null;
}
}
代码解释与协作分析
GeoAnimatable
接口是 GeckoLib 动画系统的核心,定义了所有可动画对象(如实体、方块实体)的基础行为。以下是其方法解析及与其他类的协作关系:
1. registerControllers(AnimatableManager.ControllerRegistrar)
• 作用
注册动画控制器(AnimationController
),管理动画逻辑(如状态切换、条件触发)。
• 协作类
• AnimatableManager
:通过 ControllerRegistrar
添加控制器,最终由 AnimatableManager
统一管理所有控制器的更新。
• AnimationController
:用户自定义控制器,处理动画状态机、过渡逻辑。
• 示例
// 实体类实现
public class MyEntity implements GeoAnimatable {
@Override
public void registerControllers(ControllerRegistrar registrar) {
registrar.add(new AnimationController<>(this, "walk_controller", state -> state.setAndContinue(WALK_ANIM)));
}
}
2. getAnimatableInstanceCache()
• 作用
提供动画实例缓存,管理动画数据(如实体ID到动画状态的映射)。
• 协作类
• AnimatableInstanceCache
:默认实现为 SingletonAnimatableInstanceCache
或 InstancedAnimatableInstanceCache
,用于高效复用动画实例。
• GeoEntity/GeoBlockEntity
:具体类通过此方法返回缓存,例如实体使用实体ID作为缓存键。
• 示例
// GeoEntity 实现
public class MyEntity implements GeoEntity {
private final AnimatableInstanceCache cache = new SingletonAnimatableInstanceCache(this);
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return cache;
}
}
3. default double getBoneResetTime()
• 作用
定义骨骼复位时间(动画停止后骨骼恢复原位的时间)。
• 协作类
• GeoBone
:在动画停止时,骨骼根据此时间插值回初始位置。
• AnimationProcessor
:处理骨骼变换时参考此值。
• 覆盖场景
若需快速复位(如机械部件),可返回更小值(如 0.5
)。
4. default boolean shouldPlayAnimsWhileGamePaused()
• 作用
控制游戏暂停时是否继续播放动画(如过场动画需持续播放)。
• 协作类
• AnimatableManager
:在游戏每帧更新时检查此标志,决定是否更新动画。
• 示例
// 过场动画实体
@Override
public boolean shouldPlayAnimsWhileGamePaused() {
return true; // 游戏暂停时继续播放
}
5. double getTick(Object)
• 作用
获取当前时间刻(驱动动画进度的核心参数)。
• 协作类
• GeoModel
:渲染时调用此方法获取时间,计算动画帧。
• AnimationController
:根据 tick
值推进动画进度,处理关键帧事件。
• 实现示例
// 实体实现
@Override
public double getTick(Object entity) {
return ((Entity) entity).tickCount;
}
6. default AnimatableInstanceCache animatableCacheOverride()
• 作用
允许覆盖默认缓存实现(高级用途)。
• 协作类
• InstancedAnimatableInstanceCache
:若需为每个实例独立缓存(如动态生成的实体),可返回自定义缓存。
• 示例
// 自定义缓存策略
@Override
public AnimatableInstanceCache animatableCacheOverride() {
return new MyCustomCache(this);
}
协作流程图
渲染循环 (Game Render Loop)
├── 调用 GeoModel.render()
│ ├── 从 GeoAnimatable.getTick() 获取时间
│ └── 根据时间计算动画进度
│
├── AnimatableManager.updateAnimations()
│ ├── 检查 shouldPlayAnimsWhileGamePaused()
│ ├── 遍历所有 AnimationController
│ └── 调用控制器逻辑(触发动画、状态切换)
│
└── 网络同步 (GeckoLibNetwork)
├── 服务端发送 EntityAnimTriggerPacket
└── 客户端通过 AnimatableInstanceCache 更新动画状态
总结
GeoAnimatable
是 GeckoLib 动画系统的核心接口,通过定义动画控制器注册、缓存管理、时间获取等行为,与以下关键类协作:
• AnimatableManager
:管理动画控制器生命周期。
• AnimatableInstanceCache
:优化动画实例复用。
• AnimationController
:实现具体动画逻辑。
• GeoModel
:驱动渲染流程。
• 网络包:同步客户端与服务端动画状态。
开发者通过实现此接口,可将自定义实体、方块实体等无缝接入 GeckoLib 的动画框架,无需关注底层细节,专注于动画逻辑设计。
AnimatableManager类
AnimatableManager 类在 GeckoLib 中的作用解析
AnimatableManager
是 GeckoLib 动画系统的核心管理类,负责协调 动画控制器(AnimationController)、骨骼状态(Bone Snapshot) 和 动画数据(Extra Data),确保动画逻辑的更新、同步与渲染。以下是其核心功能及在库中的协作关系:
1. 动画控制器的生命周期管理
• 作用
通过 ControllerRegistrar
收集并管理所有注册的动画控制器。
• 协作流程
• 注册阶段:当实体(如 GeoEntity
)实现 GeoAnimatable
接口时,需在 registerControllers
方法中调用 registrar.add(...)
添加控制器(例如移动、攻击控制器)。
• 初始化阶段:AnimatableManager
构造函数调用 animatable.registerControllers(registrar)
,将控制器存入 animationControllers
Map。
• 运行时操作:通过 addController
/removeController
动态增删控制器,支持热更新或条件化动画逻辑。
• 示例代码
// 实体类注册控制器
public class MyEntity implements GeoAnimatable {
@Override
public void registerControllers(ControllerRegistrar registrar) {
registrar.add(new AnimationController<>(this, "walk", this::handleWalk));
registrar.add(new AnimationController<>(this, "attack", this::handleAttack));
}
}
2. 骨骼快照(Bone Snapshot)管理
• 作用
保存骨骼的当前变换状态(位置、旋转、缩放),用于动画混合(Blending)或插值(Interpolation)。
• 协作流程
• 渲染前:GeoModel
调用 getBoneSnapshotCollection()
获取上一帧骨骼状态。
• 动画更新:动画控制器修改骨骼状态后,新状态会被缓存至 boneSnapshotCollection
。
• 平滑过渡:通过对比新旧快照,计算插值以实现平滑动画过渡。
• 关键字段
private final Map<String, BoneSnapshot> boneSnapshotCollection = new Object2ObjectOpenHashMap();
3. 动画数据(Extra Data)传递
• 作用
提供类型安全的键值对存储(DataTicket
),用于跨控制器或动画状态共享数据。
• 协作流程
• 存储数据:通过 setData(ticket, value)
存储自定义数据(如 IS_MOVING
布尔值)。
• 读取数据:控制器在 predicate
或 state
中调用 getData(ticket)
读取数据,决定动画逻辑。
• 示例
// 存储移动状态
animatableManager.setData(DataTicket.BOOLEAN, "IS_MOVING", true);
// 在控制器中读取
if (animatableManager.getData(DataTicket.BOOLEAN, "IS_MOVING")) {
state.setAnimation(WALK_ANIM);
}
4. 动画时间管理
• 作用
跟踪动画更新时间(lastUpdateTime
)和首次更新标志(isFirstTick
),确保动画进度与游戏时间同步。
• 协作流程
• 更新触发:游戏每帧调用 AnimatableManager.updateAnimations()
,根据 getTick()
计算时间差。
• 首帧处理:isFirstTick
标记首次更新,初始化时间基准(firstTickTime
)。
• 时间驱动:控制器通过 getLastUpdateTime()
获取时间差,推进动画状态机。
• 关键方法
public void updatedAt(double updateTime) {
this.lastUpdateTime = updateTime;
}
5. 动画触发与协调
• 作用
通过 tryTriggerAnimation
方法触发特定动画,支持全局或指定控制器触发。
• 协作流程
• 外部调用:如实体受伤时调用 triggerAnim("attack_controller", "hit")
。
• 内部路由:tryTriggerAnimation
遍历控制器,找到匹配名称的控制器并触发动画。
• 网络同步:若在服务端触发,通过 GeckoLibNetwork
发送数据包同步至客户端。
• 代码逻辑
public void tryTriggerAnimation(String controllerName, String animName) {
AnimationController<?> controller = this.animationControllers.get(controllerName);
if (controller != null) {
controller.tryTriggerAnimation(animName);
}
}
协作关系总览
游戏主循环 (Game Loop)
│
├── 实体更新 (Entity Update)
│ └── 调用 AnimatableManager.updateAnimations()
│ ├── 更新 lastUpdateTime
│ ├── 遍历所有 AnimationController
│ │ ├── 调用控制器逻辑(状态机、过渡)
│ │ └── 修改骨骼状态(BoneSnapshot)
│ └── 更新 extraData
│
├── 渲染流程 (Rendering)
│ └── GeoModel 渲染器
│ ├── 读取 boneSnapshotCollection
│ └── 计算骨骼变换(插值/混合)
│
└── 网络事件 (Network Events)
├── 服务端触发动画(EntityAnimTriggerPacket)
└── 客户端接收后调用 tryTriggerAnimation()
设计亮点
- 集中化管理
所有动画相关状态(控制器、骨骼、数据)统一由AnimatableManager
管理,降低耦合度。 - 扩展性
ControllerRegistrar
允许动态注册控制器,支持复杂动画逻辑的组合。 - 性能优化
•Object2ObjectOpenHashMap
提供高效的数据存取。
• 骨骼快照缓存减少重复计算,提升渲染性能。 - 跨线程安全
时间戳和首次更新标志确保动画在客户端和服务端的一致性。
总结
AnimatableManager
是 GeckoLib 动画系统的“指挥中心”,负责整合控制器、骨骼状态、时间与数据,实现高效、可扩展的动画管理。它通过标准化的接口与 GeoAnimatable
、AnimationController
、GeoModel
等组件协作,使开发者能够专注于动画逻辑设计,而无需处理底层细节。
CoreGeoBone接口
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package software.bernie.geckolib.core.animatable.model;
import java.util.List;
import software.bernie.geckolib.core.state.BoneSnapshot;
public interface CoreGeoBone {
String getName();
CoreGeoBone getParent();
float getRotX();
float getRotY();
float getRotZ();
float getPosX();
float getPosY();
float getPosZ();
float getScaleX();
float getScaleY();
float getScaleZ();
void setRotX(float var1);
void setRotY(float var1);
void setRotZ(float var1);
default void updateRotation(float xRot, float yRot, float zRot) {
this.setRotX(xRot);
this.setRotY(yRot);
this.setRotZ(zRot);
}
void setPosX(float var1);
void setPosY(float var1);
void setPosZ(float var1);
default void updatePosition(float posX, float posY, float posZ) {
this.setPosX(posX);
this.setPosY(posY);
this.setPosZ(posZ);
}
void setScaleX(float var1);
void setScaleY(float var1);
void setScaleZ(float var1);
default void updateScale(float scaleX, float scaleY, float scaleZ) {
this.setScaleX(scaleX);
this.setScaleY(scaleY);
this.setScaleZ(scaleZ);
}
void setPivotX(float var1);
void setPivotY(float var1);
void setPivotZ(float var1);
default void updatePivot(float pivotX, float pivotY, float pivotZ) {
this.setPivotX(pivotX);
this.setPivotY(pivotY);
this.setPivotZ(pivotZ);
}
float getPivotX();
float getPivotY();
float getPivotZ();
boolean isHidden();
boolean isHidingChildren();
void setHidden(boolean var1);
void setChildrenHidden(boolean var1);
void saveInitialSnapshot();
void markScaleAsChanged();
void markRotationAsChanged();
void markPositionAsChanged();
boolean hasScaleChanged();
boolean hasRotationChanged();
boolean hasPositionChanged();
void resetStateChanges();
BoneSnapshot getInitialSnapshot();
List<? extends CoreGeoBone> getChildBones();
default BoneSnapshot saveSnapshot() {
return new BoneSnapshot(this);
}
}
代码解释
一、接口基础结构
-
核心定义
CoreGeoBone
是用于描述骨骼动画模型的Java接口,定义骨骼的旋转、位移、缩放等三维空间属性和层级关系。
•getName()
:获取骨骼唯一标识(如"head"或"arm")
•getParent()
:获取父骨骼引用,构建骨骼层级树结构 -
接口特性
符合Java接口的抽象性规范:
• 所有方法默认public abstract
(未显式声明修饰符)
• 包含默认方法(如updateRotation()
)实现通用逻辑
• 无成员变量(仅通过方法操作状态)
二、三维空间属性控制
1. 旋转控制(Rotation)
float getRotX/Y/Z()