Files
udxandClaude Opus 5.5 89e0fa246f 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>
2026-10-21 12:23:55 +08:00

75 lines
3.8 KiB
C#

using System;
using System.IO;
using System.Linq;
using HotUpdate.Gameplay;
using Newtonsoft.Json;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
/// <summary>Pipeline authoring of the UiSfx prefab: one AudioSource plus the v3 synthesized UI clips in UiSound order.</summary>
public static class BuildUiSfx
{
public const string PrefabPath = "Assets/Bundles/Sound/Prefab/UiSfx.prefab";
const string ClipFolder = "Assets/Bundles/Sound/SFX/UI/";
public static string Build()
{
if (EditorApplication.isPlaying) throw new Exception("Build UI SFX in Edit Mode");
var names = Enum.GetNames(typeof(UiSound));
var clips = names.Select(n =>
{
string path = ClipFolder + "Ui_" + n + ".wav";
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
return AssetDatabase.LoadAssetAtPath<AudioClip>(path) ?? throw new Exception("Missing " + path);
}).ToArray();
var longest = clips.OrderByDescending(c => c.length).First();
if (longest.length > 1f) throw new Exception(longest.name + " is still " + longest.length + "s; import the one-shots first");
var original = SceneManager.GetActiveScene();
var temporary = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
try
{
SceneManager.SetActiveScene(temporary);
var root = new GameObject(UiSfx.PrefabName);
var source = root.AddComponent<AudioSource>();
source.playOnAwake = false; source.loop = false; source.spatialBlend = 0; source.volume = .8f;
var player = root.AddComponent<UiSfx>();
var serialized = new SerializedObject(player);
serialized.FindProperty("_source").objectReferenceValue = source;
var list = serialized.FindProperty("_clips"); list.arraySize = clips.Length;
for (int i = 0; i < clips.Length; i++) list.GetArrayElementAtIndex(i).objectReferenceValue = clips[i];
serialized.ApplyModifiedPropertiesWithoutUndo();
PrefabUtility.SaveAsPrefabAsset(root, PrefabPath);
}
finally
{
SceneManager.SetActiveScene(original);
EditorSceneManager.CloseScene(temporary, true);
}
return Verify();
}
public static string Verify()
{
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabPath) ?? throw new Exception("Missing " + PrefabPath);
var player = prefab.GetComponent<UiSfx>(); var audio = prefab.GetComponent<AudioSource>();
if (player == null || player.Source != audio || audio.playOnAwake || audio.loop || audio.spatialBlend != 0) throw new Exception("UiSfx prefab configuration invalid");
var rows = Enum.GetValues(typeof(UiSound)).Cast<UiSound>().Select(s =>
{
var clip = player.Clip(s);
if (clip == null || clip.name != "Ui_" + s || clip.length > 1f || clip.frequency != 44100) throw new Exception("Bad clip for " + s + ": " + clip?.name);
return new { sound = s.ToString(), clip = AssetDatabase.GetAssetPath(clip), seconds = Math.Round(clip.length, 3), clip.channels, clip.frequency };
}).ToArray();
string report = JsonConvert.SerializeObject(new
{
batch = "ui_sfx_v3", prefab = PrefabPath, address = UiSfx.PrefabName, package = "Art", group = "Sound",
volume = audio.volume, sfxToggle = "PrototypeClientPrefs.Sfx", clips = rows, bundleBuilt = false, verifiedUtc = DateTime.UtcNow.ToString("o")
}, Formatting.Indented);
string repo = Directory.GetParent(Application.dataPath).Parent.FullName;
File.WriteAllText(Path.Combine(repo, "Doc/Design/Sound/UiSfx/playback_assets.json"), report);
return report;
}
}