using OctoberStudio.Easing; using OctoberStudio.Rendering; using OctoberStudio.VAT; using UnityEngine; using UnityEngine.Events; namespace OctoberStudio { public class CharacterBehavior : MonoBehaviour, ICharacterBehavior { protected static readonly int DEFEAT_TRIGGER = Animator.StringToHash("Defeat"); protected static readonly int REVIVE_TRIGGER = Animator.StringToHash("Revive"); protected static readonly int SPEED_FLOAT = Animator.StringToHash("Speed"); protected static readonly int _Overlay = Shader.PropertyToID("_Overlay"); // 把"角色逻辑事件"翻译成"VAT clip 名"。3D 模式下挂 controller 即可,2D 模式留空不影响。 protected const string VAT_CLIP_IDLE = "Idle"; protected const string VAT_CLIP_RUN = "Run"; protected const string VAT_CLIP_DIE = "Die"; protected const float VAT_SPEED_THRESHOLD = 0.01f; [Tooltip("可选:挂载 RendererAdapter 切换 2D/3D 渲染。未挂时回退到下方 SpriteRenderer 字段。")] [SerializeField] protected RendererAdapter rendererAdapter; [SerializeField] protected SpriteRenderer playerSpriteRenderer; [SerializeField] protected Animator animator; [Tooltip("可选:3D VAT 模式下使用。SetSpeed/PlayDefeatAnimation/PlayReviveAnimation 会同步切换 VAT clip。")] [SerializeField] protected VATAnimationController vatAnimationController; [SerializeField] protected Color hitColor; [SerializeField] protected Transform centerTransform; public Transform CenterTransform => centerTransform; public Transform Transform => transform; protected IEasingCoroutine damageCoroutine; /// /// 优先用 RendererAdapter 的 Material,否则回退到 SpriteRenderer.material。 /// protected virtual Material HitMaterial { get => rendererAdapter != null ? rendererAdapter.Material : playerSpriteRenderer.material; set { if (rendererAdapter != null) rendererAdapter.Material = value; else playerSpriteRenderer.material = value; } } protected virtual void Reset() { if (rendererAdapter == null) rendererAdapter = GetComponent(); if (vatAnimationController == null) vatAnimationController = GetComponentInChildren(true); } protected virtual void Awake() { // 编辑器没拖时,运行时再尝试自动找(支持 nested prefab 中后挂上来的情况) if (vatAnimationController == null) vatAnimationController = GetComponentInChildren(true); } public virtual void SetSpeed(float speed) { if (animator != null) animator.SetFloat(SPEED_FLOAT, speed); if (vatAnimationController != null) { vatAnimationController.Play(speed > VAT_SPEED_THRESHOLD ? VAT_CLIP_RUN : VAT_CLIP_IDLE); } } public virtual void SetLocalScale(Vector3 scale) { // VAT 3D 模式: PlayerBehavior 传过来的 scale.x=±1 是 2D 翻转语义, // 3D 模型不能用 scale.x=-1 (会翻转法线 / culling),改用 VATAnimationController.SetFacing 旋转 if (vatAnimationController != null) { vatAnimationController.SetFacing(scale.x >= 0 ? 1 : -1); return; } transform.localScale = scale; } public virtual void PlayReviveAnimation() { if (animator != null) animator.SetTrigger(REVIVE_TRIGGER); if (vatAnimationController != null) vatAnimationController.Play(VAT_CLIP_IDLE); } public virtual void PlayDefeatAnimation() { if (animator != null) animator.SetTrigger(DEFEAT_TRIGGER); if (vatAnimationController != null) vatAnimationController.Play(VAT_CLIP_DIE, loop: false); } public virtual void SetSortingOrder(int order) { if (rendererAdapter != null) rendererAdapter.SortingOrder = order; else if (playerSpriteRenderer != null) playerSpriteRenderer.sortingOrder = order; } public virtual void FlashHit(UnityAction onFinish = null) { if (damageCoroutine.ExistsAndActive()) return; var transparentColor = hitColor; transparentColor.a = 0; var mat = HitMaterial; if (mat == null || !mat.HasProperty(_Overlay)) { onFinish?.Invoke(); return; } mat.SetColor(_Overlay, transparentColor); damageCoroutine = mat.DoColor(_Overlay, hitColor, 0.05f).SetOnFinish(() => { damageCoroutine = mat.DoColor(_Overlay, transparentColor, 0.05f).SetOnFinish(onFinish); }); } } }