Unity - (3) Input Manager, Scene, Transform

1. Input Manager
1.1. 빌드
- File/Build Setting/에서 빌드할 수 있다.
- 상당히 많은 시간이 걸린다.
- 단축키는 Ctrl Shift B
1.2. 개요
- 유저 입력을 받는 것을 관리한다.
- 심도 있는 것은 없다.
- Unity 6에서는 아예 Input System이라고 해서 기본적으로 사용한다.
- 그럼에도 이것을 사용하는 것은 구버전은 이것을 사용할 수 있고 Input System은 이벤트 기관으로 돌아가므로 Input Manager보다는 직관적이지는 않다.
1.3. 설명
- Input.GetKey가 기본.
- 키를 누르는 순간에만 반응하는 것도 있고 땔 때 반응하는 것도 있다.
- 그럴 것을 대비해서 GetKeyDown, GetKeyUp, KeyCode라는 Enum을 통해 직관적으로 구현되어 있다.
- 위 함수는 bool형이다. 눌렸는가 아니냐이니까.
- Input.GetAxis, GetAxisRaw의 경우는 특정 축에 입력받는 것이다. Horizontal, Vertical 등 여기에 관련됨.
- 위 2개는 float로 반환한다.
2. Scene
2.1. 개요
- 게임 오브젝트들과 설정들을 모아둔 파일을 일컫는다.
- 큰 기능을 담당하는 장면으로도 유효하다.
- 어떤 오브젝트가 연결되고 컴포넌트가 연결된 것을 확인할 수 있다.
- guid가 깨지면 Scene은 쓸 수 없다.
- 여러 오브젝트가 관련되어 서술되어 있는데 guid가 깨지면 뭘 할 수 없다.
- 객체지향을 생각해보면 scene 하나에 모든 기능들을 떄려 넣었다고 생각하면 매우 난감하다.
- 이것도 Scene을 나눠서 설정하면 관리 측면에서 편해진다.
- 실제 게임에 적용시키려면 Build Setting에 들어가서 설정해야 한다.
- 테스트나 UI 등을 사용해야 할 때는 별도로 만들어서 설정하는 경우가 많기 때문에 빌드에 추가되지 않도록 한다.
- 물론 Scene의 이름으로도 불러올 수 있지만 우측에 있는 빌드 인덱스로도 불러올 수 있다.
2.2. 사용
- 다음 이미지와 같이 Scene을 만들어서 Title을 실행.

- 그리고 상위 폴더에 Script라는 폴더 생성.

- 그 다음, 그 폴더 안에 Script를 만들어 SceneChanger라 명명

- 만든 C# Script를 들어가 사용할 헤더를 설정.

- Update로 매 프레임마다 한 번씩 Space가 눌렸는지 확인한 다음

- 빌드에 세팅을 올려둔 다음

- Title Scene에서 게임 오브젝트를 추가하여 그곳에 만든 C# Script를 붙여주고

- 실행 시 Scene 전환이 이뤄지는 것을 확인할 수 있게 된다.

- 기능을 추가해보도록 할 것인데 만일 Esc키를 눌리면

- 설정을 했어도 Esc로 불가하다.
- Game Scene에서는 추가를 안해놨기 때문이다.
- 그래서 Scene이 넘어가더라도 살려놔야 한다.
- 단순하게는 GameScene에도 넣어놔도 상관은 없을 것이다. 그러나 다른 방법이 존재한다.

- 로드가 되더라도 파괴되지 않게끔 하겠다고 직관적인 표현으로 되어 있다.
- 자동으로 편성이 된다.



- 문제는 이를 반복하면 자동으로 계속 늘어나게 된다. SceneChanger가 늘어나면 늘어날수록 Scene Changer가 다수를 동시 구동한다.

- 추가적인 처리가 필요한다. 이것은 Singleton Pattern에서 다루게 된다.
- 지금으로써는 동일한 객체가 있다면 죽여놓아야 한다.

private void Start()
{
// Scene 전체적으로 판단할 수 밖에 없으므로 Find를 사용해볼 것이다.
SceneChanger s = FindObjectOfType<SceneChanger>();
// 만일 다른 SceneChanger가 있으면 찾겠지만 단 하나 남은 SceneChanger가 있을 수 있다.
// 그래서 null이 아니고 자신이 아니어야 한다.
if (s != null && s != this)
{
// 파괴
}
DontDestroyOnLoad(gameObject);
}
- 파괴의 측면에서 싱글톤 패턴이 이런 아이디어로 접근하게 되는 것.
- 지금은 직관적으로 두 곳 모두에 이것을 적용시키도록 하자.
- 현재 문자열 기반으로 실수가 많으므로 빌드 인덱스 기준으로 가보도록 하면

