카테고리 없음

유니티 플레이 캐릭터 이동시키기

akama 2024. 9. 16. 16:27

내가 만들어서 조정하는 캐릭터를 어떻게 이동시켜 볼까를 다뤄보겠습니다. 캐릭터의 측면을 보는 2D 플랫폼 타입은 좌우 이동, 점프, 사다리 타고 오르고 내리기, 낙하가 있으나 이번 예시에서는 위에서 내려다 보는 캐릭터의 이동을 한 번 만들어 볼까 합니다. 점프가 필요없기 때문에 물리적인 부분을 배제할 예정입니다. 제가 생각하는 플레이 캐릭터는 '스테이크 게임'이라고 한 번쯤 접해 보셨을 것이라 생각합니다. 이 게임은 시작하면 뱀이 있고 상하좌우로 조작하여 수직 이동만 가능하며 한 번 이동을 하면 한 방향으로만 멈추지 않고 직진 이동을 하는 게임입니다. 뱀이 음식을 먹고 꼬리가 길어지는데 자신의 꼬리나 벽에 부딪히면 끝나는 단순한 게임인데요. 이 조작 방식으로 게임 캐릭터를 만들어 볼까 합니다.

 

1. 캐릭터 오브젝트 만들기

Hierarchy뷰에서 마우스 우클릭 2D object- sprites- square 를 통해 게임 오브젝트를 생성합니다. 예시이기 때문에 square 말고 circle, capsule로 만들어도 상관없습니다만 대부분 애니메이션 때문에 정사각형인 square로 생성합니다. 화면에 흰색 정사각형만 덩그러니 있으니 모양새가 좀 않좋습니다. 대충 그린 로봇 그림을 하나 적용시켜줍니다. 예시에서는 그림을 잘 못그려서 단순하게 그려서 적용했습니다. 캐릭터에 색도 넣기 귀찮아서 흰색으로 했고, 캐릭터만 돋보이게 배경 화면 색을 살짝 바꿔주었습니다. 배경 화면 색 변경은 Hierarchy뷰에서 Camera 클릭 후 inspector Backgroud에서 바꿀 수 있습니다.

 

이 상태는 그냥 캐릭터 껍데기만 씌워 놓은 상태이니 조정할 수 있게 만들어서 게임의 기본 토대를 마련해야 합니다.

캐릭터 조정을 위해서 스크립트를 생성하고 PlayerMovement 라고 지정합니다.

 

public class PlayerMovement : MonoBehaviour
{
    public Transform player;
    public float moveSpeed = 5f;
    float setSpeed;
    bool isMoveUp = false;
    bool isMoveDown = false;
    bool isMoveRight = false;
    bool isMoveLeft = false;
    
    void Start ()
    {
        player = GameObject.FindGameObjectWithTag("Player").transform;
        setSpeed = moveSpeed;
    }

   void Update ()
   {
        Vector3 moveDirectionRight = new Vector3(1, 0, 0).normalized;
        Vector3 moveDirectionLeft = new Vector3(-1, 0, 0).normalized;
        Vector3 moveDirectionUp = new Vector3(0, 1, 0).normalized;
        Vector3 moveDirectionDown = new Vector3(0, -1, 0).normalized;
   }
    
    void FixedUpdate ()
    {
        if (isMoveUp)
            {
                player.transform.Translate(moveDirectionUp * moveSpeed * Time.deltaTime);
            }
            if (isMoveDown)
            {
                player.transform.Translate(moveDirectionDown * moveSpeed * Time.deltaTime);
            }
            if (isMoveRight)
            {
                player.transform.Translate(moveDirectionRight * moveSpeed * Time.deltaTime);
            }
            if (isMoveLeft)
            {
                player.transform.Translate(moveDirectionLeft * moveSpeed * Time.deltaTime);
            }
    }

    public void MoveUp ()
    {
        moveSpeed = setSpeed;
        isMoveUp = true;
        isMoveDown = false;
        isMoveLeft = false;
        isMoveRight = false;
    }

    public void MoveDown()
    {
        moveSpeed = setSpeed;
        isMoveUp = false;
        isMoveDown = true;
        isMoveLeft = false;
        isMoveRight = false;
    }

    public void MoveRight()
    {
        moveSpeed = setSpeed;
        isMoveUp = false;
        isMoveDown = false;
        isMoveLeft = false;
        isMoveRight = true;
    }
    public void MoveLeft()
    {
        moveSpeed = setSpeed;
        isMoveUp = false;
        isMoveDown = false;
        isMoveLeft = true;
        isMoveRight = false;
    }
}

