#if UNITY_EDITOR
using System;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using Object=UnityEngine.Object;
/// Offline source-motion editing; saves assets, never adds runtime correction.
public static class FinalizeEnemyMotions
{
[Serializable] public sealed class Recipe
{
public string id,assetRoot,rootBone,chestBone,rightArm,leftArm,outputDir,profile,attackStyle,attackEvent,usage;
public float height,idleSeconds,idleBreath,attackSeconds,attackWindup,attackImpact,attackRecover;
public float attackLeanDegrees,attackArmDegrees,attackLunge,attackReach,idleSourceWeight,attackSourceWeight;
}
sealed class Pose
{
public Vector3[] p,s;
public Quaternion[] q;
public static Pose Read(Transform[] b)=>new Pose { p=b.Select(t=>t.localPosition).ToArray(),s=b.Select(t=>t.localScale).ToArray(),q=b.Select(t=>t.localRotation).ToArray() };
public void Write(Transform[] b) { for(int i=0;iPath.GetFullPath(Path.Combine(Application.dataPath,"../.."));
static float Smooth(float t)=>Mathf.SmoothStep(0,1,Mathf.Clamp01(t));
static float Strike(Recipe r,float t)
{
if(tb.localToWorldMatrix*bind[i]).ToArray();
return mesh.vertices.Select((v,i)=> { var w=weights[i];return matrices[w.boneIndex0].MultiplyPoint3x4(v)*w.weight0
+matrices[w.boneIndex1].MultiplyPoint3x4(v)*w.weight1+matrices[w.boneIndex2].MultiplyPoint3x4(v)*w.weight2
+matrices[w.boneIndex3].MultiplyPoint3x4(v)*w.weight3; }).ToArray();
}
static float Bottom(GameObject model)=>model.GetComponentsInChildren().SelectMany(Vertices).Min(v=>v.y);
static void Save(AnimationClip clip,Transform model,Transform[] bones,Pose[] poses,float seconds,bool loop)
{
Undo.RecordObject(clip,"Author character motion");
clip.ClearCurves();clip.frameRate=30;
int count=poses.Length;
for(int f=1;f(File.ReadAllText(Path.Combine(Repo,recipePath)));
if(string.IsNullOrEmpty(r.attackStyle))r.attackStyle="body_slam";
if(string.IsNullOrEmpty(r.attackEvent))r.attackEvent=r.usage=="companion_visual" ? "OnCompanionAttackImpact" : "OnEnemyAttackImpact";
if(r.attackStyle!="body_slam" && r.attackStyle!="ranged_cast")throw new ArgumentException("Unsupported attack style");
if(r.height<=0 || !(0(r.assetRoot+"/Rigged/"+r.id+"_Rigged.fbx"));
try
{
var anim=model.GetComponent();if(anim!=null) anim.enabled=false;
var bones=model.GetComponentsInChildren().Where(t=>t!=model.transform && (t.name=="Armature" || t.name.StartsWith("bone_"))).ToArray();
var root=bones.First(t=>t.name==r.rootBone);
var chest=bones.FirstOrDefault(t=>t.name==r.chestBone);
var arms=new[] {bones.FirstOrDefault(t=>t.name==r.rightArm),bones.FirstOrDefault(t=>t.name==r.leftArm)};
if(r.profile=="biped_short" && (chest==null || arms.Any(t=>t==null))) throw new Exception("Missing profile bones");
var bind=Pose.Read(bones);var bindRootPosition=root.position;float bindBottom=Bottom(model);
var skins=model.GetComponentsInChildren();
float unitScale=(skins.SelectMany(Vertices).Max(v=>v.y)-bindBottom)/r.height;
var source=AssetDatabase.LoadAllAssetsAtPath(r.assetRoot+"/Anims/"+r.id+"_"+name+".fbx").OfType().First(c=>!c.name.StartsWith("__preview__"));
source.SampleAnimation(model,0);var reference=Pose.Read(bones);
bool idle=name=="Idle";float seconds=idle ? r.idleSeconds : r.attackSeconds;
int count=Mathf.RoundToInt(seconds*30)+1;var poses=new Pose[count];
for(int f=0;f0 ? r.idleSourceWeight : .2f;
float attackWeight=r.attackSourceWeight>0 ? r.attackSourceWeight : .35f;
float weight=Mathf.Pow(Mathf.Sin(Mathf.PI*t),2)*(idle ? idleWeight : attackWeight);
bind.Write(bones);source.SampleAnimation(model,t*source.length);var raw=Pose.Read(bones);
for(int b=0;b(r.assetRoot+"/Clips/"+name+".anim");
Save(clip,model.transform,bones,poses,seconds,idle);
if(!idle)
{
AnimationUtility.SetAnimationEvents(clip,new[] {new AnimationEvent {
time=r.attackImpact*seconds,functionName=r.attackEvent,messageOptions=SendMessageOptions.DontRequireReceiver
}});
}
sb.AppendLine($"{name}: {seconds:F3}s loop={idle} start/end=bind generated source + offline {r.profile}/{r.attackStyle} authoring");
}
finally {Object.DestroyImmediate(model);}
}
AssetDatabase.SaveAssets();
string report=Path.Combine(Repo,r.outputDir,"motion_processing.txt");Directory.CreateDirectory(Path.GetDirectoryName(report));File.WriteAllText(report,sb.ToString());
return sb.ToString();
}
}
#endif