나 개발자 진짜 되냐?

[ Unity 뱀서라이크 게임을 만들어보자 2 ] Input 그리고 이동 구현 본문

유니티를 공부해봐요!/초급이에요!

[ Unity 뱀서라이크 게임을 만들어보자 2 ] Input 그리고 이동 구현

Snow Rabbit 2024. 10. 8. 13:21

 

지난 시간에 이어

계속 좋은 코드를 만들기 위한 수정이 있을 예정이다.

 

그다음

Input에 대해 조금 깊게 알아볼 것이다.

지난 블로그에도 작성했지만

Input은 입력해 주는 친구들로

마우스 조이스틱 키보드 등등

외부에서 입력받을 수 있도록 해준다.

 

하지만 이 Input한테도 더 좋은 방향이 있다는데...

 

함께 알아보자!


 

유니티에는 

InputManager라는 친구가 있다.

 

하지만 이 친구의 문제점이 하나 있다.

이 친구는 기능들이 한 클래스에 들어있어서

확장성과 유지보수가 많이 떨어진다고 합니다.

 

즉,

기능별로 클래스를 나누는 설계가 도움이 되고

이것을 우리는 단일책임원칙 이라고 합니다.!

 

그래서 업그레이드된 친구

NEW Input System

를 사용합니다.

 

 

🌟 NEW Input System 🌟

 

장점

 

다양한 플랫폼과 입력 장치에 대해 일관된 방식으로 작동

 

플레이어가 게임 내에서 자신의 입력 설정을 변경 가능

 

여러 플레이어가 동일한 장치에서 게임을 플레이하거나,

각각의 장치에서 게임을 플레이할 때 입력을 쉽게 처리

 

핵심 개념

 

Input Action

 

이름만 봐도 아는 개념!

입력할 행동!

예를 들어 "점프", "공격" 등의 행동을 정의하고,

이러한 행동을 트리거하는 키 또는 버튼을 지정할 수 있다.

 

Input Action Asset

 

위에 나온 인풋 액션들을 모아둔 그룹이라고 볼 수 있다.

이를 통해 재사용 가능한 입력 설정을 만들어

게임 내의 다른 캐릭터나 메뉴에 적용할 수 있다.

 

Player Input Component

Unity의 New Input System에 추가된 새로운 컴포넌트로,

간단하게 말하면 위에 그룹들 그리고 인풋액션들을 처리하고 적용하는 기능이다.

자동으로 입력 행동을 처리하고 해당 게임 오브젝트에 메시지를 보내는 기능이 있다.

 

 

자!

우리는 이제 이 input친구를 쓰려면

패키지를 따로 받아야 한다.

 

Window > 맨 위에 Package Manager를 Unity Register로 변경 > Input System > Install

 

 

 

자! 다음

input 파일 하나 만들어보자

Asset에서 만들고

그 안에 들어가서

Create > Input Action 클릭 > Top Down Controller 2D로 수정

 

다음에 신나게 눌러보면 어떤 창이 뜬다.

 

 

먼저 우리는 왼쪽 상단 역삼각형을 눌러

키보드와 마우스를 입력장치로 쓰겠다는 선언을 해주자!

 

 

이름설정하고 키보드마우스를 +로 선택한다.

우클릭으로 선택해야 하는 흠이 있다 ㅋㅎ 참고!

 

다음! 우리는 player이라는 맵을 만들어본다.

맵이라고 표현해서 조금 어색할 수 있는데

player의 인풋을 넣는다는 의미로,

이제 player의 움직임, 점프 뭐 행동등등을 여기서 관리할 것이다.

 

그래서!

 

이렇게 왼쪽에 +를 통해

플레이어라는 맵을 만들고

그 안에 액션에서 +

해서

move look fire를 추가해 준다.

 

move는 움직이는 것

look은 왔다 갔다 생동감주는 느낌으로 눈도 움직이고 얼굴 움직이게 하는 거

fire는 클릭했을 때

 

이렇게 나눠볼 수 있다.

 

 

다음으로 각각의 값을 정해줘야 하는데

 

이렇게 액션타입을 설정해주어야 한다.

