이는 누군가를 위한 학습 관련 블로그가 아닌 개인의 평범한 공부일지로써 틀리거나 애매모호한 부분이 있을 수 있습니다. 만약 틀린 부분을 발견시 댓글을 달아주신다면 감사하겠습니다.
1. 2D & 3D Geometry
| 2D & 3D Coordinates | Vectors | Quaternions |
- Coordinate Systems (3D)
유니티에서의 Coordinate System (좌표 시스템 구조)는 여타 다른 3D 프로그램과 다르다. 유니티에서 3D 방향을 쉽게 알 수 있는 점은 왼손을 활용하여 총 모양을 만들었을때 X-방향은 중지, Y-방향은 엄지 그리고 Z-방향을 검지라고 가정하면 쉽게 유니티에서의 Coordinate System을 유추할 수 있다. 이는 오른손 총 모양을 활용한 3D 방식의 Maya (마야)와 정반대이다.

이때 유니티 상에서 한 GameObject을 올려두었을때 그 GameObject을 이루는 포인트들중 하나는 이와 같은 방식으로 정의될 수 있다.

- Vectors
Vector는 특정한 길이와 방향을 나타낸 2D나 3D상에서의 화살표를 뜻한다. 이는 위의 Diagram에서 본 각기 X/Y/Z 방향을 나타내는 화살표도 Vector로 치부될 수 있다.
Vector Maths
Vector 값들의 계산을 통해 어느 방향과 길이로써 특정 GameObject들을 움직이게 할 지 지정하게끔 만들 수 있는데 우선적으로 2D상에서의 Vector값들의 계산은 이와 같이 나타낼 수 있다.
Addition and Subtraction
만약 이와 같은 방향과 길이를 가진 각각의 UV Vector가 있다고 가정했을시 UV값의 합을 구하면 어떠한 Vector값을 형성하게 될까?

이때 U와 V는 아래와 같은 수식값을 가지게 된다.
$$ u=(u_{x},u_{y},u_{z}) $$
$$ v=(v_{x},v_{y},v_{z}) $$
만약 이를 합하려는 계산을 하려면 아래와 같이 수식을 작성하여 새로운 Vector 값을 얻어낼 수 있다.
$$ u+v=(u_{x}+v_{x},u_{y}+v_{y},u_{z}+v_{z}) $$

그렇다면 Vector값도 음수의 값을 가지면 어떤 방향으로 향할까? 이는 간단하다. 특정 하나의 Vector값의 음수는 기존의 길이와 동일하지만 반대의 방향으로 향하며 이는 아래와 같은 수식을 통해 이와 같이 그려질 수 있다.
$$ -v=(-v_{x}-v_{y}-v_{z}) $$

이 때, 방금전 U와 V의 Vector값의 합을 구했던 것처럼 U에서 부터 V의 차를 구하면 어떠한 결과가 나올까? 이는 햇갈릴 수 도 있지만 간단하게 U의 Vector값과 방금전 구한 음수값의 -V의 Vector값을 더하면 마치 U-V같은 결과가 나타내게 된다.
$$ u-v=(u_{x}-v_{x},u_{y}-v_{y},u_{z}-v_{z}) $$

Points and Vectors
그렇다면 지금까지 배워왔던 것들중 착각하기 쉬운것이 있다. 과연 Point와 Vector의 차이는 정확히 무엇일까? Vector는 길이와 방향을 나타내는 화살표 전부를 나타내며 Point는 점 하나의 Coordinate 위치를 나타낸다. 이는 아래와 같은 수식과 Diagram을 통해 설명될 수 있다.
$$ P=(P_{x},P_{y},P_{z}) $$
$$ V=(V_{x},V_{y},V_{z}) $$
$$ Q=P+V $$
$$ Q=(q_{x},q_{y},q_{z}) $$
$$ V=Q-P $$

Length, Scaling
Vector값의 Length (길이)를 얻는 과정 중 가장 쉬운 방법은 무엇이 있을까? 이는 Pythagoras Theorem (피타고라스 정리)의 방식을 통해 직각삼각형을 구성하는 Vector값의 길이를 얻을 수 있다.
$$ |v|=\sqrt{v_{x}^{2}+v_{y}^{2}+v_{z}^{2}} $$
그렇다면 Vector값의 Scaling (스케일)값을 얻으려면 어떻게 할까? 이는 기준을 이루는 Vector의 길이를 1이라 가정한 다음 각기 비율을 통해 다른 Vector값의 길이를 추정하는 방법이다. 이때 유의해야 할점은 모든 Length값과 Scale값은 음수로 측정되지 않으며 모든 음수 방향의 Vector값은 절댓값으로 변환되어 양수가 되어버린다.

