#if UNITY_EDITOR using System; using System.IO; using System.Linq; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; using Object=UnityEngine.Object; public static class BuildEnemyReview { [Serializable] public sealed class Recipe {public string id,assetRoot,staticPrefab,enemyPrefab,visualPrefab,reviewScene,usage;public float attackImpact,height,reviewOriginX;} static Recipe Read(string path) { var r=JsonUtility.FromJson(File.ReadAllText(Path.GetFullPath(Path.Combine(Application.dataPath,"../..",path)))); if(string.IsNullOrEmpty(r.visualPrefab))r.visualPrefab=r.enemyPrefab; if(r.reviewOriginX==0)r.reviewOriginX=20; return r; } static bool Companion(Recipe r)=>r.usage=="companion_visual"; static string Prefix(Recipe r)=>Companion(r) ? "CompanionReview_"+r.id : "EnemyReview"; static void Folder(string path) { string current="Assets"; foreach(string part in path.Split('/').Skip(1)) {string next=current+"/"+part;if(!AssetDatabase.IsValidFolder(next))AssetDatabase.CreateFolder(current,part);current=next;} } public static string Publish(string recipePath="Config/Art/EnemyCharacters/RockBall.json") { var r=Read(recipePath);Folder(Path.GetDirectoryName(r.visualPrefab).Replace('\\','/')); var scene=EditorSceneManager.NewPreviewScene(); try { var source=AssetDatabase.LoadAssetAtPath(r.assetRoot+"/"+r.id+"_Rigged.prefab"); var go=(GameObject)PrefabUtility.InstantiatePrefab(source,scene);go.name=r.id+(Companion(r) ? "_Companion" : "_Enemy"); var animator=go.GetComponentInChildren(); if(animator.runtimeAnimatorController.animationClips.Select(c=>c.name).Distinct().Count()!=3)throw new Exception("Three actions required before publishing character visual"); PrefabUtility.SaveAsPrefabAsset(go,r.visualPrefab); } finally {EditorSceneManager.ClosePreviewScene(scene);} AssetDatabase.SaveAssets();return r.visualPrefab; } public static string Build(string recipePath="Config/Art/EnemyCharacters/RockBall.json") { if(EditorApplication.isPlaying)throw new Exception("Build in Edit Mode"); var r=Read(recipePath);Publish(recipePath); var existing=SceneManager.GetSceneByPath(r.reviewScene); if(existing.IsValid() && existing.isLoaded) { if(existing.isDirty)throw new Exception("Existing character review has unsaved edits; preserve before rebuilding"); EditorSceneManager.CloseScene(existing,true); } Folder(Path.GetDirectoryName(r.reviewScene).Replace('\\','/')); // Additive + a separate render layer preserves any user edits in other scenes. var scene=EditorSceneManager.NewScene(NewSceneSetup.EmptyScene,NewSceneMode.Additive); SceneManager.SetActiveScene(scene); var root=new GameObject((Companion(r) ? "CompanionProductionReview_" : "EnemyProductionReview_")+r.id); const int layer=29; const string floorPath="Assets/Bundles/Scenes/Review/EnemyReviewFloor.mat"; var mat=AssetDatabase.LoadAssetAtPath(floorPath); if(mat==null){mat=new Material(Shader.Find("Universal Render Pipeline/Lit"));mat.color=new Color(.48f,.56f,.50f);AssetDatabase.CreateAsset(mat,floorPath);} float spacing=Mathf.Max(1,r.height*1.4f); var floor=GameObject.CreatePrimitive(PrimitiveType.Plane);floor.name="Ground_Y0";floor.transform.SetParent(root.transform,false); floor.transform.position=new Vector3(r.reviewOriginX,0,0);floor.transform.localScale=Vector3.one*(.8f*spacing);floor.GetComponent().sharedMaterial=mat; string[] names={"Static","Idle","Walk","Attack","Cycle"}; for(int i=0;i(i==0 ? r.staticPrefab : r.visualPrefab); var go=(GameObject)PrefabUtility.InstantiatePrefab(asset,root.transform);go.name=Prefix(r)+"_"+names[i]; go.transform.position=new Vector3(r.reviewOriginX+(i-2)*spacing,0,0); var label=new GameObject("NameLabel");label.transform.SetParent(go.transform,false);label.transform.position=go.transform.position+Vector3.up*(r.height+.21f); label.transform.localScale=Vector3.one/go.transform.lossyScale.x; var text=label.AddComponent();text.text=names[i];text.anchor=TextAnchor.MiddleCenter;text.characterSize=.01f;text.fontSize=42;text.color=new Color(.12f,.18f,.2f); var marker=GameObject.CreatePrimitive(PrimitiveType.Cube);marker.name="ForwardMinusZ";marker.transform.SetParent(go.transform,false); marker.transform.position=go.transform.position+new Vector3(0,.012f,-.4f);marker.transform.localScale=new Vector3(.035f,.015f,.22f)/go.transform.lossyScale.x; Object.DestroyImmediate(marker.GetComponent()); if(i>0) { var cycle=go.AddComponent();cycle.enabled=false; var so=new SerializedObject(cycle);var modes=so.FindProperty("motionNames");modes.arraySize=3; for(int m=0;m<3;m++)modes.GetArrayElementAtIndex(m).stringValue=new[]{"Idle","Walk","Attack"}[m]; so.FindProperty("fixedMotion").intValue=i==4 ? -1 : i-1; so.ApplyModifiedPropertiesWithoutUndo();cycle.enabled=true; } } var sun=new GameObject(Prefix(r)+"Sun").AddComponent();sun.transform.SetParent(root.transform,false); sun.type=LightType.Directional;sun.intensity=1.0f;sun.shadows=LightShadows.Soft;sun.cullingMask=1<();camera.transform.SetParent(root.transform,false); camera.clearFlags=CameraClearFlags.SolidColor;camera.backgroundColor=new Color(.66f,.76f,.81f);camera.depth=50+r.reviewOriginX-20;camera.cullingMask=1<(true))t.gameObject.layer=layer; RenderSettings.ambientMode=UnityEngine.Rendering.AmbientMode.Trilight; RenderSettings.ambientSkyColor=new Color(.65f,.72f,.8f); RenderSettings.ambientEquatorColor=new Color(.45f,.5f,.48f); RenderSettings.ambientGroundColor=new Color(.3f,.28f,.25f); EditorSceneManager.SaveScene(scene,r.reviewScene); 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") { var r=Read(recipePath);var camera=GameObject.Find(Prefix(r)+"Camera").GetComponent(); var units=new[]{"Idle","Walk","Attack","Cycle"}.Select(n=>GameObject.Find(Prefix(r)+"_"+n)).ToArray(); var cycles=units.Select(g=>g.GetComponent()).ToArray(); var labels=units.Select(g=>g.transform.Find("NameLabel").GetComponent()).ToArray(); var labelTexts=labels.Select(t=>t.text).ToArray(); var animators=units.Select(g=>g.GetComponentInChildren()).ToArray(); var transforms=units.SelectMany(g=>g.GetComponentsInChildren()).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 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(r.assetRoot+"/Clips/"+name+".anim"); clip.SampleAnimation(animators[i].gameObject,clip.length*(i==0 ? .5f : i==1 ? .25f : r.attackImpact)); } 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; } finally { RenderTexture.active=active;camera.targetTexture=null;rt.Release();Object.DestroyImmediate(rt);Object.DestroyImmediate(tex); for(int i=0;i