- 첫 화면

- Space 입력 시

- Esc 입력 시

- 부가적인 옵션을 불러오면서 Scene을 불러오는 경우도 존재.
- 현재는 단일 Scene을 불러오지만 중복으로 열어서 쓰는 경우도 존재한다. LoadSceneMode.Additive
- 이렇게 특정 영역가면 중복으로 Scene을 로드해서 보여주는 기법도 존재.
- 추가된 Scene을 언로드하는 방법도 존재.
- Scene을 비동기 방식으로 불러오는 방식이 존재하는데 크면 클수록 시간이 걸리므로 다른 작업을 수행해줘야 한다.
- Scene 단위를 불러올 때 멈춰서 좋을 것은 없기 때문에 로딩바를 돌리도록 하거나 하는 식으로 지속적으로 돌아간다. 이럴 때 비동기 방식을 취한다.
- 물론 규모가 작다면 크게 필요는 없겠지만.
- 유니티 자체에서 구조체 Scene을 제공하기는 한다.
- Scene을 다중으로 처리하는 게임이 아닌 이상 적극적으로 이를 사용하진 않음.
- 특정 키가 눌렸다고 처리는 했지만 나중 Scene은 버튼 하나 추가해서 처리하는 식으로 처리가 될 것.
3. Transform
3.1. 개요
- 3개 축에 대한 것을 다룰 때 Vector3를 사용한다. 단순히 숫자 3개를 보관할 때 사용하기도 한다.
- 하나의 구조체라고 생각하면 된다.
- 게임이라는 것은 공간 개념이 중요하다보니 직관적으로 단위나 방향 벡터 등이 되어 있다.
- Vector3.zero, Vector3.one, Vector3.사방향
3.2. 단위 벡터
- 벡터는 단순 좌표로도 표기하지만 얼마나 좌표 이동을 하는지에 대한 값이다.
- 게임에서 잘 쓰려면 크기를 배제시킨 상태(방향)거나 크기만을 사용하거나가 많다.
- 방향만을 가지는 벡터에 속도를 곱해주면 매 프레임마다 이동을 할 것이고 구현 원리이다.
- 이 크기가 1인 벡터를 단위 벡터라고 부른다.
- 크기만 가진 벡터를 Scalar이다.
3.3. 사용
- Transform은 행렬 연산을 통해 구동이 된다.
- Vector3만이 아닌 Vector2도 있다.
- GameScene로 이동을 하여 키 입력을 받아보도록 해볼 것이다. Cube 하나와 C# Script를 하나 생성한다.

- 트랜스폼은 모든 게임 오브젝트에 다 달려있다.
- 이 상황에서 소스 코드에서 제어를 하려면 가져와야 할 것이 있다.

private void Move()
{
float x = Input.GetAxisRaw("Horizontal"); // Raw는 단위 벡터로 설정된다.
float z = Input.GetAxisRaw("Vertical");
Vector3 movement = new Vector3(x, 0, z); // 이제 이것을 토대로 단위 벡터를 만들어야 한다.
//movement.Normalize(); // 연산을 통해 단위벡터 출력.
//movement.normalized; // 값만을 가져올 경우.
}
- 이제 이동 속도가 필요.
- transform.을 들어가 position을 살펴보면 기능들이 잘 구현이 되어 있음을 알 수 있다.
- Translate, Rotate 등 함수 호출들이 되어 있는 상태.

- 하지만 각 PC마다의 이동 속도의 차이가 프레임에 따라 다를 수 있기 때문에

- 이렇게 Time.deltaTime을 추가 처리해줘야 한다.

- 다음과 같이 플레이 시 이동이 가능함을 볼 수 있다. 하지만 나중에는 물리 엔진에서 속도를 지정해서 명령을 내리는 것이 필요하다. 현재 형태와는 다를 것이다. 이젠 다른 방법을 해볼 것이다.

- 지금은 적용을 했을 때 내부적으로 더해주는 것.

- 아까와 동일하게 적용이 된다.
- 그런데 이런 경우도 있다. 타겟팅하여 이동하는 방식도 있다. 이 방식에서는 당연히 찍어서 가면 되는데 이 상태에서는 시도할만한 요소가 존재한다.


- MoveForward를 쓰면 되는데 구조체에서 지원하는 기능을 써볼 것이다.


- 뱀서류를 이렇게 추적시킬 수 있게 된다.일정한 속도로 움직이게 할 수도 있지만 멀수록 빠르게 가까울수록 느리게 하는 방법도 가능하다. Vector3에서 제공하는 Lerp를 사용해볼 것이다.Lerp는 선형 보간이다.