Normalisation
Normalisation은 후디니에서 배웠었듯이 특정 Vector의 방향은 유지하고 싶지만 길이값을 1로 만들고 싶을때 쓸 수 있는 일종의 Function (함수)이다. Normalisation의 수식은 아래와 같이 작성된다.
$$ normalise(v)=\frac{v}{|u|} $$

Rotation in 2D
그렇다면 Rotation을 2D 그래픽상에서는 어떻게 진행을 하면 완성시킬 수 있을까? 이는 이와 같이 특정 Vector의 길이값을 바꾸지 않은 채 방향만 살짝 바꿔주면 성공적으로 Vector의 Rotation을 진행시킬 수 있다.

Rotation in 3D with Quaternions
여기까지 오는데는 원활했던 것 같다. 문제는 Rotation (회전)을 3D 그래픽 상에서 어떠한 원리로 진행되는지 아려면 Quaternion이라는 개념을 알 필요가 있다. 이걸 본 순간 후디니 과정에서 배웠던 쿼터니언의 악몽이 떠올라 순간 괴로울 뻔 했지만 그래도 이미 어느정도 아는 개념이라 쉽게 넘어 갈 수 있었던 것 같다.
Quaternion은 Angle (각도) 그리고 Axis (축)을 이용하여 3D상에서 특정 Object을 회전시키는 원리이다. 원래 본 수식은 복잡하지만 이와 같이 rotate()이란 함수안에 Angle과 Axis가 순서대로 들어가 회전을 완성시킨다라는 개념을 이해하기만 하면 된다.
$$ q=rotate(θ,v) $$
3D Vector값을 Rotation해주기 위해서는 특정 Vector값에 Quaternion값을 곱해주면 작동된다.
$$ v=q·v $$
이때 Quaternion값의 Quaternion을 곱해주어 Vector값에 또한 곱해주면 또 다른 방향의 Rotation값을 적용해 줄 수 있다.
$$ q_{2}·(q_{1}·u)=(q_{2}·q_{1})·u $$
Euler Angles
새로운 Euler Angles는 이름만 들어도 어질어질해진다. 이는 Quaternion을 통해 어느 축을 기준삼아 Rotation이 적용된 값은 또한 X/Y/Z축으로 또 나뉘어져 질수 있게 만든다. 이는 Euler Angles로 해결가능하며 이는 각기 X/Y/Z축을 기준삼아 적용하려는 GameObject을 회전시킨다. 우리는 이러한 Euler Angles을 각기 θx, θy, θz이렇게 표현하며 아래와 같이 각기 특정 이름을 가지고 있는 것을 알 수 있다.
| The heading (θy) | Rotation about Y |
| The pitch (θx) | Rotation about X |
| The roll (θz) | Rotation about Z |
- In Unity
유니티에서는 Vector값들은 각기 Class로 Variable Type이 정해진다. (위치 값 타입)
| Vector2 | for 2D |
| Vector3 | for 3D |
또한 유니티에서는 Quaternion값도 Class로 Variable Type이 정해진다. (회전 값 타입)
| Quaternion | for 3D |
- Summary
Vector와 Quaternion은 기본적으로 게임 프로그래밍을 하는데 있어 가장 중요하고 기본적인 개념들이므로 유니티 상에서 많이 활용해 보며 사용법에 적응하는 것이 중요하다.
2. Transforms
| The Transform Component |
| Translation in World and Self Space |
| The tansform hierarchy (or scene graph) |
| Transforming points and vectors |
- Transform
유니티 안에 모든 GameObject은 Transform이라는 Component를 가지고 있다. 이때 이 Transform Component를 이루는 파라미터들은 각기 position/rotation/scale을 담당하며 이들의 수치를 조절하여 GameObject의 위치, 회전, 그리고 스케일을 바꿔줄 수 있다.
Transform은 모든 GameObject안에 들어있는 Component로써 Transform transform = GetComponent<Transform>(); 과 같은 Instantiation 없이 바로 transform이라는 Object (객체)를 활용하여 세부사항을 코드로 조절할 수 있다.
그대신 Vector2나 Vector3같은 타입들은 Class이기도 함으로 이를 통해 값을 지정하려고 할때 new Vector3와 같은 Instatiation 선언이 필요하다.
void Update()
{
// can call this without GetComponent()
transform.position = new Vector3 (1, 2, 3); // GameObject의 포지션값을 (1, 2, 3)으로 설정
}
Transform의 Vector값을 따로 설정해주기 귀찮을때는 이와 같이 Vector3 class안에 있는 variable들을 불러와 손쉽게 지정할 수 있다.
eg) transform.position = Vector3.forward; // Vector3.forward는 (0, 0, 1)값과 동일하며 현제 GameObject의 포지션을 이로 설정해보았다.
더욱 자세하고 많은 Vector3 Class안의 variable들은 다음 웹페이지에서 확인할 수 있다.
https://docs.unity3d.com/ScriptReference/Vector3.html
Unity - Scripting API: Vector3
This structure is used throughout Unity to pass 3D positions and directions around. It also contains functions for doing common vector operations. Besides the functions listed below, other classes can be used to manipulate vectors and points as well. For e
docs.unity3d.com
Directly modifying fields
코드상에서 Field를 따로 세팅해줌으로써 GameObject을 곧바로 움직이게 할 수 있다. 이때 Instantiation없이 쓸 수 있는 Object (객체)들을 활용할 수 있는데 이때 GameObject의 Transform값을 설정해줄 수 있는 Object들은 다음과 같다.
- Transform.position은 GameObject의 World Position (월드 위치)값을 가진 Vector3 형태의 값이다.
- Transform.rotation은 GameObject의 World Rotation (월드 회전)값을 가진 Quaternion형태의 값이다.
- Transform.eulerAngle은 GameObject의 Rotation을 위한 Euler Angle값을 가진 Vector3 형태의 값이다. (윗 회전값이 Quaternion 형태임으로 Transform.eulerAngle을 통해 Vector3 형태로 바꾸어 유저가 인식할 수 있도록 해줄 수 있다).
public class MoveAndRotate : MonoBehaviour
{ // meter per second (앞으로)
// Vector3 variable velocity 값을 (0, 0, 1)으로 설정
public Vector3 velocity = new Vector3 (0, 0, 1);
// float variable rotation 값을 30으로 설정
public float rotation = 30; // degrees per second
// 매 프레임마다 업데이트 해주는 Method
void Update()
{
// 매 프레임마다 GameObject의 position을 z방향으로 업데이트함
transform.position += velocity * Time.deltaTime;
// 매 프레임마다 float variable angle값을 30으로 설정하는데 Time.deltaTime을 곱하여 천천히 값이 적용되도록 설정
float angle = rotation * Time.deltaTime;
// 매 프레임마다 GameObject의 rotation값을 30도씩 z방향을 축으로 돌아가게끔 설정
transform.rotation *= Quaternion.AngleAxis (angle, Vector3.up);
}
}
원래 방법으로는 위와 같은 방법으로 GameObject의 Transform을 영향을 주게끔 만드는데 프로그래밍을 하다보면 위와 같은 방법보다는 더욱 간편하고 readable (가독성)을 더욱 편하게 하는 방법을 사용할 수 도 있다. 이는 아래와 같다.
이때 transform.Translat()과 transform.Rotate이 써넣어진것을 알 수 있는데 이는 프레임이 진행될때마다 서서히 바뀌게끔 만드는 Method들로써:
transform.Translate()에 들어가는 값으로는 어떤 방향으로 얼마나 빠르게 움직일지 velocity로 계산해서 집어 넣어 줄 수 있고
transform.Roate()에 들어가는 값으로는 eulerAngle값들을 집어넣어 X/Y/Z 방향으로 얼마나의 각도값만큼 회전시켜줄지 집어넣어줄 수 있다.
void Update() // 매 프레임마다
{
// velocity 방향으로 GameObject이 움직이게끔 만들어라
transform.Translate (velocity * Time.deltaTime);
// note: Rotate uses Euler angles (in degrees)
// float angle값을 매 프레임마다 variable rotation 값이 되게끔 업데이트
float angle = rotation * Time.deltaTime;
// GameObject을 입력된 Vector 방향의 축을 삼아서 입력된 값만큼 rotation 시켜라
// y-axis를 기준으로 90도 그리고 z-axis를 기준으로 40도씩 서서히 매 프레임마다 업데이트 하여라
transform.Rotate (0, 90, 40);
}
Self and World Spaces
이때 GameObject으로 부터 Vector의 방향이 어떤 것이 기준인지에 따라 달라질 수 도 있다. 만약 GameObject 그 자체가 기준이라면 이미 방향이 틀어진 GameObject을 기준으로 Vector의 방향이 결정되고 World가 기준이라면 GameObject이 어느 방향을 향하고 있는지와 관계없이 고정된 X/Y/Z 방향으로 Vector가 결정된다.

