Initial commit: Tiny Tackle Heroes Unity project

This commit is contained in:
2026-09-15 17:37:42 +09:00
commit 591fa9a826
1623 changed files with 182420 additions and 0 deletions
@@ -0,0 +1,342 @@
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;
}
}
}
}