Unity - 9. 트랜스폼

땡구의 개발일지·2025년 4월 16일

Unity마스터

목록 보기
7/78
post-thumbnail

위치와 회전, 스케일을 관리하는 컴포넌트 Transform. 어떻게 캐릭터를 움직일지 알아보자
유니티 Transform 메뉴얼
유니티 Transform 스크립트예시

Transform

  • 모든 게임오브젝트는 트랜스폼 컴포넌트를 기본적으로 하나씩 가지고 있다. 이는 다른 컴포넌트 들과는 다르게 지울 수 없다. 중복으로 추가 또한 안된다

  • 트랜스폼은 게임오브젝트의 위치, 회전, 크기를 저장하는 컴포넌트
  • 게임오브젝트의 부모-자식 상태를 저장하는 컴포넌트
  • 유니티는 연산을 4x4 행렬로 구현한다. 대학교에서 배웠었던 사원수 즉, 헤밀턴수가 여기서 나온다.
  • 이동, 스케일은 큰 문제가 없는데 회전에서 약간 문제가 있다.
  • 직관적으로 우리는 오일러 각도로 사용하는데, 만약 이를 이용해 회전을 구현하면 짐벌락 현상이 생긴다. 뒤에서 알아보자
  • Transform의 프로퍼티씬 창(직접 잡아서 이동 등), 인스펙터 창(직접 값 입력), C# 스크립트(값 지정 또는 수정)에서 바꿀 수 있다.

수학적 원리

  • transform은 행렬을 연산해서 구현한다

  • 이동

  • 회전

  • x 축 회전
  • y 축 회전
  • z 축 회전
  • 스케일

    • 스케일은 간단해서 생략한다

Position

  • 게임 오브젝트의 좌표는 x,y,z 3축으로 이루어져 있다. Vector3로 간단하게 표현 가능하다

    포지션을 직접적으로 변경하기

  • Vector3

    • Vector3에는 방향 단위 키워드들이 있다. forward,back,up,
    Transform.position = new Vector3(x, y, z);
    // position을 이용한 이동
    transform.position += new Vector3(0, 0, moveSpeed * Time.deltaTime);
     // 직접적으로 좌표 값인 x, y, z를 이용한 이동
    transform.Translate(0, 0, moveSpeed * Time.deltaTime);
  • Translate

    • 동사 뜻 그대로 옮기다
    Transform.Translate(Direction * Speed);
    // 벡터를 이용한 이동
    transform.Translate(Vector3.forward * moveSpeed * Time.deltaTime);
  • Lerp

    • Linear Interpolation
    • 선형 보간법을 이용한 이동 방법
    • 제논의 역설을 떠올리면 이해가 쉽다
      Transform.position = Vector3.Lerp(StartPosition, EndPosition, Interpolation)
    • 시작점, 목표점, 보간수치(Interpolation) 이다. 보간치는 현재 위치에서 목적지 까지의 거리가 1일때, 0.5면 중간을 반환한다. 즉, 한 프레임마다 거리가 절반씩 줄어드는 식으로 이동한다.
    • 보간수치에 deltaTime을 곱할 경우, 1초당 해당 값이 적용된다
    • 원리 상으로는 영원히 목적지에 도달하지 못한다 (극한)
  • MoveTowads

    • 목적지를 정해 해당 방향으로 이동시키는 방법
    Transform.position = Vector3.MoveTowards(transform.position, Target.Position, Speed);

이동 기준

  • 월드 기준

    • 절대좌표
    transform.Translate(0, 0, 1, Space.World);
    
  • 로컬 기준

    • 본인(부모) 기준 좌표
    transform.Translate(0, 0, 1, Space.Self);
    
  • 상대적 기준 (다른 대상)

    transform.Translate(0, 0, 1, otherTransform);

DeltaTime

  • 프레임을 그리는 시간 단위다. 예를들어 한 프레임 이후 다음 프레임을 그려내는데 0.02초 걸렸다면, 해당 프레임의 DeltaTime0.02가 된다
  • 60프레임이면 DeltaTime은 한 프레임당 1/60초가 된다. 30프레임이면 1/30초
  • 유니티 엔진의 업데이트는 한 프레임마다 수행된다

예시) 물체의 이동

transform.Translate(Vector3.forward * 0.1f);
  • 이는 60프레임이면 60번, 30프레임이면 30번 수행된다. 같은 1초 기준으로 60프레임이면 6만큼, 30프레임이면 3만큼 움직이게 되는거다. 이 문제를 해결하기 위해 DeltaTime을 곱해줘야한다