유니티에서는 기본적으로 따로 지정을 해주지 않는다면 Self Space (Local)을 기준으로 Vector 방향이 결정된다.
지정을 해주는 방법으로는 다음과 같다.
방금전 배운 transform.Translate() method에 향하고 싶은 방향 Vector값을 입력해준 다음 Space.Self나 Space.World을 적으면 된다. 이는 다음과 같이 쓸 수 있다.
transform.Translate (Vector3.right); // self를 기준으로 오른쪽 방향으로 이동
transform.Translate (Vector3.right, Space.Self); // self를 기준으로 오른쪽 방향으로 이동
transform.Translate (Vector3.right, Space.World); // world를 기준으로 오른쪽 방향으로 이동
- Transform.Hierarchy
모든 Unity의 GameObject들은 Hierarchy (계층 구조)로써 정렬되어있는 것을 알 수 있다. 이는 Scene Graph라고도 불리며 각기 GameObject들의 position은 모두 자신의 Parent's Coordinate Frame (부모 좌표 프레임)으로 부터 기반된 것을 알 수 있다. 이때 이를 조절하기 위해서는 다음과 같은 Object과 Method들이 있다.
- transform.parent
- transform.GetChild()
GameObject의 포지션은 아래와 같은 Object들로 부여된 Parent들으로 부터 상대적이다. 이들로 지정하면 부모 Object의 위치와 회전을 기준으로 설정되므로 GameObject의 Inspector안에서 볼 수 있든 Transform Component안의 값과 일치하게 된다.
- transform.localPosition
- transform.localRotation
- Transforming points and vectors
가끔은 GameObject의 local space로부터 world space로 point나 vector을 바꾸는 것이 더욱 유용하게 여겨질 때도 있다. 이는 반대로도 마찬가지이다. 그렇다면 이는 어떻게 자연스럽게 바꿔버릴 수 있을까? 이는 다음과 같은 Method들과 함께 작동이 가능하다.
transform.TransformPoint() // local to world (point)
transform.InverseTransformPoint() // world to local (point)
transform.TransformVector() // local to world (vector)
transform.InverseTransformVector() // world to local (vector)
그렇다면 궁금하다. 과연 local로부터 world나 아니면 그 반대로 point나 vector의 방향을 바꾸는 것이 언제 유용한 것인가? 이는 이와 같은 예시와 함께 설명될 수 있다.
만약 우리가 두 개의 GameObject이 유니티 Scene 상에 있다고 가정했을시에 하나의 GameObject이 다른 하나의 GameObject 왼쪽에 위치하고 있는지 알고싶다는 가정상황이 생기면 Self로 지정된 position을 World로 바꾸어야 하는 상황이 생기게 된다. 이를 해결해주기 위해 transform.TransformVector()을 사용하여 만약 world로 지정된 Vector 방향을 기준으로 x값이 더욱 작은 값이 왼쪽에 위치하고 있다는 것을 알 수 있게 할 수 있다.
이와 같이 car2의 Self Vector방향을 기준으로 보았을때 car1의 포지션은 (1, -3)으로 지정되게 된다. 그렇게 되어 car2의 입장에서는 car1이 오른쪽에 위치하고 car2가 왼쪽에 있는 것처럼 느껴지게 된다.

