Chapter 13-6. 미니 RPG : 공격
카테고리: Unity Lesson 2
태그: Unity Game Engine
인프런에 있는 Rookiss님의 [C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
Chapter 13. 미니 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>();
🚀 공격하기
- 보스를 한 번만 클릭하면 👉 한 번만 때림
- 보스를 한 번 클릭한 상태에서 안떼고 계속 누르고 있는 상태 👉 보스에게 달려가서 마우스를 뗄 때까지 계속 때림
✈ 공격 애니메이션
공격 애니메이션이 재생이 얼추 되고나서 다시 재생하게끔 텀을 주어야 한다. 게속 클릭하면 공격 애니메이션이 그 클릭에 맞춰 새롭게 재생되서 부자연스럽고 이상할 것이다. (칼을 휘두르기 시작하는 장면만 계속 재생하게 되는 모습이 나올 것이다.) 그리고 공격 애니메이션 얼추 끝나가면 계속 공격을 해도 되는건지 멈춰야 하는건지 알 수 있어야 한다. 따라서 공격 애니메이션이 끝나갈즈음부터 애니메이션이 재생될 수 있게끔 해야 한다. 애니메이션 끝나가는 즈음을 알 수 있도록 공격 애니메이션 파일의 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
이 체크되어 있지 않더라도 이렇게 코드로 반복 재생 시킬 수 있다.
- anim.Play 를 부드럽게 하는 것이라고 보면 된다. 부드럽고 자연스럽게 블렌딩되듯 애니메이션이 재생된다.
- 파라미터 및 Transsition과 조건을 사용하지 않고 바로 애니메이션을 재생 시킨 이유
- 공격 파라미터가 false 이면 걸을 수도 있고 뛸 수도 있다. 두 상태가 다 가능하므로 또 애매해져서 이를 구별할 스피드 파라미터가 또 필요한 것이다. 이런식으로 파라미터가 많아지면 복잡하다.
- 차라리 이렇게 파라미터를 가지고 하는 Transition 조건으로 애니메이션을 재생 시키지 않고 그냥 Play 시키듯 애니메이션 클립을 재생 딱 시키는게 더 나을 수 있다!
- 부드럽게 Play 하는 CrossFade 사용.
- anim.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이거나 달리는 중인데
- 처음으로 마우스를 눌렀다면 공격해야 하므로
_stopSkill
이 false가 되야 한다. - 마우스를 꾹 누르다가 뗏다면 공격 그만해야 하므로
_stopSkill
이 true가 되야 한다.
- 처음으로 마우스를 눌렀다면 공격해야 하므로
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기