학회 XMC도 끝났겠다.. 드디어 미루고미루고미루고미뤘던 나만의 게임 개발을 시작해본다.사실 방학때 바로 시작할 계획이었는데 이제 개강 2주정도 남겨두고 시작한다..
게임을 개발하기 전에 대략적으로 어떤 게임을 만들지 정해야하는데.. 이거까지 정하고 시작하려면 또 미룰 것 같기 때문에 일단 시작해본다.
그렇다고 아무런 계획이 없는 건 아니고.. VR이 아닌 3D게임 제작 예정이고, 오픈월드 형식으로 주인공인 먼지라는 친구가 세계를 돌아다니면서 다양한 활동들을 할 수 있는 그런 게임을 생각 중인다. 말하자면 3D게임 속에서 여러 상호작용들을 구현하고 공부해보면서 한 마디로 짬뽕을 만들겠다는 소리.
그래도 하고싶은 상호작용 구현하다보면 공부는 많이 될 것 같다.
A. 캐릭터 움직임 (WASD)
오늘은 우리 게임의 주인공의 움직임을 구현해 볼 예정. VR만 했더니 이것마저 기억이 가물하니 여러 자료들 참고해서 만들겠다.. (참고자료들은 항상 글 아래에)

먼저 플레이어의 역할을 할 캡슐에 카메라를 달아준다. 먼저 1인칭 시점의 움직임을 구현할 것이기 때문에 캡슐의 위에 카메라를 달아주었다.

그리고, 캐릭터 컨트롤러 컴포넌트를 붙여준다. 캐릭터 컨트롤러 컴포넌트는 자체로 콜라이더를 가지므로 캡슐 콜라이더는 삭제해줘도 된다. 먼저, 이 캡슐 주인공이 WASD 키로 움직이게하기 위한 스크립트를 작서한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 12f;
public float gravity = -9.81f;
private CharacterController characterController;
private Vector3 velocity;
void Start()
{
characterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
characterController.Move(move * speed * Time.deltaTime);
velocity.y += gravity * Time.deltaTime;
characterController.Move(velocity * Time.deltaTime);
}
}
1번 레퍼런스의 영상을 보고 따라 작성해본 것이고, 구현하면서 공부하게된 것을 적고 설명할 예정이다.
public float speed = 12f;
public float gravity = -9.81f;
private CharacterController characterController;
private Vector3 velocity;
speed는 플레이어의 속도,
gravity는 플레이어에 작용하는 중력,
characterController는 플레이어에 부착된 캐릭터 컨트롤러 컴포넌트,
velocity는 플레이어의 y방향 속도를 뜻한다.
void Start()
{
characterController = GetComponent<CharacterController>();
}
player에 부착한 캐릭터 컨트롤러를 GetComponent를 통해 가져온다.
void Update()
{
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Vector3 move = transform.right * x + transform.forward * z;
characterController.Move(move * speed * Time.deltaTime);
velocity.y += gravity * Time.deltaTime;
characterController.Move(velocity * Time.deltaTime);
}
Unity에서는 W와 S키로 Verticle한움직임, A와 D키로 Horizontal한 움직임이 가능하도록 이미 매핑이 되어있다.
따라서 float x와 z는 Input.GetAxis로 가져올 수 있다. 즉, W키를 누르면 z는 +1 값을, S키를 누르면 z는 -1 값을 갖게 될것이다.
trasnform.right은 world space에서 플레이어의 transform의 x axis이다. 이 값은 플레이어의 회전에 따라 값이 바뀐다.
transform.forward도 z axis인 것만 빼고 같다.
따라서 각각의 값에 x와 z를 곱하여 합쳐주면 move 벡터가 나온다.
캐릭터 컨트롤러에는 Move라는 함수가 있는데 이 함수 안에 move 벡터, 속도, 그리고 Time.deltaTime을 곱해서 넣어주면 캐릭터는 WASD 키에 따라 움직인다. 여기서 Time.deltaTime을 곱해주는 이유는 framerate에 따라 속도가 영향을 받지않게하기 위해서이다.
자 지금까지, 플레이어의 x, z방향의 움직임을 구현했으니 y방향 움직임만 남았다.
캐릭터 컨트롤러는 rigidbody를 사용해서 중력을 부여할 수 없다. 직접 y방향의 속도를 만들어서 캐릭터 컨트롤러에 적용해야한다.
-9.81f로 정한 gravity값에 Time.deltaTime을 곱하여 velocity의 y 컴포넌트 값으로 설정한다. 그리고 해당 velocity에 Time.deltaTime을 다시한번 곱하여 캐릭터 컨트롤러의 Move함수에 적용해준다.
최종적으로 gravity에 Time.deltaTime을 두번 곱해주는 이유는 물리적 법칙때문이다.
(delta y = -1/2 * gravity * time^2) 그만 알아보도록 하자
B. Ground Check
하지만 여기서 문제는 velocity를 Update에서 값을 설정해주기 때문에 게임 플레이동안 y방향 velocity는 무한대로 작아질 것이다(velocity.y는 음의 값). 따라서, 플레이어가 땅 위에 있을 때는 해당 값을 초기화해주면 된다.

empty object를 만들어서 플레이어의 아래에 배치한다. 해당 부분에 CheckSphere를 통해 플레이어가 바닥에 있는지 없는지 확인할 수 있다. Ground Check가 추가된 스크립트는 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float speed = 12f;
public float gravity = -9.81f;
public Transform groundCheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
private CharacterController characterController;
private Vector3 velocity;
private bool isGrounded;
void Start()
{
characterController = GetComponent<CharacterController>();
}
// Update is called once per frame
void Update()
{
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
float x = Input.GetAxis("Horizontal");
float z = Input.GetAxis("Vertical");
Debug.Log(transform.right);
Vector3 move = transform.right * x + transform.forward * z;
characterController.Move(move * speed * Time.deltaTime);
velocity.y += gravity * Time.deltaTime;
characterController.Move(velocity * Time.deltaTime);
}
}
수정된 부분을 설명해보겠다.
public Transform groundCheck;
public float groundDistance = 0.4f;
public LayerMask groundMask;
private bool isGrounded;
ground check는 아까 위에서 배치한 empty object의 transform이고,
groundDistance는 CheckSphere를 할때 sphere의 radius이고,
groundMask는 ground라고 정할 layer를 뜻한다.
마지막으로 isGrounded는 플레이어가 땅 위에 있는지 없는지 체크하는 bool이다.
isGrounded = Physics.CheckSphere(groundCheck.position, groundDistance, groundMask);
if (isGrounded && velocity.y < 0)
{
velocity.y = -2f;
}
Update 함수안에 해당 부분이 추가되었다. Physics,CheckSphere를 사용한다.
groundCheck.position 위치에, groundDistace만큼의 radius를 가진 sphere가, groundMask와 충돌하는지 확인하여, 충돌하면 isGrounded = true, 아니면 isGrounded = false이다.
따라서, isGrounded이고 velocity,y가 0보다 작으면 velocity.y를 초기화해준다.

C. Result Video
이렇게 완성!
다음 글에서는 마우스로 카메라 및 플레이어의 회전을 컨트롤할 수 있도록 해보겠다.
Reference
1. https://www.youtube.com/watch?v=_QajrabyTJc&t=1155s
2. https://docs.unity3d.com/ScriptReference/Transform-forward.html
3. https://madelinephysics.weebly.com/projectile-motion-lab.html