- 이미지 상 확인은 안되지만 실질적으로 속도가 점점 느려져서 다가오는 것을 확인할 수 있다.
3.4. 회전
- 3축 회전은 짐벌락 현상을 야기할 수 있다.
- 보간을 할 때 예상치 못한 회전이 일어날 수 있다.
- 그래서 쿼터니언을 사용한다.
- 효율적인 연산이 가능하다.
- 주로 쓸 것은 Euler값 변환을 사용할 것이고 Transform.Rotation은 Quaternion 형식이어야 한다.
- 바로 사용해보도록 하자.


- R키를 누르고 있으면 회전하고 이동까지 되는 것을 확인할 수 있다.
- 물론 Rotate도 보간이 가능하다.
- Quaternion에서도 Lerp를 지원한다.
- RotateAround라는 것이 존재하는데 Goal에 작업을 걸 것인데


- 이렇게 공전처럼 돌아가는 형태가 된다.
- 이외에도 LookRotation도 존재. 그럼 이것을 이용하여
using System;
using UnityEngine;
public class CubeMovement : MonoBehaviour
{
//private Transform _transform;
private void Awake()
{
// CubeMovement를 찾아다가 붙이기.
// _transform = GetComponent<Transform>();
// 이미 Transform은 모든 오브젝트에 달려있으므로 transform.으로 바로 얻어와 쉽게 쓸 수 있다.
}
[SerializeField] private float _moveSpeed;
[SerializeField] private Transform _target;
[SerializeField] private float _rotateSpeed;
private void Update()
{
Vector3 movement = GetMovement();
Move(movement);
CubeRotate();
/*Move();
//MoveToTarget();
CubeRotate();*/
}
private void CubeRotate()
{
// 키가 계속 눌리는 동안 돌아가게끔 만들 것이다.
/*if (Input.GetKey(KeyCode.R))
{
// 회전은 특정 축을 기준으로 회전한다. up, right, forward를 쓰는데 이게 축 기준의 방향인 것.
transform.Rotate(Vector3.up * _rotateSpeed * Time.deltaTime);
}*/
}
private void MoveToTarget()
{
// 헌재 위치, 타깃은 Transform이므로 포지션으로 접근 가능, moveSpeed에 DeltaTime.
/*transform.position = Vector3.MoveTowards(transform.position,
_target.position,
_moveSpeed * Time.deltaTime);*/
// MoveTowards와는 다르게 원리상 0~1의 범위값인데 0.5면 반에 도달하면 멈추고 또 반 이런 식으로 진행된다.
transform.position = Vector3.Lerp(transform.position, _target.position, _moveSpeed * Time.deltaTime);
}
private void Move(Vector3 movement)
{
if (movement == Vector3.zero)
{
return;
}
/*float x = Input.GetAxisRaw("Horizontal"); // Raw는 단위 벡터로 설정된다.
float z = Input.GetAxisRaw("Vertical");
Vector3 movement = new Vector3(x, 0, z); // 이제 이것을 토대로 단위 벡터를 만들어야 한다.
movement.Normalize(); // 연산을 통해 단위벡터 출력.*/
//movement.normalized; // 값만을 가져올 경우.
// 방향에다가 속도를 곱해서 더해주면 이동이 된다.
//transform.position += movement * _moveSpeed * Time.deltaTime;
// 하지만 프레임 단위가 필요.
transform.Translate(movement * _moveSpeed * Time.deltaTime);
}
private Vector3 GetMovement()
{
float x = Input.GetAxisRaw("Horizontal"); // Raw는 단위 벡터로 설정된다.
float z = Input.GetAxisRaw("Vertical");
Vector3 movement = new Vector3(x, 0, z); // 이제 이것을 토대로 단위 벡터를 만들어야 한다.
movement.Normalize(); // 연산을 통해 단위벡터 출력.
//movement.normalized; // 값만을 가져올 경우.
return movement.normalized;
}
}
- 이제 보간과 LookRotation 적용

