343 lines
13 KiB
C#
343 lines
13 KiB
C#
using UnityEngine;
|
|
using UnityEngine.InputSystem;
|
|
using BumpCombat.Core;
|
|
using BumpCombat.Constants;
|
|
|
|
namespace BumpCombat.Player
|
|
{
|
|
[RequireComponent(typeof(Rigidbody2D), typeof(SpriteRenderer), typeof(Animator))]
|
|
[RequireComponent(typeof(PlayerStats))]
|
|
public sealed class PlayerController : MonoBehaviour
|
|
{
|
|
private static readonly int IsMovingParameter = Animator.StringToHash("IsMoving");
|
|
private static readonly int BumpAttackState =
|
|
Animator.StringToHash("Base Layer.BumpAttack");
|
|
private static readonly int BackBumpAttackState =
|
|
Animator.StringToHash("Base Layer.BackBumpAttack");
|
|
private static readonly int ArtifactUseState =
|
|
Animator.StringToHash("Base Layer.ArtifactUse");
|
|
|
|
public const string BumpAttackStateName = "BumpAttack";
|
|
public const string BackBumpAttackStateName = "BackBumpAttack";
|
|
public const string ArtifactUseStateName = "ArtifactUse";
|
|
public const string HurtStateName = "Hurt";
|
|
public const string DashStateName = "Dash";
|
|
public const string DeathStateName = "Death";
|
|
|
|
private Rigidbody2D body;
|
|
private SpriteRenderer spriteRenderer;
|
|
private Animator animator;
|
|
private PlayerStats playerStats;
|
|
private DashController dashController;
|
|
private ActiveArtifactController activeArtifactController;
|
|
private PlayerHealth playerHealth;
|
|
private Vector2 moveInput;
|
|
private Vector2 pendingImpactRecoil;
|
|
private Vector2 damageKnockbackDirection;
|
|
private float damageKnockbackDistance;
|
|
private float damageKnockbackDuration;
|
|
private float damageKnockbackElapsed;
|
|
|
|
public Vector2 MoveDirection { get; private set; }
|
|
public Vector2 FacingDirection { get; private set; } = Vector2.right;
|
|
public Vector2 CurrentVelocity { get; private set; }
|
|
public Vector2 AttackIntentVelocity { get; private set; }
|
|
public bool IsDamageKnockbackActive { get; private set; }
|
|
public bool IsHurtMovementLocked =>
|
|
playerHealth != null && playerHealth.IsHurtMovementLocked;
|
|
public Vector2 PlayerClampHalfExtents
|
|
{
|
|
get
|
|
{
|
|
ArenaBounds bounds = ArenaBounds.Resolve();
|
|
return bounds != null
|
|
? bounds.PlayerClampHalfExtents
|
|
: new Vector2(
|
|
Mathf.Max(0f, GameplayConstants.Current.Player.ArenaHalfExtents.x
|
|
- GameplayConstants.Current.Player.ArenaPadding.x),
|
|
Mathf.Max(0f, GameplayConstants.Current.Player.ArenaHalfExtents.y
|
|
- GameplayConstants.Current.Player.ArenaPadding.y));
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
body = GetComponent<Rigidbody2D>();
|
|
spriteRenderer = GetComponent<SpriteRenderer>();
|
|
animator = GetComponent<Animator>();
|
|
playerStats = GetComponent<PlayerStats>();
|
|
dashController = GetComponent<DashController>();
|
|
activeArtifactController = GetComponent<ActiveArtifactController>();
|
|
playerHealth = GetComponent<PlayerHealth>();
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
if (animator == null
|
|
|| animator.runtimeAnimatorController == null
|
|
|| !animator.isActiveAndEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
animator.SetBool(IsMovingParameter, false);
|
|
animator.Rebind();
|
|
animator.Update(0f);
|
|
}
|
|
|
|
public bool TryPlayBumpAttackAnimation(bool isBackHit = false)
|
|
{
|
|
return TryPlayCombatAnimation(isBackHit ? BackBumpAttackState : BumpAttackState, true);
|
|
}
|
|
|
|
public bool TryPlayArtifactUseAnimation()
|
|
{
|
|
return TryPlayCombatAnimation(ArtifactUseState, false);
|
|
}
|
|
|
|
private bool TryPlayCombatAnimation(
|
|
int stateHash,
|
|
bool blockActiveArtifactUse)
|
|
{
|
|
if (animator == null
|
|
|| animator.runtimeAnimatorController == null
|
|
|| !animator.isActiveAndEnabled
|
|
|| !animator.HasState(0, stateHash))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
AnimatorStateInfo current = animator.GetCurrentAnimatorStateInfo(0);
|
|
AnimatorStateInfo next = animator.IsInTransition(0)
|
|
? animator.GetNextAnimatorStateInfo(0)
|
|
: default;
|
|
bool priorityStateActive = IsPriorityAnimationState(current)
|
|
|| IsPriorityAnimationState(next);
|
|
bool artifactUseStateActive = blockActiveArtifactUse
|
|
&& (IsAnimationState(current, ArtifactUseStateName)
|
|
|| IsAnimationState(next, ArtifactUseStateName));
|
|
bool isDead = playerHealth != null
|
|
&& playerHealth.CurrentHealth <= 0f;
|
|
bool isDashing = dashController != null && dashController.IsDashing;
|
|
if (isDead
|
|
|| IsHurtMovementLocked
|
|
|| isDashing
|
|
|| priorityStateActive
|
|
|| artifactUseStateActive)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
animator.Play(stateHash, 0, 0f);
|
|
return true;
|
|
}
|
|
|
|
private static bool IsPriorityAnimationState(AnimatorStateInfo state)
|
|
{
|
|
return IsAnimationState(state, HurtStateName)
|
|
|| IsAnimationState(state, DashStateName)
|
|
|| IsAnimationState(state, DeathStateName);
|
|
}
|
|
|
|
private static bool IsAnimationState(
|
|
AnimatorStateInfo state,
|
|
string stateName)
|
|
{
|
|
return state.IsName(stateName);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!RunManager.GameplayInputEnabled || IsHurtMovementLocked)
|
|
{
|
|
SetMoveInput(Vector2.zero);
|
|
return;
|
|
}
|
|
|
|
Keyboard keyboard = Keyboard.current;
|
|
if (keyboard == null)
|
|
{
|
|
SetMoveInput(Vector2.zero);
|
|
return;
|
|
}
|
|
|
|
float horizontal = (keyboard.rightArrowKey.isPressed ? 1f : 0f)
|
|
- (keyboard.leftArrowKey.isPressed ? 1f : 0f);
|
|
float vertical = (keyboard.upArrowKey.isPressed ? 1f : 0f)
|
|
- (keyboard.downArrowKey.isPressed ? 1f : 0f);
|
|
|
|
SetMoveInput(Vector2.ClampMagnitude(new Vector2(horizontal, vertical), 1f));
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
AttackIntentVelocity = Vector2.zero;
|
|
if (dashController != null && dashController.IsDashing)
|
|
{
|
|
pendingImpactRecoil = Vector2.zero;
|
|
IsDamageKnockbackActive = false;
|
|
CurrentVelocity = Vector2.zero;
|
|
return;
|
|
}
|
|
|
|
Vector2 startPosition = body.position;
|
|
if (IsDamageKnockbackActive)
|
|
{
|
|
UpdateDamageKnockback();
|
|
return;
|
|
}
|
|
|
|
if (activeArtifactController != null
|
|
&& activeArtifactController.LocksMovement)
|
|
{
|
|
body.linearVelocity = Vector2.zero;
|
|
CurrentVelocity = Vector2.zero;
|
|
return;
|
|
}
|
|
|
|
if (IsHurtMovementLocked)
|
|
{
|
|
body.linearVelocity = Vector2.zero;
|
|
CurrentVelocity = Vector2.zero;
|
|
return;
|
|
}
|
|
|
|
if (pendingImpactRecoil.sqrMagnitude > 0f)
|
|
{
|
|
Vector2 recoilTarget = ClampToArena(startPosition + pendingImpactRecoil);
|
|
pendingImpactRecoil = Vector2.zero;
|
|
body.linearVelocity = Vector2.zero;
|
|
body.MovePosition(recoilTarget);
|
|
CurrentVelocity = Vector2.zero;
|
|
return;
|
|
}
|
|
|
|
AttackIntentVelocity = moveInput * playerStats.MoveSpeed;
|
|
Vector2 targetPosition = ClampToArena(
|
|
startPosition + AttackIntentVelocity * Time.fixedDeltaTime);
|
|
|
|
body.MovePosition(targetPosition);
|
|
CurrentVelocity = (targetPosition - startPosition) / Time.fixedDeltaTime;
|
|
if (CurrentVelocity.sqrMagnitude > 0.0001f)
|
|
{
|
|
activeArtifactController?.AddMovementCharge(Time.fixedDeltaTime);
|
|
}
|
|
}
|
|
|
|
public Vector2 ClampToArena(Vector2 position)
|
|
{
|
|
ArenaBounds bounds = ArenaBounds.Resolve();
|
|
return bounds != null
|
|
? bounds.ClampPlayerPosition(position)
|
|
: new Vector2(
|
|
Mathf.Clamp(
|
|
position.x,
|
|
-GameplayConstants.Current.Player.ArenaHalfExtents.x
|
|
+ GameplayConstants.Current.Player.ArenaPadding.x,
|
|
GameplayConstants.Current.Player.ArenaHalfExtents.x
|
|
- GameplayConstants.Current.Player.ArenaPadding.x),
|
|
Mathf.Clamp(
|
|
position.y,
|
|
-GameplayConstants.Current.Player.ArenaHalfExtents.y
|
|
+ GameplayConstants.Current.Player.ArenaPadding.y,
|
|
GameplayConstants.Current.Player.ArenaHalfExtents.y
|
|
- GameplayConstants.Current.Player.ArenaPadding.y));
|
|
}
|
|
|
|
public bool TryReposition(Vector2 position)
|
|
{
|
|
if (body == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
pendingImpactRecoil = Vector2.zero;
|
|
IsDamageKnockbackActive = false;
|
|
body.linearVelocity = Vector2.zero;
|
|
body.position = ClampToArena(position);
|
|
transform.position = body.position;
|
|
return true;
|
|
}
|
|
|
|
public void ApplyImpactRecoil(Vector2 direction, float distance)
|
|
{
|
|
if (direction.sqrMagnitude <= 0f || distance <= 0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector2 recoil = direction.normalized * distance;
|
|
pendingImpactRecoil = Vector2.ClampMagnitude(
|
|
pendingImpactRecoil + recoil,
|
|
distance);
|
|
}
|
|
|
|
public void ApplyDamageKnockback(Vector2 direction, float distance, float duration)
|
|
{
|
|
if (direction.sqrMagnitude <= 0f || distance <= 0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pendingImpactRecoil = Vector2.zero;
|
|
body.linearVelocity = Vector2.zero;
|
|
damageKnockbackDirection = direction.normalized;
|
|
damageKnockbackDistance = distance;
|
|
damageKnockbackDuration = Mathf.Max(duration, Time.fixedDeltaTime);
|
|
damageKnockbackElapsed = 0f;
|
|
IsDamageKnockbackActive = true;
|
|
CurrentVelocity = Vector2.zero;
|
|
}
|
|
|
|
private void UpdateDamageKnockback()
|
|
{
|
|
float previousProgress = Mathf.Clamp01(
|
|
damageKnockbackElapsed / damageKnockbackDuration);
|
|
damageKnockbackElapsed = Mathf.Min(
|
|
damageKnockbackElapsed + Time.fixedDeltaTime,
|
|
damageKnockbackDuration);
|
|
float currentProgress = Mathf.Clamp01(
|
|
damageKnockbackElapsed / damageKnockbackDuration);
|
|
float previousEase = 1f - (1f - previousProgress) * (1f - previousProgress);
|
|
float currentEase = 1f - (1f - currentProgress) * (1f - currentProgress);
|
|
float stepDistance = damageKnockbackDistance * (currentEase - previousEase);
|
|
|
|
body.linearVelocity = Vector2.zero;
|
|
body.MovePosition(ClampToArena(
|
|
body.position + damageKnockbackDirection * stepDistance));
|
|
CurrentVelocity = Vector2.zero;
|
|
if (damageKnockbackElapsed >= damageKnockbackDuration)
|
|
{
|
|
IsDamageKnockbackActive = false;
|
|
}
|
|
}
|
|
|
|
private void SetMoveInput(Vector2 input)
|
|
{
|
|
moveInput = input;
|
|
MoveDirection = input;
|
|
if (IsDamageKnockbackActive
|
|
|| IsHurtMovementLocked
|
|
|| (activeArtifactController != null
|
|
&& activeArtifactController.LocksMovement))
|
|
{
|
|
animator.SetBool(IsMovingParameter, false);
|
|
return;
|
|
}
|
|
|
|
animator.SetBool(IsMovingParameter, input.sqrMagnitude > 0f);
|
|
|
|
if (input.sqrMagnitude <= 0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FacingDirection = input.normalized;
|
|
if (!Mathf.Approximately(input.x, 0f))
|
|
{
|
|
spriteRenderer.flipX = input.x < 0f;
|
|
}
|
|
}
|
|
}
|
|
}
|