如何在不破坏动画的情况下重命名我的生物模型的层次结构?
How can I rename my creature's model's hierarchy without breaking animations?
就技能而言,我是 Unity 的初学者,所以如果可以的话,请像和 child 交谈一样解释!
问题
我想在这里更改这些名称:
我想重命名它们有两个原因:
所以他们更容易理解
因为我使用了商店中的许多不同资产,每个资产都有不同的层次结构和不同的名称,我想标准化名称,以便我可以使用下面的代码来确定哪一部分生物的 body 被击中,因此它适用于每个生物
public void CreatureHit(string bodyPart, GunInfo usedWeapon, float intensity) // for guns
{
usedWeapon.PlayHit(creatureSounds);
if (creatureInfo.healthPoints > 0) // to prevent dead creatures from being shot
{
if ("Torso" == bodyPart || "LeftUpperArm" == bodyPart // if the part that was hit was the arms or torso
|| "RightUpperArm" == bodyPart || "LeftLowerArm" == bodyPart // if the part that was hit was the arms or torso
|| "RightLowerArm" == bodyPart)
{
creatureInfo.healthPoints -= usedWeapon.damage * intensity; // deal standard dmg
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.BODYSHOT;
}
else if ("Head" == bodyPart) // if the part that was hit was the head
{
creatureInfo.healthPoints -= usedWeapon.damage * 10 * intensity; // deal 10x dmg
audioSource.PlayOneShot(creatureSounds.hitHead, 1);
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.HEADSHOT;
}
else if ("RightUpperLeg" == bodyPart || "LeftUpperLeg" == bodyPart
|| "RightLowerLeg" == bodyPart || "LeftLowerLeg" == bodyPart)
{
creatureInfo.healthPoints -= usedWeapon.damage / 2 * intensity; // deal half dmg
if (creatureInfo.healthPoints <= 0)
creatureInfo.deathType = CreatureInfo.BODYSHOT;
}
}
}
我尝试了什么
我在层次结构中重命名了它们,但随后动画停止工作。我在 Unity 论坛上发现了一个旧帖子,询问这在 2015 年是否可行,OP 被告知不可能。后来有一些技术回复,我感到不知所措,所以我想我应该创建一个自己的线程。
注意:有几十个角色,每个角色都有 10 多个动画,所以理想情况下我需要一个非常有效的解决方案。
不幸的是,总的来说你还是做不到。 (至少没那么简单,见下文)。
AnimationClip
是基于字符串存储 相对路径 从 Animator
到相应的 GameObject
type 相应组件,最后是动画序列化字段和属性的 name。
如果其中任何一个发生变化,例如因为您重命名了对象或更改了层次结构,所以连接丢失并且动画中断。
您可以实现一个编辑器脚本方法
- 遍历对象
受影响的Animator
(GetComponentInParent
)
- 遍历所有使用的
AnimationClip
s
- 遍历每个剪辑 属性 绑定
- 根据您的重命名重定向 属性 路径
这看起来有点像这样
private static void RenameObject(GameObject gameObject, Animator animator, string newName)
{
if (!gameObject)
{
throw new ArgumentException("No object provided", nameof(gameObject));
}
if (string.IsNullOrWhiteSpace(newName))
{
throw new ArgumentException("Object name may not be empty!", nameof(newName));
}
if (!animator)
{
throw new ArgumentException($"Selected object {gameObject} is not a child of an {nameof(Animator)}!", nameof(gameObject));
}
if (gameObject.transform == animator.transform)
{
return;
}
// get the relative path from the animator root to this object's parent
var path = AnimationUtility.CalculateTransformPath(gameObject.transform.parent, animator.transform);
if (gameObject.transform.parent != animator.transform)
{
path += "/";
}
// then append the old and new names
var oldPath = path + gameObject.name;
var newPath = path + newName;
// get the runtime Animation controller
var controller = animator.runtimeAnimatorController;
// get all clips used by this controller
var clips = controller.animationClips;
var changeableObjects = new List<Object>(clips.Length + 1) { gameObject };
changeableObjects.AddRange(clips);
Undo.RecordObjects(changeableObjects.ToArray(), "Change animated object name");
// Go through all clips
foreach (var clip in clips)
{
var floatBindingInfo = new List<AnimationFloatBindingInfo>();
// Get and store all FLOAT keyframe bindings
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
var curveInfo = new AnimationFloatBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
floatBindingInfo.Add(curveInfo);
}
var objectBindingInfos = new List<AnimationObjectBindingInfo>();
// also do the same for all reference keyframe bindings
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
{
var curve = AnimationUtility.GetObjectReferenceCurve(clip, binding);
var curveInfo = new AnimationObjectBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
objectBindingInfos.Add(curveInfo);
}
// a little check to avoid unnecessary work -> are there any affected property curves at all?
if (floatBindingInfo.Count + objectBindingInfos.Count > 0)
{
// Now erase all curves
clip.ClearCurves();
// and assign back the stored ones
AnimationUtility.SetEditorCurves(clip, floatBindingInfo.Select(info => info.Binding).ToArray(), floatBindingInfo.Select(info => info.Curve).ToArray());
AnimationUtility.SetObjectReferenceCurves(clip, objectBindingInfos.Select(info => info.Binding).ToArray(), objectBindingInfos.Select(info => info.Curve).ToArray());
EditorUtility.SetDirty(clip);
}
}
// finally rename the object
gameObject.name = newName;
EditorUtility.SetDirty(gameObject);
}
由于这个用例很常见,我花了一些时间为此实现了一个 EditorWindow
。它仍然有点原始但是可以工作并且还支持撤消重做;)你可以找到它here
-> Select 层次结构中的对象 -> 右键单击 -> “Rename safe for Animator”
您当然可以为其添加一些快捷方式等。这取决于您 ;)
这里是对话框的一个小演示,重命名一些嵌套对象并执行一些 undo/redo
但是,在您的用例中,另一种方法是简单地让您的代码使用名称,因为它们可能正在使用 tags。
据我所知,您的代码基于三种不同的情况,因此您可以简单地为每种情况添加一个标签,例如Head
、Arms
、Legs
并相应地分配和检查它们 (GameObject.CompareTag
),并且根本不要触摸名称和动画。
我有两个计划。
在要重命名的节点下创建一个空的GameObject,并在其上附加collider组件。
CATRigHub001Bone004
└ CATRigHub001Bone004Bone001
└ Rig <-------- Collider
在编辑器中重命名骨骼并创建脚本以在播放时自动将其重命名为原始名称。
public class Rename : MonoBehaviour
{
public string boneName;
[NonSerialized] public string partName;
void Awake()
{
partName = name;
name = boneName;
}
}
就技能而言,我是 Unity 的初学者,所以如果可以的话,请像和 child 交谈一样解释!
问题
我想在这里更改这些名称:
我想重命名它们有两个原因:
所以他们更容易理解
因为我使用了商店中的许多不同资产,每个资产都有不同的层次结构和不同的名称,我想标准化名称,以便我可以使用下面的代码来确定哪一部分生物的 body 被击中,因此它适用于每个生物
public void CreatureHit(string bodyPart, GunInfo usedWeapon, float intensity) // for guns { usedWeapon.PlayHit(creatureSounds); if (creatureInfo.healthPoints > 0) // to prevent dead creatures from being shot { if ("Torso" == bodyPart || "LeftUpperArm" == bodyPart // if the part that was hit was the arms or torso || "RightUpperArm" == bodyPart || "LeftLowerArm" == bodyPart // if the part that was hit was the arms or torso || "RightLowerArm" == bodyPart) { creatureInfo.healthPoints -= usedWeapon.damage * intensity; // deal standard dmg if (creatureInfo.healthPoints <= 0) creatureInfo.deathType = CreatureInfo.BODYSHOT; } else if ("Head" == bodyPart) // if the part that was hit was the head { creatureInfo.healthPoints -= usedWeapon.damage * 10 * intensity; // deal 10x dmg audioSource.PlayOneShot(creatureSounds.hitHead, 1); if (creatureInfo.healthPoints <= 0) creatureInfo.deathType = CreatureInfo.HEADSHOT; } else if ("RightUpperLeg" == bodyPart || "LeftUpperLeg" == bodyPart || "RightLowerLeg" == bodyPart || "LeftLowerLeg" == bodyPart) { creatureInfo.healthPoints -= usedWeapon.damage / 2 * intensity; // deal half dmg if (creatureInfo.healthPoints <= 0) creatureInfo.deathType = CreatureInfo.BODYSHOT; } } }
我尝试了什么
我在层次结构中重命名了它们,但随后动画停止工作。我在 Unity 论坛上发现了一个旧帖子,询问这在 2015 年是否可行,OP 被告知不可能。后来有一些技术回复,我感到不知所措,所以我想我应该创建一个自己的线程。
注意:有几十个角色,每个角色都有 10 多个动画,所以理想情况下我需要一个非常有效的解决方案。
不幸的是,总的来说你还是做不到。 (至少没那么简单,见下文)。
AnimationClip
是基于字符串存储 相对路径 从 Animator
到相应的 GameObject
type 相应组件,最后是动画序列化字段和属性的 name。
如果其中任何一个发生变化,例如因为您重命名了对象或更改了层次结构,所以连接丢失并且动画中断。
您可以实现一个编辑器脚本方法
- 遍历对象 受影响的
- 遍历所有使用的
AnimationClip
s - 遍历每个剪辑 属性 绑定
- 根据您的重命名重定向 属性 路径
Animator
(GetComponentInParent
)
这看起来有点像这样
private static void RenameObject(GameObject gameObject, Animator animator, string newName)
{
if (!gameObject)
{
throw new ArgumentException("No object provided", nameof(gameObject));
}
if (string.IsNullOrWhiteSpace(newName))
{
throw new ArgumentException("Object name may not be empty!", nameof(newName));
}
if (!animator)
{
throw new ArgumentException($"Selected object {gameObject} is not a child of an {nameof(Animator)}!", nameof(gameObject));
}
if (gameObject.transform == animator.transform)
{
return;
}
// get the relative path from the animator root to this object's parent
var path = AnimationUtility.CalculateTransformPath(gameObject.transform.parent, animator.transform);
if (gameObject.transform.parent != animator.transform)
{
path += "/";
}
// then append the old and new names
var oldPath = path + gameObject.name;
var newPath = path + newName;
// get the runtime Animation controller
var controller = animator.runtimeAnimatorController;
// get all clips used by this controller
var clips = controller.animationClips;
var changeableObjects = new List<Object>(clips.Length + 1) { gameObject };
changeableObjects.AddRange(clips);
Undo.RecordObjects(changeableObjects.ToArray(), "Change animated object name");
// Go through all clips
foreach (var clip in clips)
{
var floatBindingInfo = new List<AnimationFloatBindingInfo>();
// Get and store all FLOAT keyframe bindings
foreach (var binding in AnimationUtility.GetCurveBindings(clip))
{
var curve = AnimationUtility.GetEditorCurve(clip, binding);
var curveInfo = new AnimationFloatBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
floatBindingInfo.Add(curveInfo);
}
var objectBindingInfos = new List<AnimationObjectBindingInfo>();
// also do the same for all reference keyframe bindings
foreach (var binding in AnimationUtility.GetObjectReferenceCurveBindings(clip))
{
var curve = AnimationUtility.GetObjectReferenceCurve(clip, binding);
var curveInfo = new AnimationObjectBindingInfo(binding, curve);
ReplaceBindingPath(curveInfo, oldPath, newPath);
objectBindingInfos.Add(curveInfo);
}
// a little check to avoid unnecessary work -> are there any affected property curves at all?
if (floatBindingInfo.Count + objectBindingInfos.Count > 0)
{
// Now erase all curves
clip.ClearCurves();
// and assign back the stored ones
AnimationUtility.SetEditorCurves(clip, floatBindingInfo.Select(info => info.Binding).ToArray(), floatBindingInfo.Select(info => info.Curve).ToArray());
AnimationUtility.SetObjectReferenceCurves(clip, objectBindingInfos.Select(info => info.Binding).ToArray(), objectBindingInfos.Select(info => info.Curve).ToArray());
EditorUtility.SetDirty(clip);
}
}
// finally rename the object
gameObject.name = newName;
EditorUtility.SetDirty(gameObject);
}
由于这个用例很常见,我花了一些时间为此实现了一个 EditorWindow
。它仍然有点原始但是可以工作并且还支持撤消重做;)你可以找到它here
-> Select 层次结构中的对象 -> 右键单击 -> “Rename safe for Animator”
您当然可以为其添加一些快捷方式等。这取决于您 ;)
这里是对话框的一个小演示,重命名一些嵌套对象并执行一些 undo/redo
但是,在您的用例中,另一种方法是简单地让您的代码使用名称,因为它们可能正在使用 tags。
据我所知,您的代码基于三种不同的情况,因此您可以简单地为每种情况添加一个标签,例如Head
、Arms
、Legs
并相应地分配和检查它们 (GameObject.CompareTag
),并且根本不要触摸名称和动画。
我有两个计划。
在要重命名的节点下创建一个空的GameObject,并在其上附加collider组件。
CATRigHub001Bone004 └ CATRigHub001Bone004Bone001 └ Rig <-------- Collider
在编辑器中重命名骨骼并创建脚本以在播放时自动将其重命名为原始名称。
public class Rename : MonoBehaviour { public string boneName; [NonSerialized] public string partName; void Awake() { partName = name; name = boneName; } }