BaseController.cs 생성
PlayerController와 MonsterController는 중복되는 메서드가 많이 발생한다. 이에 BaseController.cs를 생성하여 공용되는 부분은 상속받아 사용하도록 로직을 구성하자.
애니메이션 상태 정의
PlayerController.cs에서 enum으로 정의해놓은 PlayerState는 Monster도 사용할 수 있는 것이기 때문에 Define.cs에 State으로 정의해놓으면 훨씬 유용해진다.
현재 PlayerState로 명명된 코드를 모두 Define.State라고 일괄 변경한다.
ㄴ Monster의 애니메이션 이름도 기존 설정과 같도록 모두 변경 ("WAIT", "RUN", "ATTACK")
💡 BaseController.cs
public class BaseController : MonoBehaviour
{
[SerializeField] protected Define.State _state = Define.State.Idle;
[SerializeField] protected Vector3 _destPos;
[SerializeField] protected GameObject _lockTarget;
public virtual Define.State State
{
get { return _state; }
set
{
_state = value;
Animator anim = GetComponent<Animator>();
switch (_state)
{
case Define.State.Die:
break;
case Define.State.Idle:
anim.CrossFade("WAIT", 0.1f);
break;
case Define.State.Moving:
anim.CrossFade("RUN", 0.1f);
break;
case Define.State.Skill:
anim.CrossFade("ATTACK", 0.1f, -1, 0);
break;
}
}
}
private void Start()
{
Init();
}
public abstract void Init();
void Update()
{
//애니메이션 실행
switch (State)
{
case Define.State.Die:
UpdateDie();
break;
case Define.State.Moving:
UpdateMoving();
break;
case Define.State.Idle:
UpdateIdle();
break;
case Define.State.Skill:
UpdateSkill();
break;
}
}
protected virtual void UpdateDie() { }
protected virtual void UpdateMoving() { }
protected virtual void UpdateIdle() { }
protected virtual void UpdateSkill() { }
}
💡 PlayerController.cs
public class PlayerController : BaseController
{
int _mask = (1 << (int)Define.Layer.Ground) | (1 << (int)Define.Layer.Monster);
PlayerStat _stat;
bool _stopSkill = false;
publicoverride void Init()
{
_stat = gameObject.GetComponent<PlayerStat>();
Managers.Input.MouseAction -= OnMouseEvent;
Managers.Input.MouseAction += OnMouseEvent;
//HPBar 부착
Managers.UI.MakeWorldSpaceUI<UI_HPBar>(transform);
}
protected override void UpdateMoving() //이동 및 회전
{
//적이 player의 사정거리보다 가까우면 공격
if(_lockTarget != null)
{
_destPos = _lockTarget.transform.position;
float distance = (_destPos - transform.position).magnitude;
if(distance <= 1)
{
State = Define.State.Skill;
return;
}
}
Vector3 dir = _destPos - transform.position;
//player가 destination에 도착하면
if (dir.magnitude < 0.1f)
{
State = Define.State.Idle;
}
else
{
//AI Navigation에 따라 목적지로 이동
NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
float moveDist = Mathf.Clamp(_stat.MoveSpeed * Time.deltaTime, 0, dir.magnitude);
nma.Move(dir.normalized * moveDist);
//목적지로 이동
//transform.position += dir.normalized * moveDist;
//장애물 판별 Raycast
Debug.DrawRay(transform.position + Vector3.up * 0.5f, dir.normalized, Color.green);
if(Physics.Raycast(transform.position + Vector3.up * 0.5f, dir, 1.0f, LayerMask.GetMask("Block")))
{
if (Input.GetMouseButton(0) == false)
{
State = Define.State.Idle;
}
return;
}
//목적지를 주시하며 이동 (자연스러운 회전)
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
}
}
protected override 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);
}
}
void OnHitEvent()
{
if(_lockTarget != null)
{
Stat targetStat = _lockTarget.GetComponent<Stat>();
PlayerStat myStat = GetComponent<PlayerStat>();
int damage = Mathf.Max(0,myStat.Attack - targetStat.Defense);
Debug.Log(damage);
targetStat.Hp -= damage;
}
if (_stopSkill)
{
State = Define.State.Idle;
}
else
{
State = Define.State.Skill;
}
}
void OnMouseEvent(Define.MouseEvent evt)
{
switch (State)
{
case Define.State.Die:
break;
case Define.State.Idle:
OnMouseEvent_IdleRun(evt);
break;
case Define.State.Moving:
OnMouseEvent_IdleRun(evt);
break;
case Define.State.Skill:
{
if(evt == Define.MouseEvent.PointerUp)
_stopSkill = true;
}
break;
}
}
void OnMouseEvent_IdleRun(Define.MouseEvent evt)
{
RaycastHit hit;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
bool raycastHit = Physics.Raycast(ray, out hit, 100.0f, _mask);
//Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
switch (evt)
{
case Define.MouseEvent.PointerDown:
{
if (raycastHit)
{
_destPos = hit.point;
State = Define.State.Moving;
_stopSkill = false;
if (hit.collider.gameObject.layer == (int)Define.Layer.Monster)
{
_lockTarget = hit.collider.gameObject;
}
else
{
_lockTarget = null;
}
}
}
break;
case Define.MouseEvent.Press:
{
if (_lockTarget == null && raycastHit)
{
_destPos = hit.point;
}
}
break;
case Define.MouseEvent.PointerUp:
_stopSkill = true;
break;
}
}
}
public class MonsterController : BaseController
{
Stat _stat;
public override void Init()
{
_stat = gameObject.GetComponent<Stat>();
//HPBar 부착
if(gameObject.GetComponent<UI_HPBar>() == null)
Managers.UI.MakeWorldSpaceUI<UI_HPBar>(transform);
}
protected override void UpdateDie()
{
Debug.Log("UpdateDie");
}
protected override void UpdateIdle()
{
Debug.Log("UpdateIdle");
}
protected override void UpdateMoving()
{
Debug.Log("UpdateMoving");
}
protected override void UpdateSkill()
{
Debug.Log("UpdateSkill");
}
void OnHitEvent()
{
Debug.Log("Monster OnHitEvent");
}
}
public class MonsterController : BaseController
{
Stat _stat;
[SerializeField] float _scanRange = 10;
[SerializeField] float _attackRange = 2;
public override void Init()
{
_stat = gameObject.GetComponent<Stat>();
//HPBar 부착
if(gameObject.GetComponent<UI_HPBar>() == null)
Managers.UI.MakeWorldSpaceUI<UI_HPBar>(transform);
}
protected override void UpdateDie()
{
Debug.Log("UpdateDie");
}
protected override void UpdateIdle()
{
Debug.Log("UpdateIdle");
//사정거리 내 player 감지
GameObject player = GameObject.FindGameObjectWithTag("Player");
if (player == null)
return;
float distance = (player.transform.position - gameObject.transform.position).magnitude;
if (distance < _scanRange)
{
//감지 시 추적모드로 전환
_lockTarget = player;
State = Define.State.Moving;
return;
}
}
protected override void UpdateMoving()
{
NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
if (_lockTarget != null)
{
_destPos = _lockTarget.transform.position;
float distance = (_destPos - transform.position).magnitude;
if (distance <= _attackRange)
{
nma.SetDestination(transform.position);
State = Define.State.Skill;
return;
}
}
Vector3 dir = _destPos - transform.position;
nma.SetDestination(_destPos);
nma.speed = _stat.MoveSpeed;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
}
protected override 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);
}
}
void OnHitEvent()
{
if(_lockTarget != null)
{
Stat targetStat = _lockTarget.GetComponent<Stat>();
_stat = GetComponent<Stat>();
int damage = Mathf.Max(0, _stat.Attack - targetStat.Defense);
targetStat.Hp -= damage;
if(targetStat.Hp > 0)
{
float distance = (_lockTarget.transform.position - transform.position).magnitude;
if (distance < _attackRange)
State = Define.State.Skill;
else
State = Define.State.Moving;
}
else
{
State = Define.State.Idle;
}
}
else
{
State = Define.State.Idle;
}
}
}
지금은 Player가 Monster를 밀면서 지나가고 있다. 이는 Player가 Nev Mesh Agent에 의해 움직이고 있기 때문이다. 사실상 Player는 Raycast를 통해 장애물 판별을 해주고 있기 때문에 Nev Mesh에 따른 이동이 불필요하다. 이 부분을 수정해보자.
1. UnityChan에 붙어 있는 Nev Mesh Agent 컴포넌트 제거
2. playerController.cs의 UpdateMoving() 수정