用脚本分割动画 unity3d

2015年01月23日 11:37 0 点赞 0 评论 更新于 2025-11-21 15:11

在项目开发过程中,我遇到了动画分割相关的问题,在此将涉及到的一些资源和方法分享给大家,供大家参考和学习。

项目背景

近期在项目里,为了缩减资源包,我尝试把一个角色的所有动作全部整合到原始模型中。但后期证实,这样做并没有起到明显的优化效果,反而给维护工作增添了许多不必要的麻烦,所以最终还是把动作文件和原始模型文件拆分成了单独的文件。

原始模型中包含 600 多帧动作,每个模型有 30 个动作,一共有 25 个主角模型。若要手动设置每个 AnimationClip 的起始帧、结束帧以及是否循环,工作量巨大。于是,我开始研究 Unity 的资源处理器 AssetPostprocessor

实现思路

在导入模型之前(OnPreprocessModel),我们可以通过脚本实现动画分割。由于导入脚本无法自动知晓某个动作的动作名、开始帧、结束帧和是否循环,因此需要一个配置表来存储这些信息。

为了让结构更加清晰,我将需要导表的数据用类进行封装。clipST 类用于记录一个 AnimationClip 的数据,而一个模型由多个 AnimationClip 组成,所以还需要一个 modelST 类来封装这一堆 AnimationClip,并且记录这些 AnimationClip 对应的模型。最后,使用一个 List 来存储所有的 modelST

以下是具体的代码实现:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public static class AnimationClipConfig
{
public static bool isInit = false;
public static List<modelST> modelList = new List<modelST>();

public static void init()
{
if (isInit)
return;
isInit = true;

modelST tempModel = new modelST();
tempModel.ModelName = "id_3000"; // 模型名字
tempModel.clipSTs = new clipST[]{
new clipST("stand01", 0, 60, true),
new clipST("stand02", 143, 193, true),
new clipST("stand01_to_02", 61, 100, false),
new clipST("stand02_to_01", 120, 142, false),
new clipST("shouji01", 101, 119, false),
new clipST("run01", 253, 275, true),
new clipST("jump01", 606, 626, false),
new clipST("jump01_01", 627, 645, false),
new clipST("jump02", 646, 663, false),
new clipST("jump02_01", 664, 686, false),
new clipST("jump02_atk01", 687, 714, false),
new clipST("jump02_atk02", 442, 456, false),
new clipST("1atk01", 275, 285, false),
new clipST("1atk02", 297, 317, false),
new clipST("1atk03", 329, 349, false),
new clipST("1atk04_end", 361, 381, false),
new clipST("1atk01_01", 286, 296, false),
new clipST("1atk02_01", 318, 328, false),
new clipST("1atk03_01", 350, 360, false),
new clipST("2atk01", 382, 392, false),
new clipST("2atk02", 404, 424, false),
new clipST("2atk03", 436, 456, false),
new clipST("2atk04_end", 468, 488, false),
new clipST("2atk01_01", 393, 403, false),
new clipST("2atk02_01", 425, 435, false),
new clipST("2atk03_01", 457, 467, false),
new clipST("3atk01", 489, 499, false),
new clipST("3atk02", 521, 541, false),
new clipST("3atk03", 553, 573, false),
new clipST("3atk04_end", 585, 605, false),
new clipST("3atk01_01", 500, 520, false),
new clipST("3atk02_01", 542, 552, false),
new clipST("3atk03_01", 574, 584, false),
new clipST("xl_atk01_01", 194, 252, false),
new clipST("xl_atk01_02", 194, 252, true),
new clipST("xl_atk01_03", 194, 252, false),
new clipST("dead01", 715, 745, false)
};
modelList.Add(tempModel);
}

#region ST
public class clipST
{
public string name;
public int firstFrame;
public int lastFrame;
public bool isloop;

public clipST(string _n, int _f, int _l, bool _i)
{
name = _n;
firstFrame = _f;
lastFrame = _l;
isloop = _i;
}
}

public class modelST
{
public string ModelName;
public clipST[] clipSTs;
}
#endregion
}

在上述代码中,init 方法用于将美工提供的动作信息写入数据结构。

自动切割 AnimationClip

知道了所需的数据后,接下来就是实现自动切割 AnimationClip 的功能。以下是具体代码:

using UnityEditor;
using UnityEngine;

public class FBXAnimationsFix : AssetPostprocessor
{
public void OnPreprocessModel()
{
// 当前正在导入的模型
ModelImporter modelImporter = (ModelImporter)assetImporter;

AnimationClipConfig.init();

foreach (AnimationClipConfig.modelST item in AnimationClipConfig.modelList)
{
// 当前导入模型的路径包含我们 modelST 动作数据表中的模型名字,那就要对这个模型的动画进行切割
if (assetPath.Contains(item.ModelName))
{
modelImporter.animationType = ModelImporterAnimationType.Legacy;
modelImporter.generateAnimations = ModelImporterGenerateAnimations.GenerateAnimations;

ModelImporterClipAnimation[] animations = new ModelImporterClipAnimation[item.clipSTs.Length];
for (int i = 0; i < item.clipSTs.Length; i++)
{
animations[i] = SetClipAnimation(item.clipSTs[i].name, item.clipSTs[i].firstFrame, item.clipSTs[i].lastFrame, item.clipSTs[i].isloop);
}

modelImporter.clipAnimations = animations;
}
}
}
}

ModelImporterClipAnimation SetClipAnimation(string _name, int _first, int _last, bool _isLoop)
{
ModelImporterClipAnimation tempClip = new ModelImporterClipAnimation();
tempClip.name = _name;
tempClip.firstFrame = _first;
tempClip.lastFrame = _last;
tempClip.loop = _isLoop;
if (_isLoop)
tempClip.wrapMode = WrapMode.Loop;
else
tempClip.wrapMode = WrapMode.Default;

return tempClip;
}

OnPreprocessModel 方法中,我们遍历 AnimationClipConfig.modelList,如果当前导入模型的路径包含 modelST 中的模型名字,就对该模型的动画进行切割。

动作文件导入设置

将动作和原始模型分成单独文件后,每个动作文件导入 Unity 时,会“非常智能”地把关联的材质、贴图再创建一个 .fbm 的文件夹导入一遍。但原始模型已经导入了一套材质和贴图,动作文件就没必要再导入这些。因此,需要对动作文件进行设置,让它们不关联材质。以下是实现代码:

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

public class FBXScaleFix : AssetPostprocessor
{
public void OnPreprocessModel()
{
ModelImporter modelImporter = (ModelImporter)assetImporter;

if (assetPath.Contains("@"))
{
modelImporter.importMaterials = false;
}
}
}

OnPreprocessModel 方法中,通过判断 assetPath 是否包含 @ 来确定是否为动作文件,如果是,则将 importMaterials 设置为 false,从而避免重复导入材质。

通过以上步骤,我们可以实现使用脚本分割动画的功能,提高开发效率并避免一些不必要的资源浪费。