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

50 lines
2.2 KiB
C#

using UnityEngine;
namespace HotUpdate.Gameplay
{
/// <summary>Order matches the clips serialized on the UiSfx prefab (Ui_Click … Ui_Notify).</summary>
public enum UiSound { Click, Confirm, Back, Open, Close, Tab, Claim, Deny, Toggle, Notify }
/// <summary>One-shot UI feedback from the authored UiSfx prefab; obeys the SFX setting, independent of music.</summary>
public sealed class UiSfx : MonoBehaviour
{
public const string PrefabName = "UiSfx";
public const string PrefabFolder = "Assets/Bundles/Sound/Prefab";
// Ignore the same sound re-requested within this window (e.g. a refresh firing twice in one frame).
const float RepeatGuard = .06f;
[SerializeField] AudioSource _source;
[SerializeField] AudioClip[] _clips;
static UiSfx _instance;
UiSound _last;
float _lastAt = -1;
public AudioSource Source => _source;
public AudioClip Clip(UiSound sound) => _clips != null && (int)sound < _clips.Length ? _clips[(int)sound] : null;
void OnEnable() { _instance = this; }
void OnDisable() { if (_instance == this) _instance = null; }
public static void Play(UiSound sound)
{
var player = _instance;
if (player == null || player._source == null || !PrototypeClientPrefs.Sfx) return;
var clip = player.Clip(sound);
if (clip == null) return;
float now = Time.unscaledTime;
if (sound == player._last && now - player._lastAt < RepeatGuard) return;
player._last = sound; player._lastAt = now;
player._source.PlayOneShot(clip);
}
/// <summary>Button names from the UI JSON decide the feedback; anything unlisted is a plain click.</summary>
public static UiSound ForButton(string name)
{
if (name == "Back" || name == "Home" || name == "Close") return UiSound.Close;
if (name == "CancelPreview" || name == "RemovePet" || name == "Abandon") return UiSound.Back;
if (name.Contains("Tab") || name == "MailPrev" || name == "MailNext") return UiSound.Tab;
if (name.StartsWith("Toggle") || name == "CycleQuality") return UiSound.Toggle;
return UiSound.Click;
}
}
}