마우스 커서를 적용하고 관리하는
CursorManager와 키 입력을 관리할InputManager에 대해 배웠다
MouseManager마우스의 텍스쳐를 적용시키고, 마우스 커서의 활성화 비활성화 & 잠금까지 적용시켜보자
마우스 커서는 씬이 바뀌더라도 파괴되지 않고 그대로 적용되기 때문에 싱글톤을 상속받는다
마우스 텍스쳐는 Resources 폴더에 넣고 런타임 중에 폴더에 들어있는 텍스쳐를 문자열 매칭을 통해 가져온다
Resources 폴더 참고 : https://velog.io/@yangju058/Unity-13.-런-게임-기획-구상
커서 이미지 타입은 Cursor로 지정한다

Resources.Load<타입>("경로이름");

Cursor 클래스 사용마우스 커서를 제어하는 데 사용되는 Cursor 클래스
-> 이 클래스를 사용하여, 커서를 활성/비활성화 & 잠금 & 커서 이미지 변경이 가능하다
이 클래스는 UnityEngine 네임스페이스에 포함되어있다
Cursor.visiblevisible은 bool 타입으로 true로 설정하면 커서 활성화(커서 보임), false로 설정하면 비활성화(커서 숨김)
Cursor.lockState커서의 잠금 상태를 제어한다
CursorLockMode.None : 커서 자유롭게 움직임 가능
CursorLockMode.Locked : 커서를 화면 중앙에 잠금상태로
CursorLockMode.Confined : 커서를 게임 화면 안에 제한하여, 화면 밖으로 나가지 않게 함

Cursor.SetCursor()커서의 이미지를 변경하고, 커서의 위치, 커서 모드 설정 가능
Cursor.SetCursor(Texture2D texture, Vector2 hotspot, CursorMode cursorMode);
Texture2D texture : 커서로 사용할 이미지
Vector2 hotspot : 커서의 핸들 위치를 지정하는 Vector2의 값
-> 이미지에서 클릭이 발생하는 지점
CursorMode cursorMode : 주로 ForceSoftware 사용
-> 커서를 소프트웨어 모드로 설정하여 가속 커서를 사용하지 않게 함
-> 커서가 픽셀 정확도로 움직일때 유용하다

타이틀 씬에서 마우스를 활성화 하고, 게임 씬에 들어가면 마우스 비활성화 & 잠금
매개변수로 들어오는 state 값은 씬 번호
SetCursor()를 사용하여 커서의 이미지를 설정
타이틀씬(0) 이라면 커서 활성화 & 잠금모드 No
게임씬(1) 이라면 커서 비활성화 & 잠금모드 On

case를 사용한 코드


SceneManager 클래스는 씬을 관리하는 클래스이다sceneLoaded는 씬이 로드될 때 발생하는 이벤트이다OnSceneLoaded는 이벤트를 구독한 함수이다즉, 씬이 로드될때 구독된
OnSceneLoaded함수가 호출되는 것
scene을 접근하여 buildIndex를 얻을 수 있다State함수의 매개변수로 들어갈 값 (씬 번호)
이유는?
라이프 사이클 상, texture2D를 적용하는 로직이 Start()에 있을 경우, Start() 보다 OnEnable()이 먼저 실행된다
-> 따라서 텍스쳐 적용보다 이벤트 등록이 먼저 되기 때문에 타이틀 화면에서는 텍스쳐가 적용되지 않는다
첫 타이틀 씬이 로드될때, 이벤트로 등록된 OnSceneLoaded가 호출된다
-> 근데 Start보다 OnEnable이 먼저 호출되기 때문에 State 함수가 먼저 등록되고 그 뒤에 텍스쳐가 적용되는 것 (Start())