가장 먼저 플레이어 캐릭터를 할당해 줘야 합니다. Transform을 public으로 선언하고 게임 시작 시  Start()에서 플레이어를 지정하게 해 줍니다. 그 전에 캐릭터 오브젝트를 클릭하고 inspector창에서 tag를 설정해 줍니다. 만약 tag를 설정하지 않고, 플레이를 하면 Start()에서 실행하는 player = GameObject.FindGameObjectWithTag("Player").transform; 명령에서 null reference exception이 발생합니다. 참조하라고 명령했는데 tag Player가 달린 오브젝트를 찾지 못 해서 발생하는 것이니 이 명령어를 사용했다면 태그를 설정해야 시작 함수에서 Player 태그가 있는 게임 오브젝트를 찾아서 실행 할 수 있습니다.

 

유니티에는 오브젝트를 움직이는 방법이 몇 가지 있는데 Translate, Addforce, velocity 등이 대표적 입니다. Translate을 설명하기 전에 tranform을 알 필요가 있습니다. 게임 오브젝트 생성과 함께 기본으로 있는 컴포넌트입니다. 이 컴포넌트에는 position, rotattion, scale이 있는데 각각 위치, 회전, 크기를 담당하며 값 조정을 통해 위치 변경, 각도 변경, 크기 및 이미지 반전을 시킬 수 있습니다. 따라서 오브젝트의 이동은 결국 transform의 position 값을 계속 바꿔주면 된다는 것을 뜻합니다.

그래서 오브젝트 이동을 위해 Translate를 사용해 보았습니다. Translate은 기본적으로 Vector3를 사용합니다. 비슷하지만 표현법만 다른 두가지가 있습니다. 

첫번째, Vector3는 up, down, right, left, foward, back을 갖고 있으며 지정하여 방향을 설정할 수 있습니다.

두번째,  Vector3는 x, y, z 방향을 지정하며 normalized는 Vector3의 정규화로 현재 벡터와 같은 방향은 normalized=1 다르면 normalized=0으로 반환합니다. 이동의 방향을 지정해 주고 정규화만 시켜주기 때문에 Vector3를 선언하고 새 vector3 값을 갖겠다는 의미로 new Vector3(x, y, z).정규화( normalized) 합니다.

 

그러면 방향을 계속 감지하고 실행할 수 있는 함수가 필요한데 이 때 Update()를 사용하면 됩니다.

유니티 스크립트 monobehaviour에 있는 Update 함수는 Update 내에 있는 명령들을 매 초 실행하는 함수(function) 입니다.

위 스크립트에서 Update()에 Vector3 선언한 4개의 명령줄 넣었습니다. 즉 Update를 통해 플레이 시 이 Vector3를 계속 실행하면서 방향을 향하게 됩니다. 

이 값을 Translate에 대입하면 Translate은 Vector3 방향쪽으로 오브젝트의 transform position 값을 계속 바꾸면서 결과적으로는 오브젝트가 이동하게 됩니다. 여기에 moveSpeed를 곱하고, time.DeltaTime(마지막 프레임에서 현재 프레임까지 초)까지 곱해서 실행하게 합니다.

 

isMoveUp, isMoveDown, isMoveRight, isMoveLeft는 불리언(bool) 변수입니다. true, false로 설정을 하는데 게임 시작 시에는 움직임이 없어야 하기에 false로 값을 지정했습니다. 각 방향은 public 함수로 만들어서 어디서든 접근하거나 노출시켜 사용할 수 있도록 만들었습니다. 방향 함수별로 이동시키고자 하는 방향만 true로 열고 나머지 방향은 false로 닫도록 합니다. 방향키 마다 moveSpeed = setSpeed 명령을 넣은 이유는 나중에 캐릭터가 정지했다가 출발하는 기능을 넣을 때 원래의 속도 값을 가져와서 움직이도록 하려고 미리 넣어 두었습니다.

해당 스크립트를 hierarchy뷰 Canvas-방향키 버튼들에 드래그 앤 드롭합니다. 여기서 방향키는 4개이지만 나중에 스크립트를 다수의 오브젝트에 적용시킬 경우도 있으니 간단한 다수 적용 Tip도 쓰겠습니다. 

 