벨류로 설정해 준다.

 

가장 일반적으로 사용되며

눌렀을 때, 누르고 있을 때, 뗄 때 등

다양한 상황에 대응할 수 있고,

다양한 컨트롤에 대응할 수 있다.

 

기본적으로는 버튼으로 되어있는데

버튼은 눌렀을 때 바로 발생하는 액션에 사용하며

처음에는 버튼으로 고정되어 있어서 벨류로 바꿔주어야 한다.

 

여기에 move나 look의 경우 상하좌우로 움직이기 때문에

벡터 2를 써줘야 한다.

상하를 합쳐서 벡터하나

좌우를 합쳐서 벡트하나

그래서 총 벡터 2다

 

그리고 fire는 벨류에

에니

뭐 포괄적인 친구이다.

 

 

다음!

 

다음에 우리는 바인딩을 설정해주어야 한다.

 

 

우리는 두 번째 거

상하좌우 다 있는 컴포짓을 누른다.

 

 

path을 누르고 저기 파란색을 누르면

빈창이 뜬다

여기에 우리가 쓸 키보드를 툭 치면

뜬다.

나는 wasd로 해줄 예정이다!

 

look은 어렵지 않다.

마우스 위치에 따라 오른쪽보고 왼쪽 보는 시스템이기 때문에

path > mouse > position 해주면 된다.

 

fire는 더 쉽다.

마우스왼쪽 클릭이라

아까랑 같은 방식으로

> left button 눌러주면 된다.

 

 

 

위에 save 잊지 말고 꼭! 해주자.. 안 그러면 휘리릭 날아간다

 

 

다음 이제 진짜 플레이어를 만들어보자

 

빈 오브젝트 생성 > 이름 player > transform 오른쪽키 reset

 

습관처럼 해야 하는 부분이라고 한다 ㅎㅋ

 

다음에

플레이어 밑에 빈오브젝트를 또 만들어주고

 Mainsprite를 만들어준다.

이렇게 해주는 이유는

나중에 뭐 위치나 회전 등등 변경이 있을 때

플레이어 자체에서 건들지 않게 하기 위해 만들어 둔 친구다.

 

다음에 이 안에다가 spriteRenderer

를 해준다.

 

그림은 knight f idle로 해주고

 

 

리지드바디 추가해서

아래로 내려가지 않게 0

회전하지 않게 z 축 막기

 

다음 player Input 추가

 

액션 넣고

만들어둔 것들을 하나씩 채 운다.

 

여기 밑에

Behavior 집중!

행동이라는 이 친구는

꼭 설정해주어야 한다.

 

종류가 4개 있는데

Send Messages 같은 게임오브젝트의 모든 컴포넌트에 뿌려지는 메시지를 보낸다.
Broadcast Messages 같은 게임오브젝트와 그 자식 게임오브젝트의 모든 컴포넌트에 뿌려지는 메시지를 보낸다.
Invoke Unity Event OnClick처럼 UnityEvent형태로 관리되며 편하다.
Invoke C Sharp Events PlayerInput.OnActionTriggered 에 호출하고 싶은 이벤트 등록해서 어떤 이벤트인지 확인해서 쓸 수 있다.

 

 

자!

이제 코드를 짜보자

스크립트 생성! 이름은 TopDownController

 

이 친구 안에

액션들을 넣어놓고

 

다른 곳에서 액션이 발생되면 했다는

정보가 여기에 등록된다.

 

그러면 등록에 따라

이 컨트롤러 스크립트 안에서 invoke를 사용해서

오케이 그에 맞는 행동을 또 보여준다.

이것을 이벤트 라고 하며

 

우리는 이벤트와 액션을 사용해서 스크립트를 작성할 것이다.

 

 

?.이라는 친구가 굉장히 신기했다.

없으면 말고라니

너.. 똑똑해졌구나?

 

자 다음

우리는 플레이어 컨트롤러를 만들어 줄 겁니다.

지금까지 변수를 다 탑다운 친구에게 넣어놨으니

상속을 받아야겠지요

 

이렇게 해준 후

클래스 누르고 ctrl. 을 누르면 