그렇지만 이는 우리가 보는 입장에서 말이 안된다. 우리가 그대로 Scene을 보는 입장에서는 car2가 오른쪽에 있고 car1이 왼쪽에 있기 때문이다. 그렇기 때문에 기준이 되는 vector값의 방향을 world로 지정하여 정확하게 유니티에서 계산할 수 있도록 해야한다.

이러한 이유를 기반으로 self로 지정된 vector값을 world로 바꿔주어 유니티가 상대적인 판단이 아닌 객관적인 판단을 내리게 만들어 더욱 정확한 위치값을 내릴 수 있게 한다.
- Summary
- Transform Component는 GameObject의 position, rotation 그리고 scale값을 컨트롤 할 수 있게 만든다
- Translate()과 Rotate()이라는 transform이라는 object안의 method를 이용하여 심플한 movement (움직임)을 생성할 수 있다.
- Transform은 Global (World)와 Local (Self) coordinate spaces를 만들어 낸다.
- 우리들은 point와 vector을 world를, self로 self를 world로 변환하여 줄 수 있다.
3. Input
| Keyboard Input | Mouse Input | Botton Input | Axis Input | Attack/Decay Curves |
- Input in Unity
유니티의 Input (키보드나 마우스를 눌러 무언가 실행시키는 방식)은 유니티에서 제공하는 Input Manager이라는 Input Class로 조정할 수 있다. 이는 개발자가 Input Control을 쉽게 재구성할 수 있는 방식으로 직접 세팅 가능케 하는 시스템으로 이루어져 있으며 모든 Input들은 코드가 아닌 Input Manager상에서 설정되어야 한다. 안타까운 점은 Input Manager는 2020년에 실시된 유니티의 업데이트로 많은 것들이 바뀌었지만 우선은 3.61 버전 상 그대로 살펴보도록 하겠다.
Input Manager는 Project Setting으로 들어가 Input Manager이라는 탭을 눌러 확인 가능하다.

