Unity - (3) Input Manager, Scene, Transform

이형눈이이상해
이형눈이이상해
∙∙조회 7
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에서 다루게 된다.
  • 지금으로써는 동일한 객체가 있다면 죽여놓아야 한다.

csharp
 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를 하나 생성한다.

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

csharp
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도 존재. 그럼 이것을 이용하여
csharp
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 적용

  • 하지만 문제점은 방향 유지가 되어야 하는데 안되고 있다.
csharp
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;
    }
}

  • 완성된 바라보며 이동하는 방식.
csharp
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.