유니티/게임 프로젝트

간단한 2D RPG 프로젝트 - (2) PlayerController

monstro 2024. 11. 20. 19:25
728x90
반응형

이번에는 BaseController에서 파생된 PlayerController를 만들어보겠습니다.

코드 자체는 매우 간단하고 개선할 부분이 매우 많으므로

프로젝트에 완전히 적용하기에는 무리가 있다는 점을 알아주셨으면 좋겠습니다.

 

1) Animator

플레이어는 많은 애니메이션을 사용합니다.

이번 프로젝트에서는 총 6개의 애니메이션을 사용합니다.

우선 Animator의 구성을 먼저 확인해보겠습니다.

 

 

움직이지 않는 상태의 KnightIdle, 기본 이동 상태의 KnightMove, 대시 이동 상태의 KnightDash

점프하고 떨어지는 상태의 JumpFall, 그리고 콤보 공격에 따라 수행되는 KnightAttack1,2,3이 있습니다.

 

위의 애니메이션들의 전환은 다음과 같이 정리할 수 있습니다.

KnightAttack1,2,3의 경우 Animation Event를 통해 Animator 제어 변수를 설정하게 됩니다.

이제 코드 영역으로 넘어가보겠습니다.

 

2) PlayerController

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class PlayerController : BaseController
{
    [Header("Input")]
    [SerializeField]
    InputAction moveAction;
    [SerializeField]
    InputAction jumpAction;
    [SerializeField]
    InputAction dashAction;
    [SerializeField]
    InputAction attackAction;

    [Header("Dash")]
    [SerializeField]
    float dashDuaration;
    float dashTime;
    [SerializeField]
    float dashSpeed;
    [SerializeField]
    float dashCooldown;
    float dashCooldownTimer;

    [Header("Action Parameters")]
    [SerializeField]
    float moveSpeed = 10.0f;
    [SerializeField]
    float jumpForce = 5.0f;

    [Header("Attack Parameters")]
    [SerializeField]
    private int comboCounter;
    [SerializeField]
    private float comboCooldown = 0.3f;
    private float comboCooldownTimer;
   
    bool bIsMoving = false;
    bool bIsDashing = false;
    bool bIsAttacking = false;

    private void OnEnable()
    {
        moveAction.Enable();
        jumpAction.Enable();
        dashAction.Enable();
        attackAction.Enable();
    }

    private void OnDisable()
    {
        moveAction.Disable();
        jumpAction.Disable();
        dashAction.Disable();
        attackAction.Disable();
    }

    protected override void Update()
    {
        base.Update();

        dashTime -= Time.deltaTime;
        dashCooldownTimer -= Time.deltaTime;

        comboCooldownTimer -= Time.deltaTime;
        
        bIsMoving = (rigidbody.velocity.x != 0);
        bIsDashing = dashTime < 0.0f ? false : true;

        UpdateMoving();
    }

    protected override void Start()
    {
        base.Start();
    }

    void UpdateMoving()
    {
        // Dash
        if (dashAction.IsPressed())
            DoDash();
        
        // Move 
        DoMove();

        // Flip
        DoFlip();

        // Jump
        if (jumpAction.IsPressed())
            DoJump();

        if (attackAction.IsPressed())
            DoAttack();
        
        // Update Parameters for Animator
        UpdateAnimatiorParameters();
    }

    void DoMove()
    {
        float horizontalValue = moveAction.ReadValue<Vector2>().x;

        if (bIsAttacking)
            rigidbody.velocity = Vector2.zero;
        else if (dashTime > 0)
            rigidbody.velocity = new Vector2(facingDirection * dashSpeed, 0);
        else
            rigidbody.velocity = new Vector2(horizontalValue * moveSpeed, rigidbody.velocity.y);
    }

    void DoJump()
    {
        if (bIsAttacking)
            return;

        if (bIsGrounded)
        {
            rigidbody.velocity = new Vector2(rigidbody.velocity.x, jumpForce);
        }
    }  

    void DoDash()
    {
        if(bIsAttacking)
            return;

        if (dashCooldownTimer < 0)
        {
            dashTime = dashDuaration;
            dashCooldownTimer = dashCooldown;
        }
    }

    void DoAttack()
    {
        if (bIsGrounded == false || bIsDashing == true)
            return;

        if (comboCooldownTimer < 0)
                comboCounter = 0;

        bIsAttacking = true;
        comboCooldownTimer = comboCooldown;
    }

    void UpdateAnimatiorParameters()
    {
        animator.SetFloat("yVelocity", rigidbody.velocity.y);
        animator.SetBool("bIsMoving", bIsMoving);
        animator.SetBool("bIsGrounded", bIsGrounded);
        animator.SetBool("bIsDashing", bIsDashing);
        animator.SetBool("bIsAttacking", bIsAttacking);
        animator.SetInteger("comboCounter", comboCounter);
    }

    public void AttackOver()
    {
        bIsAttacking = false;
        comboCounter++;

        if (comboCounter > 2)
            comboCounter = 0;
    }
}

 

