길이와 방향을 가지는 값
정해진 갯수의 원소를 가지는 모든 값을 벡터로 표현할 수 있다.
ex) Vector2 : (x, y). Vector3 : (x, y, z)
유니티에서 벡터는 한 객체의 좌표와 이동을 나타낼 때 사용한다.
y(상대의 위치) - x(나의 위치) = z(이동 거리 / 간격)
ex) 상대의 위치 (10, 1, 5) 이고 나의 위치가 (5, 5, 5) 일 때 상대와 나의 간격은 (5, -4, 0)
몇차원의 공식인지, 시작 위치가 어디인지, 목표 지점이 어디인지는 관여하지 않는다.
'얼만큼의 거리를 어디로' 가 핵심
Vector3 mover = new Vector3(-5, -5, 5);
transform.position = transform.position += mover;
결과
현재 위치보다 mover(-5, -5, 5) 만큼 이동한다 ex) (1, 1, 1) => (-4, -4, 6)
Vector를 통해 정하는 값은 목표 위치가 아닌 이동하는 상대적인 거리를 나타낸다.
Trasnform?
유니티에서 제공되는 기능으로 해당 객체의 속성을 나타낸다.
별도의 변수 선언 없이 사용이 가능하며,
transform 앞에 별도의 지정이 없으면 자기 자신을 나타낸다.
ex)
transform.position; //자기 자신의 위치 설정
target.transform.position; //target 이라는 이름의 객체의 position
코드 실행
객체의 Transform 값을 바꿀 때 항상 유의해야 하는 점으로, Space의 기준 상태를 봐야 한다.
자기 자신/부모를 기준으로 속성 변화
1. 회전
객체가 회전한 상태라면 회전한 상태의 좌표계에 따라서 이동한다.
2. 부모 - 자식 관계
Cube의 위치는 (2, 2, 2) , Sphere의 위치는 (5, 5, 5)이다.
하지만 이 Sphere는 Cube의 자식개체이기 때문에 좌표값을 Cube를 기준으로 보여준다.
별도의 좌표값 조작 없이, 자식 개체에서 고유 객체로 변경만 했는데 좌표값이 변하였다.
=> Local Space로 나타나는 좌표값은 Global Space와는 다르게 나타난다는 것이다.
실제 레벨의 좌표값으로는 (7, 7, 7)에 위치해있지만, 부모 객체였던 Cube를 기준으로 (5, 5, 5)만큼 떨어져 있다는 것이다.
게임 세상(Scene/Level)의 절대 좌표에 따라 속성 변화
객체가 회전을 하던 안하던, 좌표를 나타내는 좌표계는 변하지 않는다.
=> 객체의 현재 상태가 어떠한지 관여하지 않고, 절대적인 좌표 기준에 따라 이동한다.
Q. Global Space와 Local Space는 엔진 설정으로만 가능한가?
한 레벨의 두 객체가 서로 다른 상태에 있을 때 설정은 어떻게 해야하나?
A. 추가 코드 작성을 통해 상태 설정 가능하다.
=> Translate(mover, Space.World) 에서 Space.World 가 그 지정 방식 코드
서로 다른 두 객체를 월드에 배치하고 같은 수치만큼 회전시키지만,
한 객체는 Global Space 기준으로 이동, 다른 객체는 Local Space 를 기준으로 이동시켜라.
힌트)
1. Vector3 mover = new Vector3(5, 5, 5);
2. 객체마다 스크립트를 별도로 작성할 것
//Local Space
Vector3 mover = new Vector3(5, 5, 5);
void Start()
{
transform.position = transform.position + mover;
//혹은
transfor.Translate(mover);
}
//Global Space
Vector3 mover = new Vector3(5, 5, 5);
void Start()
{
transform.Translate(mover,Space.World);
}
같은 Cube를 두개 만들어서 하나는 Global 좌표로, 다른 하나는 Local 좌표로 이동시킨다.
GlobalCube의 좌표는 (2, 0, 0), LocalCube의 좌표는 (0, 0, 0) 으로 설정하였다.
실행 결과
GlobalCube는 (7, 5, 5) 로 mover로 추가한 만큼 정확하게 이동하였고,
LocalCube는 mover로 추가한 것보다 다른 수치로 이동하였다.,
=> 객체의 현재 상태에 따라 좌표계가 어떻게 변하는지 명확하게 판단 가능하다.
Local Sube의 Y값이 5로 정확하게 나온 이유는 LocalCube의 회전이 Y축을 기준으로 45만 이동하였기 때문에, X와 Z에만 영향을 미친 것이다.
객체를 회전시키려면 어떻게 해야할까?
단순하게 생각했을 때, 이동과 비슷한 방식으로 작동할 수 있을 것 같다.
transform.rotation = new Vector3(30, 30, 30);
하지만 이 코드는 에러가 발생한다. 왜일까?
회전은 Vector 값이 아닌 Quaternion 값을 사용한다.
근데 왜 쿼터니언(Quaternion) 을 사용해야 하나?
회전 시, 한 축이 다른 축과 겹칠 때 서로의 구분이 되지 않아 한 축의 정보가 사라지는 현상을 '짐벌락 현상'이라고 부른다.
https://www.youtube.com/watch?v=zc8b2Jo7mno
그럼 해결책으로 어떻게 해야 하나?
자세한 풀이를 하기엔 난해하고, 우리가 이해할 정도로 설명하자면,
쿼터니언 방식은 (x, y, z, w) 총 4개의 인자를 사용하여 짐벌락 현상을 해결하였고, 이로 인해 한 축의 정보소실에 대한 걱정 없이 회전을 시킬 수 있다
라고만 보면 된다.
코드 예시
Quaternion newRotation = Quaternion.Euler(new Vector3(30, 30, 30));
void Start()
{
transform.rotation = newRotation;
}
1. LookAt()
방향(벡터값)을 할당 시, 해당 방향을 바라보게 할 수 있다. (Z값이 회전하는 방식)
다른 물체를 바라보게 하려면 우선
1. 바라보려는 물체를 구하고
2. 자신(회전시키려는 물체)를 해당 물체를 향해 회전
해야 한다.
코드
public Transform target;
Vector3 direction;
void Start()
{
direction = target.position - transform.position;
transform.LookAt(direction);
}
엔진 설정
public으로 선언한 'target' 이라는 이름의 변수 Transform 에 우리가 바라보게 하려는 물체, LocalCube 를 할당해주었다.
결과
GlobalCube의 Z축잋 LocalCube 방향으로 회전한 모습을 볼 수 있다.
2. Lerp()
초기값과 마지막값 사이의 중간값을 찾아주는 기능 이라고 이해할 수 있다.
예시코드
Quaternion aRotation = Quaternion.Euler(new Vector3(30, 30, 30));
Quaternion bRotation = Quaternion.Euler(new Vector3(60, 60, 60));
void Start()
{
Quaternion targetRotation = Quaternion.Lerp(aRotation, bRotation, 0.5f);
transform.rotation = targetRotation;
}
코드 작성 방식
Quaternion.Lerp(초기값, 마지막값, 찾으려는 값의 비율)
ex) 0.5f면 aRotation 과 bRotation의 50%의 값
0.8f면 aRotation 과 bRotation 의 80%의 값
+ tip! Lerp의 다른 사용 방식
같은 Lerp() 함수이더라도 앞에 어떤 속성을 사용하느냐에 따라 함수의 사용 용도가 달라질 수 있다.
ex) 선형보간 이동
transform.position = Vector3.Lerp(transfom.position, target, speed);
주로 부드럽고 자연스러운 이동을 구현하기 위해서 사용한다.
이처럼 같은 함수이더라도 사용 방식이 다를 수 있으니 용도를 잘 알고 사용에 유의해야 한다.
3. 회전한 상태에서 추가 회전
- 현재 회전값을 구해서 그에 추가적 회전값 더하기
코드
// 현재 회전값
Quaternion origin = transform.rotation;
// 현재 회전값을 벡터화
Vector3 originVector = origin.eulerAngles;
// 최종 회전값 = 현재 회전값 + 더하려는 회전값
Vector3 newVector = originVector + new Vector3(20, 20, 20);
// 최종 회전값을 다시 벡터화
Quaternion targetRotation = Quaternion.Euler(newVector);
// 벡터화된 최종 회전값을 적용
transform.rotation = targetRotation;
쿼터니언으로 구한 회전값은 서로 더할 수 없는 값이다.
이 값은 eulerAngles 를 통해 벡터화 하여 더하고, 그 값을 다시 쿼터니언화 한다.
- 쿼터니언을 사용해서 추가 회전하기
코드
Quaternion originRotation = Quaternion.Euler(new Vector3(30, 30, 30));
Quaternion plusRotation = Quaternion.Euler(new Vector3(45, 45, 45));
// 곱하기!
Quaternion target = originRotation * plusRotation;
transform.rotation = target;
Vector를 사용하지 않고 쿼터니언에서 자체적으로 값을 구해 곱하여, 최종 회전값을 만들어냈다.
여기서 명심해야 할 것은, 회전에 추가회전을 더하려면 쿼터니언값을 서로 곱해야 한다는 것이다.
- Rotate()
transform.Rotate(new Vector3(30, 30, 30));
Rotate() 함수는 자체적으로 현재 회전값에 인자로 들어오는 값을 더해주는 함수이다.
현재 회전값을 구하지 않더라도 추가 회전이 가능하다.
transform(30, 45, 60) 설정 후 transform.Rotate(30, 0, 0) 을 했을 때 최종 회전값이 (60, 45, 60) 일 것이라고 생각했지만 다른 결과가 나온다
A. Local Space 와 Global Space의 문제!
우리가 기대한 (60, 45, 60) 이란 값은 Global Space 기준으로 생각한 것이다.
Local Space에서는 자기 자신을 기준으로 회전하기 때문에 수치상으로는 예상과 다를 수 있다.
(30, 45, 60) 인 상태에 Rotate(30, 0, 0) 을 더한 상태이다.
객체 자체는 X축 기준으로 30도 회전을 한 것이 맞지만,
자식 객체가 아닌 고유 객체이기 때문에 inspector 창에서 나오는 Transform 의 수치가 Global Space 기준으로 나타나게 되는 것이다.
#6. 오버로드(Overload) (0) | 2024.07.28 |
---|---|
#5. 인스턴스(Instance) (0) | 2024.07.24 |
#3. 클래스와 오브젝트 (0) | 2024.07.18 |
#2. 변수와 함수 (0) | 2024.07.18 |
#1. 유니티 엔진의 이해 (0) | 2024.07.17 |