popcorn/AnimationValidator/Editor/AnimationValidator.cs

279 lines
10 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
namespace StaTech.AnimationValidator {
public static class AnimationValidator {
private static Animator _animator;
private static readonly string[] CurveNames = {
"m_PositionCurves",
"m_ScaleCurves",
"m_FloatCurves",
"m_PPtrCurves",
"m_EditorCurves",
"m_EulerEditorCurves"
};
private static readonly string PathPropName = "path";
private static readonly string AttributePropName = "attribute";
private static List<ClipValidationContainer> _clipValidations;
public static List<ClipValidationContainer> ValidateAnimation(){
if(Selection.activeGameObject == null){
return null;
}
var selectedTransform = Selection.activeGameObject.transform;
// アニメーションクリップを取り出すAnimator
_animator = selectedTransform.GetComponent<Animator>();
if(_animator == null){
// 警告window出す
EditorUtility.DisplayDialog("エラー", "選択したオブジェクトにAnimatorがついていません", "閉じる");
return null;
}
var runTimeAnimatorController = _animator.runtimeAnimatorController;
var animationController = runTimeAnimatorController as AnimatorController;
if(animationController == null){
// 警告window出す
EditorUtility.DisplayDialog("エラー", "AnimatorにAnimationControllerが設定されていません", "閉じる");
return null;
}
if(_clipValidations == null){
_clipValidations = new List<ClipValidationContainer>();
}else{
_clipValidations.Clear();
}
// 全てのレイヤーを取り出す
for(var i = 0; i < animationController.layers.Length; ++i){
var layer = animationController.layers[i];
var stateMachine = layer.stateMachine;
// 全てのステートを取り出す
for(var j = 0; j < stateMachine.states.Length; ++j){
var state = stateMachine.states[j];
var clip = state.state.motion as AnimationClip;
if(clip){
var validationData = FindLostAnimations(clip, selectedTransform);
_clipValidations.Add(validationData);
}
}
}
return _clipValidations;
}
public static void ExecuteUnitRecovery(GameObject selected, ClipValidationContainer container, Action<string, float> onProgressChanged){
var childObjectsNames = GetChildObjectNames(selected);
Recovery(container, ref childObjectsNames, onProgressChanged);
EditorUtility.ClearProgressBar();
}
public static void ExecuteAllRecovery(GameObject selected, List<ClipValidationContainer> containers, Action<string, float> onProgressChanged){
var childObjectsNames = GetChildObjectNames(selected);
var targetCount = containers.Count;
for(var i = 0; i < containers.Count; ++i){
var container = containers[i];
Recovery(container, ref childObjectsNames);
if(onProgressChanged != null){
onProgressChanged.Invoke(container.ClipName, (float)i / targetCount);
}
}
AssetDatabase.SaveAssets();
EditorUtility.ClearProgressBar();
}
private static ClipValidationContainer FindLostAnimations(AnimationClip clip, Transform root){
var lostAnimations = new List<LostProperty>();
var serializedClip = new SerializedObject(clip);
// clipはいくつかのカーブをもっている
foreach(var curveName in CurveNames){
var curves = serializedClip.FindProperty(curveName);
var curveCount = curves.arraySize;
for(var i = 0; i < curveCount; ++i){
var curve = curves.GetArrayElementAtIndex(i);
var pathProperty = curve.FindPropertyRelative(PathPropName);
var attributeProperty = curve.FindPropertyRelative(AttributePropName);
var attribute = attributeProperty != null ? attributeProperty.stringValue : "Position";
// ルートの直下からの相対パスが入ってる
var path = pathProperty.stringValue;
var result = root.Find(path);
if(result == null){
var lost = new LostProperty {
ObjectName = GetObjectName(path),
PropPath = path,
AttributeName = attribute,
SerializedProperty = pathProperty,
SerializedClip = serializedClip,
State = FixState.Lost
};
lostAnimations.Add(lost);
}
}
}
var clipLost = new ClipValidationContainer(lostAnimations, clip);
return clipLost;
}
private static void Recovery(ClipValidationContainer validationContainer, ref List<PathModel> paths, Action<string, float> onUnitProgressChanged = null){
for(var i = 0; i < validationContainer.LostProperties.Count; ++i){
var anim = validationContainer.LostProperties[i];
var pathInHierarchy = paths.Where(path => path.ObjectName == anim.ObjectName).ToList();
var hierarchyCount = pathInHierarchy.Count;
if(onUnitProgressChanged != null){
onUnitProgressChanged.Invoke(anim.ObjectName, (float)i / validationContainer.LostProperties.Count);
}
if(hierarchyCount == 0){
//同名のobjectNameが存在しない
anim.State = FixState.ErrorNoSameName;
continue;
}
if(hierarchyCount >= 2){
// 複数のobjectが存在するから修正出来ない
anim.State = FixState.ErrorDuplicate;
continue;
}
// 修復処理
var correctPath = pathInHierarchy.First().RelativePath;
Debug.Log(anim.PropPath + "を" + correctPath + "に修正");
anim.State = FixState.Fixed;
anim.SerializedProperty.stringValue = correctPath;
anim.SerializedClip.ApplyModifiedProperties();
}
}
public static string GetObjectName(string path){
if(string.IsNullOrEmpty(path)){
return "";
}
var separated = path.Split('/');
return separated[separated.Length - 1];
}
private static List<PathModel> GetChildObjectNames(GameObject root){
var children = GetAllObjects(root);
return children.Select(obj => {
// なんか進捗とか出す
return new PathModel(obj, root.name);
}).ToList();
}
public static List<GameObject> GetAllObjects(GameObject obj){
var allChildren = new List<GameObject>();
GetChildren(obj, ref allChildren);
return allChildren;
}
public static void GetChildren(GameObject obj, ref List<GameObject> allChildren){
var children = obj.GetComponentInChildren <Transform>();
// 子要素がいなければ終了
if(children.childCount == 0){
return;
}
foreach(Transform ob in children){
allChildren.Add(ob.gameObject);
GetChildren(ob.gameObject, ref allChildren);
}
}
public static string ParentRelativePath(Transform t, string path, string rootName){
var parent = t.parent;
if(parent == null){
if(t.name != rootName){
Debug.LogError("不正な階層指定してます" + rootName);
}
return path;
}
if(parent.name == rootName){
return path;
}
path = parent.name + "/" + path;
return ParentRelativePath(parent, path, rootName);
}
private class PathModel {
public readonly string ObjectName;
public readonly string RelativePath;
public PathModel(GameObject go, string rootName){
ObjectName = go.name;
RelativePath = ParentRelativePath(go.transform, ObjectName, rootName);
}
}
}
public class ClipValidationContainer{
public ClipValidationContainer(List<LostProperty> lostAnims, AnimationClip clip){
LostProperties = lostAnims;
ClipName = clip.name;
}
public string ClipName {
get;
set;
}
public bool HasNoError {
get{
return LostProperties.Count == 0 || LostProperties.All(p => p.State == FixState.Fixed);
}
}
public List<LostProperty> LostProperties {
get;
private set;
}
}
public class LostProperty {
public string AttributeName;
/// <summary>
/// オブジェクトの名前
/// 一意である必要がある
/// </summary>
public string ObjectName;
/// <summary>
/// アニメーション内のプロパティのパス
/// </summary>
public string PropPath;
public SerializedObject SerializedClip;
public SerializedProperty SerializedProperty;
/// <summary>
/// 修復済みフラグ
/// </summary>
public FixState State;
}
public enum FixState {
None,
Lost,
ErrorNoSameName,
ErrorDuplicate,
Fixed
}
}