Input 항목에서는 대쉬, 공격, 점프, 이동을 처리하는 Input System을 사용합니다.

 

구성은 위와 같습니다.

 

Dash 항목에서는 대쉬의 지속시간과 대쉬의 쿨타임을 정의하였습니다.

ActionParameters 항목에서는 이동과 점프에 필요한 힘의 크기를 정의하였습니다.

또한 AttackParameters 항목에서는 공격의 콤보와 콤보를 유지하는 시간을 정의하였습니다.

 

또한 각각의 불리언 변수Animator에서 사용하는 불리언 변수와 이름을 통일하였습니다.

 

OnEnable 함수와 OnDisable 함수에서는 InputAction을 적용하고 해제합니다.

 

Update 함수에서는 부모인 BaseController의 Update를 지속적으로 호출하면서

대쉬의 지속시간과 대쉬의 쿨타임을 지속적으로 감소시킵니다.

또한 공격의 콤보 유지시간 역시 지속적으로 감소시킵니다.

대쉬와 이동을 판단하는 불리언 변수도 주기적으로 판단하고 있습니다.

불리언 변수의 경우 각각의 함수로 옮길 수 있지만, 일단은 위의 형식을 유지시키겠습니다.

최종적으로 UpdateMoving 함수를 호출하여 이동과 관련한 요소를 처리하겠습니다.

 

UpdateMoving 함수각각의 동작을 수행하는 함수들을 호출하고 있습니다.

각 동작과 연결된 키가 입력된 경우에 각 함수를 호출하고

최종적으로는 UpdateAnimatiorParameters 함수를 호출해 애니메이터에 사용하는 변수들을 초기화합니다.

 

DoMove 함수는 간단합니다.

받아온 키의 입력값을 받아와 3단계로 처리합니다.

공격중일때는 제자리를 위치하도록 설정하며

대쉬중일때는 높이를 유지하면서 x축으로의 속도를 높입니다.

일반적인 경우에는 기존의 moveSpeed 값으로 x축으로의 속도를 유지합니다.

 

DoJump 함수의 경우도 간단합니다.

공격중일때는 점프를 막고,

땅을 딛고 있는 중에만 점프를 수행합니다.

이때 jumpForce만큼 y축으로의 속도를 높입니다.

 

DoDash함수는 대쉬를 수행합니다.

공격중일때는 대쉬를 할 수 없게 막고, 

dashCooldownTimer가 0보다 작은 경우, 즉 대쉬를 할 수 있는 경우에만

대쉬를 수행하며 Update에서 계속 감소시키던

대쉬의 수행시간dashTime을 dashDuaration
대쉬의 쿨타임dashCooldownTimer을 dashCooldown로 초기화합니다.

 

DoAttack함수는 공격을 수행합니다.

땅에 발을 딛고 있지 않거나, 대쉬중일때는 공격을 수행하지 않도록 막겠습니다.

업데이트에서 지속적으로 감소시키던 공격의 콤보 유지시간

comboCooldownTimer가 0보다 작다콤보를 0으로 초기화하여 0부터 시작하도록 합니다.

이후에 현재 상태를 공격 상태로 바꾸고 comboCooldownTimer를 comboCooldown로 초기화합니다.

 

UpdateAnimatiorParameters함수는 Animator에 필요한 변수들을 설정합니다.

Animator에서 호출할 수 있는 Set 시리즈를 사용하였습니다.

 

AttackOver 함수의 경우, 애니메이션 이벤트로 동작합니다.

공격 애니메이션의 마지막 프레임에서 호출하며 

공격상태를 끝내고 공격의 콤보를 +1합니다.

만약 마지막 공격콤보라면 이를 다시 0으로 설정합니다.

 

3) 이후의 처리

코드 작업이 끝났다면 이후 캐릭터의 세부사항을 처리하겠습니다.

우선 캐릭터를 이동하다보면 벽에 달라붙어서 입력을 처리할 수 있는 경우가 발생합니다.

이를 위해 PhysicsMaterial2D를 사용하겠습니다.

 

PhysicsMaterial2D를 하나 생성하고 마찰력을 의미하는 Friction값을 0으로 설정합니다.

 

이렇게 생성된 PhysicsMaterial2D를 RigidBody2D의 Material에 붙여주겠습니다.

 

이후에는 RigidBody의 Constrains에서 Z축을 Freeze하여 캐릭터가 넘어가는 일을 방지하겠습니다.

 

최종적으로 실행하면 다음과 같이 실행됩니다.

 

 

 

728x90
반응형