- Keyboard Input
Unity Methods
Input Manger상에서 어떤 키를 누르면 작동될지 세팅을 완료한 상태에서 이제 본격적으로 코드상에서 다음과 같이 작성해주어야 유니티가 어떠한 상황에서 특정 event를 실행시킬지 알 수 있다. 이때 키보드의 키를 눌렀을때 반응하게끔 만드는 Method는 총 세 개가 있는데 이는 다음과 같은 각기 다른 특징을 가지고 있다.
- Input.GetKey() - 키가 눌러지는 동안 boolean으로 true가 적용되어 특정 event가 실행되게끔 만든다.
- Input.GetKeyDown() - 키가 눌러질때 그 순간만 boolean으로 true가 적용되어 특정 event가 실행되게끔 만든다.
- Input.GetKeyUp() - 키가 때질때 그순간만 boolean으로 true가 적용되어 특정 event가 실행되게끔 만든다.
그치만 유니티에서 권고하는 바는 GetKey() method를 사용하기 보단 GetButton() method를 사용하기를 권장한다. 이는 거의 동일하게 작용되며 더욱 개발자가 설정을 따로 가능케 만들어 편리하게 조절할 수 있기 때문이다.
그럼에도 불구하고 GetKey() Method를 사용해야 하는 상황이 온다면 이와 같은 포맷을 사용하여 작성되기를 권장된다. 원래부터 쓰이던 GetKey() 안에 " "사이 안 유저가 누를 키 이름을 직접적으로 적는 것 보단 KeyCode라는 Enumaration을 입력한다음 Space를 직접 쓰는 것이 권장된다. 이는 string으로 직접적으로 쓸 시 Compiler가 잡지 못하는 에러를 촉진시키기 때문이다.
Input.GetKey (KeyCode.Space); // Good
Input.GetKey ("Space"); // Bad
- Mouse Input
Unity Methods
유니티는 또한 키보드뿐만 아닌 마우스를 활용하여 유저가 인터렉티브하게 게임을 즐길 수 있게끔 만들어 줄 수 있다. 이는 OnMouse[up/down]이라는 method를 이용하여 유저가 특정 GameObject을 마우스를 클릭했을때 event를 실행하게끔 작동하게끔 만들어 주어야 한다. OnMouse()는 키와는 다르게 collider가 적용된 target object을 설정해 주어야 유니티 상에서 OnMouse()가 작동됬는지 알아차릴 수 있다. 하지만 이 또한 Input.GetButton()이 Input.GetMouseButton()을 대체하여 쓸 수 있으므로 유니티 상에서 Input.GetButtion()을 사용하는 것이 더욱 권장된다.
Configuration
Input Manager은 특정 Button이나 Axis를 정의하여 Event를 실행시킬수 있게끔 만들게 해주는데 이는 코드를 수정할 필요 없이 유니티 인터페이스 상에서 직접 디자이너도 재구성 할 수 있어 Designer Friendly Programming의 대표적인 예시로 간주된다.
- Button Input
Unity Methods
그렇다면 알겠다. 도대체 Button은 무엇인가? Button은 마우스든 키보드든 조이스틱 버튼이든 전부다 조종 가능하케 만들 수 있는 Method로써 통일성을 추구함으로 개발자들에게 더욱 편의성을 제공한다. 이들은 GetKey()에서 설명한것과 같이 비슷하게 Method의 종류가 있으며 다음과 같다.
- Input.GetButton() - 버튼이 눌러지는 동안 boolean으로 true가 적용되어 특정 event가 실행되게끔 만든다.
- Input.GetButtonDown() - 버튼이 눌러질때 그 순간만 boolean으로 true가 적용되어 특정 event가 실행되게끔 만든다.
- Input.GetButtonUp() - 버튼이 때질때 그순간만 boolean으로 true가 적용되어 특정 event가 실행되게끔 만든다.
Button관련 Method를 사용하는 것은 키나 마우스 버튼을 유니티 Input Manager에서 수정할때 더욱 쉽게 할 수 있으므로 유니티 상에서 권장되는 바이다. 그렇지만 Input.GetButton()을 사용하려 할때는 특정 string을 요구함으로 Input Manager상에서 특정 이름을 설정할때 고민해서 선택하는 것이 중요하며 또한 코드상에서 불러올때 " "안에 제대로 string name과 일치하게끔 작성하는 것이 중요하다.
만약 Input Manager상에서 Fire1이라는 이름의 Button을 지정했을 시에 다음과 같이 코드상에서 어떻게 string을 작성하느냐에 따라 코드가 실행될 수, 에러가 날 수 있다.