Tip 1. 적용시키고자 하는 오브젝트들이 많은데 hierarchy뷰에서 흩어져 있을 때는 ctrl 키를 누른 채로 적용시키고자 하는 오브젝트들만 클릭
Tip 2. 적용시키고자 하는 오브젝트들이 많은데 hierarchy뷰에서 모여서 정렬되어 있을 때는 상단 오브젝트를 클릭하고,
shift 키를 누른 채로 하단 오브젝트를 클릭하면 한꺼번에 선택이 됩니다.

player 오브젝트 transform의 Translate를 이용하여 움직일 수 있도록 명령어는 짰습니다. 다음은 이 스크립트를 Camera-Canvas-각 방향 버튼들에 드래그 앤 드랍하여 PlayerMovement 스크립트 컴포넌트를 추가해 줍니다. 버튼에 있는 on Click에서 public으로 선언한 스크립트 내 각 방향 함수들을 지정합니다.

 

유니티에서 플레이 버튼을 누르고 게임 화면에서 각 방향키(UI)를 눌러서 게임 캐릭터가 4방향으로 잘 움직이는지 테스트를 해보면 됩니다.

각 방향키를 눌러 보면 이동하고자 하는 방향으로 계속 향하는 것을 알 수 있습니다. 이 게임을 목표물을 채울 때까지 계속 플레이 캐릭터를 non-stop 게임으로 만들고자 하였으나 만약 방향키를 누르고 있을 때만 움직이게 만들고자 한다면 이 방법보다는 가상 조이스틱을 만들어 사용하는 것이 좋습니다. 가상 조이스틱 조작법이란 조이스틱을 누르고 있는 동안에만 방향대로 움직이고 손을 떼면 캐릭터가 멈추는 방법입니다. 다만 가상 조이스틱을 UI로 띄워야 하기 때문에 조이스틱 이미지를 별도로 준비해 놓아야만 합니다.

가상 조이스틱 조작법(조이스틱을 통한 방향만 설정하는 스크립트)

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class JoystickScript : MonoBehaviour, IDragHandler, IPointerUpHandler, IPointerDownHandler
{
    private Image bgImg;
    private Image joystickImg;
    private Vector3 inputVector;

    void Start()
    {
        bgImg = GetComponent<Image>();
        joystickImg = transform.GetChild(0).GetComponent<Image>();
    }

    public virtual void OnDrag(PointerEventData ped)
    {
        Vector2 pos;
        if (RectTransformUtility.ScreenPointToLocalPointInRectangle(bgImg.rectTransform, ped.position, ped.pressEventCamera, out pos))
        {
            pos.x = (pos.x / bgImg.rectTransform.sizeDelta.x);
            pos.y = (pos.y / bgImg.rectTransform.sizeDelta.y);

            inputVector = new Vector3(pos.x * 2 + 1, pos.y * 2 - 1, 0);
            inputVector = (inputVector.magnitude > 1.0f) ? inputVector.normalized : inputVector;

            joystickImg.rectTransform.anchoredPosition = new Vector3(inputVector.x * (bgImg.rectTransform.sizeDelta.x / 3), inputVector.y * (bgImg.rectTransform.sizeDelta.y / 3), 0);
        }
    }

    public virtual void OnPointerDown(PointerEventData ped)
    {
        OnDrag(ped);
    }

    public virtual void OnPointerUp(PointerEventData ped)
    {
        inputVector = Vector3.zero;
        joystickImg.rectTransform.anchoredPosition = Vector3.zero;
    }
}

 

위 스크립트를 배경 오브젝트에 붙입니다. 화면의 터치 값을 받아서 조이스틱 이미지를 이동시키면서 벡터값을 계산합니다. 이 벡터값(스크립트 내 inputVetor)을 이용하여 캐릭터의 방향도 조이스틱의 방향과 일치하게 되며 이를 활용하여 캐릭터를 이동시킬 수 있습니다. 유니티에서 방향 설정은 Vector2, Vector3로 하고, 게임 오브젝트의 이동은 Translate, Addforce, velocity로 한다는 점을 이해하고 이를 응용해서 접목시키면 다양한 방법으로 게임 오브젝트(캐릭터)를 조작할 수 있습니다.

 

다시 본론으로 돌아와서 캐릭터를 움직였을 때 아직 많이 이상한 부분이 많습니다. 캐릭터가 움직여도 화면 밖으로 나가버리는데 이는 카메라가 캐릭터를 따라다니면서 움직이지 않기 때문입니다.

다음에는 이동하는 캐릭터를 따라다니는 카메라에 대해 포스팅하겠습니다.