소소한 나의 하루들

탑다운 2d RPG(2) - 쯔꾸르식 액션 구현하기 본문

개발/유니티

탑다운 2d RPG(2) - 쯔꾸르식 액션 구현하기

소소한 나의 하루 2024. 1. 30. 12:55

출처: https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2&feature=shared

 

📚 유니티 기초 강좌

유니티 게임 개발을 배우고 싶은 분들을 위한 기초 강좌

www.youtube.com

쯔꾸르 느낌으로 가려면, 대각선 이동을 제한해야한다.


#1. 십자 이동

플래그 변수 하나로 수평, 수직 이동을 결정한다. 수평, 수직 이동 버튼 이벤트를 변수로 저장한다.

이전에는 수평, 수직키를 같이 누르면 대각선으로 이동했었다.

public class PlayerAction : MonoBehaviour
{
    public float Speed;
    float h;
    float v;
    bool isHorizonMove;

    Rigidbody2D rigid;

    private void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();    
    }

    private void FixedUpdate()
    {
        //Move
        Vector2 moveVec = isHorizonMove ? new Vector2(h, 0) : new Vector2(0, v);
        rigid.velocity = moveVec * Speed;
    }

    // Update is called once per frame
    void Update()
    {
        //Move Value
        h = Input.GetAxisRaw("Horizontal");
        v = Input.GetAxisRaw("Vertical");

        //Check Button Down & Up
        bool hDown = Input.GetButtonDown("Horizontal");
        bool vDown = Input.GetButtonDown("Vertical");
        bool hUp = Input.GetButtonUp("Horizontal");
        bool vUp = Input.GetButtonUp("Vertical");

        //Check Horizontal Move
        if (hDown || vUp)
            isHorizonMove = true;
        else if (vDown || hUp)
            isHorizonMove = false;

    }
}

그런데 문제가 생긴다. 수평/수직 키를 같이 동시에 누르다가 한쪽을 떼면 그 자리에 정지하게 된다.

따라서 버튼 Up으로도 수평이동을 체크한다.


#2. 애니메이션

플레이어에게 들어가는 애니메이션도 넣어준다.

Any State는 현재 머물고 있는 State에 상관없이 조건에만 부합하면 상태전이를 발생시키는 State 이다.

상/하/좌/우 각각 하나의 방향에 대해 서있기, 걷기 총 2개의 State를 구성한다. (총 8개의 State)

기본 State는 플레이어가 아래방향으로 숨쉬는 Idle 모션이다. 상하좌우 어떤 키를 누르든지 애니메이션이 실행되어야한다.

따라서 Any State에 Player_Down_Walk 애니메이션을 연결시키고, 다시 Player_Down_Idle 애니메이션을 연결시킨다.

이렇게 상/하/좌/우 모두 애니메이션을 Any State → Walk → Idle로 연결시킨다.

그리고 수직, 수평 값을 받을 Int형 매개변수 hAxisRaw, vAxisRaw를 생성한다.

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

public class PlayerAction : MonoBehaviour
{
    public float Speed;
    float h;
    float v;
    bool isHorizonMove;

    Rigidbody2D rigid;
    Animator anime;

    private void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        anime = GetComponent<Animator>();
    }

    private void FixedUpdate()
    {
        //Move
        Vector2 moveVec = isHorizonMove ? new Vector2(h, 0) : new Vector2(0, v);
        rigid.velocity = moveVec * Speed;
    }

    // Update is called once per frame
    void Update()
    {
        //Move Value
        h = Input.GetAxisRaw("Horizontal");
        v = Input.GetAxisRaw("Vertical");

        //Check Button Down & Up
        bool hDown = Input.GetButtonDown("Horizontal");
        bool vDown = Input.GetButtonDown("Vertical");
        bool hUp = Input.GetButtonUp("Horizontal");
        bool vUp = Input.GetButtonUp("Vertical");

        //Check Horizontal Move
        if (hDown || vUp)
            isHorizonMove = true;
        else if (vDown || hUp)
            isHorizonMove = false;

        //Animation
        anime.SetInteger("hAxisRaw", (int)h);
        anime.SetInteger("vAxisRaw", (int)v);
    }
}

서로 타입이 다르면, 명시적 형 변환으로 처리한다. : 강제 형변환

그런데 여기서 문제가, 여기서 걷는 모션을 재생해주지 않는다. 왜냐하면 -1값을 연속적으로 부여해서 Transition을 연속적으로 태우면 애니메이션이 작동되지 않는다.