Input.GetButton ("Fire1"); // correct
Input.GetButton ("Fire 1"); // Error
Input.GetButton("fire1"); // Error
이때 C# 코드 규칙상으로 모든 Button과 Axes들의 이름은 Pascal Case로 작성되는 것이 옳다. (eg - PascalCase)
- Axis Input
Unity Methods
그렇다면 지금까지 계속 언급되던 Axis는 도대체 무엇일까. 이는 -1 아니면 1이라는 값을 어떤 버튼이 눌러졌을때 반환하게 만드는 Control Input이다. 이는 Joystick이나 Pair of keys (마치 WASD 컨트롤의 A/D와 같은 관계같은)를 설정할때 유용하게 써먹을 수 있다. 쉽게 말하자면 각기 서로 반대되는 키를 설정하여 Input Manager상에서 하나의 Control input으로 두 개의 버튼이 눌려졌을때 작동되게끔 설정하는 방법이다. 사용되는 Method는 다음과 같다.
- Input.GetAxis()
InputAxes Class
그렇지만 이렇게 Input Manager로부터 직접 코드로 불러와 method를 작성하는 방식은 여전히 버튼의 이름의 중복이나 버튼 자체의 중복이든 여러가지 위험성이 동반된다. 이를 해결하기 위해 InputAxes라는 Class를 직접적으로 작성하여 axis와 button이름을 static 상수로 만들어 버리게 만들 수 있다. (어느 값 변화에도 고정 될 수 있도록 만들 수 있다).
class InputAxes
{
// Input Manager에 지정된 string을 각기 Horizontal과 Vertical이라는 variable에 저장
public const string Horizontal = "Horizontal";
public const string Vertical = "Vertical";
}
여기서 작성된 const는 constant (상수)의 약자로써 초기화이후 값을 절대로 변경할 수 없게끔 만든다. 그렇게 되면 Complier상에서 Input으로 인해 에러가 나는 빈도를 확 떨어뜨릴 수 있다. 이렇게 InputAxes라는 class를 만들게 되면 다음과 같이 본 Class 상에서 이용 가능하다.
public class CurrentClass : MonoBehaviour
{
private float horizontalTranslation;
private float verticalTranslation;
// 그치만 이 경우에는 굳이 Instatiation이 필요 없다고 나온다. 그러므로 이 부분은 생략 가능하다
// 이때 Instantiation이 필요없다고 했을때 왜 그런지 궁금하였다. 이는 더욱 알아봐야겠다.
InputAxes inputAxes = new InputAxes(); // Instantiation of the class InputAxes
void Update()
{
// translation값을 Horizontal으로 지정된 버튼을 누르면 horizontalTranslation값이 매 프레임마다 늘어난다.
horizontalTranslation = Input.GetAxis(InputAxes.Horizontal) * Time.deltaTime;
// translation값을 Vertical로 지정된 버튼을 누르면 verticalTranslation값이 매 프레임마다 늘어난다.
verticalTranslation = Input.GetAxis(InputAxes.Vertical) * Time.deltaTime;
transform.Translate (horizontalTranslation, 0, verticalTranslation); // GameObject이 x와z방향으로 움직이기 시작함
}
}
앞서 말했듯이 InputAxes inputAxes = new InputAxes();의 과정을 생략한체 곧바로 Class이름뒤에 Object variable을 붙여 Instantiation의 과정을 필요없게 만든다. 에러로는 다음과 같이 뜨고 (Instance로는 해당 Class안의 variable들을 접근할 수 없다고 나온다) 이는 궁금하여 나중에 교수진들에게 물어보든가 검색하여 알아보든가 해야겠다. 아시는 분 있으시면 댓글 달아주시면 감사하겠다.
다음과 같은 형식으로 코드를 작성하면 에러가 뜬다.
public class CurrentClass : MonoBehaviour
{
private float horizontalTranslation;
private float verticalTranslation;
InputAxes inputAxes = new InputAxes(); // Instantiation of the class InputAxes
void Update()
{
horizontalTranslation = Input.GetAxis(inputAxes.Horizontal) * Time.deltaTime;
verticalTranslation = Input.GetAxis(inputAxes.Vertical) * Time.deltaTime;
}
}

