122 lines
4.9 KiB
C#
122 lines
4.9 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// 优先用 RendererAdapter 的 Material,否则回退到 SpriteRenderer.material。
|
||
/// </summary>
|
||
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<RendererAdapter>();
|
||
if (vatAnimationController == null) vatAnimationController = GetComponentInChildren<VATAnimationController>(true);
|
||
}
|
||
|
||
protected virtual void Awake()
|
||
{
|
||
// 编辑器没拖时,运行时再尝试自动找(支持 nested prefab 中后挂上来的情况)
|
||
if (vatAnimationController == null) vatAnimationController = GetComponentInChildren<VATAnimationController>(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);
|
||
});
|
||
}
|
||
}
|
||
} |