DeltaTime 사용

transform.Translate(Vector3.forward * 0.1f * Time.deltaTime);
  • 이 경우 60프레임이면 DeltaTime은 1/60초, 1초동안 움직이는 거리는 0.1×(1/60s)600.1\times(1/60s)*60
  • 30프레임이면 DeltaTime은 1/30초, 1초동안 움직이는 거리는 0.1×(1/30s)300.1\times(1/30s)*30이다
  • DeltaTime을 쓰면 몇 프레임이든 같은 값으로 출력 할 수 있다

DeltaTime의 특징

  • 오차범위는 float만큼의 오차가 있음
  • DeltaTime의 값이 갱신되는 방법은 유니티의 라이프 사이클에서 알아볼 수 있음. Update 직전에 갱신됨

DeltaTime 예시

public class TankControll : MonoBehaviour
{
    [SerializeField] GameObject go;
    [Range(0,5)]
    [SerializeField] float movePower;
    void Update()
    {
        if(Input.GetKey(KeyCode.Space))
        {
            go.transform.Translate(transform.forward * movePower * Time.deltaTime);
        }
    }
}
  • Space를 누르면, movePower만큼 탱크가 움직인다
  • 사이드얘기

  • 그래서 원래 일전에 했던 공굴리기의 경우, AddForce를 하는 것은 바람직 하지 않음. AddForce의 경우 FixedUpdate()에서 쓰는것이 권장된다

Rotation

  • 오일러 각을 그대로 쓰기에는 짐벌락 현상이 생긴다. 쉽게말하자면, 물체를 돌리다 보면 축이 서로 겹치는 경우가 생긴다.x, y 축이 겹쳤을때, x축 방향으로는 돌릴 방법이 사라지는 경우가 생기는거다
  • 유니티 내부적으로는 쿼터니언(사원수,헤밀턴수)으로 회전을 구현한다. 해당 내용은 대학교에서 수의 체계를 배울 때 있던 내용으로, 관련 내용을 배워보지 못한 학생들은 직관적으로 이해할 수 없다

사원수 원리

  • 가장 중요한 개념은 아래와 같다
  • 사원수(헤밀턴 수) : i2=j2=k2=ijk=1i^2 = j^2 = k^2 = ijk = -1
  • 사원수라고 하는데, 여기에 이제 오일러 등식과 함께 사용하면 각도를 표현할 수 있게 된다
  • 오일러 항등식 : eiπ+1=0e^{i\pi}+1 = 0

Unity의 Quaternion & Euler

  • Quaternion

    • x, y, z에 각도(방향)을 저장한다. 회전 시 새로운 방향을 연산하여 저장
    • 장점 : 기하학으로 회전하니 짐벌락 안생김
    • 단점 : 사원수를 이해하기 어렵고 직관적이지 않음
  • EulerAngle

    • 3 축을 기준으로 수치만큼 회전시키는 방법
    • 장점 : 직관적으로 계산가능
    • 단점 : 짐벌락 현상이 발생함
  • Quaternion을 통해 회전각도를 계산하는 것은 직관적이지 않고 이해하기 어려움

  • 유니티의 경우 QuaternionEular의 변환 함수를 지원하므로 필요에 따라 변환하여 사용

  • 변환 함수

    Quaternion quaternion;
    Vector3 euler;
    
    quaternion = transform.rotation;
    • transform의 회전 값은 Euler 각도 표현이 아닌 Quaternion을 사용
  • 변환 방법

    • Euler를 Quaternion으로 변환
      euler = quaternion.eulerAngles;
    • Quaternion을 Euler로 변환
      quaternion = Quaternion.Euler(euler);

회전직접 지정

  • rotation에 값을 직접 지정해 회전하는 방법
    • Euler를 이용해 Quaternion을 변환하여 사용 권장
Transform.rotation = Quaternion.Euler(x, y, z);

회전 함수

  • Rotate

    • Rotate 함수를 이용한 회전
    • 축을 기준으로 회전
    Transform.Rotate(Vector3.up, Speed);
    // 축을 이용한 회전 (축을 기준으로 시계방향으로 회전)
    transform.Rotate(Vector3.up, rotateSpeed * Time.deltaTime);
    // 오일러를 이용한 회전
    transform.Rotate(Vector3.up * rotateSpeed * Time.deltaTime);
    // rotation을 이용한 회전
    transform.Rotate(0, rotateSpeed * Time.deltaTime, 0);
  • RotateAround

    • RotateAround를 이용한 중심 축 기준으로 회전
    • 한 지점을 정해, 그 지점을 기준으로 회전한다. 궤도운동을 구현할 때 쓸 수 있다
    Transform.RotateAround(Target.position, Vector3.up, Speed);
  • LookAt

    • LookAt을 이용한 대상을 향해 회전시키기
    • Target으로 지정한 지점을 바라본다
    Transform.LookAt(Target.position);
  • Quaternion 보간

    • 보간수치를 넣어 회전시킬 수 있다
    • QuaternionLerp가 있다
    Vector3 targetDir = (target.position - transform.position).normalized;
    Quaternion targetRot = Quaternion.LookRotation(targetDir);
    transform.rotation = Quaternion.Lerp(transform.rotation,targetRot,rate);