Gravity and Sensitivity
Input Manager에는 Gravity와 Sensitivity라는 파라미터들이 있는데 이를 조절하여 얼마나 빠르게 GameObject이 반응하여 Event를 실행할지 설정할 수 있는 값들이다. 이는 컨트롤러의 Smoothness (부드러움)와 Responsivity (반응력)를 전체적으로 조절한다. Gravity와 Sensitivity는 다음과 같이 계산된다.
$$ Attack Time = 1/sensitivity $$
$$ Decay Time = 1/gravity $$
예를 들어 만약 Sensitivity값이 1로 설정되어 Attack Time이 1이고 Gravity값이 2로 설정되어 Decay Time값이 0.5가 되면 다음과 같이 본래 1초에서 시작하여 4초에 끝나야할 event가 2초에 본격적으로 값이 1이 되기 시작하고 끝날때도 0.5초 늦게 끝나게 된다. 이로 인해 만약 transform.Translate에 적용하였을때 처음 엔진이 가동되는 시간과 관성의 법칙에 따른 관성시간을 표현하여 더욱 게임을 리얼하게 표현할 수 있다.

4. Tweening, Lerping, and Easing
| Interpolation & Tweening | Lerping | Easing with Animation Curves | Designer-friendly Code |
- Interpolation & Tweening
보통 애니메이션에서는 두 개의 중요 키프레임들이 있다. 시작과 끝인데 이 사이의 스무스한 transition을 적용해주기 위해서는 start와 end사이의 프레임들을 계산하여 Interpolation (보간)을 스무스하게끔 만들어주어야 한다. 이 과정을 Tweening이라 한다. 마치 After Effects에서 키프레임들의 사이를 Linear나 Auto-Bezier처럼 조절하던 순간이 떠올랐다. 이를 조절함으로써 더욱 스무스하고 자연스러운 GameObject의 움직임을 만들어 줄 수 있다.

Lerping
가장 간단한 Interpolation configuration (보간 형태)는 Linear Interpolation (선형 보간)으로써 Lerping이라고도 불린다. 이는 다음과 같은 원리와 식으로 계산되어 이루어지는데
- P0을 t = 0 즉 시간이 0초일때의 위치로 설정하고
- P1을 t = 1 즉 시간이 1초일때의 위치로 설정한다면
P0과 P1사이의 중간 Transition은 다음과 같은 식으로 작동돠는 것을 알 수 있다. 아래의 식중 P(t)는 중간 Transition 즉 Lerping을 나타낸다.
$$ P(t)=(1-t)P_{0}+tP_{1} $$
이는 앞서 언급했듯이 Linear Interpolation이라고 불리며 이는 중간 단계의 포인트 (키프레임)들이 고루 일정한 간격으로 분포되어있으며 P0과 P1사이 시간이 갈 수록 포지션의 변화를 그린 그래프 상에서 직선으로 된 라인으로 연결되어있기 때문이다.

유니티는 다음과 같이 여러가지의 Object Methods들을 제공해주어 각기 다른 두 값을 Lerp형태로 transition해주고 싶을때 개발자가 코드상에서 쓸 수 있도록 해준다.
- Vector3.Lerp() - Interpolates between two 3D points (두 개의 다른 3D 포인트들 사이의 보간을 담당)
- Quaternion.Lerp() - Interpolates between two rotations (두 개의 다른 회전 사이의 보간을 담당)
- Color.Lerp() - Interpolates between two RGB colours (두 가지의 다른 RGB 색상 사이의 보간을 담당)
Lerping을 적용한 코드 사용법의 예시는 다음과 같다.
[RequireComponent(typeof(SpriteRenderer))]
public class SpriteLerp : MonoBehaviour
{
// parameters
public Color startColor = Color.red;
public Color endColor = Color.blue;
public Vector3 startPosition;
public Vector3 endPosition;
public float duration; // seconds
// state
private SpriteRenderer sprite;
private float startTime;
void Start()
{
startTime = Time.time; // 처음 시작 타임을 현 시간 (Time.time)으로 설정함
sprite = GetComponent<SpriteRenderer>(); // SpriteRenderer Component를 불러와 세부 Property를 코드상에서 수정해주기 위한 방법
sprite.color = startColor; // 처음 시작 sprite의 색상을 빨강으로 설정
transform.position = startPosition; // 처음 시작 sprite의 포지션을 설정
}
void Update() // 매 프레임이 지나갈때마다
{
// 현재 시간에서부터 처음 시작 타임을 뺀값을 duration값으로 나누어 Transition사이의 시간을 계산하게끔 함
float t = (Time.time - startTime)/duration;
// sprite의 컬러값을 Color.Lerp로 t시간동안 서서히 변하게끔 만듬 (Linear Interpolation)
sprite.color = Color.Lerp (startColor, endColor, t);
// sprite의 위치값을 Vector3.Lerp로 t시간동안 서서히 변하게끔 만듬 (Linear Interpolation)
transform.position = Vector3.Lerp (startPosition, endPosition, t);
}
}
Easing
완벽해 보이던 Lerping도 문제점은 있기 마련이다. 이는 Lerping은 Ordinary (평범하게) 보일 수 있다는 단점이 있고 리얼하지 않고 Geometrical (기하학적으로) 보일 수 있다는 점을 초래할 수 있다. 이를 해결해 주기 위해 Easing이라는 방법을 대신하여 사용하면 좋다. 이는 그래프상에서 직선으로 이루어진 Linear Interpolation이 아닌 곡선인 Interpolation을 추구하여 다음과 같은 그래프를 나타낸다.

