using System; using System.IO; using System.Linq; using HotUpdate.Gameplay; using Newtonsoft.Json; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; /// Persistent combat assets, imported sound settings and an isolated review. Run through Pipeline. public static class BuildEnemyCombatFx { public const string Folder = "Assets/Bundles/Effects/EnemyCombat"; public const string Bank = Folder + "/EnemyCombatPresentation.prefab"; const string Arena = "Assets/Bundles/Scenes/Prototype/PrototypeArena.prefab"; static readonly string[] Names = { "HornletAttack", "DashletAttack", "ShellhornAttack", "HornCaptainAttack", "GreatHornAttack", "HitSoft", "HitArmor", "HitShield" }; static readonly Color[] Colors = { new Color(1,.66f,.23f), new Color(.65f,.9f,1), new Color(1,.7f,.35f), new Color(1,.38f,.14f), new Color(1,.3f,.12f), new Color(1,.94f,.63f), new Color(1,.7f,.28f), new Color(.3f,.85f,1) }; static void Folders(string path) { if (AssetDatabase.IsValidFolder(path)) return; Folders(Path.GetDirectoryName(path).Replace('\\','/')); AssetDatabase.CreateFolder(Path.GetDirectoryName(path).Replace('\\','/'), Path.GetFileName(path)); } static Material Material() { string png = Folder + "/CombatSoft.png"; var tex = new Texture2D(64,64,TextureFormat.RGBA32,false); for (int y=0;y<64;y++) for (int x=0;x<64;x++) { float r = Vector2.Distance(new Vector2(x,y),new Vector2(31.5f,31.5f))/31.5f; tex.SetPixel(x,y,new Color(1,1,1,Mathf.Pow(Mathf.Clamp01(1-r),2))); } tex.Apply(); File.WriteAllBytes(png,tex.EncodeToPNG()); UnityEngine.Object.DestroyImmediate(tex); AssetDatabase.ImportAsset(png, ImportAssetOptions.ForceUpdate); var imp=(TextureImporter)AssetImporter.GetAtPath(png); imp.alphaIsTransparency=true; imp.wrapMode=TextureWrapMode.Clamp; imp.mipmapEnabled=false; imp.SaveAndReimport(); string path=Folder+"/CombatGlow.mat"; var mat=AssetDatabase.LoadAssetAtPath(path); if(mat==null) { mat=new Material(Shader.Find("AIGame/EnemyCombatParticle") ?? throw new Exception("Particle shader missing")); AssetDatabase.CreateAsset(mat,path); } mat.mainTexture=AssetDatabase.LoadAssetAtPath(png); EditorUtility.SetDirty(mat); return mat; } static Mesh Arc(string name, float degrees) { const int count=40; var verts=new Vector3[(count+1)*2]; var uv=new Vector2[verts.Length]; var tri=new int[count*6]; for(int i=0;i<=count;i++) { float a=(i/(float)count*degrees-degrees/2)*Mathf.Deg2Rad; float width=.065f*Mathf.Sin(Mathf.PI*i/count)+.006f; for(int j=0;j<2;j++) { float r=.5f+(j==0?-width:width); verts[i*2+j]=new Vector3(Mathf.Sin(a)*r,Mathf.Cos(a)*r,0); uv[i*2+j]=new Vector2(.5f,j==0?.35f:.65f); } if(i(path); if(mesh==null) { mesh=new Mesh();AssetDatabase.CreateAsset(mesh,path); } else mesh.Clear(); mesh.vertices=verts; mesh.uv=uv; mesh.triangles=tri;mesh.RecalculateBounds();EditorUtility.SetDirty(mesh);return mesh; } static ParticleSystem Part(Transform parent,string name,Material mat,Color color,int count,float life,float size,float speed,Mesh mesh=null) { var go=new GameObject(name);go.layer=30;if(parent!=null)go.transform.SetParent(parent,false); var ps=go.AddComponent();var main=ps.main; main.playOnAwake=false;main.loop=false;main.duration=.8f;main.startLifetime=new ParticleSystem.MinMaxCurve(life*.7f,life);main.startSpeed=new ParticleSystem.MinMaxCurve(speed*.5f,speed);main.startSize=new ParticleSystem.MinMaxCurve(size*.65f,size);main.startColor=color;main.maxParticles=48;main.simulationSpace=ParticleSystemSimulationSpace.Local; var em=ps.emission;em.rateOverTime=0;em.SetBursts(new[]{new ParticleSystem.Burst(0,(short)count)}); var shape=ps.shape;shape.shapeType=ParticleSystemShapeType.Sphere;shape.radius=.055f; var col=ps.colorOverLifetime;col.enabled=true; var grad=new Gradient();grad.SetKeys(new[]{new GradientColorKey(color,0),new GradientColorKey(color,1)},new[]{new GradientAlphaKey(.95f,0),new GradientAlphaKey(.65f,.25f),new GradientAlphaKey(0,1)});col.color=grad; var sz=ps.sizeOverLifetime;sz.enabled=true;sz.size=new ParticleSystem.MinMaxCurve(1,AnimationCurve.Linear(0,1,1,.08f)); var rend=ps.GetComponent();rend.sharedMaterial=mat; if(mesh!=null) {rend.renderMode=ParticleSystemRenderMode.Mesh;rend.mesh=mesh; main.startRotation3D=true;main.startRotationZ=0;} else {rend.renderMode=ParticleSystemRenderMode.Stretch;rend.velocityScale=.08f;rend.lengthScale=1.3f;} return ps; } public static string Build() { if(EditorApplication.isPlaying)throw new Exception("Author in Edit Mode"); Folders(Folder);var mat=Material();var arc=Arc("CombatArc",200);var ring=Arc("CombatRing",360); var original=SceneManager.GetActiveScene();var scene=EditorSceneManager.NewScene(NewSceneSetup.EmptyScene,NewSceneMode.Additive); try { SceneManager.SetActiveScene(scene);var assets=new GameObject[8]; for(int i=0;i<8;i++) { var root=Part(null,"Vfx_Enemy_"+Names[i],mat,Colors[i], i<5?14:10,i==4?.55f:.32f,i==4?.18f:.10f,i==4?2.8f:1.7f); var glow=Part(root.transform,"ContactFlash",mat,Colors[i],1,.15f,i==4?1.25f:.65f,0);glow.GetComponent().renderMode=ParticleSystemRenderMode.Billboard; var sweep=Part(root.transform,i<5?"StrikeArc":"ImpactRing",mat,Colors[i],1,i==4?.46f:.22f,i==4?2.5f:i<5?1.6f:.85f,0,i<5&&i!=2?arc:ring); var sz=sweep.sizeOverLifetime;sz.size=new ParticleSystem.MinMaxCurve(1,AnimationCurve.Linear(0,.65f,1,1.3f)); if(i==1) {sweep.transform.localScale=new Vector3(.45f,1.5f,1);sweep.transform.localRotation=Quaternion.Euler(0,0,65);} if(i==3) {var second=Part(root.transform,"CaptainCrossStrike",mat,Colors[i],1,.26f,1.6f,0,arc);second.transform.localRotation=Quaternion.Euler(0,0,95);} if(i==4) { var shock=Part(root.transform,"GroundShock",mat,Colors[i],1,.55f,3.5f,0,ring);shock.transform.localPosition=Vector3.down*.75f;shock.transform.localRotation=Quaternion.Euler(90,0,0);var s=shock.sizeOverLifetime;s.size=new ParticleSystem.MinMaxCurve(1,AnimationCurve.Linear(0,.1f,1,1.2f)); } assets[i]=PrefabUtility.SaveAsPrefabAsset(root.gameObject,Folder+"/Vfx_Enemy_"+Names[i]+".prefab");UnityEngine.Object.DestroyImmediate(root.gameObject); } var bank=new GameObject("EnemyCombatPresentation");bank.layer=30;var player=bank.AddComponent();var so=new SerializedObject(player); var pools=so.FindProperty("_effects");pools.arraySize=8;var clips=so.FindProperty("_clips");clips.arraySize=8; for(int i=0;i<8;i++) { var list=pools.GetArrayElementAtIndex(i).FindPropertyRelative("instances");list.arraySize=4; for(int n=0;n<4;n++) { var go=(GameObject)PrefabUtility.InstantiatePrefab(assets[i],bank.transform);go.name=Names[i]+"_"+n;list.GetArrayElementAtIndex(n).objectReferenceValue=go.GetComponent(); } string path="Assets/Bundles/Sound/SFX/EnemyCombat/Enemy_"+Names[i]+".wav";AssetDatabase.ImportAsset(path,ImportAssetOptions.ForceUpdate); var imp=(AudioImporter)AssetImporter.GetAtPath(path);if(imp==null)throw new Exception("Missing "+path); var st=imp.defaultSampleSettings;st.loadType=AudioClipLoadType.DecompressOnLoad;st.compressionFormat=AudioCompressionFormat.PCM;st.sampleRateSetting=AudioSampleRateSetting.PreserveSampleRate;st.preloadAudioData=true;imp.defaultSampleSettings=st;imp.forceToMono=false;imp.SaveAndReimport(); clips.GetArrayElementAtIndex(i).objectReferenceValue=AssetDatabase.LoadAssetAtPath(path); } var voices=so.FindProperty("_voices");voices.arraySize=8; for(int n=0;n<8;n++) {var go=new GameObject("Voice"+n);go.transform.SetParent(bank.transform,false);var source=go.AddComponent();source.playOnAwake=false;source.spatialBlend=0;source.volume=.40f;source.priority=100;voices.GetArrayElementAtIndex(n).objectReferenceValue=source;} so.ApplyModifiedPropertiesWithoutUndo();PrefabUtility.SaveAsPrefabAsset(bank,Bank);UnityEngine.Object.DestroyImmediate(bank); } finally {SceneManager.SetActiveScene(original);EditorSceneManager.CloseScene(scene,true);} var arena=PrefabUtility.LoadPrefabContents(Arena); try {var old=arena.transform.Find("EnemyCombatPresentation");if(old!=null)UnityEngine.Object.DestroyImmediate(old.gameObject);PrefabUtility.InstantiatePrefab(AssetDatabase.LoadAssetAtPath(Bank),arena.transform);PrefabUtility.SaveAsPrefabAsset(arena,Arena);} finally {PrefabUtility.UnloadPrefabContents(arena);} AssetDatabase.SaveAssets();return Verify(); } public static string Verify() { foreach(var name in new[]{"review","battle"}) if(File.Exists("Assets/Screenshots/enemy_combat_fx_"+name+".png"))AssetDatabase.ImportAsset("Assets/Screenshots/enemy_combat_fx_"+name+".png",ImportAssetOptions.ForceUpdate); var bank=AssetDatabase.LoadAssetAtPath(Bank);var p=bank.GetComponent(); if(ShaderUtil.ShaderHasError(Shader.Find("AIGame/EnemyCombatParticle")))throw new Exception("Combat shader compilation failed"); if(p.EffectCount!=8||p.VoiceCount!=8)throw new Exception("Missing bank slots"); var clips=Enumerable.Range(0,8).Select(i=> {var c=p.Clip(i);if(c==null||c.length>1||c.frequency!=44100||c.channels!=2)throw new Exception("Invalid clip "+i);var imp=(AudioImporter)AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(c));if(imp.defaultSampleSettings.compressionFormat!=AudioCompressionFormat.PCM||imp.defaultSampleSettings.loadType!=AudioClipLoadType.DecompressOnLoad)throw new Exception("Unexpected importer for "+c.name);return new {c.name,c.length,c.frequency,c.channels};}).ToArray(); if(bank.GetComponentsInChildren().Any(ps=>ps.main.playOnAwake||ps.main.loop))throw new Exception("Loop/autoplay in one shot"); if(AssetDatabase.LoadAssetAtPath(Arena).GetComponentInChildren(true)==null)throw new Exception("Arena missing bank"); var report=JsonConvert.SerializeObject(new {status="pass",bank=Bank,effects=8,poolInstances=32,voices=8,clips},Formatting.Indented); string path="../Doc/Design/Sound/EnemyCombat/unity_assets.json";Directory.CreateDirectory(Path.GetDirectoryName(path));File.WriteAllText(path,report);return report; } }