189 lines
6.2 KiB
C#
189 lines
6.2 KiB
C#
|
|
using System.Collections;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using UnityEngine;
|
|||
|
|
using Platformer.Gameplay;
|
|||
|
|
using static Platformer.Core.Simulation;
|
|||
|
|
using Platformer.Model;
|
|||
|
|
using Platformer.Core;
|
|||
|
|
using UnityEngine.InputSystem;
|
|||
|
|
|
|||
|
|
namespace Platformer.Mechanics
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// This is the main class used to implement control of the player.
|
|||
|
|
/// It is a superset of the AnimationController class, but is inlined to allow for any kind of customisation.
|
|||
|
|
/// </summary>
|
|||
|
|
public class PlayerController : KinematicObject
|
|||
|
|
{
|
|||
|
|
public AudioClip jumpAudio;
|
|||
|
|
public AudioClip respawnAudio;
|
|||
|
|
public AudioClip ouchAudio;
|
|||
|
|
/// <summary>
|
|||
|
|
/// Attack sound effect (BT5-Dev 2단계 신설). 미지정 시 무음.
|
|||
|
|
/// </summary>
|
|||
|
|
public AudioClip attackAudio;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Max horizontal speed of the player.
|
|||
|
|
/// </summary>
|
|||
|
|
public float maxSpeed = 7;
|
|||
|
|
/// <summary>
|
|||
|
|
/// Initial jump velocity at the start of a jump.
|
|||
|
|
/// </summary>
|
|||
|
|
public float jumpTakeOffSpeed = 7;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Cooldown between attacks in seconds (기획 04 §5-1 Phase 3-B 튠 대상).
|
|||
|
|
/// </summary>
|
|||
|
|
public float attackCooldown = 0.35f;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// Attack hitbox component (자동 GetComponent, 없으면 null — PlayerAttack.Execute에서 null 체크).
|
|||
|
|
/// </summary>
|
|||
|
|
public AttackHitbox attackHitbox;
|
|||
|
|
|
|||
|
|
public JumpState jumpState = JumpState.Grounded;
|
|||
|
|
private bool stopJump;
|
|||
|
|
/*internal new*/ public Collider2D collider2d;
|
|||
|
|
/*internal new*/ public AudioSource audioSource;
|
|||
|
|
public Health health;
|
|||
|
|
public bool controlEnabled = true;
|
|||
|
|
|
|||
|
|
bool jump;
|
|||
|
|
Vector2 move;
|
|||
|
|
SpriteRenderer spriteRenderer;
|
|||
|
|
internal Animator animator;
|
|||
|
|
readonly PlatformerModel model = Simulation.GetModel<PlatformerModel>();
|
|||
|
|
|
|||
|
|
private InputAction m_MoveAction;
|
|||
|
|
private InputAction m_JumpAction;
|
|||
|
|
private InputAction m_AttackAction;
|
|||
|
|
|
|||
|
|
// 마지막 공격 시각 — attackCooldown과 비교해 연타 제한
|
|||
|
|
float nextAttackTime = 0f;
|
|||
|
|
// 현재 facing 방향 (마지막 이동 입력 기반, 정지 시 이전 값 유지)
|
|||
|
|
Vector2 facing = Vector2.right;
|
|||
|
|
|
|||
|
|
public Bounds Bounds => collider2d.bounds;
|
|||
|
|
|
|||
|
|
void Awake()
|
|||
|
|
{
|
|||
|
|
health = GetComponent<Health>();
|
|||
|
|
audioSource = GetComponent<AudioSource>();
|
|||
|
|
collider2d = GetComponent<Collider2D>();
|
|||
|
|
spriteRenderer = GetComponent<SpriteRenderer>();
|
|||
|
|
animator = GetComponent<Animator>();
|
|||
|
|
if (attackHitbox == null) attackHitbox = GetComponent<AttackHitbox>();
|
|||
|
|
|
|||
|
|
m_MoveAction = InputSystem.actions.FindAction("Player/Move");
|
|||
|
|
m_JumpAction = InputSystem.actions.FindAction("Player/Jump");
|
|||
|
|
m_AttackAction = InputSystem.actions.FindAction("Player/Attack");
|
|||
|
|
|
|||
|
|
m_MoveAction.Enable();
|
|||
|
|
m_JumpAction.Enable();
|
|||
|
|
if (m_AttackAction != null) m_AttackAction.Enable();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override void Update()
|
|||
|
|
{
|
|||
|
|
if (controlEnabled)
|
|||
|
|
{
|
|||
|
|
move.x = m_MoveAction.ReadValue<Vector2>().x;
|
|||
|
|
if (jumpState == JumpState.Grounded && m_JumpAction.WasPressedThisFrame())
|
|||
|
|
jumpState = JumpState.PrepareToJump;
|
|||
|
|
else if (m_JumpAction.WasReleasedThisFrame())
|
|||
|
|
{
|
|||
|
|
stopJump = true;
|
|||
|
|
Schedule<PlayerStopJump>().player = this;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 공격 입력 처리 (마우스 좌클릭 / 게임패드 RT / 모바일 터치 — Phase 3-B UX)
|
|||
|
|
if (m_AttackAction != null && m_AttackAction.WasPressedThisFrame() && Time.time >= nextAttackTime)
|
|||
|
|
{
|
|||
|
|
nextAttackTime = Time.time + attackCooldown;
|
|||
|
|
var ev = Schedule<PlayerAttack>();
|
|||
|
|
ev.player = this;
|
|||
|
|
ev.direction = facing;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
move.x = 0;
|
|||
|
|
}
|
|||
|
|
UpdateJumpState();
|
|||
|
|
base.Update();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
void UpdateJumpState()
|
|||
|
|
{
|
|||
|
|
jump = false;
|
|||
|
|
switch (jumpState)
|
|||
|
|
{
|
|||
|
|
case JumpState.PrepareToJump:
|
|||
|
|
jumpState = JumpState.Jumping;
|
|||
|
|
jump = true;
|
|||
|
|
stopJump = false;
|
|||
|
|
break;
|
|||
|
|
case JumpState.Jumping:
|
|||
|
|
if (!IsGrounded)
|
|||
|
|
{
|
|||
|
|
Schedule<PlayerJumped>().player = this;
|
|||
|
|
jumpState = JumpState.InFlight;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case JumpState.InFlight:
|
|||
|
|
if (IsGrounded)
|
|||
|
|
{
|
|||
|
|
Schedule<PlayerLanded>().player = this;
|
|||
|
|
jumpState = JumpState.Landed;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
case JumpState.Landed:
|
|||
|
|
jumpState = JumpState.Grounded;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
protected override void ComputeVelocity()
|
|||
|
|
{
|
|||
|
|
if (jump && IsGrounded)
|
|||
|
|
{
|
|||
|
|
velocity.y = jumpTakeOffSpeed * model.jumpModifier;
|
|||
|
|
jump = false;
|
|||
|
|
}
|
|||
|
|
else if (stopJump)
|
|||
|
|
{
|
|||
|
|
stopJump = false;
|
|||
|
|
if (velocity.y > 0)
|
|||
|
|
{
|
|||
|
|
velocity.y = velocity.y * model.jumpDeceleration;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (move.x > 0.01f)
|
|||
|
|
{
|
|||
|
|
spriteRenderer.flipX = false;
|
|||
|
|
facing = Vector2.right;
|
|||
|
|
}
|
|||
|
|
else if (move.x < -0.01f)
|
|||
|
|
{
|
|||
|
|
spriteRenderer.flipX = true;
|
|||
|
|
facing = Vector2.left;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
animator.SetBool("grounded", IsGrounded);
|
|||
|
|
animator.SetFloat("velocityX", Mathf.Abs(velocity.x) / maxSpeed);
|
|||
|
|
|
|||
|
|
targetVelocity = move * maxSpeed;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public enum JumpState
|
|||
|
|
{
|
|||
|
|
Grounded,
|
|||
|
|
PrepareToJump,
|
|||
|
|
Jumping,
|
|||
|
|
InFlight,
|
|||
|
|
Landed
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|