With AnimationCurves
유니티 Class 중 AnimationCurves는 Designer-friendly interface를 제공해주는 Class로써 디자이너가 게임을 디자인할때 직접적으로 인터페이스 상에서 커브 그래프를 수정할 수 있게끔 만들어 줄 수 있다.

Easing을 생성하기 위한 코드 작성 방식의 예시는 다음과 같다. 이때 쓰여지는 easingCurve.Evaluate()이라는 method는 본디 AnimationCurve라는 Class안에 작성된 Method임으로 디자이너가 인터페이스 상에서 조절한 곡선 값을 float값으로 리턴하여 코드상에서 수치로 쓸 수 있게끔 한다. 이때 파라미터로는 t값 즉 본래 시간값이 들어간다.
public AnimationCurve easingCurve; // AnimationCurve Class로 인터페이스 상에서 Easing curve를 수정 가능케함
void Update()
{
// 현재 시간에서부터 처음 시작 타임을 뺀값을 duration값으로 나누어 Transition사이의 시간을 계산하게끔 함
float t = (Time.time - startTime)/duration;
// AnimationCurve안에 있는 Evaluate이라는 Method를 사용하여 곡선값을 float variable t에 저장
t = easingCurve.Evaluate (t);
// sprite의 컬러값을 Color.Lerp로 t시간동안 서서히 변하게끔 만듬 (Easing Interpolation)
sprite.color = Color.Lerp (startColor,endColor, t);
// sprite의 위치값을 Vector3.Lerp로 t시간동안 서서히 변하게끔 만듬 (Easing Interpolation)
transform.position = Vector3.Lerp (startPosition, endPosition, t);
}
- Designer Friendly Code
항상 강조하지만 코드를 쓰는데 있어서 가장 중요한 원리 원칙은 Designer-friendly code (디자이너가 다루는데 불편함이 없을만한 코드)를 작성하는 것이다. 이는 다음과 같은 방법들을 통해 지켜낼 수 있다.
- 코드상에서 특정 파라미터를 public으로 지정하여 Inspector에서 Tunable parameter로 작동되게끔 설정하여라.
- 파라미터 값들의 단위들을 쉽게 이해할 수 있는 단위들로 설정하여라.
- 어렵고 난해한 코드로 값들을 조절하기 보단 UI element 상에서 디자이너도 보고 이해할 수 있게끔 세팅을 해두어 디자이너도 쉽게 값을 조절할 수 있게끔 만들어라. 예를 들면 다음과 같다.
// 디자이너가 이해할 수 있게끔 UI를 제공하는 Class들은 다음과 같다.
public Transform start;
public Transform end;
public AnimationCurve easingCurve;
이때 앞서 설명하지 못한 Transform Class는 유니티의 GameObject안에 있는 Transform Component를 Class처럼 나타낸 것으로 Inspector에서 특정 GameObject을 골라 그 GameObject의 position/rotation/scale값을 코드상으로 불러올 수 있게끔 할 수 있다. 예를 들면 보통 이때는 Empty Object을 골라 각기 start와 end 포지션에 위치하게끔 세팅한다음 Inspector상에서 각기 start와 end 파라미터를 Empty Object들로 각각 선택하여 현제 GameObject의 처음과 끝 포지션이 Empty Object들의 위치로 지정되게 할 수 있다.

eg) transform.position = start.position; // 현재 GameObject의 위치를 다른 특정한 GameObject 의 위치로 설정하여라.
생각보다 이번시간에는 수학적인 개념들을 많이 배웠던 것 같다. 이번시간의 핵심은 Vector의 개념으로써 2D와 3D를 가지는 공간상에서의 컴퓨터 그래픽적인 이해를 높이는 것이 중요할 것 같다.