● 개발목적
유니티 3D를 통해 키보드의 방향키와 WASD 버튼을 통해 메인 카메라를 따라 이동하는 플레이어 (Character Move) 를 만든다. 동시에, 마우스를 움직이는 방향에 따라 화면 시점이 이동되도록 제작한다.
● 참고
사실 유니티 에셋 스토어 (Asset Store) 에서 제공하는 FPSController 에셋과 FirstPersonController 에셋을 이용하는게 빠르고 정확하다. 심지어 무료이고, 에셋을 프로젝트에 그대로 적용시키면 되기 때문에 빠르고 편리하다. 그럼에도 불구하고, 유니티로 제작하는 캐릭터 컨트롤러 구조를 이해하기 위해 학습해 보았다.
● 프로젝트 생성
유니티 허브에 접속해 3D 프로젝트를 하나 생성한다. 유니티 프로젝트 생성 시, 경로에 한글이 들어가 있으면 오류가 발생하는 경우가 잦으니 C드라이브 최상단이나 D드라이브 최상단에 폴더를 하나 만들어서 사용하는 걸 추천한다.
본문에서 사용한 에디터 버전은 2020.3.32f1 LTS 버전이고, 하단에 첨부할 프로젝트 첨부파일 또한 해당 버전이니 참고 바란다.
● 실습을 위한 기본 준비
Hierarchy → + → 3D Object → Plane 를 눌러 간단하게 바닥 역할을 해 줄 면을 하나 생성한다.
Plane의 Inspector → Transform의 속성값을 아래와 같이 지정한다.
Postion : X : 0 | Y : 0 | Z : 0
Rotation : X : 0 | Y : 0 | Z : 0
Scale : X : 2 | Y : 2 | Z : 2
Hierarchy → + → Create Empty 객체를 하나 생성하고, Inspector 속성값과 Transform 속성값을 아래와 같이 지정한다.
Tag : Player
Postion : X : 0 | Y : 0 | Z : 0
Rotation : X : 0 | Y : 0 | Z : 0
Scale : X : 1 | Y : 1 | Z : 1
'Add Component' 버튼을 클릭하고, 'Character Controller' 컴포넌트를 추가한다.
Hierarchy의 Main Camera객체를 Player객체로 드래그 앤 드롭 해, 부모객체 Player의 자식객체 Main Camera가 되도록 한다.
※ Main Camera의 Position도 아래와 같이 설정한다.
Postion : X : 0 | Y : 0 | Z : 0
Project의 Assets폴더 하위로 Create → Folder을 클릭해 'Materials' 폴더와 'Scripts' 폴더를 생성한다.
온통 하얀색 화면이면 플레이어가 정상적으로 이동하는지 직관적으로 확인하기가 어렵다. 따라서, 적절한 색상을 입혀 직관적으로 확인하기 위한 과정이니, Material 요소 생성 과정은 불필요할 경우 생략해도 좋다. 필자는 직관적으로 확인하기 위해 간단한 색상 Red, Green, Blue, Black 네 개를 추가했고, Material 요소를 통한 객체에 색상을 입히는 방법은 아래 링크를 참고하면 좋겠다.
필자는 움직임을 효과적으로 확인하기 위해 위와 같이 화면을 구성했다.
Scripts 폴더에 우측 마우스를 클릭하고, Create → C# Script 를 두개 생성하고, 스크립트 이름은 아래와 같이 지정한다.
- CharacterMove
- MouseMove
● 키보드 입력을 받아, 움직이는 플레이어 스크립트 작성 (CharacterMove.cs)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CharacterMove : MonoBehaviour
{
public Transform cameraTransform;
// Transform값은 카메라 움직임에 따라 달라지므로,해당 값을 카메라에 넘겨주기 위한
// CameraTransform 변수 선언
public CharacterController characterController;
// CharacterController에 3D 오브젝트를 적용하기 위한 characterController 변수 선언
public float moveSpeed = 10f;
// 이동 속도
public float jumpSpeed = 10f;
// 점프 속도
public float gravity = -20f;
// 중력
public float yVelocity = 0;
// Y축 움직임
void Start()
{
}
void Update()
{
float h = Input.GetAxis("Horizontal");
// h 변수에 키보드의 가로값 (좌, 우) 을 읽어온 결과를 넘긴다.
// ◀, ▶, A, D 키
float v = Input.GetAxis("Vertical");
// v 변수에 키보드의 세로값 (상, 하) 을 읽어온 결과를 넘긴다.
// ▲, ▼, W, S 키
Vector3 moveDirection = new Vector3(h, 0, v);
// (x축, y축, z축 = h 변수, 0, v 변수) 에서 읽어온 값을 Vector3으로 만듦
// 해당 값을 Vector3 형식의 moveDirection 값으로 넘긴다.
moveDirection = cameraTransform.TransformDirection(moveDirection);
// moveDirection 값은 카메라 위치
moveDirection *= moveSpeed;
// 최종적인 moveDirection 값은 moveDirection * moveSpeed 값을 서로 곱한 것.
if (characterController.isGrounded)
// 만약, characterController가 땅에 붙어있다면
{
yVelocity = 0;
// y축 움직임 값은 0이고,
if (Input.GetKeyDown(KeyCode.Space))
// 스페이스 바 키를 통해 점프를 실시하고,
{
yVelocity = jumpSpeed;
// 사용자가 설정한 jumpSpeed 값을 yVelocity 값으로 넘겨서 처리한다.
}
}
yVelocity += (gravity * Time.deltaTime);
// yVelocity 값은 yVelocity + (중력값 * Time.deltaTime)
moveDirection.y = yVelocity;
// 계산한 yVelocity 값을 moveDirection.y (Y축 움직임 방향) 로 넘겨준다.
characterController.Move(moveDirection * Time.deltaTime);
// 최종적으로 characterController의 움직임은 방향 * Time.deltaTime 값
}
}
코드 이해가 안 되는데요?
명령어마다 주석처리로 상세히 설명을 달아두었고, 사용한 주요 함수들 또는 명령어들은 아래를 참고하면 된다.
▣ void Start() 함수
프로그램이 실행될 때 한 번 호출되는 함수이다.
말 그대로 시작 함수.
▣ void Update() 함수
프로그램이 실행되고, 계속 반복되며 실행되는 함수이다.
▣ public 전역변수를 선언하는 이유
해당 코드 전역에서 사용이 가능하고, 유니티 3D Inspector창에서 손쉽게 값 변경이 가능하다. 아주 유용하다.
▣ Input.GetAxis
특정 축의 값을 받아오는 명령어.
▣ Vector3
유니티 3D는 말 그대로 3D 입체 환경이기 때문에 X축, Y축, Z축을 모두 사용한다.
각 3개의 축들의 위치 정보들을 3개의 float들로 저장하고 있는 구조체라고 볼 수 있다.
▣ Input.GetKeyDown(KeyCode.Key)
키 입력을 받아들이는 명령어이다.
▣ Time.deltaTime
코드를 적용시키면 해당 코드는 Update() 함수 ↔ Update() 함수 ↔ ... ↔ Update() 함수가 반복되며 실행된다.
Update 함수가 종료되고 다시 실행되는 업데이트 사이의 시간이 Time.deltaTime 함수이다.
● 마우스 이동을 인식해, 화면이 따라 움직이는 스크립트 작성 (MouseMove.cs)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MouseMove : MonoBehaviour
{
public float sesitivity = 500f;
// 마우스 민감도
public float rotationX;
// X축의 위치
public float rotationY;
// Y축의 위치
void Start()
{
}
void Update()
{
float mouseMoveX = Input.GetAxis("Mouse X");
// 마우스 X축 움직임값을 받아서 mouseMoveX 변수에 저장
float mouseMoveY = Input.GetAxis("Mouse Y");
// 마우스 Y축 움직임값을 받아서 mouseMoveY 변수에 저장
rotationY += mouseMoveX * sesitivity * Time.deltaTime;
// rotationY 변수의 값은 rotationY + (mouseMoveX * 마우스 민감도 * Time.deltaTime)
rotationX += mouseMoveY * sesitivity * Time.deltaTime;
// rotationX 변수의 값은 rotationX + (mouseMoveY * 마우스 민감도 * Time.deltaTime)
/*
* 아래의 if문은 마우스로 이동하는 시야가 사람의 시야처럼 보이기 하기 위함
*/
if (rotationX > 35f)
{
rotationX = 35f;
// 위로 35도 이상 넘어가지 못하게 (고개가 과하게 젖혀지지 않게)
}
if(rotationX < -30f)
{
rotationX = -30f;
// 아래로 30도 이상 넘어가지 못하게 (고개가 과하게 꼬꾸라지지 않게)
}
transform.eulerAngles = new Vector3(-rotationX, rotationY, 0);
// rotationX값, rotationY값, Z는 0값을 Vector3 형식으로 변환하고,
// transform 오일러각에 저장함.
}
}
다른 명령어들이 많이 보이는데요?
CharacterMove.cs 스크립트를 작성했을 때 처럼, 명령어마다 주석처리로 상세히 설명을 달아두었다. CharacterMove.cs 스크립트에서 사용하지 않았던, 새로운 함수 또는 명령어는 아래를 참고하면 된다.
▣ transform.eulerAngles
X축, Y축, Z축, 총 3개의 축을 기준으로 0도 ~ 360도 회전하는 Euler 좌표각을 사용하기 위한 함수이다.
● 스크립트 적용
CharacterMove.cs 스크립트와 MouseMove.cs 스크립트 작성이 완료되면, 'Ctrl + S' 버튼을 눌러 저장하고 유니티 3D로 돌아온다.
작성 완료된 C# 스크립트 두 개를 Player 객체에 드래그 앤 드롭해서 적용 시킨다.
스크립트가 적용이 되면, 아까 스크립트 상에서 선언했었던 전역변수값들을 자유로이 수정할 수 있고, 지정할 수 있다.
드래그 앤 드롭을 통해, 아래와 같이 속성값을 적용한다.
Camera Transform : Main Camera
Character Controller : Player
● 프로젝트 파일 다운로드
어디까지나 참고용으로 배포한다. 필자와 에디터 버전이 맞지 않으면 변환 과정에서 오류가 나거나, 프로젝트가 일부 깨지는 현상이 발생할 수 있으니 주의 바란다.
● 결과
키보드의 ↑, ←, ↓,→ 버튼과 W, A, S, D 버튼으로 캐릭터 위치가 이동되고, 마우스가 움직이는 위치에 맞게 카메라 시점이 움직이는 모습을 확인할 수 있었다.
※ 땅 (바닥) 역할을 하는 Plane을 크게 제작하지 않았기 때문에, Plane 영역 밖으로 나가면 멀리 떨어지게 된다. 다시 말해, Plane이 Player의 중력 효과를 받아주고 있었지만, Plane영역 밖으로 나가면 Player을 지탱해 줄 땅이 없기 때문이다.