动画资源制作
ONI 的动画资源最后会变成 xxx_kanim,代码里用 Assets.GetAnim("xxx_kanim") 读取。建筑、物品、植物、生物、特效都吃这一套,只是需要的动画状态不一样。
这一章先讲 Mod 里最常用的资源组织和代码调用,不展开讲每个动画工具的按钮。
文件放哪
最常见的做法是把动画工具导出的资源放进 Mod 的 anim/assets 目录。目录通常长这样:
MyMod/
├── anim/
│ └── assets/
│ └── MyMachine/
│ ├── MyMachine_anim.bytes
│ ├── MyMachine_build.bytes
│ └── MyMachine.png
└── mod.yaml一个建筑 Mod 的输出结构可以整理成这样:
MyBuildingMod/
├── anim/
│ └── assets/
│ └── MyBuilding/
│ ├── MyBuilding_anim.bytes
│ ├── MyBuilding_build.bytes
│ └── MyBuilding.png
├── MyBuildingMod.dll
├── mod_info.yaml
└── mod.yaml游戏加载后,这组资源对应的动画名通常是 MyBuilding_kanim,所以代码里这样写:
Assets.GetAnim("MyBuilding_kanim")植物、种子、果实也是同样思路,只是每个实体一套资源目录。示例:
MyPlantMod/
└── anim/
└── assets/
├── MyPlant/
│ ├── MyPlant_anim.bytes
│ ├── MyPlant_build.bytes
│ └── MyPlant.png
├── MyPlantFruit/
│ ├── MyPlantFruit_anim.bytes
│ ├── MyPlantFruit_build.bytes
│ └── MyPlantFruit.png
└── MyPlantSeed/
├── MyPlantSeed_anim.bytes
├── MyPlantSeed_build.bytes
└── MyPlantSeed.png代码里写的是动画资源名,不是文件路径,也不要写 .png、.bytes 这类扩展名:
Assets.GetAnim("my_machine_kanim")如果 Assets.GetAnim("my_machine_kanim") 返回 null,说明资源没有被游戏加载到。先检查导出文件是否真的进了 Mod 输出目录,再检查资源名是不是和代码里的字符串一致。
元素材质用的动画资源有时会放在 anim/elements,例如:
MyElementMod/
└── anim/
├── assets/
│ └── MyElement/
│ ├── MyElement_anim.bytes
│ ├── MyElement_build.bytes
│ └── MyElement.png
└── elements/
└── MyElement/
├── MyElementSubstance_anim.bytes
├── MyElementSubstance_build.bytes
└── MyElementSubstance.png普通建筑、物品、植物先放 anim/assets 就够了;只有在做元素 Substance 显示时,再考虑 anim/elements。
建筑动画状态
建筑最常用的状态:
off:关闭。on:待机或开启。working_pre:开始工作。working_loop:工作循环。working_pst:工作结束。
建筑配置里要填默认状态:
BuildingDef def = BuildingTemplates.CreateBuildingDef(
ID,
1,
1,
"my_machine_kanim",
30,
60f,
BUILDINGS.CONSTRUCTION_MASS_KG.TIER2,
MATERIALS.REFINED_METALS,
1600f,
BuildLocationRule.OnFloor,
DECOR.NONE,
NOISE_POLLUTION.NONE);
def.DefaultAnimState = "off";如果接了状态机,就在状态里播放:
off.PlayAnim("off");
working.PlayAnim("working_loop", KAnim.PlayMode.Loop);
finish.PlayAnim("working_pst").OnAnimQueueComplete(idle);物品动画状态
掉落物通常只要一个 object:
EntityTemplates.CreateLooseEntity(
ID,
name,
desc,
1f,
false,
Assets.GetAnim("my_item_kanim"),
"object",
Grid.SceneLayer.Front,
EntityTemplates.CollisionShape.RECTANGLE,
0.6f,
0.6f,
true,
0,
SimHashes.Creature);如果临时借用植物或建筑动画测试,状态名可能不是 object。这时先用资源查看工具确认动画里到底有哪些状态。
植物动画状态
植物常见状态更多:
idle_empty:没结果。idle_full:成熟。grow:生长过程。grow_pst:进入成熟。harvest:收获。wilting或分级枯萎动画:枯萎。place:种植预览。
注册预览时会用到 place:
EntityTemplates.CreateAndRegisterPreviewForPlant(
seed,
"MyPlant_preview",
Assets.GetAnim("my_plant_kanim"),
"place",
1,
2);植物动画名写错时,最常见表现是种植预览空白,或生长到某个阶段突然消失。
特效动画
特效可以用 FXHelpers.CreateEffect():
KBatchedAnimController fx = FXHelpers.CreateEffect(
"upgrade_fx_kanim",
Grid.CellToPosCCC(cell, Grid.SceneLayer.FXFront),
null,
false,
Grid.SceneLayer.FXFront,
false);
fx.destroyOnAnimComplete = true;
fx.Play("idle", KAnim.PlayMode.Once);如果是循环特效,记得自己安排销毁:
fx.destroyOnAnimComplete = false;
fx.Play("loop", KAnim.PlayMode.Loop);
GameScheduler.Instance.Schedule("DestroyFx", 3f, _ => Object.Destroy(fx.gameObject));符号和挂点
复杂建筑经常会把某个部件拆成单独对象,比如机械臂、炮口、转盘。可以通过符号找位置:
KBatchedAnimController controller = GetComponent<KBatchedAnimController>();
Vector3 pos = controller.GetSymbolTransform(new HashedString("gun_target"), out _).GetColumn(3);额外的机械臂、炮口或指示灯,都可以用这种方式挂到建筑符号上。
符号名错了,位置会非常怪。做这种动画前,先把关键符号命名记下来。
调试方式
最直接的调试代码:
KAnimFile anim = Assets.GetAnim("my_machine_kanim");
if (anim == null)
{
Debug.LogError("missing anim: my_machine_kanim");
}对象生成后也可以临时强制播放:
GetComponent<KBatchedAnimController>()?.Play("off");如果 Assets.GetAnim() 是 null,先看资源有没有打包。不是 null 但画面空白,再看动画状态名、图层和缩放。
命名建议
- 建筑:
my_machine_kanim,状态用off/on/working_*。 - 物品:
my_item_kanim,状态用object。 - 植物:
my_plant_kanim,状态用idle_empty/idle_full/place。 - 特效:
my_fx_kanim,状态用idle/loop/pst。
名字朴素一点反而好维护。资源名、代码 ID、文件名最好能一眼对应。