▶▷ PenguinRun
오늘은 플레이어가 부스터 아이템을 먹었을 때의 속도 증가 및 무적 효과와 장애물에 부딫였을 때 일시적인 무적 효과를 구현하였다.
PlayerController
public class PlayerController : MonoBehaviour
{
// 플레이어 상태 변수들
private bool isDead; // 플레이어가 죽었는지 여부
[SerializeField] private bool isJumping; // 점프 상태 여부
[SerializeField] private bool isSliding; // 슬라이딩 상태 여부
// 점프 관련 변수
[SerializeField] private int jumpForce; // 점프 힘
[SerializeField] private int jumpCount = 2; // 남은 점프 가능 횟수
[SerializeField] private float deathY = -10f; // 사망 Y축 좌표
public float DeathY => deathY;
// 컴포넌트 및 매니저 참조 변수
private GameManager gameManager; // 게임 매니저 참조
private StatHandler statHandler; // 상태 관리 핸들러
public StatHandler Stat => statHandler;
public AnimationHandler animationHandler; // 애니메이션 핸들러
private Rigidbody2D rb; // Rigidbody2D 컴포넌트 참조
// 이벤트 선언: 체력 변화, 속도 변화, 점수 추가 시 호출
public event Action<PlayerController, int> OnAddScore;
private void Awake()
{
isDead = false;
rb = GetComponent<Rigidbody2D>();
statHandler = GetComponent<StatHandler>();
animationHandler = GetComponent<AnimationHandler>();
}
private void Start()
{
if (rb == null)
{
Debug.Log("Not Founded Rigidbody");
}
isDead = false;
isJumping = false;
gameManager = GameManager.Instance;
}
/// <summary>
/// 체력이 0 이하이면 게임 오버 처리
/// 스페이스바 입력 시 점프 활성화
/// 왼쪽 Shift 입력 시 슬라이딩 활성화
/// 일정 높이 이하로 떨어지면 게임 오버 처리
/// </summary>
private void Update()
{
animationHandler.Move();
if (!isDead)
{
// 점프 입력 감지
if (Input.GetKeyDown(KeyCode.Space))
{
animationHandler.Jump();
isJumping = true;
}
// 슬라이딩 입력 감지
if (Input.GetKey(KeyCode.LeftShift))
{
animationHandler.Slide();
isSliding = true;
}
else if (Input.GetKeyUp(KeyCode.LeftShift))
{
animationHandler.Move();
isSliding = false;
}
}
// 플레이어가 사망 영역(높이 아래로 떨어짐)에 도달하면 게임 오버 처리
if (transform.position.y < deathY)
{
gameManager.GameOver();
}
}
/// <summary>
/// 전진 이동, 점프, 슬라이딩, 바닥 감지 등의 물리 처리
/// </summary>
private void FixedUpdate()
{
if (isDead)
return;
Move();
Jump();
Sliding();
}
/// <summary>
/// 플레이어 이동 처리
/// 현재 속도를 statHandler.Speed 값으로 설정
/// </summary>
public void Move()
{
Vector2 velocity = rb.velocity;
velocity.x = statHandler.Speed;
rb.velocity = velocity;
}
/// <summary>
/// 플레이어 점프 처리
/// 남은 점프 횟수가 있을 경우 점프를 수행하고 점프 횟수를 감소
/// </summary>
public void Jump()
{
if (isJumping)
{
if (jumpCount > 0)
{
Vector2 velocity = rb.velocity * 0;
rb.velocity = velocity;
Vector2 vel = rb.velocity + Vector2.up * jumpForce;
rb.velocity = vel;
--jumpCount;
isJumping = false;
}
}
}
/// <summary>
/// 플레이어 슬라이딩 처리
/// 슬라이딩 중이면 90도로 회전, 그렇지 않으면 원래 상태 유지
/// </summary>
public void Sliding()
{
if (isSliding)
{
transform.rotation = Quaternion.Euler(0, 0, 90);
}
else
{
transform.rotation = Quaternion.Euler(0, 0, 0);
}
}
/// <summary>
/// 트리거 충돌 발생 시 상호작용 가능한 오브젝트와의 상호작용 처리
/// </summary>
/// <param name="collision">충돌한 콜라이더</param>
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag.Equals("Interactable"))
{
InteractObject inter = collision.GetComponent<InteractObject>();
if (inter == null)
return;
inter.OnInteraction(statHandler);
}
}
/// <summary>
/// 지면과의 충돌 감지하여 점프 횟수 초기화
/// </summary>
/// <param name="collision">충돌한 오브젝트 정보</param>
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
jumpCount = 2;
}
}
/// <summary>
/// 점수 추가 이벤트 호출
/// </summary>
/// <param name="amount">추가할 점수</param>
public void AddScore(int amount = 1)
{
OnAddScore?.Invoke(this, amount);
}
}
StatHandler
public class StatHandler : MonoBehaviour
{
// 플레이어 컨트롤러 및 애니메이션 핸들러 참조
private PlayerController player;
public AnimationHandler animationHandler;
// 체력 및 속도 관련 변수
[SerializeField, Range(0f, 100f)] private float hp; // 현재 체력
public float Hp => hp;
[SerializeField, Range(0f, 100f)] private float maxHp; // 최대 체력
public float MaxHp => maxHp;
[SerializeField, Range(0f, 100f)] private float speed; // 현재 이동 속도
public float Speed => speed;
private float decreaseHPRatio; // 체력 자연 감소 비율
// 무적 상태 관련 변수
private float invincibilityTime; // 무적 지속 시간
private float invincibilityDurationTime; // 무적 경과 시간
[SerializeField] private bool isInvincibility; // 무적 여부
private void Awake()
{
// 초기값 설정
decreaseHPRatio = 1f; // 초당 체력 감소량
invincibilityTime = 3f; // 무적 지속 시간
invincibilityDurationTime = 0f;
isInvincibility = false;
// 컴포넌트 가져오기
player = GetComponent<PlayerController>();
animationHandler = GetComponent<AnimationHandler>();
}
private void Update()
{
// 체력 자연 감소
hp -= decreaseHPRatio * Time.deltaTime;
// 체력이 0 이하가 되면 게임 오버 처리
if (hp <= 0)
{
GameManager.Instance.GameOver();
return;
}
// 무적 상태 시간 확인
if (isInvincibility)
{
invincibilityDurationTime += Time.deltaTime;
if (invincibilityDurationTime >= invincibilityTime)
{
isInvincibility = false;
invincibilityDurationTime = 0f;
}
}
}
/// <summary>
/// 체력을 변경하는 함수
/// 양수 값이면 회복, 음수 값이면 데미지 처리
/// </summary>
/// <param name="figure">변경할 체력 값</param>
public void ChangeHP(float figure)
{
if (figure > 0f)
{
Heal(figure);
}
else
{
Damage(figure);
}
}
/// <summary>
/// 체력을 회복하는 함수
/// </summary>
/// <param name="figure">회복할 체력량</param>
private void Heal(float figure)
{
hp += figure;
hp = hp >= maxHp ? maxHp : hp; // 최대 체력을 초과하지 않도록 설정
}
/// <summary>
/// 체력 감소(데미지 처리) 함수
/// 무적 상태가 아닐 경우만 적용됨
/// </summary>
/// <param name="figure">감소할 체력량 (음수 값)</param>
private void Damage(float figure)
{
if (!isInvincibility)
{
animationHandler.Damage(); // 피격 애니메이션 재생
isInvincibility = true;
hp += figure; // figure가 음수이므로 실제로는 체력이 감소함
}
}
/// <summary>
/// 속도를 변경하는 함수
/// 양수 값이면 부스터 효과 적용
/// </summary>
/// <param name="amount">속도 변경 값</param>
/// <param name="duration">지속 시간 (초)</param>
public void ChangeSpeed(int amount, int duration)
{
if (amount > 0)
{
Booster(amount, duration);
}
}
/// <summary>
/// 부스터 효과 적용 (일정 시간 동안 속도 증가)
/// </summary>
/// <param name="amount">추가할 속도 값</param>
/// <param name="duration">지속 시간 (초)</param>
public void Booster(int amount, int duration)
{
if (amount > 0)
{
isInvincibility = true; // 부스터 중에는 무적 상태
speed += amount; // 속도 증가
Invoke("ResetSpeed", duration); // 지정된 시간이 지나면 속도 초기화
}
}
/// <summary>
/// 속도를 기본값(8)으로 초기화하는 함수
/// </summary>
public void ResetSpeed()
{
speed = 8f;
}
}
기존의 플레이어 스크립트에 포함된 내용이 너무 많아서 가시성이 좋지 않기도 하고 작성하면서 헷갈리는 부분이 자꾸 생겨 StatHandler 스크립트를 추가해서 체력, 속도에 관한 기능을 나누어 작성하였다.
AnimationHandler
public class PlayerController : MonoBehaviour
{
// 플레이어 상태 변수들
private bool isDead; // 플레이어가 죽었는지 여부
[SerializeField] private bool isJumping; // 점프 상태 여부
[SerializeField] private bool isSliding; // 슬라이딩 상태 여부
// 점프 관련 변수
[SerializeField] private int jumpForce; // 점프 힘
[SerializeField] private int jumpCount = 2; // 남은 점프 가능 횟수
[SerializeField] private float deathY = -10f; // 사망 Y축 좌표
public float DeathY => deathY;
// 컴포넌트 및 매니저 참조 변수
private GameManager gameManager; // 게임 매니저 참조
private StatHandler statHandler; // 상태 관리 핸들러
public StatHandler Stat => statHandler;
public AnimationHandler animationHandler; // 애니메이션 핸들러
private Rigidbody2D rb; // Rigidbody2D 컴포넌트 참조
// 이벤트 선언: 체력 변화, 속도 변화, 점수 추가 시 호출
public event Action<PlayerController, int> OnAddScore;
private void Awake()
{
isDead = false;
rb = GetComponent<Rigidbody2D>();
statHandler = GetComponent<StatHandler>();
animationHandler = GetComponent<AnimationHandler>();
}
private void Start()
{
if (rb == null)
{
Debug.Log("Not Founded Rigidbody");
}
isDead = false;
isJumping = false;
gameManager = GameManager.Instance;
}
/// <summary>
/// 체력이 0 이하이면 게임 오버 처리
/// 스페이스바 입력 시 점프 활성화
/// 왼쪽 Shift 입력 시 슬라이딩 활성화
/// 일정 높이 이하로 떨어지면 게임 오버 처리
/// </summary>
private void Update()
{
animationHandler.Move();
if (!isDead)
{
// 점프 입력 감지
if (Input.GetKeyDown(KeyCode.Space))
{
animationHandler.Jump();
isJumping = true;
}
// 슬라이딩 입력 감지
if (Input.GetKey(KeyCode.LeftShift))
{
animationHandler.Slide();
isSliding = true;
}
else if (Input.GetKeyUp(KeyCode.LeftShift))
{
animationHandler.Move();
isSliding = false;
}
}
// 플레이어가 사망 영역(높이 아래로 떨어짐)에 도달하면 게임 오버 처리
if (transform.position.y < deathY)
{
gameManager.GameOver();
}
}
/// <summary>
/// 전진 이동, 점프, 슬라이딩, 바닥 감지 등의 물리 처리
/// </summary>
private void FixedUpdate()
{
if (isDead)
return;
Move();
Jump();
Sliding();
}
/// <summary>
/// 플레이어 이동 처리
/// 현재 속도를 statHandler.Speed 값으로 설정
/// </summary>
public void Move()
{
Vector2 velocity = rb.velocity;
velocity.x = statHandler.Speed;
rb.velocity = velocity;
}
/// <summary>
/// 플레이어 점프 처리
/// 남은 점프 횟수가 있을 경우 점프를 수행하고 점프 횟수를 감소
/// </summary>
public void Jump()
{
if (isJumping)
{
if (jumpCount > 0)
{
Vector2 vel = rb.velocity + Vector2.up * jumpForce;
rb.velocity = vel;
--jumpCount;
isJumping = false;
}
}
}
/// <summary>
/// 플레이어 슬라이딩 처리
/// 슬라이딩 중이면 90도로 회전, 그렇지 않으면 원래 상태 유지
/// </summary>
public void Sliding()
{
if (isSliding)
{
transform.rotation = Quaternion.Euler(0, 0, 90);
}
else
{
transform.rotation = Quaternion.Euler(0, 0, 0);
}
}
/// <summary>
/// 트리거 충돌 발생 시 상호작용 가능한 오브젝트와의 상호작용 처리
/// </summary>
/// <param name="collision">충돌한 콜라이더</param>
private void OnTriggerEnter2D(Collider2D collision)
{
if (collision.tag.Equals("Interactable"))
{
InteractObject inter = collision.GetComponent<InteractObject>();
if (inter == null)
return;
inter.OnInteraction(statHandler);
}
}
/// <summary>
/// 지면과의 충돌 감지하여 점프 횟수 초기화
/// </summary>
/// <param name="collision">충돌한 오브젝트 정보</param>
private void OnCollisionEnter2D(Collision2D collision)
{
if (collision.gameObject.CompareTag("Ground"))
{
jumpCount = 2;
}
}
/// <summary>
/// 점수 추가 이벤트 호출
/// </summary>
/// <param name="amount">추가할 점수</param>
public void AddScore(int amount = 1)
{
OnAddScore?.Invoke(this, amount);
}
}
사전에 제작해둔 애니메이션을 애니메이터에서 파라미터를 통해 연결하기 위해 작성한 AnimationHandler 스크립트이다. 애니메이션에는 움직임, 점프, 슬라이딩, 피격, 무적 등이 있고 Bool형 파라미터로 관리하였다.
이렇게 해서 현재까지 2단 점프
▶▷ 알게된 점
OnValidate() 메소드는 컴파일 시점에 작동
▶▷ 트러블 슈팅
막혔던 부분
1. StatHandler 스크립트를 추가하면서 여기저기에 퍼져있는 참조 부분에 오류들이 생기는 현상
2. 두번째 점프를 할 때 전진을 잠시 멈추는 현상
3. 첫번째 점프 후 바로 두번째 점프를 하면 정상적으로 점프하는 것처럼 보이지만 사이에 딜레이를 주고 누르면 두번째 점프가 힘을 받지 못하는 현상
시도한 점
1-1. 유니티 에디터 하단에 나오는 오류 메시지를 통해 문제가 되는 스크립트 확인
1-2. 우클릭 메뉴에서 모든 참조 찾기를 눌러 참조중인 모든 부분에 이동하여 확인
2-1. 점프 로직 수정
3-1. 캐릭터가 받는 힘을 점프 직전에 0으로 변경
해결
1. 오류가 생긴 모든 부분으로 이동해서 각 요소들이 Playercontroller를 참조하는지 StatHandler를 참조하는지 구분하여 수정
2. 점프 시 전진할 때 받는 힘 추가
3. Vector2 velocity = rb.velocity * 0; 와 rb.velocity = velocity;를 점프 힘을 받기 직전에 추가
'게임 개발 공부 기록' 카테고리의 다른 글
26일차 - PenguinRun 팀 프로젝트 마무리 (0) | 2025.02.27 |
---|---|
25일차 - PenguinRun 팀 프로젝트 3 (0) | 2025.02.26 |
23일차 - PenguinRun 팀 프로젝트 1 (0) | 2025.02.24 |
22일차 - 디버깅, 단축키, 객체 지향 (0) | 2025.02.21 |
21일차 - SimpleMetaverse 트러블 슈팅 (0) | 2025.02.20 |