unity animator 动画数据 保存为json,然后传输到网络,接收端接收josn数据恢复为ainimator动画数据驱动gameobject 播放动画
确保以下步骤在 Unity 项目中完成:
-
配置 Animator 和 Avatar:
- 创建并配置 Avatar,并将其绑定到 Animator 组件中。
- 确保 Animator Controller 正确设置。
-
添加和配置脚本:
- 将
AnimatorAvatarChecker
、DynamicAnimationCreator
和DynamicAnimationLoader
脚本添加到同一个 GameObject 上,并配置相应的 public 属性
- 将
// 检测 Animator 和 Avatar 绑定关系
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class AnimatorAvatarChecker : MonoBehaviour
{
public Animator animator;
void Start()
{
if (animator == null)
{
Debug.LogError("Animator component is not assigned.");
return;
}
if (animator.avatar == null)
{
Debug.LogError("Animator does not have an Avatar assigned.");
return;
}
if (!animator.avatar.isValid)
{
Debug.LogError("Assigned Avatar is not valid.");
return;
}
if (!animator.avatar.isHuman)
{
Debug.LogError("Assigned Avatar is not a humanoid.");
return;
}
Debug.Log("Animator and Avatar are correctly configured.");
}
}
public class DynamicAnimationCreator : MonoBehaviour
{
public Animator animator;
public SkinnedMeshRenderer[] skinnedMeshRenderers;
void Start()
{
if (animator == null)
{
Debug.LogError("Animator component is not assigned.");
return;
}
if (skinnedMeshRenderers.Length == 0)
{
Debug.LogError("No SkinnedMeshRenderers assigned.");
return;
}
List<Transform> bones = new List<Transform>();
foreach (HumanBodyBones bone in System.Enum.GetValues(typeof(HumanBodyBones)))
{
Transform boneTransform = animator.GetBoneTransform(bone);
if (boneTransform != null)
{
bones.Add(boneTransform);
}
}
AnimationClip clip = new AnimationClip();
clip.legacy = false;
List<float> keyframeTimes = new List<float> { 0f, 1f, 2f };
foreach (Transform bone in bones)
{
Keyframe[] posXKeys = { new Keyframe(0f, bone.localPosition.x), new Keyframe(1f, bone.localPosition.x + 0.1f), new Keyframe(2f, bone.localPosition.x) };
Keyframe[] posYKeys = { new Keyframe(0f, bone.localPosition.y), new Keyframe(1f, bone.localPosition.y + 0.1f), new Keyframe(2f, bone.localPosition.y) };
Keyframe[] posZKeys = { new Keyframe(0f, bone.localPosition.z), new Keyframe(1f, bone.localPosition.z + 0.1f), new Keyframe(2f, bone.localPosition.z) };
Keyframe[] rotXKeys = { new Keyframe(0f, bone.localEulerAngles.x), new Keyframe(1f, bone.localEulerAngles.x + 45f), new Keyframe(2f, bone.localEulerAngles.x) };
Keyframe[] rotYKeys = { new Keyframe(0f, bone.localEulerAngles.y), new Keyframe(1f, bone.localEulerAngles.y + 45f), new Keyframe(2f, bone.localEulerAngles.y) };
Keyframe[] rotZKeys = { new Keyframe(0f, bone.localEulerAngles.z), new Keyframe(1f, bone.localEulerAngles.z + 45f), new Keyframe(2f, bone.localEulerAngles.z) };
Keyframe[] scaleXKeys = { new Keyframe(0f, bone.localScale.x), new Keyframe(1f, bone.localScale.x * 1.1f), new Keyframe(2f, bone.localScale.x) };
Keyframe[] scaleYKeys = { new Keyframe(0f, bone.localScale.y), new Keyframe(1f, bone.localScale.y * 1.1f), new Keyframe(2f, bone.localScale.y) };
Keyframe[] scaleZKeys = { new Keyframe(0f, bone.localScale.z), new Keyframe(1f, bone.localScale.z * 1.1f), new Keyframe(2f, bone.localScale.z) };
string bonePath = GetRelativePath(bone);
clip.SetCurve(bonePath, typeof(Transform), "localPosition.x", new AnimationCurve(posXKeys));
clip.SetCurve(bonePath, typeof(Transform), "localPosition.y", new AnimationCurve(posYKeys));
clip.SetCurve(bonePath, typeof(Transform), "localPosition.z", new AnimationCurve(posZKeys));
clip.SetCurve(bonePath, typeof(Transform), "localEulerAngles.x", new AnimationCurve(rotXKeys));
clip.SetCurve(bonePath, typeof(Transform), "localEulerAngles.y", new AnimationCurve(rotYKeys));
clip.SetCurve(bonePath, typeof(Transform), "localEulerAngles.z", new AnimationCurve(rotZKeys));
clip.SetCurve(bonePath, typeof(Transform), "localScale.x", new AnimationCurve(scaleXKeys));
clip.SetCurve(bonePath, typeof(Transform), "localScale.y", new AnimationCurve(scaleYKeys));
clip.SetCurve(bonePath, typeof(Transform), "localScale.z", new AnimationCurve(scaleZKeys));
}
foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
{
string rendererPath = GetRelativePath(skinnedMeshRenderer.transform);
for (int i = 0; i < skinnedMeshRenderer.sharedMesh.blendShapeCount; i++)
{
string blendShapeName = skinnedMeshRenderer.sharedMesh.GetBlendShapeName(i);
Keyframe[] blendShapeKeys = { new Keyframe(0f, 0f), new Keyframe(1f, 100f), new Keyframe(2f, 0f) };
clip.SetCurve(rendererPath, typeof(SkinnedMeshRenderer), "blendShape." + blendShapeName, new AnimationCurve(blendShapeKeys));
}
}
AnimatorOverrideController overrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);
overrideController["DefaultAnimation"] = clip;
animator.runtimeAnimatorController = overrideController;
animator.Play("DefaultAnimation");
string json = SaveAnimationData(clip, bones, keyframeTimes);
File.WriteAllText(Application.dataPath + "/animationData.json", json);
}
private string GetRelativePath(Transform transform)
{
List<string> pathElements = new List<string>();
Transform current = transform;
while (current != transform.root)
{
pathElements.Insert(0, current.name);
current = current.parent;
}
return string.Join("/", pathElements);
}
private string SaveAnimationData(AnimationClip clip, List<Transform> bones, List<float> keyframeTimes)
{
AnimationDataContainer dataContainer = new AnimationDataContainer { animations = new List<AnimationData>() };
foreach (Transform bone in bones)
{
AnimationData data = new AnimationData { boneName = bone.name, keyframeTimes = keyframeTimes.ToArray(), positions = new List<Vector3>(), rotations = new List<Vector3>(), scales = new List<Vector3>() };
var bindings = AnimationUtility.GetCurveBindings(clip);
foreach (var binding in bindings)
{
if (binding.path == GetRelativePath(bone))
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
if (binding.propertyName == "localPosition.x")
{
foreach (var key in curve.keys)
{
if (!data.positions.Exists(pos => pos.x == key.value))
{
Vector3 position = data.positions.Count > 0 ? data.positions[data.positions.Count - 1] : Vector3.zero;
data.positions.Add(new Vector3(key.value, position.y, position.z));
}
}
}
else if (binding.propertyName == "localPosition.y")
{
foreach (var key in curve.keys)
{
if (!data.positions.Exists(pos => pos.y == key.value))
{
Vector3 position = data.positions.Count > 0 ? data.positions[data.positions.Count - 1] : Vector3.zero;
data.positions[data.positions.Count - 1] = new Vector3(position.x, key.value, position.z);
}
}
}
else if (binding.propertyName == "localPosition.z")
{
foreach (var key in curve.keys)
{
if (!data.positions.Exists(pos => pos.z == key.value))
{
Vector3 position = data.positions.Count > 0 ? data.positions[data.positions.Count - 1] : Vector3.zero;
data.positions[data.positions.Count - 1] = new Vector3(position.x, position.y, key.value);
}
}
}
else if (binding.propertyName == "localEulerAngles.x")
{
foreach (var key in curve.keys)
{
if (!data.rotations.Exists(rot => rot.x == key.value))
{
Vector3 rotation = data.rotations.Count > 0 ? data.rotations[data.rotations.Count - 1] : Vector3.zero;
data.rotations.Add(new Vector3(key.value, rotation.y, rotation.z));
}
}
}
else if (binding.propertyName == "localEulerAngles.y")
{
foreach (var key in curve.keys)
{
if (!data.rotations.Exists(rot => rot.y == key.value))
{
Vector3 rotation = data.rotations.Count > 0 ? data.rotations[data.rotations.Count - 1] : Vector3.zero;
data.rotations[data.rotations.Count - 1] = new Vector3(rotation.x, key.value, rotation.z);
}
}
}
else if (binding.propertyName == "localEulerAngles.z")
{
foreach (var key in curve.keys)
{
if (!data.rotations.Exists(rot => rot.z == key.value))
{
Vector3 rotation = data.rotations.Count > 0 ? data.rotations[data.rotations.Count - 1] : Vector3.zero;
data.rotations[data.rotations.Count - 1] = new Vector3(rotation.x, rotation.y, key.value);
}
}
}
else if (binding.propertyName == "localScale.x")
{
foreach (var key in curve.keys)
{
if (!data.scales.Exists(scale => scale.x == key.value))
{
Vector3 scale = data.scales.Count > 0 ? data.scales[data.scales.Count - 1] : Vector3.one;
data.scales.Add(new Vector3(key.value, scale.y, scale.z));
}
}
}
else if (binding.propertyName == "localScale.y")
{
foreach (var key in curve.keys)
{
if (!data.scales.Exists(scale => scale.y == key.value))
{
Vector3 scale = data.scales.Count > 0 ? data.scales[data.scales.Count - 1] : Vector3.one;
data.scales[data.scales.Count - 1] = new Vector3(scale.x, key.value, scale.z);
}
}
}
else if (binding.propertyName == "localScale.z")
{
foreach (var key in curve.keys)
{
if (!data.scales.Exists(scale => scale.z == key.value))
{
Vector3 scale = data.scales.Count > 0 ? data.scales[data.scales.Count - 1] : Vector3.one;
data.scales[data.scales.Count - 1] = new Vector3(scale.x, scale.y, key.value);
}
}
}
}
}
dataContainer.animations.Add(data);
}
foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
{
string rendererPath = GetRelativePath(skinnedMeshRenderer.transform);
for (int i = 0; i < skinnedMeshRenderer.sharedMesh.blendShapeCount; i++)
{
AnimationData data = new AnimationData { boneName = rendererPath, keyframeTimes = keyframeTimes.ToArray(), positions = new List<Vector3>(), rotations = new List<Vector3>(), scales = new List<Vector3>() };
string blendShapeName = skinnedMeshRenderer.sharedMesh.GetBlendShapeName(i);
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, new EditorCurveBinding { path = rendererPath, type = typeof(SkinnedMeshRenderer), propertyName = "blendShape." + blendShapeName });
if (curve != null)
{
foreach (var key in curve.keys)
{
data.positions.Add(new Vector3(key.time, key.value, 0));
}
dataContainer.animations.Add(data);
}
}
}
return JsonUtility.ToJson(dataContainer);
}
[System.Serializable]
public class AnimationData
{
public string boneName;
public float[] keyframeTimes;
public List<Vector3> positions;
public List<Vector3> rotations;
public List<Vector3> scales;
}
[System.Serializable]
public class AnimationDataContainer
{
public List<AnimationData> animations;
}
}
public class DynamicAnimationLoader : MonoBehaviour
{
public Animator animator;
public SkinnedMeshRenderer[] skinnedMeshRenderers;
void Start()
{
if (animator == null)
{
Debug.LogError("Animator component is not assigned.");
return;
}
string json = File.ReadAllText(Application.dataPath + "/animationData.json");
AnimationDataContainer dataContainer = JsonUtility.FromJson<AnimationDataContainer>(json);
AnimationClip clip = new AnimationClip();
clip.legacy = false;
foreach (var data in dataContainer.animations)
{
Keyframe[] posXKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] posYKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] posZKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] rotXKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] rotYKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] rotZKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] scaleXKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] scaleYKeys = new Keyframe[data.keyframeTimes.Length];
Keyframe[] scaleZKeys = new Keyframe[data.keyframeTimes.Length];
for (int i = 0; i < data.keyframeTimes.Length; i++)
{
if (i < data.positions.Count)
{
posXKeys[i] = new Keyframe(data.keyframeTimes[i], data.positions[i].x);
posYKeys[i] = new Keyframe(data.keyframeTimes[i], data.positions[i].y);
posZKeys[i] = new Keyframe(data.keyframeTimes[i], data.positions[i].z);
}
if (i < data.rotations.Count)
{
rotXKeys[i] = new Keyframe(data.keyframeTimes[i], data.rotations[i].x);
rotYKeys[i] = new Keyframe(data.keyframeTimes[i], data.rotations[i].y);
rotZKeys[i] = new Keyframe(data.keyframeTimes[i], data.rotations[i].z);
}
if (i < data.scales.Count)
{
scaleXKeys[i] = new Keyframe(data.keyframeTimes[i], data.scales[i].x);
scaleYKeys[i] = new Keyframe(data.keyframeTimes[i], data.scales[i].y);
scaleZKeys[i] = new Keyframe(data.keyframeTimes[i], data.scales[i].z);
}
}
string bonePath = data.boneName;
clip.SetCurve(bonePath, typeof(Transform), "localPosition.x", new AnimationCurve(posXKeys));
clip.SetCurve(bonePath, typeof(Transform), "localPosition.y", new AnimationCurve(posYKeys));
clip.SetCurve(bonePath, typeof(Transform), "localPosition.z", new AnimationCurve(posZKeys));
clip.SetCurve(bonePath, typeof(Transform), "localEulerAngles.x", new AnimationCurve(rotXKeys));
clip.SetCurve(bonePath, typeof(Transform), "localEulerAngles.y", new AnimationCurve(rotYKeys));
clip.SetCurve(bonePath, typeof(Transform), "localEulerAngles.z", new AnimationCurve(rotZKeys));
clip.SetCurve(bonePath, typeof(Transform), "localScale.x", new AnimationCurve(scaleXKeys));
clip.SetCurve(bonePath, typeof(Transform), "localScale.y", new AnimationCurve(scaleYKeys));
clip.SetCurve(bonePath, typeof(Transform), "localScale.z", new AnimationCurve(scaleZKeys));
}
foreach (var skinnedMeshRenderer in skinnedMeshRenderers)
{
foreach (var data in dataContainer.animations)
{
if (data.boneName == GetRelativePath(skinnedMeshRenderer.transform))
{
for (int i = 0; i < skinnedMeshRenderer.sharedMesh.blendShapeCount; i++)
{
string blendShapeName = skinnedMeshRenderer.sharedMesh.GetBlendShapeName(i);
Keyframe[] blendShapeKeys = new Keyframe[data.keyframeTimes.Length];
for (int j = 0; j < data.keyframeTimes.Length; j++)
{
blendShapeKeys[j] = new Keyframe(data.keyframeTimes[j], data.positions[j].y);
}
clip.SetCurve(data.boneName, typeof(SkinnedMeshRenderer), "blendShape." + blendShapeName, new AnimationCurve(blendShapeKeys));
}
}
}
}
AnimatorOverrideController overrideController = new AnimatorOverrideController(animator.runtimeAnimatorController);
overrideController["DefaultAnimation"] = clip;
animator.runtimeAnimatorController = overrideController;
animator.Play("DefaultAnimation");
}
private string GetRelativePath(Transform transform)
{
List<string> pathElements = new List<string>();
Transform current = transform;
while (current != transform.root)
{
pathElements.Insert(0, current.name);
current = current.parent;
}
return string.Join("/", pathElements);
}
[System.Serializable]
public class AnimationData
{
public string boneName;
public float[] keyframeTimes;
public List<Vector3> positions;
public List<Vector3> rotations;
public List<Vector3> scales;
}
[System.Serializable]
public class AnimationDataContainer
{
public List<AnimationData> animations;
}
}
-
相对路径计算:
GetRelativePath
方法用于计算Transform
的相对路径。确保路径在设置和读取动画曲线时是一致的。
-
动画数据保存和恢复:
- 更新
SaveAnimationData
方法以保存AnimationClip
中的曲线数据。 - 更新
DynamicAnimationLoader
以从 JSON 文件中恢复动画曲线。
- 更新
-
多
SkinnedMeshRenderer
支持:- 在创建和读取动画时,处理每个
SkinnedMeshRenderer
的变形键。
- 在创建和读取动画时,处理每个
使用步骤
-
绑定 Animator 和 SkinnedMeshRenderer:
- 在 Unity Editor 中,将
Animator
组件和所有需要处理的SkinnedMeshRenderer
组件拖放到相应的脚本组件中。
- 在 Unity Editor 中,将
-
运行场景:
- 运行 Unity 场景,确保控制台中没有错误信息并且动画正常播放。
-
验证 JSON 数据:
- 确认生成的 JSON 文件包含正确的动画数据,并且可以通过
DynamicAnimationLoader
脚本正确加载和播放动画。
- 确认生成的 JSON 文件包含正确的动画数据,并且可以通过
通过这些更新和检查,可以确保代码逻辑正确且能够动态控制人体骨骼动画,并支持多 SkinnedMeshRenderer
的变形键。