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:
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace HotUpdate.Gameplay
|
||||
{
|
||||
/// <summary>Authored bounded VFX/audio pools; consumes resolved facts without touching battle state.</summary>
|
||||
public sealed class EnemyCombatPresentation : MonoBehaviour
|
||||
{
|
||||
[Serializable] public sealed class EffectPool
|
||||
{
|
||||
public ParticleSystem[] instances;
|
||||
[NonSerialized] public int next;
|
||||
}
|
||||
[SerializeField] EffectPool[] _effects;
|
||||
[SerializeField] AudioClip[] _clips;
|
||||
[SerializeField] AudioSource[] _voices;
|
||||
[SerializeField, Range(0, 1)] float _mixBudget = .7f;
|
||||
[SerializeField, Range(0, 1)] float _voiceVolume = .4f;
|
||||
readonly float[] _lastSound = new float[8];
|
||||
int _voice;
|
||||
bool _paused;
|
||||
float _speed = 1;
|
||||
public int PlayedEffects { get; private set; }
|
||||
public int PlayedSounds { get; private set; }
|
||||
public int EffectCount => _effects?.Length ?? 0;
|
||||
public AudioClip Clip(int index) => _clips[index];
|
||||
public int VoiceCount => _voices?.Length ?? 0;
|
||||
|
||||
void OnEnable() { PrototypeClientPrefs.AudioPreferencesChanged += ApplyAudio; ResetPlayback(); }
|
||||
void OnDisable() { PrototypeClientPrefs.AudioPreferencesChanged -= ApplyAudio; ResetPlayback(); }
|
||||
void ApplyAudio() { if (!PrototypeClientPrefs.Sfx) foreach (var v in _voices) v.Stop(); }
|
||||
|
||||
public void ResetPlayback()
|
||||
{
|
||||
if (_effects != null) foreach (var pool in _effects)
|
||||
foreach (var ps in pool.instances) ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
if (_voices != null) foreach (var v in _voices) v.Stop();
|
||||
for (int i = 0; i < _lastSound.Length; i++) _lastSound[i] = -100;
|
||||
_paused = false; _speed = 1; PlayedEffects = PlayedSounds = 0;
|
||||
}
|
||||
|
||||
public void SetPlayback(bool paused, float speed)
|
||||
{
|
||||
if (_paused == paused && Mathf.Approximately(_speed, speed)) return;
|
||||
bool speedChanged = !Mathf.Approximately(_speed, speed);
|
||||
_speed = speed;
|
||||
foreach (var pool in _effects) foreach (var ps in pool.instances)
|
||||
{
|
||||
if (speedChanged) foreach (var child in ps.GetComponentsInChildren<ParticleSystem>()) { var m = child.main; m.simulationSpeed = speed; }
|
||||
if (paused && !_paused) ps.Pause(true);
|
||||
else if (!paused && _paused && ps.IsAlive(true)) ps.Play(true);
|
||||
}
|
||||
if (paused != _paused) foreach (var v in _voices) { if (paused) v.Pause(); else v.UnPause(); }
|
||||
_paused = paused;
|
||||
}
|
||||
|
||||
public void Play(PrototypeBattle.CombatCue cue)
|
||||
{
|
||||
if (_paused || _effects == null) return;
|
||||
int index = cue.Kind == PrototypeBattle.CueKind.EnemyAttack ? Mathf.Clamp(cue.Type, 0, 4) : (cue.Type >= 2 ? 6 : 5);
|
||||
Burst(index, cue.Kind == PrototypeBattle.CueKind.EnemyAttack ? cue.AttackPosition : cue.Position, cue.Direction);
|
||||
if (cue.Kind == PrototypeBattle.CueKind.EnemyAttack)
|
||||
Burst(cue.Shield ? 7 : 5, cue.Position, -cue.Direction);
|
||||
}
|
||||
|
||||
void Burst(int index, Vector3 position, Vector3 direction)
|
||||
{
|
||||
var pool = _effects[index];
|
||||
var ps = pool.instances[pool.next++ % pool.instances.Length];
|
||||
ps.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear);
|
||||
ps.transform.position = transform.TransformPoint(position);
|
||||
ps.transform.rotation = Quaternion.LookRotation(direction);
|
||||
foreach (var child in ps.GetComponentsInChildren<ParticleSystem>()) { var m = child.main; m.simulationSpeed = _speed; }
|
||||
ps.Play(true); PlayedEffects++;
|
||||
// Mix budget and same-cue guard prevent a wave of hits becoming a wall of sound.
|
||||
if (!PrototypeClientPrefs.Sfx || Time.unscaledTime - _lastSound[index] < .075f) return;
|
||||
_lastSound[index] = Time.unscaledTime;
|
||||
var voice = _voices[_voice++ % _voices.Length]; voice.Stop(); voice.clip = _clips[index]; voice.Play(); PlayedSounds++;
|
||||
int active = 0; foreach (var v in _voices) if (v.isPlaying) active++;
|
||||
float gain = Mathf.Min(_voiceVolume, _mixBudget / Mathf.Max(1, active));
|
||||
foreach (var v in _voices) if (v.isPlaying) v.volume = gain;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user