본문 바로가기

유니티

유니티 강좌 02. 2D 플레이어 이동

지난 강좌에서 유니티의 기본 인터페이스에 대해서 배웠습니다. 그럼 이제 본격적으로 유니티를 사용해 봐야겠죠. 게임에서 가장 기본적인 요소 중 하나가 바로 플레이어입니다. 그중에서도 제일 기본은 움직임이죠. 유니티에서는 다양한 방법으로 플레이어가 이동할 수 있도록 제어가 가능한데요, 이번 강좌에서는 Rigidbody(리지드바디)를 사용한 2D 플레이어 상하좌우 이동을 구현해 보겠습니다.


 

유니티 강좌 01. 기본 인터페이스

유니티는 게임 개발을 조금 더 쉽게 할 수 있도록 도움을 주는 엔진입니다. 3D와 2D 환경의 게임을 모두 개발할 수 있고, C#을 지원하기 때문에 초보자가 쉽게 접근할 수 있습니다. 그리고 유니티

torotoblog.tistory.com

 

백준으로 배우는 C 언어 01. Xcode 설치하기

C 언어를 배우기 시작하면서 이것저것 찾아보다가 백준(Baekjoon)이라는 사이트를 알게 되었는데요, 기초부터 고급까지 수만 개의 문제들을 풀어볼 수 있는 사이트입니다. 코딩에 관한 기초 지식

torotoblog.tistory.com


코딩을 하기 앞서 플레이어를 먼저 만들어 줘야겠죠. 그러기 위해서는 플레이어 이미가 필요합니다. 플레이어로 사용할 이미지를 하나 구한 후, 프로젝트 탭에 이미지를 추가해 줍시다. 추가하는 방법은 아주 간단합니다. 이미지 파일를 프로젝트 탭으로 드래그해서 끌고 오면 됩니다. 이미지 이름도 알아보기 쉽게 Player이라고 설정해 줍시다.

프로젝트 탭에 플레이어 이미지가 추가가 됐다면 이제 플레이어 오브젝트를 만들어야 합니다. 오브젝트(Object)란 게임 속에서 작동하는 각각의 객체들을 뜻합니다. 플레이어도, 적, 땅, 나무, 총알 등 게임 속에 존재하는 모든 객체들을 오브젝트라고 부릅니다. 

오브젝트를 만드는 방법은 여러 가지가 있지만, 저희는 간단하게 플레이어 이미지를 Hierarchy 탭으로 드래그하는 방법으로 만들어 보겠습니다. 이미지를 Hierarchy 탭으로 끌고 오면, 새로 생성되는 오브젝트에 이미지 파일이 바로 적용되고 이름도 이미지와 동일한 이름으로 설정됩니다. 

생성된 플레이어 오브젝트를 선택한 후 화면 오른쪽에 있는 Inspector 탭을 살펴보면 오브젝트에 적용된 컴포넌트들을 볼 수 있습니다. 모든 오브젝트에는 Transform 컴포넌트가 적용되어 있는데요, 오브젝트의 포지션(위치), 로테이션(회전), 그리고 스케일(크기)을 조정하는 컴포넌트입니다. 그다음은 Sprite Renderer 컴포넌트가 있습니다. 저희는 플레이어 이미지를 Hierarchy 탭으로 끌고 오는 방식으로 오브젝트를 생성했기 때문에 자동으로 Sprite Renderer 컴포넌트가 적용이 되어 있는 겁니다. 이 컴포넌트는 이미지를 오브젝트에 적용하는 역할을 합니다. 

플레이어의 이동을 구현하기 위해서는 한 가지 컴포넌트를 더 추가해야 합니다. 저희는 Rigidbody를 기반으로 이동을 구현할 것이기 때문에 Rigidbody2D라는 컴포넌트를 추가해 줘야 합니다. Rigidbody는 오브젝트가 중력, 충돌 등 물리 현상을 구현할 수 있도록 도와주는 컴포넌트입니다. 컴포넌트를 추가하기 위해서는 Inspector 탭 가장 하단에 있는 Add Component 버튼을 클릭해 줍시다.

