[Unity] 적 생성 및 스탯, 마우스 커서

에셋 불러오기

https://assetstore.unity.com/packages/3d/characters/animals/dog-knight-pbr-polyart-135227

위 에셋을 적으로 사용했다.

https://assetstore.unity.com/packages/2d/textures-materials/basic-rpg-cursors-139404

위 에셋을 커서로 사용했다.

스탯 만들기

적과 플레이어의 전투를 위해 스탯을 관리하는 스크립트를 생성하도록 한다.

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

public class Stat : MonoBehaviour
{
    [SerializeField]
    protected int _level;
    [SerializeField]
    protected int _hp;
    [SerializeField]
    protected int _maxHp;
    [SerializeField]
    protected int _attack;
    [SerializeField]
    protected int _defense;
    [SerializeField]
    protected float _moveSpeed;

    public int Level { get { return _level; } set { _level = value; } }
    public int Hp { get { return _hp; } set { _hp = value; } }
    public int MaxHp { get { return _maxHp; } set { _maxHp = value; } }
    public int Attack { get { return _attack; } set { _attack = value; } }
    public int Defense { get { return _defense; } set { _defense = value; } }
    public float MoveSpeed { get { return _moveSpeed; } set { _moveSpeed = value; } }

    private void Start()
    {
        _level = 1;
        _hp = 100;
        _maxHp = 100;
        _attack = 10;
        _defense = 5;
        _moveSpeed = 5.0f;
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerStat : Stat
{
    [SerializeField]
    protected int _exp; 
    [SerializeField]
    protected int _gold;

    public int Exp { get { return _exp; } set { _exp = value; } }
    public int Gold { get { return _gold; } set { _gold = value; } }
    private void Start()
    {
        _level = 1;
        _hp = 100;
        _maxHp = 100;
        _attack = 10;
        _defense = 5;
        _moveSpeed = 5.0f;
        _exp = 0;
        _gold = 0;
    }
}

플레이어에게만 필요한 스탯이 따로 있기 때문에 PlayerStat이라는 스크립트를 하나 더 생성하여 플레이어의 스탯을 관리한다.

이후 플레이어의 Prefab에 PlayerStat 스크립트를 컴포넌트로 추가하고, 방금 추가한 적의 프리팹에는 일반 Stat 스크립트를 컴포넌트로 추가해준다.

마우스 커서

이제는 게임 내에서 적군을 클릭하거나 적군 위에 마우스를 올렸을 때의 경우를 따로 처리하고 싶다. 이를 위해 적군의 Layer를 Monster로 설정해준다.그리고 기존의 Wall 레이어를 편의를 위해 이름을 Ground로 변경해 주었다.

코드 수정에 앞서, 에셋 스토어에서 불러온 커서 에셋의 텍스쳐 타입을 Cursor로 변경해준다.빌드 세팅에서 기본 커서를 변경할 수 있지만 우리는 상황에 따라 계속해서 커서의 모양이 변경되기를 바라기 때문에 커서의 변경을 따로 코드로 작성하도록 한다.

이제 PlayerController의 코드를 수정해야 한다. 그 전에 먼저 Layer의 관리를 편리하게 하기 위해 Define 클래스에 enum을 하나 추가해준다.

public enum Layer
{
    Monster = 8,
    Ground = 9,
    Block = 10,
}

이제 PlayerController를 수정한다.

/*
[SerializeField]
float _speed = 10.0f;
위 부분을 아래 코드로 대체한다.
*/
PlayerStat _stat;

Texture2D _attackIcon;
Texture2D _handIcon;
// 커서의 모양을 바꾸기 위해 커서의 텍스쳐를 저장하는 변수를 선언한다.

Vector3 _destPos;


void Start()
{
    _stat = gameObject.GetComponent<PlayerStat>();
    // 스탯 정보를 가져오는 코드를 추가한다.
    _attackIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Attack");
    _handIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Hand");
    // 위에 선언한 변수에 Resource Manager를 이용해 텍스쳐들을 저장한다.
    Managers.Input.MouseAction -= OnMouseClicked;
    Managers.Input.MouseAction += OnMouseClicked;
    // Action을 두번 구독하는 경우가 생기지 않도록 먼저 구독을 끊고 다시 구독을 한다.
}

먼저 Stat 클래스를 통해 플레이어의 속도를 정하기로 했으므로 기존에 사용하던 변수를 제거한다.
그리고 커서의 모양을 설정하기 위해 텍스쳐를 저장하기 위한 변수를 선언하고, Start() 함수에서 변수에 텍스쳐를 저장한다.

void Update()
{
    UpadateMouseCursor();
    switch (_state)
    {
        case PlayerState.Idle:
            UpdateIdle();
            break;
        case PlayerState.Moving:
            UpdateMoving();
            break;
        case PlayerState.Die:
            UpdateDie();
            break;
    }
}

void UpadateMouseCursor()
{
    if (_state == PlayerState.Die)
        return;

    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100.0f, _mask))
    {
        if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
        {
            Cursor.SetCursor(_attackIcon, new Vector2(_attackIcon.width / 5, 0), CursorMode.Auto);
        }
        else
        {
            Cursor.SetCursor(_handIcon, new Vector2(_handIcon.width / 3, 0), CursorMode.Auto);
        }
    }
}

