更新,spine和很多优化

This commit is contained in:
2026-02-24 05:36:08 +08:00
parent 14fb281d6f
commit d5c8966abd
2039 changed files with 348643 additions and 2589 deletions
@@ -1,4 +1,7 @@
using UnityEngine;
using UnityEngine;
using Spine;
using Spine.Unity;
using SmoothShakeFree;
using UnityEngine.UI;
using TMPro;
using System.Collections.Generic;
@@ -89,6 +92,10 @@ public class teamUIController : MonoBehaviour
private Coroutine _victoryMoveCoroutine;
private Coroutine _victorySaturationCoroutine;
private Coroutine _spineFlashCoroutine;
private Coroutine _spineSpawnFadeCoroutine;
private bool _pendingSpineSpawnFadeIn;
private const float SPINE_SPAWN_FADE_DURATION = 0.5f;
[Header("Ally Statistics (Real-time)")]
[Tooltip("Total damage dealt to enemies by each of the 5 ally slots.")]
@@ -767,6 +774,14 @@ public class teamUIController : MonoBehaviour
private Coroutine enemyFadeManaCoroutine;
private int prevEnemyHP = -1;
private int prevEnemyMana = -1;
private SkeletonDataAsset _currentEnemySkeletonDataAsset;
private SkeletonGraphic _enemySkeletonGraphic;
private SkeletonAnimation _enemySkeletonAnimation;
private float[] _spineBoundsTemp;
private SkeletonClipping _spineBoundsClipping;
private Coroutine _enemySpineDeathCoroutine;
private const string EnemyIdleAnimationName = "idle";
private const string EnemyDieAnimationName = "die";
// For total enemy health bar
private int totalMaxHP = 0; // Documentation text normalized.
@@ -1013,6 +1028,17 @@ public class teamUIController : MonoBehaviour
public Image currentEnemy_hurtRedImage;
[Tooltip("Documentation text normalized.")]
public TextMeshProUGUI enemyList_rateText;
[SerializeField] private GameObject _spineSystemObject;
[SerializeField] private GameObject _imgSystemObject;
[SerializeField] private RectTransform spine_to_put;
[SerializeField] private Image spine_to_put_referenceImage;
[SerializeField] private Color spineHurtFlashColor = new Color(1f, 0.2f, 0.2f, 1f);
[SerializeField] private float spineUniformHeight = 0f;
public float SpineUniformHeight
{
get => spineUniformHeight;
set => spineUniformHeight = value;
}
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
@@ -1105,6 +1131,9 @@ public class teamUIController : MonoBehaviour
Instance = this;
else
Destroy(gameObject);
EnsureSpineSystemObject();
EnsureImgSystemObject();
}
/// <summary>
@@ -1377,11 +1406,201 @@ public class teamUIController : MonoBehaviour
}
}
private void EnsureSpineSystemObject()
{
if (spine_to_put != null)
{
_spineSystemObject = spine_to_put.gameObject;
}
if (_spineSystemObject == null)
{
Transform searchRoot = objectFather_enemy != null ? objectFather_enemy.transform : transform;
var existing = searchRoot.Find("spinesystemobject");
if (existing != null)
{
_spineSystemObject = existing.gameObject;
}
}
if (_spineSystemObject == null)
{
_spineSystemObject = new GameObject("spinesystemobject", typeof(RectTransform));
}
if (spine_to_put == null)
{
Transform targetParent = objectFather_enemy != null ? objectFather_enemy.transform : (currentEnemy_characterImage != null ? currentEnemy_characterImage.transform : transform);
if (_spineSystemObject.transform.parent != targetParent)
{
_spineSystemObject.transform.SetParent(targetParent, false);
}
}
if (_enemySkeletonGraphic == null)
{
_enemySkeletonGraphic = _spineSystemObject.GetComponent<SkeletonGraphic>();
if (_enemySkeletonGraphic == null) _enemySkeletonGraphic = _spineSystemObject.AddComponent<SkeletonGraphic>();
}
if (_enemySkeletonAnimation == null || _enemySkeletonAnimation.gameObject != _spineSystemObject)
{
_enemySkeletonAnimation = _spineSystemObject.GetComponent<SkeletonAnimation>();
if (_enemySkeletonAnimation == null) _enemySkeletonAnimation = _spineSystemObject.AddComponent<SkeletonAnimation>();
}
}
private void EnsureImgSystemObject()
{
if (_imgSystemObject == null)
{
Transform searchRoot = objectFather_enemy != null ? objectFather_enemy.transform : transform;
var existing = searchRoot.Find("imgSys");
if (existing != null)
{
_imgSystemObject = existing.gameObject;
}
}
if (_imgSystemObject == null)
{
_imgSystemObject = new GameObject("imgSys", typeof(RectTransform));
}
Transform targetParent = objectFather_enemy != null ? objectFather_enemy.transform : (currentEnemy_characterImage != null ? currentEnemy_characterImage.transform.parent : transform);
if (_imgSystemObject.transform.parent != targetParent)
{
_imgSystemObject.transform.SetParent(targetParent, false);
}
var rect = _imgSystemObject.GetComponent<RectTransform>();
if (rect != null)
{
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
}
if (currentEnemy_characterImage != null && currentEnemy_characterImage.transform.parent != _imgSystemObject.transform)
{
currentEnemy_characterImage.transform.SetParent(_imgSystemObject.transform, false);
}
}
private void UpdateSpineRectForHeight(EnemyData_SO so)
{
if (_enemySkeletonGraphic == null) return;
if (so == null) return;
var rect = _enemySkeletonGraphic.rectTransform;
if (rect == null) return;
float targetHeight = spineUniformHeight;
if (targetHeight <= 0f && spine_to_put_referenceImage != null) targetHeight = spine_to_put_referenceImage.rectTransform.rect.height;
else if (targetHeight <= 0f && currentEnemy_characterImage != null) targetHeight = currentEnemy_characterImage.rectTransform.rect.height;
if (targetHeight <= 0f) return;
var skeleton = _enemySkeletonGraphic.Skeleton;
if (skeleton == null) return;
if (_spineBoundsClipping == null) _spineBoundsClipping = new SkeletonClipping();
float minX;
float minY;
float width;
float height;
skeleton.GetBounds(out minX, out minY, out width, out height, ref _spineBoundsTemp, _spineBoundsClipping);
float skeletonHeight = height;
float skeletonWidth = width;
if (skeletonHeight <= 0f || skeletonWidth <= 0f) return;
float targetWidth = skeletonWidth * (targetHeight / skeletonHeight);
float ratio = so.ScaleRatio;
if (ratio <= 0f) ratio = 1f;
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.sizeDelta = new Vector2(targetWidth, targetHeight);
rect.anchoredPosition = Vector2.zero;
if (spine_to_put != null)
{
float xScale = so.FlipX ? -ratio : ratio;
spine_to_put.localScale = new Vector3(xScale, ratio, 1f);
spine_to_put.anchoredPosition = new Vector2(so.XOffset, so.YOffset);
}
else
{
rect.sizeDelta = new Vector2(targetWidth * ratio, targetHeight * ratio);
rect.anchoredPosition = new Vector2(so.XOffset, so.YOffset);
rect.localScale = new Vector3(so.FlipX ? -1f : 1f, 1f, 1f);
}
}
private TrackEntry TryPlayEnemySpineAnimation(string animationName, bool loop)
{
if (_enemySkeletonGraphic == null) return null;
var skeleton = _enemySkeletonGraphic.Skeleton;
if (skeleton == null) return null;
if (skeleton.Data.FindAnimation(animationName) == null) return null;
if (_enemySkeletonAnimation == null) return null;
var state = _enemySkeletonAnimation.AnimationState;
if (state == null) return null;
return state.SetAnimation(0, animationName, loop);
}
private IEnumerator PlayEnemyDieThenAdvance(float delay)
{
yield return new WaitForSeconds(delay);
_enemySpineDeathCoroutine = null;
enemyCurrentCount++;
SpawnNextEnemy();
}
private void ApplyEnemyVisuals(EnemyData_SO so)
{
if (so == null)
{
_currentEnemySkeletonDataAsset = null;
if (_spineSystemObject != null) _spineSystemObject.SetActive(false);
if (_imgSystemObject != null) _imgSystemObject.SetActive(false);
if (currentEnemy_characterImage != null) currentEnemy_characterImage.sprite = null;
return;
}
if (so.skeletonDataAsset != null)
{
_currentEnemySkeletonDataAsset = so.skeletonDataAsset;
EnsureSpineSystemObject();
EnsureImgSystemObject();
if (_enemySkeletonAnimation != null)
{
_enemySkeletonAnimation.skeletonDataAsset = _currentEnemySkeletonDataAsset;
_enemySkeletonAnimation.Initialize(true);
}
if (_enemySkeletonGraphic != null)
{
_enemySkeletonGraphic.skeletonDataAsset = _currentEnemySkeletonDataAsset;
_enemySkeletonGraphic.Initialize(true);
UpdateSpineRectForHeight(so);
TryPlayEnemySpineAnimation(EnemyIdleAnimationName, true);
}
if (_spineSystemObject != null) _spineSystemObject.SetActive(true);
if (_imgSystemObject != null) _imgSystemObject.SetActive(false);
if (currentEnemy_characterImage != null) currentEnemy_characterImage.sprite = null;
return;
}
_currentEnemySkeletonDataAsset = null;
if (_spineSystemObject != null) _spineSystemObject.SetActive(false);
EnsureImgSystemObject();
if (_imgSystemObject != null) _imgSystemObject.SetActive(true);
if (currentEnemy_characterImage != null)
{
var sprite = so.enemy_Profile != null ? so.enemy_Profile : so.enemy_Image;
if (sprite != null) currentEnemy_characterImage.sprite = sprite;
else currentEnemy_characterImage.sprite = null;
}
}
private void SpawnNextEnemy()
{
if (_enemySpineDeathCoroutine != null)
{
StopCoroutine(_enemySpineDeathCoroutine);
_enemySpineDeathCoroutine = null;
}
// handle case: no configured/recognized enemies -> show empty state but keep UI visible
if (recognizedEnemySOs == null || recognizedEnemySOs.Length == 0)
{
_currentEnemySkeletonDataAsset = null;
if (_spineSystemObject != null) _spineSystemObject.SetActive(false);
if (_imgSystemObject != null) _imgSystemObject.SetActive(false);
if (objectFather_enemy != null) objectFather_enemy.SetActive(true);
if (currentEnemy_nameText != null) currentEnemy_nameText.text = "(No Enemy)";
if (currentEnemy_characterImage != null) currentEnemy_characterImage.sprite = null;
@@ -1401,8 +1620,11 @@ public class teamUIController : MonoBehaviour
if (enemyCurrentCount >= recognizedEnemySOs.Length)
{
// All enemies processed: show empty/finished state but keep UI visible
_currentEnemySkeletonDataAsset = null;
if (_spineSystemObject != null) _spineSystemObject.SetActive(false);
if (_imgSystemObject != null) _imgSystemObject.SetActive(false);
if (objectFather_enemy != null) objectFather_enemy.SetActive(true);
if (currentEnemy_nameText != null) currentEnemy_nameText.text = "(All Defeated)";
if (currentEnemy_nameText != null) currentEnemy_nameText.text = "所有敌人已被击败";
// if (currentEnemy_characterImage != null) currentEnemy_characterImage.sprite = null;
if (currentEnemy_healthImage != null) currentEnemy_healthImage.fillAmount = 0f;
if (currentEnemy_fadehealthImage != null) currentEnemy_fadehealthImage.fillAmount = 0f;
@@ -1435,12 +1657,8 @@ public class teamUIController : MonoBehaviour
// set UI visuals (name / sprites)
string enemyName = so.enemyName ?? so.name;
if (currentEnemy_nameText != null) currentEnemy_nameText.text = enemyName;
if (currentEnemy_characterImage != null)
{
var sprite = so.enemy_Profile != null ? so.enemy_Profile : so.enemy_Image;
if (sprite != null) currentEnemy_characterImage.sprite = sprite;
else currentEnemy_characterImage.sprite = null;
}
ApplyEnemyVisuals(so);
TryStartEnemySpineSpawnFade(so);
// Push spawn message to feed
SkillTriggerFeedUI.PushEnemySpawn(enemyName);
@@ -1600,9 +1818,10 @@ public class teamUIController : MonoBehaviour
{
// Push death message to feed before advancing
string eName = "Unknown Enemy";
EnemyData_SO so = null;
if (enemyCurrentCount >= 0 && enemyCurrentCount < recognizedEnemySOs.Length)
{
var so = recognizedEnemySOs[enemyCurrentCount];
so = recognizedEnemySOs[enemyCurrentCount];
if (so != null) eName = so.enemyName ?? so.name;
}
SkillTriggerFeedUI.PushEnemyDeath(eName);
@@ -1613,7 +1832,28 @@ public class teamUIController : MonoBehaviour
BeatmapManager.Instance.enemyBallLoader.OnEnemyDefeated();
}
// advance to next enemy after death
if (so != null && so.skeletonDataAsset != null)
{
var entry = TryPlayEnemySpineAnimation(EnemyDieAnimationName, false);
float delay = 0f;
if (entry != null)
{
delay = entry.AnimationEnd - entry.AnimationStart;
if (delay <= 0f && entry.Animation != null) delay = entry.Animation.Duration;
}
if (delay > 0f)
{
if (_enemySpineDeathCoroutine != null)
{
StopCoroutine(_enemySpineDeathCoroutine);
_enemySpineDeathCoroutine = null;
}
_pendingSpineSpawnFadeIn = true;
_enemySpineDeathCoroutine = StartCoroutine(PlayEnemyDieThenAdvance(delay));
return;
}
}
enemyCurrentCount++;
SpawnNextEnemy();
}
@@ -1968,6 +2208,15 @@ public class teamUIController : MonoBehaviour
public void TriggerEnemyHurtFlash()
{
if (_currentEnemySkeletonDataAsset != null && _enemySkeletonGraphic != null)
{
if (_spineFlashCoroutine != null) StopCoroutine(_spineFlashCoroutine);
_enemySkeletonGraphic.color = Color.white;
_enemySkeletonGraphic.Skeleton.SetColor(Color.white);
_enemySkeletonGraphic.UpdateMesh(true);
_spineFlashCoroutine = StartCoroutine(SpineHurtFlash());
return;
}
if (currentEnemy_hurtRedImage == null) return;
StopCoroutine("EnemyHurtFlash");
StartCoroutine(EnemyHurtFlash());
@@ -1993,6 +2242,129 @@ public class teamUIController : MonoBehaviour
currentEnemy_hurtRedImage.color = c;
}
private IEnumerator SpineHurtFlash()
{
if (_enemySkeletonGraphic == null) yield break;
Color original = _enemySkeletonGraphic.color;
Color originalSkeleton = _enemySkeletonGraphic.Skeleton.GetColor();
Color target = spineHurtFlashColor;
target.a = 1f;
_enemySkeletonGraphic.color = target;
_enemySkeletonGraphic.Skeleton.SetColor(target);
_enemySkeletonGraphic.UpdateMesh(true);
yield return new WaitForSeconds(0.02f);
float dur = 0.08f;
float t = 0f;
while (t < dur)
{
t += Time.deltaTime;
Color lerped = Color.Lerp(target, original, t / dur);
_enemySkeletonGraphic.color = lerped;
_enemySkeletonGraphic.Skeleton.SetColor(lerped);
yield return null;
}
_enemySkeletonGraphic.color = original;
_enemySkeletonGraphic.Skeleton.SetColor(originalSkeleton);
_enemySkeletonGraphic.UpdateMesh(true);
_spineFlashCoroutine = null;
}
private void TryStartEnemySpineSpawnFade(EnemyData_SO so)
{
if (_spineSpawnFadeCoroutine != null)
{
StopCoroutine(_spineSpawnFadeCoroutine);
_spineSpawnFadeCoroutine = null;
}
if (!_pendingSpineSpawnFadeIn)
{
return;
}
_pendingSpineSpawnFadeIn = false;
if (so == null || so.skeletonDataAsset == null || _enemySkeletonGraphic == null)
{
return;
}
_spineSpawnFadeCoroutine = StartCoroutine(EnemySpineSpawnFade(SPINE_SPAWN_FADE_DURATION));
}
private IEnumerator EnemySpineSpawnFade(float duration)
{
if (_enemySkeletonGraphic == null)
{
_spineSpawnFadeCoroutine = null;
yield break;
}
var skeleton = _enemySkeletonGraphic.Skeleton;
if (skeleton == null)
{
_spineSpawnFadeCoroutine = null;
yield break;
}
Color baseColor = _enemySkeletonGraphic.color;
Color baseSkeleton = skeleton.GetColor();
baseColor.a = 1f;
baseSkeleton.a = 1f;
Color startColor = baseColor;
Color startSkeleton = baseSkeleton;
startColor.a = 0f;
startSkeleton.a = 0f;
_enemySkeletonGraphic.color = startColor;
skeleton.SetColor(startSkeleton);
_enemySkeletonGraphic.UpdateMesh(true);
if (duration <= 0f)
{
_enemySkeletonGraphic.color = baseColor;
skeleton.SetColor(baseSkeleton);
_enemySkeletonGraphic.UpdateMesh(true);
_spineSpawnFadeCoroutine = null;
yield break;
}
float t = 0f;
while (t < duration)
{
t += Time.deltaTime;
float alpha = Mathf.Clamp01(t / duration);
Color c = baseColor;
Color sc = baseSkeleton;
c.a = alpha;
sc.a = alpha;
_enemySkeletonGraphic.color = c;
skeleton.SetColor(sc);
_enemySkeletonGraphic.UpdateMesh(true);
yield return null;
}
_enemySkeletonGraphic.color = baseColor;
skeleton.SetColor(baseSkeleton);
_enemySkeletonGraphic.UpdateMesh(true);
_spineSpawnFadeCoroutine = null;
}
public void TriggerEnemySpineShake(Vector3 amplitude, Vector3 frequency, float duration)
{
if (_currentEnemySkeletonDataAsset == null) return;
if (spine_to_put == null) return;
SmoothShake ss = spine_to_put.GetComponent<SmoothShake>();
if (ss == null) ss = spine_to_put.gameObject.AddComponent<SmoothShake>();
if (ss.positionShake == null) ss.positionShake = new Shaker();
if (ss.rotationShake == null) ss.rotationShake = new Shaker();
Vector3 halfAmplitude = amplitude * 0.5f;
ss.positionShake.noiseType = Shaker.NoiseType.SineWave;
ss.positionShake.amplitude = halfAmplitude;
ss.positionShake.frequency = frequency;
ss.rotationShake.amplitude = Vector3.zero;
ss.timeSettings.constantShake = false;
ss.timeSettings.fadeInDuration = 0.05f;
ss.timeSettings.holdDuration = duration * 0.4f;
ss.timeSettings.fadeOutDuration = duration * 0.55f;
if (ss.timeSettings.fadeInCurve == null || ss.timeSettings.fadeInCurve.length == 0)
ss.timeSettings.fadeInCurve = AnimationCurve.Linear(0, 0, 1, 1);
if (ss.timeSettings.fadeOutCurve == null || ss.timeSettings.fadeOutCurve.length == 0)
ss.timeSettings.fadeOutCurve = AnimationCurve.Linear(0, 1, 1, 0);
ss.enabled = false;
ss.enabled = true;
}
public void TriggerAllyHurtFlash(int slotIndex)
{
Image img = null;