뭐 형식으로 이동 있는데

그 친구 누르면 스르르 그르륵 어디로 이동한다.

 

새로운 곳에 도착!

 

그리고 다시 코드를 써준다.

여기에는 플레이어 컨트롤이라서

먼저 카메라를 변수로 받아온다.

 

 

받아와서 카메라 태그가 붙어있는 정보를 변수에 넣는다.

 

 

다음에는 onMove라는 메서드를 만든다.

이 메서드 안에는 wasd값이 들어간다.

 

값이 들어가지 실제로 움직이는 부분은 여기서 해주지 않는다!!

 

onMove 해주었으니?

onLook과 onFire를 해주어야겠지요!

 

 

 

 

주석에 야무지게 써놨다!

 

여기서

Vector2 worldPos = _camera.ScreenToWorldPoint(newAim);

이 친구가 조금 어려웠는데

 

 

카메라가 비추는 곳은

전체에서 일부분이다.

 

그 월드에서 어느 한 부분 일 것이다.

 

우리는 카메라 속에 있는 어떠한 장소가

월드상으로 어딘지 알고 싶다.

 

그래서 우리는 이 코드를 통해

카메라에 있는 이 스크린값을

월드값으로 바꿔주라!라는 뜻이 된다.

 

마우스 기준을 월드 값으로 바꿔서

바로 밑줄에

마우스와 캐릭터와 거리가 얼마큼 차이나냐?를 구하게 된다.

A에서 B까지 가는 벡터는

B - A를 해주어야 한다,

 

그래서! 우리는 마지막 식이

 newAim = (worldPos - (Vector2) transform.position). normalized;

 

월드에서 캐릭터 값을 빼주면 된다.

 

이해가 안 가도

일단 외워가자

다음에 쓸 때 활용할 수 있게

이유를 따지지 말자

일단 외우자

A에서 B로 가는 벡터는

 B-A인 것을..

..

..

 

다음에 우리는

지금까지 플레이어한테 함수를 걸어놨으니

이제 동작하는 함수를 만들어보자

 

아까와 같이

TopDownMovement를 클래스로 만들고 작성한다.

 

 

Awake를 통해

get 해서 값을 가져온다.

 

다음 start에 컨트롤러를 사용해서

컨트롤러에 move라는 함수를 넣어줄 것이다.

 

 

move함수에는 실제로 움직이지는 않고 값만 넣어준다.

값만

넣어주어야 하니 변수도 필요하겠죠?

 

 

이 실제로 물리적인 움직임을 구현할 수 있는 친구는 따로 있다.

FixedUpdate

 

그러면 실제로 동작할 수 있게 하는 함수를 여기 넣는다.

 

applyMovement?

그 친구도 만들어보자

 

이렇게 해주면 완성

velocity는 지난 시간에도 봤지만

리지드바디 속도를 조절해 주는 친구이다.

그 값에 좌표를 넣어서 움직일 수 있게 한다!

 

 

정리하면

 

TopDownController은

몬스터와 사람 둘 다 사용이 가능한 공통적인 기능을 모아두었다.


 Invoke 해주기 위해 여기에 event를 달아놓았다.

인보크를 해줄 때 

PlayerInputController에서

바로 함수 쓰면 되는데

왜 굳이 onmove함수를 또 만들어서 인보크를 해주냐? 

 

이 답은

전처리가 필요하기 때문이다.
여기서 전처리작업은 정규화 뭐 값설정 등등이다.

 

이렇게 해주면

전처리 된 onMove를 실행시켜서

callMoveEvent로 가면

걔는

TopDownController 여기 있고

그렇게 이벤트에 의해 인보크가 된다.

값을 뿌려준다!라고 생각하면 된다.

 

후후

일단 이동 함수 틀은 이 정도에서 

끝내고

 

다음은 맵을 구현해보자!!

 

 

고생!!

 

너무 빨랐다고요?!

코드도 같이 첨부합니다 :)

 

< TopDownMovement.cs> 

using UnityEngine;

// 실제로 이동이 일어날 컴포넌트