이어서 뜨는 탭에서 Physics 2D > Rigidbody 2D를 찾아서 적용시켜 주시면 됩니다. 여기서 주의해야 할 부분이 있는데요, 저희는 지금 2D 게임을 만들고 있기 때문에 이름 뒤에 2D가 붙어있는 2D 전용 컴포넌트를 사용해야 합니다. 그냥 Rigidbody는 3D 용으로 제작된 컴포넌트이기 때문에 이를 적용하려 하면 오류 메시지가 뜹게 됩니다.

문제없이 적용했다면 Inspector 탭에 위 사진과 같이 Rigidbody 2D 컴포넌트가 있을 겁니다. 이제 여기서 현 상황에 맞게 내용을 수정해야 합니다. 지금 구현하려는 플레이어 이동은 중력의 영향을 받으면 안 되기 때문에 Body TypeKinematic으로 변경해 주겠습니다. 이렇게 하면 오브젝트에는 더 이상 중력이 적용되지 않고, 다른 오브젝트들과도 충돌하지 않게 됩니다. 

이제 본격적으로 코딩을 시작해 볼까요? 일단 C# 스크립트를 만들어 봅시다. 프로젝트 탭 > 우클릭 > Create > C# Script을 클릭해서 스크립트를 생성한 뒤 이름을 PlayerController로 설정해 줍시다. 

스크립트도 하나의 컴포넌트입니다. 따라서 플레이어 오브젝트에 적용을 시켜줘야 하죠. 방금 생성한 PlayerController 스크립트를 Inspector 창으로 드래그해서 플레이어 오브젝트에 적용시켜 줍시다. 위 사진처럼 스크립트가 추가됐다면 정상적으로 적용이 된 것입니다. 자, 이제 스크립트를 열고 코딩을 시작해 봅시다.

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float speed;
    private Rigidbody2D rb;
    private Vector2 moveVelocity;

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
    }

    private void Update()
    {
        Vector2 moveInput = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        moveVelocity = moveInput.normalized * speed;
    }

    private void FixedUpdate()
    {
        rb.MovePosition(rb.position + moveVelocity * Time.fixedDeltaTime);
    }
}

전체 코드는 이렇습니다. 간단한 상하좌우 이동만 구현하는데도 24줄이 필요하네요. 코드들을 하나씩 살펴보도록 하겠습니다.

using UnityEngine;

제일 먼저 1번 줄에는 네임스페이스를 가져오기 위한 코드가 있습니다. 이번 스크립트에서는 유니티 엔진의 컴포넌트들을 사용하기 위한 UnityEngine 네임스페이스만 사용합니다.

public float speed;
private Rigidbody2D rb;
private Vector2 moveVelocity;

자 다음으로는 저희가 사용하게 될 변수들입니다. 속력 값을 저장하는 speed, 리지드바디 rb, 그리고 속도 값을 저장하는 moveVelocity 변수가 있습니다. 조금 후에 보게 될 거지만, 유니티에서는 public 변수를 만들면 Inspector 탭에서 변수값을 직접 수정할 수가 있습니다. speed 변수는 언제든지 속력을 조절할 수 있도록 public으로 설정해두고, 나머지 두 변수들은 클래스 외부에서 접근하는 것을 막기 위해 private로 설정하겠습니다.

private void Start()
private void Update()
private void FixedUpdate()

이 세 가지 함수들은 유니티가 기본으로 제공하는 함수들입니다. 우선 Start() 함수는 스크립트가 시작될 때 한 번 실행되는 함수입니다. 그렇기 때문에 변수를 초기화하거나 할 때 주로 사용하죠. Update()와 FixedUpdate() 함수는 지속적으로 실행되어야 하는 동작을 수행할 때 사용됩니다. 두 함수의 차이점으로는 Update() 함수는 프레임마다 호출되기 때문에 컴퓨터의 성능에 따라 더 자주 호출될 수도, 더 적게 호출될 수도 있는 반면, FixedUpdate() 함수는 설정된 값에 따라 일정한 간격을 두고 호출되기 때문에 항상 같은 횟수만큼 호출된다는 점입니다. 

rb = GetComponent<Rigidbody2D>();

