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:
udx
2026-10-21 12:23:55 +08:00
co-authored by Claude Opus 5.5
parent 6c64210331
commit 89e0fa246f
780 changed files with 7117051 additions and 15906 deletions
+28 -19
View File
@@ -2,6 +2,7 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
@@ -98,37 +99,45 @@ public static class BuildEnemyReview
var view=SceneView.lastActiveSceneView;if(view!=null){view.LookAt(new Vector3(r.reviewOriginX,.35f,0),Quaternion.Euler(12,-12,0),3.6f);view.Repaint();}
return "Built additive review "+r.reviewScene+"; other scenes preserved";
}
public static string Capture(string recipePath="Config/Art/EnemyCharacters/RockBall.json")
public static async Task<string> Capture(string recipePath="Config/Art/EnemyCharacters/RockBall.json")
{
var r=Read(recipePath);var camera=GameObject.Find(Prefix(r)+"Camera").GetComponent<Camera>();
var units=new[]{"Idle","Walk","Attack","Cycle"}.Select(n=>GameObject.Find(Prefix(r)+"_"+n)).ToArray();
var cycles=units.Select(g=>g.GetComponent<CharacterReviewAnimCycle>()).ToArray();
var labels=units.Select(g=>g.transform.Find("NameLabel").GetComponent<TextMesh>()).ToArray();
var labelTexts=labels.Select(t=>t.text).ToArray();
var animators=units.Select(g=>g.GetComponentInChildren<Animator>()).ToArray();
var transforms=units.SelectMany(g=>g.GetComponentsInChildren<Transform>()).ToArray();
var p=transforms.Select(t=>t.localPosition).ToArray();var q=transforms.Select(t=>t.localRotation).ToArray();var s=transforms.Select(t=>t.localScale).ToArray();
var r=Read(recipePath);var sourceCamera=GameObject.Find(Prefix(r)+"Camera").GetComponent<Camera>();
var names=new[]{"Static","Idle","Walk","Attack","Cycle"};
var scene=EditorSceneManager.NewPreviewScene();
var rt=new RenderTexture(1900,760,24);var tex=new Texture2D(1900,760,TextureFormat.RGB24,false);var active=RenderTexture.active;
try
{
for(int i=0;i<units.Length;i++)
// Fresh prefab instances have no review-cycle state or cached Animator
// pose. The live review and its labels are never paused or modified.
var container=new GameObject("IsolatedReviewCapture");SceneManager.MoveGameObjectToScene(container,scene);
var camera=Object.Instantiate(sourceCamera,container.transform);camera.scene=scene;camera.enabled=false;
foreach(var light in sourceCamera.transform.parent.GetComponentsInChildren<Light>())Object.Instantiate(light,container.transform);
var floor=sourceCamera.transform.parent.Find("Ground_Y0");if(floor!=null)Object.Instantiate(floor,container.transform,false);
for(int i=0;i<names.Length;i++)
{
cycles[i].enabled=false;animators[i].enabled=false;
string name=i==0 ? "Idle" : i==1 ? "Walk" : "Attack";
labels[i].text=i==3 ? "Auto cycle" : name+(i==2 ? " (impact)" : " (loop)");
var source=GameObject.Find(Prefix(r)+"_"+names[i]);
var asset=AssetDatabase.LoadAssetAtPath<GameObject>(i==0 ? r.staticPrefab : r.visualPrefab);
var go=(GameObject)PrefabUtility.InstantiatePrefab(asset,scene);go.name="Capture_"+names[i];
go.transform.position=source.transform.position;go.transform.rotation=source.transform.rotation;go.transform.localScale=source.transform.localScale;
var label=Object.Instantiate(source.transform.Find("NameLabel"),go.transform,false).GetComponent<TextMesh>();
label.text=i==0 ? "Static" : i==4 ? "Auto cycle" : names[i]+(i==3 ? " (impact)" : " (loop)");
var marker=source.transform.Find("ForwardMinusZ");if(marker!=null)Object.Instantiate(marker,go.transform,false);
foreach(var t in go.GetComponentsInChildren<Transform>(true))t.gameObject.layer=source.layer;
if(i==0)continue;
var animator=go.GetComponentInChildren<Animator>();animator.enabled=false;
foreach(var skin in go.GetComponentsInChildren<SkinnedMeshRenderer>())skin.updateWhenOffscreen=true;
string name=i==1 ? "Idle" : i==2 ? "Walk" : "Attack";
var clip=AssetDatabase.LoadAssetAtPath<AnimationClip>(r.assetRoot+"/Clips/"+name+".anim");
clip.SampleAnimation(animators[i].gameObject,clip.length*(i==0 ? .5f : i==1 ? .25f : r.attackImpact));
clip.SampleAnimation(animator.gameObject,clip.length*(i==1 ? .5f : i==2 ? .25f : r.attackImpact));
}
await Task.Delay(100);
camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,1900,760),0,0);tex.Apply();
string path="Assets/Screenshots/"+(Companion(r) ? "companion_" : "enemy_")+r.id.ToLowerInvariant()+"_review.png";
File.WriteAllBytes(Path.Combine(Application.dataPath,"../",path),tex.EncodeToPNG());return path;
File.WriteAllBytes(Path.Combine(Application.dataPath,"../",path),tex.EncodeToPNG());AssetDatabase.Refresh();return path;
}
finally
{
RenderTexture.active=active;camera.targetTexture=null;rt.Release();Object.DestroyImmediate(rt);Object.DestroyImmediate(tex);
for(int i=0;i<transforms.Length;i++){transforms[i].localPosition=p[i];transforms[i].localRotation=q[i];transforms[i].localScale=s[i];}
for(int i=0;i<units.Length;i++){labels[i].text=labelTexts[i];animators[i].enabled=true;cycles[i].enabled=true;}
AssetDatabase.Refresh();
RenderTexture.active=active;rt.Release();Object.DestroyImmediate(rt);Object.DestroyImmediate(tex);EditorSceneManager.ClosePreviewScene(scene);
}
}
}