Add formal enemies, remaining formal partners and synthesized UI SFX
- UI SFX v3: procedurally synthesized one-shots (Tools/synth_ui_sfx.py) wired to buttons, rewards, errors and red dots via UiSfx, gated by the SFX setting. - Five formal enemies (Hornlet, Dashlet, Shellhorn, HornCaptain, GreatHorn): Hunyuan 50k -> 2000-tri XPbr, DGXA TokenRig, Idle/Walk/Attack, nested per type in PrototypeArena; PrototypeBattle.Enemy gains presentation-only Moving/Strikes. - Formal partners for all ten pets in the arena and portraits, including new VineSprite, ThunderLeopard and TideWhale. - Pipeline fixes: reject local TokenRig fallback, drop loose mesh islands, configurable Unity run_script timeout, companion static stage. - Also includes concurrent work from another tool in this working tree (FlameLion, enemy combat FX, challenge mode, home explorer, server updates). - Resources registered in Doc/ResDesc_*.md; verified on isolated :18081. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,8 +13,10 @@ public static class FinalizeEnemyMotions
|
||||
[Serializable] public sealed class Recipe
|
||||
{
|
||||
public string id,assetRoot,rootBone,chestBone,rightArm,leftArm,outputDir,profile,attackStyle,attackEvent,usage;
|
||||
public string headBone,neckBone,frontRightFoot,frontLeftFoot,hindRightFoot,hindLeftFoot,rightFoot,leftFoot;
|
||||
public float height,idleSeconds,idleBreath,attackSeconds,attackWindup,attackImpact,attackRecover;
|
||||
public float attackLeanDegrees,attackArmDegrees,attackLunge,attackReach,idleSourceWeight,attackSourceWeight;
|
||||
public float attackLeanDegrees,attackArmDegrees,attackLunge,attackReach,idleSourceWeight,attackSourceWeight,wingSpreadDegrees;
|
||||
public float attackHeadReach,attackHeadPitchDegrees,attackSpreadDegrees;
|
||||
}
|
||||
sealed class Pose
|
||||
{
|
||||
@@ -79,7 +81,18 @@ public static class FinalizeEnemyMotions
|
||||
var r=JsonUtility.FromJson<Recipe>(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");
|
||||
bool bird=r.profile=="avian_grounded";
|
||||
bool turtle=r.profile=="quadruped_turtle";
|
||||
bool heavy=r.profile=="quadruped_heavy";
|
||||
bool quadruped=turtle || heavy;
|
||||
bool roar=r.attackStyle=="flame_roar";
|
||||
if(r.profile!="biped_short" && !bird && !quadruped)throw new ArgumentException("Unsupported anatomy");
|
||||
if(r.attackStyle!="body_slam" && r.attackStyle!="ranged_cast" && r.attackStyle!="wing_cast" && r.attackStyle!="turtle_bubble" && r.attackStyle!="braced_bash" && !roar)throw new ArgumentException("Unsupported attack style");
|
||||
if(roar && r.profile!="biped_short")throw new ArgumentException("Flame roar requires an upright biped");
|
||||
if(bird!=(r.attackStyle=="wing_cast") || (bird && !(r.wingSpreadDegrees>0 && r.wingSpreadDegrees<=60)))
|
||||
throw new ArgumentException("Invalid grounded-bird wing cast");
|
||||
if(turtle!=(r.attackStyle=="turtle_bubble"))throw new ArgumentException("Invalid turtle bubble cast");
|
||||
if(heavy!=(r.attackStyle=="braced_bash"))throw new ArgumentException("Invalid heavy quadruped bash");
|
||||
if(r.height<=0 || !(0<r.attackWindup && r.attackWindup<r.attackImpact && r.attackImpact<r.attackRecover && r.attackRecover<1))
|
||||
throw new ArgumentException("Invalid dimensions / attack phase order");
|
||||
var sb=new StringBuilder();
|
||||
@@ -91,10 +104,18 @@ public static class FinalizeEnemyMotions
|
||||
var anim=model.GetComponent<Animator>();if(anim!=null) anim.enabled=false;
|
||||
var bones=model.GetComponentsInChildren<Transform>().Where(t=>t!=model.transform && (t.name=="Armature" || t.name.StartsWith("bone_"))).ToArray();
|
||||
var root=bones.First(t=>t.name==r.rootBone);
|
||||
var head=quadruped || roar ? bones.First(t=>t.name==r.headBone) : null;
|
||||
var neck=quadruped || roar ? bones.First(t=>t.name==r.neckBone) : null;
|
||||
var feet=(quadruped ? new[]{r.frontRightFoot,r.frontLeftFoot,r.hindRightFoot,r.hindLeftFoot} : roar ? new[]{r.rightFoot,r.leftFoot} : new string[0]).Select(n=>bones.First(t=>t.name==n)).ToArray();
|
||||
var footPositions=feet.Select(t=>t.position).ToArray();var footRotations=feet.Select(t=>t.rotation).ToArray();
|
||||
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");
|
||||
if(!quadruped && (chest==null || arms.Any(t=>t==null) || arms[0]==arms[1])) throw new Exception("Missing/distinct profile bones required");
|
||||
if((heavy || roar) && (chest==null || chest==head))throw new Exception("Independent chest and head required");
|
||||
var bind=Pose.Read(bones);var bindRootPosition=root.position;float bindBottom=Bottom(model);
|
||||
var bindRootRotation=root.rotation;
|
||||
var wingSides=bird || roar ? arms.Select(a=>Mathf.Sign(a.position.x-root.position.x)).ToArray() : new float[0];
|
||||
if((bird || roar) && wingSides[0]==wingSides[1])throw new Exception("Arm/wing roots must be on opposite sides");
|
||||
var skins=model.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
float unitScale=(skins.SelectMany(Vertices).Max(v=>v.y)-bindBottom)/r.height;
|
||||
var source=AssetDatabase.LoadAllAssetsAtPath(r.assetRoot+"/Anims/"+r.id+"_"+name+".fbx").OfType<AnimationClip>().First(c=>!c.name.StartsWith("__preview__"));
|
||||
@@ -114,28 +135,64 @@ public static class FinalizeEnemyMotions
|
||||
bones[b].localScale=bind.s[b];
|
||||
}
|
||||
root.position=new Vector3(bindRootPosition.x,root.position.y,bindRootPosition.z);
|
||||
if(r.profile=="biped_short")
|
||||
if(roar)root.rotation=bindRootRotation;
|
||||
if(quadruped)
|
||||
{
|
||||
root.rotation=bindRootRotation;
|
||||
if(idle)head.position+=Vector3.up*(r.idleBreath*Mathf.Pow(Mathf.Sin(Mathf.PI*t),2)*unitScale);
|
||||
else
|
||||
{
|
||||
float strike=Strike(r,t);
|
||||
// Heavy animals drive the shoulder and head together; a turtle
|
||||
// only extends its neck. Paws are planted in either case.
|
||||
if(heavy)chest.position+=Vector3.back*(r.attackLunge*strike*unitScale);
|
||||
neck.position+=Vector3.back*(r.attackHeadReach*strike*unitScale);
|
||||
head.rotation=Quaternion.AngleAxis(-r.attackHeadPitchDegrees*strike,Vector3.right)*head.rotation;
|
||||
}
|
||||
}
|
||||
if(r.profile=="biped_short" || bird)
|
||||
{
|
||||
if(idle)
|
||||
{
|
||||
float breath=Mathf.Pow(Mathf.Sin(Mathf.PI*t),2);
|
||||
chest.localScale=Vector3.Scale(chest.localScale,new Vector3(1+.007f*breath,1+r.idleBreath/r.height*breath,1+.007f*breath));
|
||||
if(roar)head.position+=Vector3.up*(r.idleBreath*breath*unitScale);
|
||||
else chest.localScale=Vector3.Scale(chest.localScale,new Vector3(1+.007f*breath,1+r.idleBreath/r.height*breath,1+.007f*breath));
|
||||
}
|
||||
else
|
||||
{
|
||||
float strike=Strike(r,t);
|
||||
root.rotation=Quaternion.AngleAxis(-r.attackLeanDegrees*strike,Vector3.right)*root.rotation;
|
||||
root.position+=Vector3.back*(r.attackLunge*strike*unitScale);
|
||||
foreach(var arm in arms)
|
||||
if(!roar)
|
||||
{
|
||||
float armSign=r.attackStyle=="ranged_cast" ? 1 : -1;
|
||||
root.rotation=Quaternion.AngleAxis(-r.attackLeanDegrees*strike,Vector3.right)*root.rotation;
|
||||
root.position+=Vector3.back*(r.attackLunge*strike*unitScale);
|
||||
}
|
||||
for(int side=0;side<arms.Length;side++)
|
||||
{
|
||||
var arm=arms[side];
|
||||
float armSign=r.attackStyle=="ranged_cast" || roar ? 1 : -1;
|
||||
arm.rotation=Quaternion.AngleAxis(armSign*r.attackArmDegrees*strike,Vector3.right)*arm.rotation;
|
||||
// Standing bird wings start outward/upward: negative X sweeps
|
||||
// them toward -Z. Opposite Z rotations open each wing upward.
|
||||
if(bird)arm.rotation=Quaternion.AngleAxis(wingSides[side]*r.wingSpreadDegrees*strike,Vector3.forward)*arm.rotation;
|
||||
if(roar)arm.rotation=Quaternion.AngleAxis(wingSides[side]*r.attackSpreadDegrees*strike,Vector3.forward)*arm.rotation;
|
||||
// A ranged cast presents both paws in front, without moving the actor.
|
||||
// Small offline bone translation supplies reach for very short chibi arms.
|
||||
if(r.attackStyle=="ranged_cast")arm.position+=Vector3.back*(r.attackReach*strike*unitScale);
|
||||
if(r.attackStyle=="ranged_cast" || bird || roar)arm.position+=Vector3.back*(r.attackReach*strike*unitScale);
|
||||
}
|
||||
if(roar)
|
||||
{
|
||||
neck.position+=Vector3.back*(r.attackHeadReach*strike*unitScale);
|
||||
head.rotation=Quaternion.AngleAxis(r.attackHeadPitchDegrees*strike,Vector3.right)*head.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Planted feet are baked offline for the standing braced actions.
|
||||
for(int foot=0;foot<feet.Length;foot++)
|
||||
{
|
||||
for(int b=0;b<bones.Length;b++)if(bones[b]!=feet[foot] && bones[b].IsChildOf(feet[foot]))
|
||||
{bones[b].localPosition=bind.p[b];bones[b].localRotation=bind.q[b];bones[b].localScale=bind.s[b];}
|
||||
feet[foot].position=footPositions[foot];feet[foot].rotation=footRotations[foot];
|
||||
}
|
||||
root.position+=Vector3.up*(bindBottom-Bottom(model));
|
||||
poses[f]=Pose.Read(bones);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user