적 위에 마우스를 올리기만 할 경우에도 마우스 커서가 변화하길 원하기 때문에 기존처럼 이벤트를 사용해서 처리하는것은 한계가 있다. 따라서 Update() 함수 내에서 레이캐스팅을 하며 마우스 커서가 몬스터 위에 있는지 판단할 필요가 있다.

SetCursor 함수의 두번째 인자는 커서 아이콘의 어느 부분이 마우스의 위치가 되는지 정하기 위해 넣는 값이다.

int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster);

void OnMouseClicked(Define.MouseEvent evt)
{
    if (_state == PlayerState.Die)
        return;

    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    // Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);

    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100.0f, _mask))
    {
        _destPos = hit.point;
        _state = PlayerState.Moving;

        if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
        {
            Debug.Log("Monster");
        }
        else
        {
            Debug.Log("Ground");
        }
    }
}

Define 클래스에서 선언한 enum을 사용해 _mask라는 변수를 선언하여 사용하였다.
기존처럼 레이캐스팅 시에 GetMast() 함수를 통해 String 값으로 레이어의 정보를 불러오는 대신에 _mask 변수를 인자로 넘겨주는 방식으로 변경하였다.

코드를 수정하고 테스트 해보니 원하는 대로 작동하는 것을 확인하였다. 하지만 계속해서 커서가 깜빡이는 오류가 발생하였는데, 이는 Update() 함수에서 프레임 단위로 계속해서 커서를 변경하다 보니 생기는 오류이다.

따라서 커서의 모양이 변화할 필요가 없을 경우에는 따로 커서를 변경하지 않도록 수정해야 한다.

enum CursorType
{
    None, 
    Attack,
    Hand,
}

void UpadateMouseCursor()
{
    if (_state == PlayerState.Die)
        return;

    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100.0f, _mask))
    {
        if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
        {
            if (_cursorType != CursorType.Attack)
            {
                Cursor.SetCursor(_attackIcon, new Vector2(_attackIcon.width / 5, 0), CursorMode.Auto);
                _cursorType = CursorType.Attack;
            } // 마우스가 적에 위치할 때 커서의 모양이 공격이 아니라면 커서의 모양을 변화한다.
        }
        else
        {
            if (_cursorType != CursorType.Hand)
            {
                Cursor.SetCursor(_handIcon, new Vector2(_handIcon.width / 3, 0), CursorMode.Auto);
                _cursorType = CursorType.Hand;
            } // 마우스가 바닥에 위치할 때 커서의 모양이 일반이 아니라면 커서의 모양을 변화한다.

        }
    }
}

수정한 뒤에 오류가 해결되는 것을 확인했다.

좋은 웹페이지 즐겨찾기