[유니티6] 새로운 Input System
전에는 Input.GetKey(KeyCode key), Input.GetMouseButton(int button) 등의 함수를 Update에서 지속적으로 확인하는 방식으로 마우스 및 키보드 입력을 처리했다.
하지만 이 방식은 플랫폼(PC, Mobile)에 따라 코드를 수정해야 하는 단점이 있다.
이를 해결하기 위해 유니티는 새로운 입력 시스템을 도입했다.
이제 Input Action Asset을 생성하고, Player Input 컴포넌트를 추가하여 에셋을 연결하는 방식이 권장된다.
특히 유니티6부터는 Input System이 기본적으로 설치되어 있다.
Input System
1. Input System 설정
유니티6에서 3D Rendering Pipeline 프로젝트를 생성하면 기본적으로 Input System 아이콘이 생긴다.
더블 클릭하면 Input Actions Editor 창이 띄워진다.
하지만 이를 사용하지 않고 직접 새로운 플레이어 컨트롤러를 만들었다.
- 프로젝트 창에서 Create → Input Actions를 선택하여 새로운 Input Action을 만든다.
- 생성된 Input Action의 이름을 PlayerControls로 변경한다.
- Action Maps에서 + 버튼을 눌러 Player로 이름을 변경한다.
- Actions에서 Move라는 액션을 추가한다.
- Action Properties에서
- Action Type: Value (좌우, 상하 값을 반환하기 위해)
- Control Type: Vector2 (X, Y값을 표현하기 위해)
- Composite 추가: Add Up,Down,Left,Right Composite를 추가하고 각 키를 설정한다.
- Up: W
- Down: S
- Left: A
- Right: D
- Player 오브젝트에 Player Movement 스크립트와 Player Input 컴포넌트를 추가하고, Actions에 PlayerControls를 연결한다.
- OnMove 이벤트를 통해 키 입력을 받아 처리할 수 있도록 설정할 것이다. (On + Actions 이름 → OnMove())
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
public void OnMove(InputValue value)
{
Debug.Log(value.Get<Vector2>());
}
}
위 코드를 실행하면, WASD 키 입력 시 Vector2 값이 (1, 0) (-1, 0) (0, 1) (0, -1) 형태로 반환되는 것을 확인할 수 있다.
이를 활용해 플레이어의 이동을 구현한다.
2. 플레이어 이동 구현
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
[SerializeField] private float controlSpeed = 10f; // 이동 속도
private Vector2 movement;
private void Update()
{
ProcessTranslation(); // 매 프레임 이동 처리
}
public void OnMove(InputValue value)
{
movement = value.Get<Vector2>(); // 입력된 방향 값 저장
}
private void ProcessTranslation()
{
// 입력값에 따라 위치 변경 (X, Y 축 이동)
float xOffset = movement.x * controlSpeed * Time.deltaTime;
float yOffset = movement.y * controlSpeed * Time.deltaTime;
transform.localPosition += new Vector3(xOffset, yOffset, 0f);
}
}
- OnMove() 함수에서 입력된 방향 값을 movement 변수에 저장한다.
- ProcessTranslation() 함수에서 localPosition 값을 변경하여 실제 이동을 구현한다.
- Time.deltaTime을 곱하여 프레임 속도에 관계없이 일정한 속도로 이동하도록 한다.
3. 이동 범위 제한
플레이어가 무제한으로 이동하는 것을 방지하기 위해 이동 범위를 설정한다.
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
[Header("Settings")]
[SerializeField] private float controlSpeed = 10f;
[SerializeField] private float xClampRange = 5f;
[SerializeField] private float yClampRange = 5f;
private Vector2 movement;
private void Update()
{
ProcessTranslation();
}
public void OnMove(InputValue value)
{
movement = value.Get<Vector2>();
}
private void ProcessTranslation()
{
// 입력값에 따라 이동 계산
float xOffset = movement.x * controlSpeed * Time.deltaTime;
float rawXPos = transform.localPosition.x + xOffset;
float clampedXPos = Mathf.Clamp(rawXPos, -xClampRange, xClampRange); // X축 이동 제한
float yOffset = movement.y * controlSpeed * Time.deltaTime;
float rawYPos = transform.localPosition.y + yOffset;
float clampedYPos = Mathf.Clamp(rawYPos, -yClampRange, yClampRange); // Y축 이동 제한
transform.localPosition = new Vector3(clampedXPos, clampedYPos, 0f);
}
}
- Mathf.Clamp()를 사용하여 이동 범위를 제한한다.
- xClampRange = 5f 이면 플레이어의 X 좌표는 -5 ~ 5 범위 내에서만 이동 가능하다.
- yClampRange = 5f 도 동일한 방식으로 적용된다.
4. 플레이어 회전 적용
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerMovement : MonoBehaviour
{
[Header("Settings")]
[SerializeField] private float controlRollFactor = 20f; // 회전 정도
private Vector2 movement;
private void Update()
{
ProcessRotation();
}
private void ProcessRotation()
{
// 좌우 이동 방향에 따라 Z축 회전 적용
Quaternion targetRotation = Quaternion.Euler(0f, 0f, -controlRollFactor * movement.x);
transform.localRotation = targetRotation;
}
}
- 입력 값(movement)과 회전 강도(factor)를 곱하여 Z축 회전값을 구한다.
- 플레이어가 이동할 때 Quaternion.Euler()를 사용하여 회전을 적용한다.
- 오른쪽 이동 시 movement.x = 1이므로 -controlRollFactor를 곱해 회전 방향을 조정한다.
※ Quaternion
회전을 설정할 때 transform.localRotation은 Quaternion 타입을 필요로 한다.
그런데 위치 값은 (x, y, z) 처럼 Vector3로 쉽게 표현할 수 있지만,
회전 값은 Quaternion 이라는 특수한 구조를 사용해야 한다.
만약 회전도 위치처럼 new Vector3(0f, 0f, angle); 방식으로 설정할 수 있었다면,
Gimbal Lock(짐벌락) 문제가 발생할 수 있다.
이 문제는 특정 각도에서 회전 축이 겹쳐서, 일부 회전이 제대로 적용되지 않는 현상이다.
그래서 유니티에서는 Quaternion.Euler(x, y, z)를 사용하여,
(x, y, z) 회전값을 Quaternion 타입으로 변환해야 한다.
또한 -controllRollFactor를 곱하는 이유는 다음과 같다.
movement.x 값이 1이면 오른쪽으로 이동 중이라는 뜻이고,
controllRollFactor = 20 일 경우 곱하면 20이 된다.
하지만 오른쪽으로 이동할 때, 캐릭터도 오른쪽으로 기울어야 하므로
-controllRollFactor를 사용하여 반대 방향으로 회전 시키는 것이다.
5. 부드러운 회전 적용
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
public class PlayerMovement : MonoBehaviour
{
[Header("Settings")]
[SerializeField] private float controllPitchFactor = 18f;
[SerializeField] private float controllRollFactor = 20f;
[SerializeField] private float rotationSpeed = 10f;
private Vector2 movement;
private void Update()
{
ProcessRotation();
}
private void ProcessRotation()
{
float pitch = -controllPitchFactor * movement.y;
float roll = -controllRollFactor * movement.x;
Quaternion targetRotation = Quaternion.Euler(pitch, 0f, roll);
transform.localRotation = Quaternion.Lerp(transform.localRotation, targetRotation, rotationSpeed * Time.deltaTime);
}
}
플레이어가 움직일 때 어느 방향으로 회전할지는 씬에서 직접 오브젝트를 회전시켜보면서 조절하는 것이 확실하다.
6. 마우스 클릭 감지
Input System을 사용하면 마우스 클릭 이벤트가 발생하는 조건을 세밀하게 조정할 수 있다.
- Input Actions Editor를 오른쪽 마우스 클릭 액션을 선택한다.
- 오른쪽 'Interactions' 섹션에서 + 버튼을 클릭하여 새 Interactions을 추가한다.
- 기본 설정은 Only Press인데, 이를 Press And Release로 변경하면 마우스를 누를 때뿐만 아니라 놓을 때도 이벤트가 발생한다.
이를 이용해 마우스가 현재 눌린 상태인지를 감지할 수 있다.
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerWeapon : MonoBehaviour
{
private bool isFiring = false;
private void OnFire(InputValue value)
{
isFiring = value.isPressed;
}
}
- OnFire(InputValue value): 마우스를 클릭할 때마다 호출된다.
- value.isPressed: 마우스를 누르면 true, 놓으면 false가 된다.
Only Press 설정에서는 isFiring이 true로만 변경되지만,
Press And Release 설정에서는 눌릴 때 true, 놓을 때 false로 자동 변경된다.
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerWeapon : MonoBehaviour
{
[SerializeField] private GameObject laser; // 레이저 파티클 시스템
private bool isFiring = false;
private void Update()
{
ProcessFiring();
}
private void OnFire(InputValue value)
{
isFiring = value.isPressed;
}
private void ProcessFiring()
{
var emmisionModule = laser.GetComponent<ParticleSystem>().emission;
emmisionModule.enabled = isFiring;
}
}
- Update()에서 ProcessFiring()을 호출하여 프레임마다 파티클 활성화 여부를 체크한다.
- isFiring 값이 true이면 레이저 발사, false면 멈춘다.