Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

zyeon's 작심삼일 코딩 공부

2.5D 게임에서 캐릭터 이동 구현 본문

unity

2.5D 게임에서 캐릭터 이동 구현

젼뀨 2024. 8. 28. 02:15

나는 2D 캐릭터와 3D 배경을 가진 게임을 제작하였다.

적합한 표현인지는 모르겠지만 편의상 2.5D 게임이라고 하겠다.

 

3D 오브젝트와 다르게, 보는 방향에 따라 플레이어 이미지를 달리 해줘야 한다.

 ▲보는 방향에 따라 다른 캐릭터 이미지

플레이어 오브젝트

나는 좌측/우측(Knight), 후방(Knight_back), 전방(Knight_front) 이미지를 가진 오브젝트를 미리 만들어 두고, 

입력에 따라 오브젝트를 활성화, 비활성화 해주기로 했다.

좌측 우측은 이미지를 뒤집어 주면 되기 때문에 한가지로 사용하였다.

 

이를 위해 열거형 Enum을 사용해 플레이어가 바라보는 방향을 나타내었다.

//플레이어 방향
enum PlayerLook
{
    front,
    back,
    left,
    right,
};

PlayerLook m_playerLook = PlayerLook.right;         // 시작 시 오른쪽 보고있음
Vector3 m_dirVec;                                   // 캐릭터 방향에 맞춰 벡터 설정
GameObject m_playerImage;                           // 캐릭터 이미지
Animator m_playerAni;                               // 플레이어 애니메이션

[SerializeField] GameObject m_idleFaceImage;        // 얼굴 이미지 따로 설정
[SerializeField] GameObject m_runFaceImage;         // 얼굴 이미지 따로 설정

Rigidbody m_rigidbody;
float m_speed = 5.0f;                               // 플레이어 속도
float h, v;                                         // 수평, 수직 값

void Start()
{
    m_playerImage = transform.Find("Knight").gameObject;
    m_playerAni = this.GetComponent<Animator>();
    m_rigidbody = gameObject.GetComponent<Rigidbody>();
}

플레이어 상태는 전방/후방/좌측/우측 으로 나누어 주었다.

그 후 이미지, 애니메이션, 리지드바디를 사용하기 위한 사전 작업을 해준다.


private void FixedUpdate()
{
    //조이스틱 사용하지 않고 있을 경우 (키보드 이동)
    if (!m_PanelJoyStick.GetComponent<PanelJoyStick>().isStickDown)
    {
        //대화창 열려있는 경우 입력 방향 무시
        h = UIManager.Instance.isDialogAction ? 0 : Input.GetAxis("Horizontal");
        v = UIManager.Instance.isDialogAction ? 0 : Input.GetAxis("Vertical");
    }

    // 이동, 이미지 변경, 애니메이션 변경
    Move();
    ChangeImage();
    ChangeAni();
    
}

Update()는 매 프레임마다 호출되기 때문에 호출 간격이 달라질 수 있지만,

FixedUpdate()는 일정한 시간 간격으로 호출되기 때문에 물리 엔진의 일관성을 유지할 수 있다는 장점을 가지고 있어 FixedUpdate()에 작성하였다.

 

나는 모바일 조작을 염두해두고 만들어서 조이스틱을 사용하지 않고 있는 경우와,

대화 시스템이 있어 대화창이 닫혀있을 경우에만 수평 수직값을 받도록 했다.

h = Input.GetAxis("Horizontal");
v = Input.GetAxis("Vertical");

일반적인 경우에는 이렇게 사용하면 된다.

 

그리고 특정 상태를 구분하여 플레이어가 동작하도록 해주었다.


// 플레이어 이동
void Move()
{
    m_rigidbody.MovePosition(this.transform.position + 
        new Vector3(h * m_speed, 0, v * m_speed) * Time.deltaTime);

    if (h < 0 && Mathf.Abs(v) < 0.7f) m_playerLook = PlayerLook.left;
    else if (h > 0 && Mathf.Abs(v) < 0.7f) m_playerLook = PlayerLook.right;
    else if (v < 0) m_playerLook = PlayerLook.front;
    else if (v > 0) m_playerLook = PlayerLook.back;
}

플레이어의 위치는 Rigidbody.MovePosition()을 사용해 업데이트 했다.

비교적 단순한 이동만 필요했지만 비교적 부드러운 움직임과 충돌 처리를 위해 사용했다.

 

수평 수직값에 따라 플레이어 상태를 설정해 주었다.

if (h < 0 && Mathf.Abs(v) < 0.7f) 와 같은 조건은

수직값이 미미하게 수평값과 같이 감지될 경우에는 수평값을 우선적으로 판단하도록 해준다.

여기서 Mathf.Abs()는 절대값을 알려준다.


// 플레이어 이미지 변경
void ChangeImage()
{
    switch (m_playerLook)
    {
        case PlayerLook.front:
            m_dirVec = Vector3.back;
            SetImage("Knight_front", 40, 0, 0);
            break;
        case PlayerLook.back:
            m_dirVec = Vector3.forward;
            SetImage("Knight_back", 40, 0, 0);
            break;
        case PlayerLook.left:
            m_dirVec = Vector3.left;
            SetImage("Knight", -40, 180, 0);
            break;
        case PlayerLook.right:
            m_dirVec = Vector3.right;
            SetImage("Knight", 40, 0, 0);
            break;
    }
}

switch 문을 통해 현재 플레이어 상태에 따라 벡터값과 이미지를 셋팅해주었다.

이후에 npc를 감지할 레이캐스트를 위해 벡터값을 따로 저장해두었다.

// 플레이어 이미지 셋팅
void SetImage(string s, float a, float b, float c)
{
    m_playerImage.SetActive(false);
    m_playerImage = transform.Find(s).gameObject;
    m_playerImage.SetActive(true);
    m_playerImage.transform.rotation = Quaternion.Euler(a, b, c);
}

기존에 있던 이미지는 비활성화 한 후, 새로 입력받은 이미지를 활성화 해주는 함수이다.

좌, 우 이미지는 반전해서 사용하기 때문에 Quaternion.Euler()를 사용해 이미지를 회전할 수 있도록 하였다.


// 플레이어 애니메이션 변경
void ChangeAni()
{
    //idle 애니메이션
    if (h == 0 && v == 0)
    {
        m_playerAni.SetInteger("state", 0);
        m_runFaceImage.SetActive(false);
        m_idleFaceImage.SetActive(true);
    }
    //run 애니메이션
    else
    {
        m_playerAni.SetInteger("state", 1);
        m_idleFaceImage.SetActive(false);
        m_runFaceImage.SetActive(true);
    }
}

이동값이 있을 때와 없을 때를 구분하여 애니메이션 및 이미지를 바꿔주었다.


실행 영상

이동 방향에 따라 오브젝트 활성 여부가 달라지는 걸 볼 수 있다.

 

다만 이렇게 이미지를 활성화하는 방식이 일반적인 접근 방식은 아닌 것 같다.

나는 플레이어 상태가 적고, 애니메이션 구현에 시간이 많이 걸린다고 생각해서 이런 방식을 사용하였다.

 

일반적으로는 애니메이터를 사용하여 구현하는 것 같다.

플레이어 상태를 애니메이터와 파라미터로 관리하고, 

여러 파츠로 나누어져 있는 플레이어는 애니메이터 레이어와 애니메이션 믹서를 사용하면

각 파츠의 애니메이션을 조합해 전체 애니메이션을 만들 수 있다고 한다.