最新文章
Cocos2d-x游戏开发实例详解7:对象释放时机
03-25 13:59
Cocos2d-x游戏开发实例详解6:自动释放池
03-25 13:55
Cocos2d-x游戏开发实例详解5:神奇的自动释放
03-25 13:49
Cocos2d-x游戏开发实例详解4:游戏主循环
03-25 13:44
Cocos2d-x游戏开发实例详解3:无限滚动地图
03-25 13:37
Cocos2d-x游戏开发实例详解2:开始菜单续
03-25 13:32
unity animationclip代码生成
本文将详细介绍如何使用代码在 Unity 中自动生成 AnimationClip,同时附带详细的信息和代码,供大家参考学习。
Unity2D 传统制作流程的痛点
在 Unity2D 开发中,传统的动画制作流程如下:
- 拿到美术提供的帧动画。
- 打开 Animation windows 手动创建动画文件。
- 创建 AnimationController 并手动连线。
- 创建 Prefab 文件。
这种方式非常繁琐,如果美术每次提供几十个动画资源,程序员手动处理会非常耗时费力。因此,我们需要实现自动化生成。
目标设定
我们的目标是将美术提供的所有帧动画放在 Raw 文件夹下,每个文件夹代表一组帧动画,文件夹名即为动画名。以下是实现该功能的代码:
using UnityEngine;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using UnityEditor;
using UnityEditorInternal;
public class BuildAnimation : Editor
{
// 生成出的 Prefab 的路径
private static string PrefabPath = "Assets/Resources/Prefabs";
// 生成出的 AnimationController 的路径
private static string AnimationControllerPath = "Assets/AnimationController";
// 生成出的 Animation 的路径
private static string AnimationPath = "Assets/Animation";
// 美术给的原始图片路径
private static string ImagePath = Application.dataPath + "/Raw";
[MenuItem("Build/BuildAnimaiton")]
static void BuildAniamtion()
{
DirectoryInfo raw = new DirectoryInfo(ImagePath);
foreach (DirectoryInfo dictorys in raw.GetDirectories())
{
List<AnimationClip> clips = new List<AnimationClip>();
foreach (DirectoryInfo dictoryAnimations in dictorys.GetDirectories())
{
// 每个文件夹就是一组帧动画,这里把每个文件夹下的所有图片生成出一个动画文件
clips.Add(BuildAnimationClip(dictoryAnimations));
}
// 把所有的动画文件生成在一个 AnimationController 里
AnimatorController controller = BuildAnimationController(clips, dictorys.Name);
// 最后生成程序用的 Prefab 文件
BuildPrefab(dictorys, controller);
}
}
static AnimationClip BuildAnimationClip(DirectoryInfo dictorys)
{
string animationName = dictorys.Name;
// 查找所有图片,因为测试动画是 .jpg
FileInfo[] images = dictorys.GetFiles("*.jpg");
AnimationClip clip = new AnimationClip();
AnimationUtility.SetAnimationType(clip, ModelImporterAnimationType.Generic);
EditorCurveBinding curveBinding = new EditorCurveBinding();
curveBinding.type = typeof(SpriteRenderer);
curveBinding.path = "";
curveBinding.propertyName = "m_Sprite";
ObjectReferenceKeyframe[] keyFrames = new ObjectReferenceKeyframe[images.Length];
// 动画长度是按秒为单位,1/10 就表示 1 秒切 10 张图片,根据项目的情况可以自己调节
float frameTime = 1 / 10f;
for (int i = 0; i < images.Length; i++)
{
Sprite sprite = Resources.LoadAssetAtPath<Sprite>(DataPathToAssetPath(images[i].FullName));
keyFrames[i] = new ObjectReferenceKeyframe();
keyFrames[i].time = frameTime * i;
keyFrames[i].value = sprite;
}
// 动画帧率,30 比较合适
clip.frameRate = 30;
// 有些动画希望天生它就动画循环
if (animationName.IndexOf("idle") >= 0)
{
// 设置 idle 文件为循环动画
SerializedObject serializedClip = new SerializedObject(clip);
AnimationClipSettings clipSettings = new AnimationClipSettings(serializedClip.FindProperty("m_AnimationClipSettings"));
clipSettings.loopTime = true;
serializedClip.ApplyModifiedProperties();
}
string parentName = System.IO.Directory.GetParent(dictorys.FullName).Name;
System.IO.Directory.CreateDirectory(AnimationPath + "/" + parentName);
AnimationUtility.SetObjectReferenceCurve(clip, curveBinding, keyFrames);
AssetDatabase.CreateAsset(clip, AnimationPath + "/" + parentName + "/" + animationName + ".anim");
AssetDatabase.SaveAssets();
return clip;
}
static AnimatorController BuildAnimationController(List<AnimationClip> clips, string name)
{
AnimatorController animatorController = AnimatorController.CreateAnimatorControllerAtPath(AnimationControllerPath + "/" + name + ".controller");
AnimatorControllerLayer layer = animatorController.GetLayer(0);
UnityEditorInternal.StateMachine sm = layer.stateMachine;
foreach (AnimationClip newClip in clips)
{
State state = sm.AddState(newClip.name);
state.SetAnimationClip(newClip, layer);
Transition trans = sm.AddAnyStateTransition(state);
trans.RemoveCondition(0);
}
AssetDatabase.SaveAssets();
return animatorController;
}
static void BuildPrefab(DirectoryInfo dictorys, AnimatorController animatorCountorller)
{
// 生成 Prefab 添加一张预览用的 Sprite
FileInfo images = dictorys.GetDirectories()[0].GetFiles("*.jpg")[0];
GameObject go = new GameObject();
go.name = dictorys.Name;
SpriteRenderer spriteRender = go.AddComponent<SpriteRenderer>();
spriteRender.sprite = Resources.LoadAssetAtPath<Sprite>(DataPathToAssetPath(images.FullName));
Animator animator = go.AddComponent<Animator>();
animator.runtimeAnimatorController = animatorCountorller;
PrefabUtility.CreatePrefab(PrefabPath + "/" + go.name + ".prefab", go);
DestroyImmediate(go);
}
public static string DataPathToAssetPath(string path)
{
if (Application.platform == RuntimePlatform.WindowsEditor)
return path.Substring(path.IndexOf("Assets\\"));
else
return path.Substring(path.IndexOf("Assets/"));
}
class AnimationClipSettings
{
SerializedProperty m_Property;
private SerializedProperty Get(string property)
{
return m_Property.FindPropertyRelative(property);
}
public AnimationClipSettings(SerializedProperty prop)
{
m_Property = prop;
}
public float startTime
{
get
{
return Get("m_StartTime").floatValue;
}
set
{
Get("m_StartTime").floatValue = value;
}
}
public float stopTime
{
get
{
return Get("m_StopTime").floatValue;
}
set
{
Get("m_StopTime").floatValue = value;
}
}
public float orientationOffsetY
{
get
{
return Get("m_OrientationOffsetY").floatValue;
}
set
{
Get("m_OrientationOffsetY").floatValue = value;
}
}
public float level
{
get
{
return Get("m_Level").floatValue;
}
set
{
Get("m_Level").floatValue = value;
}
}
public float cycleOffset
{
get
{
return Get("m_CycleOffset").floatValue;
}
set
{
Get("m_CycleOffset").floatValue = value;
}
}
public bool loopTime
{
get
{
return Get("m_LoopTime").boolValue;
}
set
{
Get("m_LoopTime").boolValue = value;
}
}
public bool loopBlend
{
get
{
return Get("m_LoopBlend").boolValue;
}
set
{
Get("m_LoopBlend").boolValue = value;
}
}
public bool loopBlendOrientation
{
get
{
return Get("m_LoopBlendOrientation").boolValue;
}
set
{
Get("m_LoopBlendOrientation").boolValue = value;
}
}
public bool loopBlendPositionY
{
get
{
return Get("m_LoopBlendPositionY").boolValue;
}
set
{
Get("m_LoopBlendPositionY").boolValue = value;
}
}
public bool loopBlendPositionXZ
{
get
{
return Get("m_LoopBlendPositionXZ").boolValue;
}
set
{
Get("m_LoopBlendPositionXZ").boolValue = value;
}
}
public bool keepOriginalOrientation
{
get
{
return Get("m_KeepOriginalOrientation").boolValue;
}
set
{
Get("m_KeepOriginalOrientation").boolValue = value;
}
}
public bool keepOriginalPositionY
{
get
{
return Get("m_KeepOriginalPositionY").boolValue;
}
set
{
Get("m_KeepOriginalPositionY").boolValue = value;
}
}
public bool keepOriginalPositionXZ
{
get
{
return Get("m_KeepOriginalPositionXZ").boolValue;
}
set
{
Get("m_KeepOriginalPositionXZ").boolValue = value;
}
}
public bool heightFromFeet
{
get
{
return Get("m_HeightFromFeet").boolValue;
}
set
{
Get("m_HeightFromFeet").boolValue = value;
}
}
public bool mirror
{
get
{
return Get("m_Mirror").boolValue;
}
set
{
Get("m_Mirror").boolValue = value;
}
}
}
}
代码解释
- BuildAnimation 类:继承自
Editor,用于在 Unity 编辑器中创建菜单项,执行动画生成操作。 - BuildAniamtion 方法:遍历 Raw 文件夹下的所有子文件夹,为每个子文件夹生成动画文件、动画控制器和 Prefab 文件。
- BuildAnimationClip 方法:根据文件夹中的图片生成动画剪辑,并设置动画的帧率和循环属性。
- BuildAnimationController 方法:将生成的动画剪辑添加到动画控制器中。
- BuildPrefab 方法:创建一个包含动画控制器的 Prefab 文件。
- DataPathToAssetPath 方法:将文件的绝对路径转换为 Unity 中的资源路径。
- AnimationClipSettings 类:用于设置动画剪辑的各种属性,如循环时间、循环混合等。
动画循环状态设置
由于新版的动画系统 Unity 没有提供直接的 API 来设置动画的循环状态,我们需要通过自定义的 AnimationClipSettings 类来修改动画的属性。具体实现请参考上述代码。
测试动画播放
为了验证动画是否能正常播放,我们编写了一个简单的测试脚本:
using UnityEngine;
using System.Collections;
public class NewBehaviourScript : MonoBehaviour
{
Animator animator;
void Start()
{
animator = GetComponent<Animator>();
}
void OnGUI()
{
if (GUILayout.Button("idle"))
{
animator.Play("idle");
}
}
}
将该脚本挂载到带有 Animator 组件的 GameObject 上,运行游戏后点击 “idle” 按钮,动画应能正常播放。
代码下载
你可以点击以下链接下载完整代码: 代码下载地址
有了自动生成动画的代码,无论美术提供多少组图片,或者更新了多少组图片,都能快速生成所需的动画资源。