유니티의 모든 GameObject는 기본적으로 Transform 컴포넌트를 갖고 있다.
보면 Position, Rotation, Scale을 갖고있는데 모두 Vector3 클래스를 갖고있다.
(정확히는 Rotation의 경우 Querternion을 갖고있다.)
https://docs.unity3d.com/kr/530/ScriptReference/Vector3.html
Vector3 클래스에 대한 Unity의 공식문서이다.
기본적으로 Vector3는 x, y, z에 대한 float 값을 갖고있다.
그리고 World 좌표계 기준으로 기본적으로 선언된 정적변수가 있다.
정적변수 | Vector (x,y,z) |
---|---|
back | (0,0,-1) |
forward | (0,0,1) |
left | (-1,0,0) |
right | (1,0,0 |
up | (0,1,0) |
down | (0,-1,0) |
one | (1,1,1) |
zero | (0,0,0 |
유니티의 좌표계는 왼손 좌표계이다.
위로는 y축이, 화면에서 모니터를 보는사람 쪽으로가 z축, 나머지 왼손좌표계로 나오는 곳이 x축이다.
Vector3 클래스에 있는 것들은 대부분 많이 사용되기 때문에 그때그때 공식문서를 확인해서 사용하는 것이 좋은 것 같다.
Transform 예제를 만들기 이전에 Unity의 Asset Store에서 Model 데이터를 갖고오겠다.
사용할 데이터는 Unity-Chan! Model 이다.
Art-Models에 unitychan을 선택 후 하이러키 창에 올리면 된다.
그럼 다음과 같이 Unity 뷰에 뜰 것이다.
이제 다음과 같이 Player로 GameObject의 이름을 변경하고, Player Script를 만들어 붙이겠다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
const float mSpeed = 10;
void Start(){ }
void Update()
{
if (Input.GetKey(KeyCode.W))
{
transform.position += Vector3.forward * Time.deltaTime * mSpeed;
}
if (Input.GetKey(KeyCode.S))
{
transform.position += Vector3.back * Time.deltaTime * mSpeed;
}
if (Input.GetKey(KeyCode.A))
{
transform.position += Vector3.left * Time.deltaTime * mSpeed;
}
if (Input.GetKey(KeyCode.D))
{
transform.position += Vector3.right * Time.deltaTime * mSpeed;
}
}
}
Input.GetKey 라는 정적 메서드를 통해서 해당 키가 눌렸는지 알 수 잇다.
position 또한 Vector3이기 때문에 Ctrl+P로 실행하면 문제없이 움직인다.
(Time.deltaTime은 한 틱에 걸린 시간이 반환된다. second 단위이다.)
이제 Player를 Rotation으로 y축 기준으로 45도로 설정해보겠다.
실행시켜보면 45도로 회전했지만 Player 기준으로 앞뒤로 움직이는 것이 아니라서 이상하게 보인다.
이는 Position 더한 것이 모델 좌표계 기준으로 더한 것이 아니라, 월드 좌표계 기준으로 더한 것이기 때문이다.
이를 해결하기 위해서는 크게 2가지 방법이 있다.
1. 월드 좌표계 기준에서 모델 좌표계 기준으로 Vector를 변경한다.
2. Unity에서 제공하는 메서드를 사용한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
const float mSpeed = 10;
void Start(){ }
void Update()
{
if (Input.GetKey(KeyCode.W))
{
transform.position += transform.TransformDirection(Vector3.forward * Time.deltaTime * mSpeed);
}
if (Input.GetKey(KeyCode.S))
{
transform.Translate(Vector3.back * Time.deltaTime * mSpeed);
}
if (Input.GetKey(KeyCode.A))
{
transform.position += transform.TransformDirection(Vector3.left * Time.deltaTime * mSpeed);
}
if (Input.GetKey(KeyCode.D))
{
transform.Translate(Vector3.right * Time.deltaTime * mSpeed);
}
}
}
W와 A가 월드 좌표계를 Player의 transform에 맞춰서 모델 좌표계를 월드좌표계로 변경해주고, S와 D가 내부적으로 월드 좌표계를 모델 좌표계로 변경해준다.
이를 통해서 바라보고 있는 방향으로 앞뒤로 움직일 수 있다.
회전같은 경우는 보다시피 Quaternion이라는 복소수 회전법을 이용해가지고 회전시킨다. 이는 오일러 방식대로 x축 회전시키고, y축 회전시키고, z축 회전 시킬 경우, x축 90도, y축 90도 시 축이 겹치면서 z축 회전이 제대로 되지않는 짐벌락 현상이 발생한다. 이를 해결하기 위해서 Quaternion 방식을 사용한다.
하지만 함수 사용할 때는 오일러 방식처럼 x,y,z 기준으로 값을 넘겨주면 자동으로 쿼터니언 방식으로 유니티에서 계산을 해준다.
public class Player : MonoBehaviour
{
private float mSpeed = 10;
void Start(){ }
void Update()
{
if (Input.GetKey(KeyCode.W))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.forward), 0.2f);
transform.position += Vector3.forward * Time.deltaTime * mSpeed;
}
if (Input.GetKey(KeyCode.S))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.back), 0.2f);
transform.position += Vector3.back * Time.deltaTime * mSpeed;
}
if (Input.GetKey(KeyCode.A))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.left), 0.2f);
transform.position += Vector3.left * Time.deltaTime * mSpeed;
}
if (Input.GetKey(KeyCode.D))
{
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), 0.2f);
transform.position += Vector3.right * Time.deltaTime * mSpeed;
}
}
}
이제 움직이는 것에 회전도 추가했다.
Slerp는 구형 보간을 얘기한다. 조금 더 회전을 부드럽게 하기 위해서 사용한다.
마지막 인자에 따라서 해당 보간된 Quaternion을 반환하고 이를 transform에 넣는다.
그리고 이동같은 경우에도, 회전을 해당 방향을 보게끔 했기 때문에 world 좌표계 기준으로 이동해도 문제가 없어졌다.