Files
Colosseum/Assets/Scripts/Player/PlayerMovement.cs
dal4segno eff23471d7 스킬 시스템 구현
- 애니메이션 이벤트 기반 스킬 시스템 추가
  - SkillData: 스킬 데이터 (클립, 쿨타임, 효과 목록)
  - SkillController: 스킬 실행 및 애니메이션 제어
  - AnimatorOverrideController로 단일 State에서 다양한 스킬 재생

- 스킬 효과 시스템
  - DamageEffect, HealEffect, BuffEffect
  - KnockbackEffect, SoundEffect, SpawnEffect
  - 범위 공격 및 팀 구분 지원

- Team 컴포넌트로 아군/적 구분

- 스킬 중 이동 제한
  - IsPlayingAnimation으로 애니메이션 종료까지 이동 불가
  - OnSkillEnd 호출 시 다음 스킬 시전 가능

- 입력 시스템에 스킬 슬롯 6개 추가

- 애니메이션 에셋 추가 및 정리
  - AnimationSwordCombat 패키지 추가 (검 공격 애니메이션)
  - PlayerAnimationController에 Skill 상태 추가
  - External_Used 폴더 구조 정리

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 12:08:52 +09:00

253 lines
7.3 KiB
C#

using UnityEngine;
using UnityEngine.InputSystem;
using Unity.Netcode;
using Colosseum.Network;
using Colosseum.Skills;
namespace Colosseum.Player
{
/// <summary>
/// 3인칭 플레이어 이동 (네트워크 동기화)
/// </summary>
[RequireComponent(typeof(CharacterController))]
public class PlayerMovement : NetworkBehaviour
{
[Header("Movement Settings")]
[SerializeField] private float moveSpeed = 5f;
[SerializeField] private float rotationSpeed = 10f;
[SerializeField] private float gravity = -9.81f;
[Header("Jump Settings")]
[SerializeField] private float jumpForce = 5f;
[Header("References")]
[SerializeField] private SkillController skillController;
private CharacterController characterController;
private Vector3 velocity;
private Vector2 moveInput;
private InputSystem_Actions inputActions;
private bool isJumping;
private bool wasGrounded;
/// <summary>
/// 현재 이동 속도 (애니메이션용)
/// </summary>
public float CurrentMoveSpeed => moveInput.magnitude * moveSpeed;
/// <summary>
/// 현재 지면 접촉 상태
/// </summary>
public bool IsGrounded => characterController != null ? characterController.isGrounded : false;
/// <summary>
/// 점프 중 상태
/// </summary>
public bool IsJumping => isJumping;
public override void OnNetworkSpawn()
{
// 로컬 플레이어가 아니면 입력 비활성화
if (!IsOwner)
{
enabled = false;
return;
}
characterController = GetComponent<CharacterController>();
// SkillController 참조
if (skillController == null)
{
skillController = GetComponent<SkillController>();
}
// 스폰 포인트에서 위치 설정
SetSpawnPosition();
// Input Actions 초기화
inputActions = new InputSystem_Actions();
inputActions.Player.Enable();
// Move 액션 콜백 등록
inputActions.Player.Move.performed += OnMovePerformed;
inputActions.Player.Move.canceled += OnMoveCanceled;
// Jump 액션 콜백 등록
inputActions.Player.Jump.performed += OnJumpPerformed;
// 카메라 설정
SetupCamera();
}
/// <summary>
/// 스폰 위치 설정
/// </summary>
private void SetSpawnPosition()
{
Transform spawnPoint = PlayerSpawnPoint.GetRandomSpawnPoint();
if (spawnPoint != null)
{
// CharacterController 비활성화 후 위치 설정 (충돌 문제 방지)
characterController.enabled = false;
transform.position = spawnPoint.position;
transform.rotation = spawnPoint.rotation;
characterController.enabled = true;
}
}
/// <summary>
/// 네트워크 정리리
/// </summary>
public override void OnNetworkDespawn()
{
if (inputActions != null)
{
inputActions.Player.Move.performed -= OnMovePerformed;
inputActions.Player.Move.canceled -= OnMoveCanceled;
inputActions.Player.Jump.performed -= OnJumpPerformed;
inputActions.Disable();
}
}
private void OnMovePerformed(InputAction.CallbackContext context)
{
moveInput = context.ReadValue<Vector2>();
}
private void OnMoveCanceled(InputAction.CallbackContext context)
{
moveInput = Vector2.zero;
}
private void OnJumpPerformed(InputAction.CallbackContext context)
{
if (!isJumping && characterController.isGrounded)
{
Jump();
}
}
private void SetupCamera()
{
var cameraController = GetComponent<PlayerCamera>();
if (cameraController == null)
{
cameraController = gameObject.AddComponent<PlayerCamera>();
}
cameraController.Initialize(transform, inputActions);
}
private void Update()
{
if (!IsOwner) return;
ApplyGravity();
Move();
}
private void ApplyGravity()
{
if (wasGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
else
{
velocity.y += gravity * Time.deltaTime;
}
}
private void Move()
{
if (characterController == null) return;
// 스킬 애니메이션 재생 중에는 이동 불가
if (skillController != null && skillController.IsPlayingAnimation)
{
// 중력만 적용
characterController.Move(velocity * Time.deltaTime);
return;
}
// 이동 방향 계산 (카메라 기준)
Vector3 moveDirection = new Vector3(moveInput.x, 0f, moveInput.y);
moveDirection = TransformDirectionByCamera(moveDirection);
moveDirection.Normalize();
// 이동 적용
Vector3 moveVector = moveDirection * moveSpeed * Time.deltaTime;
characterController.Move(moveVector + velocity * Time.deltaTime);
// 회전 (이동 중일 때만)
if (moveDirection != Vector3.zero)
{
Quaternion targetRotation = Quaternion.LookRotation(moveDirection);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, rotationSpeed * Time.deltaTime);
}
// 착지 체크 (Move 후에 isGrounded가 업데이트됨)
if (!wasGrounded && characterController.isGrounded && isJumping)
{
OnJumpEnd();
}
// 다음 프레임을 위해 현재 상태 저장
wasGrounded = characterController.isGrounded;
}
private void Jump()
{
isJumping = true;
velocity.y = jumpForce;
// 애니메이션 컨트롤러에 점프 알림
var animController = GetComponent<PlayerAnimationController>();
if (animController != null)
{
animController.PlayJump();
}
}
/// <summary>
/// 점프 중 상태가 끝나면 IsJumping = false;
/// </summary>
public void OnJumpEnd()
{
isJumping = false;
}
private Vector3 TransformDirectionByCamera(Vector3 direction)
{
if (Camera.main == null) return direction;
Transform cameraTransform = Camera.main.transform;
Vector3 cameraForward = cameraTransform.forward;
Vector3 cameraRight = cameraTransform.right;
// Y축 제거
cameraForward.y = 0f;
cameraRight.y = 0f;
cameraForward.Normalize();
cameraRight.Normalize();
return cameraRight * direction.x + cameraForward * direction.z;
}
}
}