[Unity] 17. Cursor & Input Manager

치치·2025년 3월 19일
0

Unity

목록 보기
19/27
post-thumbnail

마우스 커서를 적용하고 관리하는 CursorManager와 키 입력을 관리할 InputManager에 대해 배웠다

MouseManager

마우스의 텍스쳐를 적용시키고, 마우스 커서의 활성화 비활성화 & 잠금까지 적용시켜보자

마우스 커서 텍스쳐 적용

  1. 마우스 커서는 씬이 바뀌더라도 파괴되지 않고 그대로 적용되기 때문에 싱글톤을 상속받는다

  2. 마우스 텍스쳐는 Resources 폴더에 넣고 런타임 중에 폴더에 들어있는 텍스쳐를 문자열 매칭을 통해 가져온다

Resources 폴더 참고 : https://velog.io/@yangju058/Unity-13.-런-게임-기획-구상

커서 이미지 타입은 Cursor로 지정한다

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

Cursor 클래스 사용

마우스 커서를 제어하는 데 사용되는 Cursor 클래스
-> 이 클래스를 사용하여, 커서를 활성/비활성화 & 잠금 & 커서 이미지 변경이 가능하다

이 클래스는 UnityEngine 네임스페이스에 포함되어있다


  • Cursor.visible

visiblebool 타입으로 true로 설정하면 커서 활성화(커서 보임), false로 설정하면 비활성화(커서 숨김)


  • Cursor.lockState

커서의 잠금 상태를 제어한다

  1. CursorLockMode.None : 커서 자유롭게 움직임 가능

  2. CursorLockMode.Locked : 커서를 화면 중앙에 잠금상태로

  3. CursorLockMode.Confined : 커서를 게임 화면 안에 제한하여, 화면 밖으로 나가지 않게 함


  • Cursor.SetCursor()

커서의 이미지를 변경하고, 커서의 위치, 커서 모드 설정 가능

Cursor.SetCursor(Texture2D texture, Vector2 hotspot, CursorMode cursorMode);

매개변수로 들어가는 값

  1. Texture2D texture : 커서로 사용할 이미지

  2. Vector2 hotspot : 커서의 핸들 위치를 지정하는 Vector2의 값
    -> 이미지에서 클릭이 발생하는 지점

  3. 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에 정의해두면 된다

Q . 부모 싱글톤에 정의 해둔 Awake()는?

부모의 Awake()를 호출하지 않고, 텍스쳐 로직만 작성할 경우 그냥 부모의 Awake()가 덮여져버린다

이럴땐 override 재정의를 통해, 싱글톤 Awake()는 부모에 정의한 함수 호출 : base.Awake();
내가 따로 작성한 코드를 추가하면 된다



MouseManager 스크립트

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를 만들고 '키 입력이 들어왔을때만' 어떤 키인지 확인해보자

Action

Action은 매개변수와 반환 값이 없는 메서드들을 담을 수 있는 델리게이트(대리자) 타입

  1. Action 타입의 변수 생성
    -> 이 변수는 대리자(Delegate) 역할을 하며 이벤트 핸들러(메서드)를 구독할 수 있다

  2. 특정 조건을 만족하여 변수.Invoke();를 호출하면 구독된 이벤트 함수들이 호출된다


이벤트랑 동일한 처리 아닌가?


InputManager를 싱글톤으로 사용하는 이유

지금 만들고 있는 런게임에서는 키 입력이 캐릭터밖에 없지만, 더 큰 게임을 만든다면 캐릭터, UI창 열기, 인벤토리 열기 등등.. 키 입력이 많아질 것이다

각 스크립트에서 Action에 이벤트를 구독하고 사용하는 게 좋다
-> 이벤트 구독(등록)은 각 스크립트의 OnEnableOnDisable에서 처리한다



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로 설정해주자

profile
뉴비 개발자

0개의 댓글