using System.Collections; using System.Collections.Generic; using UnityEngine; // PlayerController는 플레이어 캐릭터로서 Player 게임 오브젝트를 제어한다. public class PlayerController : MonoBehaviour { public AudioClip deathClip; // 사망시 재생할 오디오 클립 public AudioClip jumpClip; // 점프시 재생할 오디오 클립 public AudioClip doubleJumpClip; // 2단 점프시 재생할 오디오 클립 public AudioClip slideClip; // 슬라이딩 시작시 재생할 오디오 클립 [Range(0f, 1f)] public float movementSfxVolume = 0.72f; // 이동 효과음 볼륨 public float jumpForce = 600f; // 점프 힘 public int maxJumpCount = 2; // 최대 점프 횟수 public float invincibleDuration = 1.5f; // 피해를 입은 뒤 무적 시간 public float groundStickGraceTime = 0.12f; // 발판 이음새에서 접지가 아주 잠깐 끊겨도 유지하는 시간 private int jumpCount = 0; // 누적 점프 횟수 private bool isGrounded = false; // 바닥에 닿았는지 나타냄 private bool isDead = false; // 사망 상태 private bool isSliding = false; // 슬라이딩 상태 private bool isInvincible = false; // 피해 후 무적 상태 private Rigidbody2D playerRigidbody; // 사용할 리지드바디 컴포넌트 private Animator animator; // 사용할 애니메이터 컴포넌트 private AudioSource playerAudio; // 사용할 오디오 소스 컴포넌트 private SpriteRenderer spriteRenderer; // 플레이어 스프라이트 표시 컴포넌트 private readonly HashSet groundContacts = new HashSet(); // 현재 밟고 있는 발판 콜라이더 private float lastGroundedTime = -999f; // 마지막으로 발판에 닿아 있던 시간 private CapsuleCollider2D playerCollider; // 플레이어의 콜라이더 충돌 범위 private Vector2 originalColliderSize; // 기본 자세일 때 콜라이더 크기 private Vector2 originalColliderOffset; // 기본 자세일 때 콜라이더 중심 위치 private CapsuleDirection2D originalColliderDirection; // 기본 자세일 때 콜라이더 방향 public Vector2 slidingColliderSize; // 슬라이딩 자세일 때 콜라이더 크기 public Vector2 slidingColliderOffset; // 슬라이딩 자세일 때 콜라이더 중심 위치 public CapsuleDirection2D slidingColliderDirection = CapsuleDirection2D.Horizontal; // 슬라이딩 자세일 때 콜라이더 방향 private void Start() { // 게임 오브젝트로부터 사용할 컴포넌트들을 가져와 변수에 할당 playerRigidbody = GetComponent(); animator = GetComponent(); playerAudio = GetComponent(); spriteRenderer = GetComponent(); ConfigureAudioSource(); LoadMovementSfxDefaults(); // 게임 콜라이더를 가져와서 현 상태를 저장 playerCollider = GetComponent(); originalColliderSize = playerCollider.size; originalColliderOffset = playerCollider.offset; originalColliderDirection = playerCollider.direction; } private void Update() { if(isDead || !GameFlowManager.IsGameplayActive || (GameManager.instance != null && GameManager.instance.IsTutorialGuidePaused)) { return; } // 슬라이딩 중이 아니라면 최대 점프 횟수까지 점프할 수 있다. if(Input.GetMouseButtonDown(0) && jumpCount < maxJumpCount && !isSliding) { isGrounded = false; jumpCount++; // 점프 직전에 속도를 순간적으로 제로(0, 0)으로 변경 playerRigidbody.linearVelocity = Vector2.zero; // 리지드바디에 위쪽으로 힘을 주기 playerRigidbody.AddForce(new Vector2(0, jumpForce)); PlayJumpSfx(); } else if(Input.GetMouseButtonUp(0) && playerRigidbody.linearVelocityY > 0) { // 마우스 왼쪽 버튼에서 손을 떼는 순간 && 속도의 y값이 양수라면(위로 상승중) // 현재 속도를 절반으로 변경 playerRigidbody.linearVelocity = playerRigidbody.linearVelocity * 0.5f; } bool groundedForMovement = IsGroundedOrRecentlyGrounded(); animator.SetBool("Grounded", groundedForMovement); // 달리는 중에 마우스 오른쪽 버튼을 누르고 있는 동안 슬라이딩 유지 if(Input.GetMouseButton(1) && groundedForMovement && !isSliding) { StartSlide(); } else if((!Input.GetMouseButton(1) || !groundedForMovement) && isSliding) { EndSlide(); } } private void Die() { if(isDead) { return; } if(isSliding) { EndSlide(); } StopAllCoroutines(); spriteRenderer.color = Color.white; // 애니메이터의 Die 트리거 파라미터를 셋 animator.SetTrigger("Die"); playerAudio.clip = deathClip; playerAudio.Play(); playerRigidbody.linearVelocity = Vector2.zero; isDead = true; GameManager.instance.OnPlayerDead(); } private void OnTriggerEnter2D(Collider2D other) { if(!GameFlowManager.IsGameplayActive) { return; } if(other.CompareTag("Dead") && !isDead) { if(other.gameObject.name == "DeadZone") { Die(); return; } TakeDamage(); } } private void OnCollisionEnter2D(Collision2D collision) { // 어떤 콜라이더와 닿았으며, 충돌 표면이 위쪽을 보고 있으면 if(IsGroundContact(collision)) { groundContacts.Add(collision.collider); SetGrounded(true); jumpCount = 0; } } private void OnCollisionExit2D(Collision2D collision) { // 바닥에서 벗어났음을 감지하는 처리 if(groundContacts.Remove(collision.collider) && groundContacts.Count == 0) { SetGrounded(false); } } private bool IsGroundContact(Collision2D collision) { for(int i = 0; i < collision.contactCount; i++) { if(collision.GetContact(i).normal.y > 0.7f) { return true; } } return false; } private void SetGrounded(bool grounded) { isGrounded = grounded; lastGroundedTime = Time.time; } private bool IsGroundedOrRecentlyGrounded() { return isGrounded || Time.time - lastGroundedTime <= groundStickGraceTime; } private void StartSlide() { isSliding = true; animator.SetBool("Sliding", true); PlayMovementSfx(slideClip, movementSfxVolume * 0.72f); playerCollider.size = slidingColliderSize; playerCollider.offset = slidingColliderOffset; playerCollider.direction = slidingColliderDirection; } private void EndSlide() { isSliding = false; animator.SetBool("Sliding", false); playerCollider.size = originalColliderSize; playerCollider.offset = originalColliderOffset; playerCollider.direction = originalColliderDirection; } private void TakeDamage() { if(isInvincible) { return; } if(GameManager.instance != null && GameManager.instance.TryBlockDamageWithItem()) { StartCoroutine(DamageBlockCooldownRoutine()); return; } bool shouldDie = GameManager.instance.TakeDamage(); if(shouldDie) { Die(); return; } StartCoroutine(InvincibleRoutine()); } private IEnumerator DamageBlockCooldownRoutine() { isInvincible = true; yield return new WaitForSeconds(0.35f); isInvincible = false; } private IEnumerator InvincibleRoutine() { isInvincible = true; float elapsedTime = 0f; float blinkInterval = 0.1f; while(elapsedTime < invincibleDuration) { spriteRenderer.color = new Color(1f, 1f, 1f, 0.35f); yield return new WaitForSeconds(blinkInterval); elapsedTime += blinkInterval; spriteRenderer.color = Color.white; yield return new WaitForSeconds(blinkInterval); elapsedTime += blinkInterval; } spriteRenderer.color = Color.white; isInvincible = false; } private void ConfigureAudioSource() { if(playerAudio == null) { playerAudio = gameObject.AddComponent(); } playerAudio.playOnAwake = false; playerAudio.spatialBlend = 0f; } private void LoadMovementSfxDefaults() { if(jumpClip == null) { jumpClip = Resources.Load("SFX/SFX_Player_Jump"); } if(doubleJumpClip == null) { doubleJumpClip = Resources.Load("SFX/SFX_Player_DoubleJump"); } if(slideClip == null) { slideClip = Resources.Load("SFX/SFX_Player_Slide"); } } private void PlayJumpSfx() { AudioClip clip = jumpCount > 1 && doubleJumpClip != null ? doubleJumpClip : jumpClip; float volume = jumpCount > 1 ? movementSfxVolume * 0.9f : movementSfxVolume; PlayMovementSfx(clip, volume); } private void PlayMovementSfx(AudioClip clip, float volume) { if(playerAudio == null || clip == null) { return; } playerAudio.PlayOneShot(clip, Mathf.Clamp01(volume)); } }