공부/인프런 - Rookiss

Part 3-13-4. 미니 RPG : 공격

셩잇님 2023. 8. 31. 19:03
반응형

 

 

미니 RPG

 

세팅

📜CursorController

커서에 관련된 부분을 따로 빼서 이사시킴

 

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

public class CursorController : MonoBehaviour
{
	int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster);

	Texture2D _attackIcon;
	Texture2D _handIcon;

	enum CursorType
	{
		None,
		Attack,
		Hand,
	}

	CursorType _cursorType = CursorType.None;

	void Start()
    {
		_attackIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Attack");
		_handIcon = Managers.Resource.Load<Texture2D>("Textures/Cursor/Hand");
	}

    void Update()
    {
		if (Input.GetMouseButton(0))
			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;
				}
			}
		}
	}
}

 

📜GameScene

public class GameScene : BaseScene
{
    protected override void Init()
    {
        //...
        gameObject.GetOrAddComponent<CursorController>();

 

공격하기

1. 보스를 한 번만 클릭하면 👉 한 번만 때림

2. 보스를 한 번 클릭한 상태에서 안떼고 계속 누르고 있는 상태 👉 보스에게 달려가서 마우스를 뗄 때까지 계속 때림

 

공격 애니메이션

 

 

공격 애니메이션이 재생이 얼추 되고나서 다시 재생하게끔 텀을 주어야 한다. 게속 클릭하면 공격 애니메이션이 그 클릭에 맞춰 새롭게 재생되서 부자연스럽고 이상할 것이다. (칼을 휘두르기 시작하는 장면만 계속 재생하게 되는 모습이 나오기 때문)

 

 그리고 공격 애니메이션 얼추 끝나가면 계속 공격을 해도 되는건지 멈춰야 하는건지 알 수 있어야 한다. 따라서 공격 애니메이션이 끝나갈즈음부터 애니메이션이 재생될 수 있게끔 해야 한다. 애니메이션 끝나가는 즈음을 알 수 있도록 공격 애니메이션 파일의 Events 부분에서 애니메이션이 끝나갈 때 즈음 “OnHitEvent”를 발생시키도록 한다. 그러면 동일한 이름의 OnHitEvent() 를 정의해주면 이 이벤트가 발생할 때 이 함수가 실행할 수 있게 된다.

 

Apply Root Motion 해제하면 공격 중에도 이동을 하지 않는다.

👉 공격 애니메이션 클립 자체가 이동량이 있음. Animation Controller에서 이를 해제하면 이 애니메이션의 이동을 따라가지 않겠다는 의미가 된다.

 

📜PlayerController

bool _stopSkill = false;

	public PlayerState State // State를 애니메이션과 같이 관리
	{
		get { return _state; }
		set
		{
			_state = value; // 상태 세팅

            // 애니메이션 재생
			Animator anim = GetComponent<Animator>();
			switch (_state)
			{
				case PlayerState.Die:
					break;
				case PlayerState.Idle:
					anim.CrossFade("WAIT", 0.1f);
					break;
				case PlayerState.Moving:
					anim.CrossFade("RUN", 0.1f);
					break;
				case PlayerState.Skill:
					anim.CrossFade("ATTACK", 0.1f, -1, 0f);
					break;
			}
		}
	}

 

_stopSkill이 true면 공격을 멈추게 할 것이다.

State프로퍼티 👉 상태 세팅과 애니메이션은 거의 한 세트 마냥 붙어 있게 되므로 아예 상태 프로퍼티를 만들어서 상태 세팅 안에 애니메이션 재생 코드를 넣어 둘이 함께 붙어다니게끔 한다.

  • 이런것도 굉장히 유용할 코딩 스킬인 것 같다. 상태 세팅 뒤에는 항상 애니메이션 재생이 뒤따른다면 상태를 프로퍼티로 만들어서 세팅시 이어서 애니메이션이 재생될 수 있도록 하는 것!

 

 

애니메이션 재생

anim.CrossFade

anim.Play 를 부드럽게 하는 것이라고 보면 된다. 부드럽고 자연스럽게 블렌딩되듯 애니메이션이 재생된다. anim.Play를 사용하면 재생할 애니메이션 클립이 바뀔 때 뚝뚝 끊기듯이 바뀌어 부자연스럽다.

 

anim.CrossFade 는 애니메이션이 아주 자연스럽게 바뀌게 된다.

첫 번째 인수 : 재생할 클립 이름

두 번째 인수 : 지연 시간 즉. 다음 애니메이션으로 바뀌는데 걸리는 fade 시간이다. (0.1f 라면 바뀔 애니메이션의 10%는 부드럽게 애니메이션이 이어지는데 쓴다.)

세 번째 인수 : layer. 애니메이션 레이어

네 번째 인수(normalizedTimeOffset) : 애니메이션의 재생 시작 시점(0~1) 0.0f 로 세팅하면 다시 처음으로 돌아가 재생한다.  클립의 Loop Time이 체크되어 있지 않더라도 이렇게 코드로 반복 재생 시킬 수 있다.

 

파라미터 및 Transsition과 조건을 사용하지 않고 바로 애니메이션을 재생 시킨 이유

  • 공격 파라미터가 false 이면 걸을 수도 있고 뛸 수도 있다. 두 상태가 다 가능하므로 또 애매해져서 이를 구별할 스피드 파라미터가 또 필요한 것이다. 이런식으로 파라미터가 많아지면 복잡하다.
  • 차라리 이렇게 파라미터를 가지고 하는 Transition 조건으로 애니메이션을 재생 시키지 않고 그냥 Play 시키듯 애니메이션 클립을 재생 딱 시키는게 더 나을 수 있다! 👉 부드럽게 Play 하는 CrossFade 사용.

 

 

트랜지션 다 없앴다!

 

	void OnHitEvent()
	{
		Debug.Log("OnHitEvent");

		// TODO
		if (_stopSkill)
		{
			State = PlayerState.Idle;
		}
		else
		{
			State = PlayerState.Skill;
		}
	}

 

애니메이션 끝나갈 때즘 _stopSkill 값을 알려주어 Idle을 재생할지 마저 Skill 공격할지 결정할 수 있도록 한다.

 

📜PlayerController

	void UpdateMoving()
	{
		// 몬스터가 내 사정거리보다 가까우면 공격
		if (_lockTarget != null)
		{
			_destPos = _lockTarget.transform.position;
			float distance = (_destPos - transform.position).magnitude;
			if (distance <= 1)
			{
				State = PlayerState.Skill;
				return;
			}
		}
        // 이동 
        // ...
    }

 

UpdateMoving

이동하다가 몬스터가 내 사정거리보다 가까우면 공격하도록 변경해야 한다.

 

_lockTarget이 null이 아니라는건 공격해야 한다는 의미

  • _destPos와 distance는 공격할 몬스터 기준으로 설정
  • distance가 1 보다 작으면 공격 가능하도록 상태를 변경
  • 그리고 이동 안하도록 곧장 빠져 나오기

 

 

    void Update()
    {
		switch (State)
		{
			case PlayerState.Die:
				UpdateDie();
				break;
			case PlayerState.Moving:
				UpdateMoving();
				break;
			case PlayerState.Idle:
				UpdateIdle();
				break;
			case PlayerState.Skill:
				UpdateSkill();
				break;
		}
	}

    void UpdateSkill()
	{
		if (_lockTarget != null)
		{
			Vector3 dir = _lockTarget.transform.position - transform.position;
			Quaternion quat = Quaternion.LookRotation(dir);
			transform.rotation = Quaternion.Lerp(transform.rotation, quat, 20 * Time.deltaTime);
		}
	}

 

공격 상태가되면 자동으로 UpdateSkill가 실행 된다. (플레이어가 공격 대상을 바라보게끔 부드럽게 회전한다!)

 

    void OnMouseEvent(Define.MouseEvent evt)
	{
		switch (State)
		{
			case PlayerState.Idle:
				OnMouseEvent_IdleRun(evt);
				break;
			case PlayerState.Moving:
				OnMouseEvent_IdleRun(evt);
				break;
			case PlayerState.Skill:
				{
					if (evt == Define.MouseEvent.PointerUp)
						_stopSkill = true;
				}
				break;
		}
	}

    void OnMouseEvent_IdleRun(Define.MouseEvent evt)
	{
		switch (evt)
		{
            case Define.MouseEvent.PointerDown: // 처음으로 누른 순간
				{
					if (raycastHit)
					{
						_destPos = hit.point;
						State = PlayerState.Moving;
						_stopSkill = false;
                        //...
                    }
                }
			//..
			case Define.MouseEvent.PointerUp:  // 0.2초 이상 누르다가 뗐을 때
				_stopSkill = true;
				break;
		}

 

공격 중인데 마우스 떼는 이벤트가 들어왔다면 공격 그만해야 하므로 _stopSkill이 true가 되야 한다.

 

Idle이거나 달리는 중인데

1. 처음으로 마우스를 눌렀다면 공격해야 하므로 _stopSkill이 false가 되야 한다.

2. 마우스를 꾹 누르다가 뗏다면 공격 그만해야 하므로 _stopSkill이 true가 되야 한다.

 

 

 

반응형