GetComponent<>() 함수를 사용하면 오브젝트에 적용된 다른 컴포넌트들을 가져올 수 있습니다. 따라서 이 코드를 통해 rb 변수가 플레이어 오브젝트에 적용되어 있는 Rigidbody 2D 컴포넌트를 가리키도록 초기화해 주도록 하겠습니다.

Vector2 moveInput = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
moveVelocity = moveInput.normalized * speed;

Vector2 변수는 x, y 두 개의 값을 가지는 변수입니다. 지금 만들게 될 moveInput이라는 변수는 x 값에 방향키 좌우 값을, y 값에 방향키 상하 값을 저장할 겁니다. 유니티는 키보드 입력 값을 가져올 수 있는 기능을 제공하는데요, Input.GetAxisRaw() 함수를 통해서 미리 지정해 둔 키가 입력됐는지를 확인할 수 있습니다. 여기서 Horizontal과 Vertical은 각각 방향키 좌우, 상하 값이 지정되어 있기 때문에 방향 키를 눌렀을 때 방향에 따라 -1 또는 1의 값을 반환합니다.

방향 키 입력을 통해 방향을 가져왔다면 이제 속력을 곱해서 속도를 구해야겠죠. moveVelocity 변수에 moveInput과 speed를 곱한 값을 저장해 주도록 하겠습니다. moveInput 뒤에 있는 normalized 함수가 보이시나요? normalized 함수는 벡터 값을 정규화 해주기 위해서 사용됩니다. 이게 없다면 상하좌우로 움직일 때는 1의 속도로 움직이지만 대각선으로 움직일 때는 1.414의 속도로 움직이게 됩니다.

rb.MovePosition(rb.position + moveVelocity * Time.fixedDeltaTime);

마지막으로 살펴볼 코드입니다. Rigidbody를 다루는 코드는 항상 FixedUpdate() 함수 내에서 사용하는 것이 좋습니다. Update() 함수는 프레임이 일정하지 않기 때문에 프레임이 느리면 충돌을 감지하지 못하고 통과해버리는 일이 생길 수 있기 때문입니다. Rigidbody의 MovePosition() 함수는 오브젝트가 이동하도록 해 주는 함수입니다. 플레이어가 이동해서 갈 새로운 좌표는 현재 위치와 아까 계산한 moveVelocity를 더하면 구할 수 있겠죠. 여기서 Time.fixedDeltaTime을 곱해주게 되는데요, 현재 프레임과 다음 프레임 사이의 간격만큼을 곱해줌으로써 speed 만큼의 거리를 1초에 나눠서 갈 수 있는 겁니다. 예를 들어 1초에 10m를 움직이고 싶고 컴퓨터 프레임이 60이라면 10 * 1/60 만큼의 거리를 프레임마다 이동하는 거죠.

그림으로 보시면 이해가 더 잘 될 겁니다. speed가 10이라고 가정한다면, 왼쪽 방향 키를 누르면 moveVelocity가 (-10, 0)이 되고 오른쪽 방향 키를 누르면 moveVelocity가 (10, 0)이 됩니다. 상하도 마찬가지고요. 

이제 스크립트를 저장하고 유니티로 다시 돌아와 봅시다. 플레이어 오브젝트를 살펴보면 아까는 없었던 Speed라는 값이 새로 생겼습니다. public 변수로 만들었기 때문에 Inspector 탭에서 변수 값을 입력할 수 있는 건데요, 10의 값을 주도록 하겠습니다. 만약 너무 빠르다면 더 적은 숫자를, 느리다면 더 높은 숫자를 입력해서 속도를 바꿀 수도 있습니다.

자 이제 모두 끝났습니다! 유니티 중앙 상단에 있는 플레이 버튼을 클릭해서 플레이어가 잘 움직이는지 테스트를 해봅시다. 

플레이어가 정상적으로 잘 움직의는걸 볼 수 있습니다. 성공이네요! 


 

유니티 강좌 03. 2D 플레이어 추적 AI 만들기

모든 게임에 적이 있지는 않지만, 대부분의 게임에는 플레이어가 무찌를 수 있는 적이 등장합니다. 하지만 적들은 사람이 직접 조종하는 캐릭터가 아니다 보니 스스로 판단하고 움직이는 AI가

torotoblog.tistory.com