그럼 텍스쳐 적용을 OnEnable보다 먼저 실행하는 Awake에 정의해두면 된다
부모의 Awake()를 호출하지 않고, 텍스쳐 로직만 작성할 경우 그냥 부모의 Awake()가 덮여져버린다
이럴땐 override 재정의를 통해, 싱글톤 Awake()는 부모에 정의한 함수 호출 : base.Awake();
내가 따로 작성한 코드를 추가하면 된다

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class MouseManager : Singleton<MouseManager>
{
[SerializeField] Texture2D texture2D;
protected override void Awake()
{
// 부모 Awake 호출하기
base.Awake();
texture2D = Resources.Load<Texture2D>("Default");
}
// Start보다 OnEbalble이 먼저 실행된다
private void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
}
public void State(int state)
{
switch(state)
{
case 0:
{
Cursor.visible = true;
Cursor.lockState = CursorLockMode.None;
}
break;
case 1:
{
Cursor.visible = false;
Cursor.lockState = CursorLockMode.Locked;
}
break;
}
Cursor.SetCursor(texture2D, Vector2.zero, CursorMode.ForceSoftware);
}
void OnSceneLoaded(Scene scene, LoadSceneMode loadSceneMode)
{
State(scene.buildIndex);
}
private void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
}
}
InputManager지금 까진 키 입력을 받을때, Update()에서 조건문을 사용해서 어떤 키 입력을 받았는지에 따라 움직임을 처리했다
하지만 다양한 키에서 입력이 많아질 경우, 모든 로직을 다 Update()에 작성해야할까?
Update()는 매 프레임 마다 실행된다
-> 즉, 매 프레임마다 내가 누른 키가 어떤 키인지를 모두 확인한다
이 방법은 키가 많아질수록 매우 비효율적이다
📌 이렇게 처리하지 말고, InputManager를 만들고 '키 입력이 들어왔을때만' 어떤 키인지 확인해보자
ActionAction은 매개변수와 반환 값이 없는 메서드들을 담을 수 있는 델리게이트(대리자) 타입
Action 타입의 변수 생성
-> 이 변수는 대리자(Delegate) 역할을 하며 이벤트 핸들러(메서드)를 구독할 수 있다
특정 조건을 만족하여 변수.Invoke();를 호출하면 구독된 이벤트 함수들이 호출된다


InputManager를 싱글톤으로 사용하는 이유지금 만들고 있는 런게임에서는 키 입력이 캐릭터밖에 없지만, 더 큰 게임을 만든다면 캐릭터, UI창 열기, 인벤토리 열기 등등.. 키 입력이 많아질 것이다
각 스크립트에서 Action에 이벤트를 구독하고 사용하는 게 좋다
-> 이벤트 구독(등록)은 각 스크립트의 OnEnable과 OnDisable에서 처리한다
InputManager 스크립트using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;
using System;
public class InputManager : Singleton<InputManager>
{
public Action action;
void Update()
{
// 키 입력이 들어오지 않았다면
if(Input.anyKey == false)
{
return;
}
// 키 입력이 들어왔다면 이벤트 실행 (등록된 이벤트들 호출)
if(action != null)
{
action.Invoke();
}
}
}
Runner 스크립트 -> InputManager 이벤트 구독using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public enum RoadLine
{
LEFT = -1,
MIDDLE = 0,
RIGHT = 1
}
public class Runner : MonoBehaviour
{
[SerializeField] RoadLine roadline;
[SerializeField] Rigidbody rigidBody;
[SerializeField] Animator animator;
[SerializeField] int positionX = 4;
[SerializeField] float speed = 20;
void Start()
{
rigidBody = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
}
private void OnEnable()
{
InputManager.Instance.action += OnKeyUpdate;
}
private void FixedUpdate()
{
rigidbodyMove();
}
void OnKeyUpdate()
{
if (Input.GetKeyDown(KeyCode.LeftArrow))
{
if (roadline != RoadLine.LEFT)
{
roadline--;
animator.Play("LeftAvoid");
}
}
if (Input.GetKeyDown(KeyCode.RightArrow))
{
if (roadline != RoadLine.RIGHT)
{
roadline++;
animator.Play("RightAvoid");
}
}
}
void rigidbodyMove()
{
rigidBody.position =
Vector3.Lerp
(
rigidBody.position,
new Vector3((int)roadline * positionX, 0, 0),
Time.deltaTime * speed
);
}
private void Disable()
{
InputManager.Instance.action -= OnKeyUpdate;
}
}
Linear Interpolation, Lerp)두 값 사이를 일정 비율로 보간하여 중간 값을 계산하는 방법



Lerp를 사용하여 플레이어의 좌우 움직임을 부드럽게 연출해보자
-> 이동이기 때문에 Vector타입의 선형 보간을 사용한다



Lerp를 사용하여 오브젝트를 이동시킬때의 카메라가 떨리는 현상현재 플레이어 오브젝트에 시네머신 카메라 (버츄얼)을 넣어뒀다
플레이어 오브젝트의 rigidbody 컴포넌트의 프로퍼티에서 interpolate로 설정해주자