그리고 Bool형 방향 변화 매개변수를 추가하여 확실히 한번만 실행되도록 변경한다. '만약 매개변수 hAxisRaw에 -1이 들어있다면 같은값을 다시 주지마' = 한번 값을 넣게되면 다른 값을 넣기 전까지는 변하지 않는다. / 계속 같은 값을 넣지 않는다.

만약 false라면 transition 전환이 안될 것이다. (값이 안들어가서 업데이트 안될것이다.)

//Check Horizontal Move
if (hDown || vUp)
	isHorizonMove = true;
else if (vDown || hUp)
	isHorizonMove = false;
//Check Horizontal Move
if (hDown)
    isHorizonMove = true;
else if (vDown)
    isHorizonMove = false;
else if (hUp || vUp)
    isHorizonMove = h != 0;

양쪽 버튼 누른 상태에서 하나만 버튼 Up이면 문제가 발생한다. 따라서 현재 AxisRaw 값에 따라 수평/수직을 판단하여 해결한다. 확실하게 Down의 경우 isHorizonMove의 논리값을 결정해주고, Up의 경우 h가 0이 아니라면 true로 만들어준다.


#3. 조사 액션

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

public class PlayerAction : MonoBehaviour
{
    public float Speed;
    float h;
    float v;
    bool hDown;
    bool vDown;
    bool hUp;
    bool vUp;
    bool isHorizonMove;
    Vector2 moveVec;

    Rigidbody2D rigid;
    Animator anime;

    private void Awake()
    {
        rigid = GetComponent<Rigidbody2D>();
        anime = GetComponent<Animator>();
    }

    private void FixedUpdate()
    {
        //Move
        
        Vector2 moveVec = isHorizonMove ? new Vector2(h, 0) : new Vector2(0, v);
        rigid.velocity = moveVec * Speed;

        //Direction
        if (vDown && v == 1)
            moveVec = Vector2.up;
        else if (vDown && v == -1)
            moveVec = Vector2.down;
        else if (hDown && h == 1)
            moveVec = Vector2.right;
        else if (hDown && h == -1)
            moveVec = Vector2.left;
    }

    // Update is called once per frame
    void Update()
    {
        //Move Value
        h = Input.GetAxisRaw("Horizontal");
        v = Input.GetAxisRaw("Vertical");

        //Check Button Down & Up
        hDown = Input.GetButtonDown("Horizontal");
        vDown = Input.GetButtonDown("Vertical");
        hUp = Input.GetButtonUp("Horizontal");
        vUp = Input.GetButtonUp("Vertical");

        //Check Horizontal Move
        if (hDown)
            isHorizonMove = true;
        else if (vDown)
            isHorizonMove = false;
        else if (hUp || vUp)
            isHorizonMove = h != 0;

        //Animation
        if (anime.GetInteger("hAxisRaw") != h)
        {
            anime.SetBool("IsChange", true);
            anime.SetInteger("hAxisRaw", (int)h);
        }

        else if (anime.GetInteger("vAxisRaw") != v)
        {
            anime.SetBool("IsChange", true);
            anime.SetInteger("vAxisRaw", (int)v);
        }
        else
            anime.SetBool("IsChange", false);

        //Ray
        Debug.DrawRay(rigid.position, moveVec * 0.7f, new Color(0, 1, 0));
    }
}

앞에 있는 사물을 스캔하는 것을 해본다.

이렇게 스캔하는 로직은 RayCast를 사용하는 것이 일반적이다. 현재 바라보고 있는 방향 값을 가진 변수가 필요하다. (방향 벡터 dirVec)

캐릭터가 바라보는 방향으로 Ray가 그려진다.

Debug.DrawRay(출발지점, 벡터(방향*힘(길이)), 색상);

RaycastHit2D rayHit = Physics2D.RayCast(출발지점, 방향벡터, 힘(길이), LayerMask.GetMask("레이어명"));

DrawRay로 미리보고, RayCast를 구현하면 쉽다. 

RayCast를 구현할 때 플레이어의 'Collider(충돌박스)'는 무시해야한다. 따라서 레이어를 사용한다.

→레이어를 생성해서 '조사가능한 오브젝트'를 다른 Layer로 설정한다. (플레이어 제외 나머지)

RaycastHit2D rayHit = Physics2D.Raycast(rigid.positoin, dirVec, 0.7f, LayerMask.GetMask(”Object”));
if (rayHit.collider ≠ null)

빔이 충돌한 오브젝트가 null이 아니면 = 무엇인가 충돌하여 값 생성됨

RayCast된 오브젝트를 변수로 저장하여 활용한다. (변수 scanObject)

만약 충돌된 오브젝트가 없다면, null 값이다. 충돌한 오브젝트의 이름을 출력해본다.

Comments