public class TopDownMovement : MonoBehaviour
{
    //탑다운컨트롤러가 필요하다.
    private TopDownController movementController;
    //중력도 추가!, 이동해야하니까
    private Rigidbody2D movementRigidbody;
    //움직이는 초기값 설정
    private Vector2 movementDirection = Vector2.zero;

    private void Awake() // 내 컴포넌트 안에서 끝나는 것
    {
        //movementController와 TopDownMovement는 같은 게임오브젝트 안에 있구나 라고 생각
        //get 자체가 여기 없으면 없다고 말해주기 때문
        movementController = GetComponent<TopDownController>();
        //Rigidbody2D도 마찬가지
        movementRigidbody = GetComponent<Rigidbody2D>();
    }

    private void Start()
    {
        //이제 컨트롤러에 접근이 가능하니까 move라는 함수를 등록한다.
        movementController.onMoveEvent += Move;
    }

    private void FixedUpdate()
    {
        // 물리 업데이트관련
        // 리지드바디에서 값을 바꾸기 때문에 물리 업데이트
        // 실제로 여기서 작동
        ApplyMovement(movementDirection);
    }

    private void Move(Vector2 direction)
    {
        // 이동방향만 정해두고 실제로 움직이지는 않음.
        // 움직이는 것은 물리 업데이트에서 진행(rigidbody가 물리니까)
        movementDirection = direction;
    }

    private void ApplyMovement(Vector2 direction)
    {
        direction = direction * 5; // 정규화가 되어서 들어갔기때문에, 

        movementRigidbody.velocity = direction;
    }
}

 

< PlayerInputController.cs >

using UnityEngine;
using UnityEngine.InputSystem;
using static UnityEngine.RuleTile.TilingRuleOutput;

//player input controller 작성, topdown controller를 상속받도록 만든다.
public partial class PlayerInputController : TopDownController
{
    private Camera _camera;
    private void Awake()
    {
        //mainCamera태그붙어있는 카메라를 가져온다.
        _camera = Camera.main;
    }

    // WSAD 값을 누르면 여기 안으로 값이 들어온다.
    public void OnMove(InputValue value) 
    {
        // 크기가 1인 벡터 만들기
        Vector2 moveInput = value.Get<Vector2>().normalized;
        //상속받았기 때문에 잘 쓸 수 있다.
        CallMoveEvent(moveInput);
    }

    public void OnLook(InputValue value)
    {
        //여기는 마우스 위치라서 정규화 필요없다.
        Vector2 newAim = value.Get<Vector2>();
        //마우스 위치는 화면 좌표이기 때문에 월드로 바꿔주어야한다.
        Vector2 worldPos = _camera.ScreenToWorldPoint(newAim);
        //그 월드 값에서 벡터2로 설정해준 지금 위치를 뺀값을 정규화 해준다.
        newAim = (worldPos - (Vector2)transform.position).normalized;

        if (newAim.magnitude >= .9f)
        // Vector 값을 실수로 변환
        {   //인보크 실행된다.
            CallLookEvent(newAim);
        }
    }

    public void OnFire(InputValue value)
    {
        Debug.Log("OnFire" + value.ToString());
    }
}

 

< TopDownController.cs >

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 여기는 몬스터와 사람 둘다 사용이 가능한 공통적인 기능을 모아두었다.
// Invoke해주기위해 여기에 event를 달아놓았다.
public class TopDownController : MonoBehaviour
{
    //벡터2를 인자로 받는 함수를 만든다. action의 특 : void만 반환한다.
    public event Action<Vector2> onMoveEvent;
    public event Action<Vector2> onLookEvent;
    
    //moveevent가 발생했을때 인보크 해주는 함수
    public void CallMoveEvent (Vector2 direction) 
    {
        onMoveEvent?.Invoke (direction); //?.는 없으면 말고 있으면 실행한다.
    }

    public void CallLookEvent(Vector2 direction)
    {
        onLookEvent.Invoke (direction);
    }
}

 

 

엇 예쁜 이미지가 없다고요?

 

2DProjectAssets.unitypackage
2.77MB

 

< 이 프로젝트는 스파르타 코딩수업에서 배운 내용을 복습 한 내용입니다. >