트랜스폼 회전 공간

  • 게임오브젝트를 회전하는 데 있어, 월드의 절대 좌표(월드 스페이스), 상속 관계에서의 상대 좌표(로컬 스페이스)를 사용할 수 있다

World Space & Local Space

  • World Space

    • 절대좌표. 고정된 좌표계다
    • 오브젝트는 각각 월드 포지션(World Position), 월드 회전(World Rotation), 월드 스케일(World Scale)을 가짐
  • Local Space

    • 오브젝트 각각의 좌표계. 부모-자식관계이면, 부모가 기준인 좌표계
    • 자식 오브젝트의 로컬 포지션이 (0, 0, 0)이면, 부모.position = 자식.position
    • 오브젝트는 각각 로컬 포지션(Local Position), 로컬 회전(Local Rotation), 로컬 스케일(Local Scale)을 가짐

예시

  • 부모는 월드를 기준으로 좌표와 각도 값을 가지는데, 자식은 부모를 기준(로컬)으로 좌표와 각도 값을 가진다
부모
자식

기준에 따른 회전 방법

  • 월드를 기준으로 회전

    transform.Rotate(0, 0, 1, Space.World);
  • 로컬을 기준으로 회전

    transform.Rotate(0, 0, 1, Space.Self);
  • 위치를 기준으로 회전

    transform.RotateAround(otherTransform.position, Vector3.up, 1);
  • 예시) 탱크회전ㄴ

[SerializeField] Transform tank;
[SerializeField] Transform turret;
[SerializeField] float turret;

turret.rotation = Quaternion.identity; // 북쪽, 월드기준이라서 탱크를 회전시켜도 포탑은 고정된다

rotate -= 30*Time.deltaTime;
tank.rotation = Quaternion.Euler(0,rotate,0); // 월드 기준으로 rotate 만큼 회전시킨다
turret.localRotation = Quaternion.Euler(0,rotate,0); // 로컬기준으로 돌아간다. 탱크의 회전 각만큼 값이 더해진다
  • Translate와의 응용

    • 월드를 기준으로 방향을 정해 이동시키는 방법이다
    • 기존에 쓰던 것처럼 Spalce.World를 생략하면, Space.Self(로컬)로 된다.
    transform.Translate(Vector3.forward * Time.deltaTime, Space.World);
  • Rotate 사용 예시

    • Space.World를 추가하면, 오브젝트의 정수리가 아닌, 월드기준 윗 방향을 축으로 회전한다
    transform.Rotate(Vector3.up,60,Space.World);

Transform의 부모-자식 관계

  • 트랜스폼부모-자식관계 구현 가능
  • 트랜스폼에게 부모 트랜스 폼이 있으면, 부모 트랜스폼의 위치, 회전, 크기 변경같이 적용
  • 이를 이용하여 계층적 구조를 구현하기 좋음
  • 하이라키 창에서 드래그 & 드롭을 통해 부모-자식 상태를 변경할 수 있음
// 트랜스폼의 부모 설정
transform.parent = otherTransform.parent;

// 부모를 기준으로한 트랜스폼
Vector3 localPosition = transform.localPosition;    // 부모트랜스폼이 있는 경우 부모를 기준으로 한 위치
Quaternion localRotation = transform.localRotation; // 부모트랜스폼이 있는 경우 부모를 기준으로 한 회전
Vector3 localScale = transform.localScale;          // 부모트랜스폼이 있는 경우 부모를 기준으로 한 크기

// 트랜스폼의 부모가 null인 경우 월드를 기준
transform.parent = null;

// 월드를 기준으로한 트랜스폼
Vector3 worldPosition = transform.position;         // 월드를 기준으로 한 위치
Quaternion worldRotation = transform.rotation;      // 월드를 기준으로 한 회전
// Vector3 worldScale = transform.localScale;       // 부모를 기준으로 한 크기
profile
개발 박살내자

0개의 댓글