- 하지만 문제점은 방향 유지가 되어야 하는데 안되고 있다.
using System;
using UnityEngine;
public class CubeMovement : MonoBehaviour
{
//private Transform _transform;
private void Awake()
{
// CubeMovement를 찾아다가 붙이기.
// _transform = GetComponent<Transform>();
// 이미 Transform은 모든 오브젝트에 달려있으므로 transform.으로 바로 얻어와 쉽게 쓸 수 있다.
}
[SerializeField] private float _moveSpeed;
[SerializeField] private Transform _target;
[SerializeField] private float _rotateSpeed;
private void Update()
{
Vector3 movement = GetMovement();
Move(movement);
CubeRotate(movement);
/*Move();
//MoveToTarget();
CubeRotate();*/
}
private void CubeRotate(Vector3 movement)
{
// 키가 계속 눌리는 동안 돌아가게끔 만들 것이다.
/*if (Input.GetKey(KeyCode.R))
{
// 회전은 특정 축을 기준으로 회전한다. up, right, forward를 쓰는데 이게 축 기준의 방향인 것.
transform.Rotate(Vector3.up * _rotateSpeed * Time.deltaTime);
}*/
//if (movement == Vector3.zero)
if (movement.z == 0)
{
return;
}
Quaternion look = Quaternion.LookRotation(movement);
transform.rotation = Quaternion.Slerp(
transform.rotation,
look,
_rotateSpeed * Time.deltaTime
);
}
private void MoveToTarget()
{
// 헌재 위치, 타깃은 Transform이므로 포지션으로 접근 가능, moveSpeed에 DeltaTime.
/*transform.position = Vector3.MoveTowards(transform.position,
_target.position,
_moveSpeed * Time.deltaTime);*/
// MoveTowards와는 다르게 원리상 0~1의 범위값인데 0.5면 반에 도달하면 멈추고 또 반 이런 식으로 진행된다.
transform.position = Vector3.Lerp(transform.position, _target.position, _moveSpeed * Time.deltaTime);
}
private void Move(Vector3 movement)
{
if (movement == Vector3.zero)
{
return;
}
/*float x = Input.GetAxisRaw("Horizontal"); // Raw는 단위 벡터로 설정된다.
float z = Input.GetAxisRaw("Vertical");
Vector3 movement = new Vector3(x, 0, z); // 이제 이것을 토대로 단위 벡터를 만들어야 한다.
movement.Normalize(); // 연산을 통해 단위벡터 출력.*/
//movement.normalized; // 값만을 가져올 경우.
// 방향에다가 속도를 곱해서 더해주면 이동이 된다.
//transform.position += movement * _moveSpeed * Time.deltaTime;
// 하지만 프레임 단위가 필요.
transform.Translate(movement * _moveSpeed * Time.deltaTime);
}
private Vector3 GetMovement()
{
float x = Input.GetAxisRaw("Horizontal"); // Raw는 단위 벡터로 설정된다.
float z = Input.GetAxisRaw("Vertical");
Vector3 movement = new Vector3(x, 0, z); // 이제 이것을 토대로 단위 벡터를 만들어야 한다.
movement.Normalize(); // 연산을 통해 단위벡터 출력.
//movement.normalized; // 값만을 가져올 경우.
return movement.normalized;
}
}
- 완성된 바라보며 이동하는 방식.
using System;
using UnityEngine;
public class CubeMovement : MonoBehaviour
{
[SerializeField] private float _moveSpeed;
[SerializeField] private Transform _target;
[SerializeField] private float _rotateSpeed;
private void Update()
{
Vector3 movement = GetMovement();
Move(movement);
CubeRotate(movement);
}
private void CubeRotate(Vector3 movement)
{
if (movement == Vector3.zero)
{
return;
}
Quaternion look = Quaternion.LookRotation(movement);
transform.rotation = Quaternion.Slerp(
transform.rotation,
look,
_rotateSpeed * Time.deltaTime
);
}
private void MoveToTarget()
{
transform.position = Vector3.Lerp(transform.position, _target.position, _moveSpeed * Time.deltaTime);
}
private void Move(Vector3 movement)
{
if (movement == Vector3.zero) // 원래가 정답이었음
{
return;
}
transform.Translate(Vector3.forward * _moveSpeed * Time.deltaTime);
}
private Vector3 GetMovement()
{
float x = Input.GetAxisRaw("Horizontal"); // Raw는 단위 벡터로 설정된다.
float z = Input.GetAxisRaw("Vertical");
Vector3 movement = new Vector3(x, 0, z);
return movement.normalized;
}
}
3.5. 정리
- 이런식으로 캐릭터 이동을 구현하면 되는 것인데 단순 코드 구현이 가능하다.
- 여기에서 점프나 이런 것을 추가하면 생각이 많아지겠지만 Transform으로 이동 구현은 잘 구축되어 있다는 것을 알 수 있다.
- 이제 본격적으로 캐릭터 구현을 해볼 수 있는 셈
- 거리를 측정해서 얻는 기능 Distance. 상대 위치에서 내 위치를 빼면 거리.
- toTarget은 벡터, magnitude는 실질 크기.
- 기능은 Vector3.Distance.




