안녕하세요.😌
오늘은 플레이어가 마우스로 선택된 몬스터(Enumy)를 따라가고 강조되는 효과를 작업했습니다.
먼저 몬스터는 이전에 Unity Asset Store에서 무료로 받았던 에셋을 사용했습니다.
다음은 플레이어의 이동을 담당하는 "PlayerMovement" 스크립트에서 Ray로 Enumy가 선택 됐는지 판단할 예정이기에
Enumy에 콜라이더를 입혀주었습니다.
다음은 "PlayerMovement" 스크립트입니다.
using UnityEngine;
using UnityEngine.AI;
public class PlayerMovement : MonoBehaviour
{
[Header("Player Component")]
[SerializeField] private Animator playerAnim;
[SerializeField] private NavMeshAgent playerNav;
[Header("Setting Value")]
[SerializeField] private float playerNavRotateSpeed = 0.05f;
private float animationSmmothTime = 0.1f;
private float playerNavRotateVel = 0f;
[Header("Effect Settings")]
[SerializeField] private GameObject[] clickQuads;
[SerializeField] private Vector3 quadOffset = new Vector3(0, 0.1f, 0);
[Header("Enumy Component")]
[SerializeField] private Transform enumyGameobject;
[SerializeField] private float stopDistance = 0f;
private Outline outline;
void Update()
{
AnimationController();
MoveController();
}
private void AnimationController()
{
float speed = playerNav.velocity.magnitude / playerNav.speed;
playerAnim.SetFloat("Speed", speed, animationSmmothTime, Time.deltaTime);
}
private void MoveController()
{
if (Input.GetMouseButtonDown(1))
{
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, Mathf.Infinity))
{
if (hit.collider.tag == "Ground")
{
PlayerNavToMove(hit.point);
PlayerRotation(hit.point);
}
else if (hit.collider.tag == "Enumy")
{
if (outline != null)
outline.enabled = false;
outline = hit.transform.GetComponent<Outline>();
PlayerNavToEnumy(hit.collider.transform);
}
}
}
if (enumyGameobject != null)
{
if (Vector3.Distance(transform.position, enumyGameobject.position) > stopDistance)
{
playerNav.SetDestination(enumyGameobject.position);
}
}
}
private void PlayerNavToMove(Vector3 getHitPoint)
{
if (outline != null)
outline.enabled = false;
playerNav.SetDestination(getHitPoint);
playerNav.stoppingDistance = 0f;
if (enumyGameobject != null)
{
enumyGameobject = null;
}
}
private void PlayerRotation(Vector3 getHitPoint)
{
Quaternion rotationToLookat = Quaternion.LookRotation(getHitPoint - transform.position);
float rotationY = Mathf.SmoothDampAngle(
transform.eulerAngles.y, // 현재 y축 회전값
rotationToLookat.eulerAngles.y, // 목표 Y축 회전값
ref playerNavRotateVel, // 보정 값
playerNavRotateSpeed * (Time.deltaTime)); // 회전하는데 걸릴 시간
transform.eulerAngles = new Vector3(0, rotationY, 0);
ActivateClickQuad(getHitPoint + quadOffset);
}
private void ActivateClickQuad(Vector3 position)
{
foreach (var quad in clickQuads)
{
if (!quad.activeSelf)
{
quad.transform.position = position;
quad.SetActive(true);
return;
}
}
}
private void PlayerNavToEnumy(Transform getEnumyTr)
{
if (outline != null)
outline.enabled = true;
enumyGameobject = getEnumyTr;
playerNav.SetDestination(getEnumyTr.transform.position);
playerNav.stoppingDistance = stopDistance;
PlayerRotation(getEnumyTr.transform.position);
}
}
1. 가장 먼저 플레이어의 실제 움직임 및 회전을 담당했던 MoveController() 함수 내부를 정리해 주었습니다.
private void MoveController()
{
if (Input.GetMouseButtonDown(1))
{
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, Mathf.Infinity))
{
if (hit.collider.tag == "Ground")
{
PlayerNavToMove(hit.point);
PlayerRotation(hit.point);
}
else if (hit.collider.tag == "Enumy")
{
if (outline != null)
outline.enabled = false;
outline = hit.transform.GetComponent<Outline>();
PlayerNavToEnumy(hit.collider.transform);
}
}
}
if (enumyGameobject != null)
{
if (Vector3.Distance(transform.position, enumyGameobject.position) > stopDistance)
{
playerNav.SetDestination(enumyGameobject.position);
}
}
}
if (hit.collider.tag == "Ground") 일 때 작성되어 있던 코드를 함수로 정리하는 시간을 가졌습니다.
private void PlayerNavToMove(Vector3 getHitPoint)
{
if (outline != null)
outline.enabled = false;
playerNav.SetDestination(getHitPoint);
playerNav.stoppingDistance = 0f;
if (enumyGameobject != null)
{
enumyGameobject = null;
}
}
private void PlayerRotation(Vector3 getHitPoint)
{
Quaternion rotationToLookat = Quaternion.LookRotation(getHitPoint - transform.position);
float rotationY = Mathf.SmoothDampAngle(
transform.eulerAngles.y, // 현재 y축 회전값
rotationToLookat.eulerAngles.y, // 목표 Y축 회전값
ref playerNavRotateVel, // 보정 값
playerNavRotateSpeed * (Time.deltaTime)); // 회전하는데 걸릴 시간
transform.eulerAngles = new Vector3(0, rotationY, 0);
ActivateClickQuad(getHitPoint + quadOffset);
}
2. 다음은 Enumy에 관련된 변수를 선언해 주었습니다.
enumyGameobject : 마우스로 선택된 오브젝트가 담길 변수입니다. (현재는 1)
stopDistance : NavMeshAgent의 멈춰야 할 거리를 제어할 변수입니다.
outline : Unity Asset Store의 무료 에셋 스크립트입니다. (몬스터가 선택될 때 강조하기 위해 가져왔습니다.)
[SerializeField] private Transform enumyGameobject;
[SerializeField] private float stopDistance = 0f;
private Outline outline;
3. 레이를 쏠 때 감지된 콜라이더의 태그가 "Enumy"인지 추가를 해주었습니다.
PlayerNavToEnumy() 함수를 실행시켜 주며 인자로는 감지된 콜라이더의 Transform입니다.
outline이 null일 때 enabled를 false로 해주는 것은,
다른 Enumy가 선택될 때 기존에 선택된 Enumy의 Outline을 제거해 주기 위함입니다.
이후 outline에 Enumy에 입혀져 있는 스크립트 Outline을 가져옵니다.
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, Mathf.Infinity))
{
if (hit.collider.tag == "Ground")
{
PlayerNavToMove(hit.point);
PlayerRotation(hit.point);
}
else if (hit.collider.tag == "Enumy")
{
if (outline != null)
outline.enabled = false;
outline = hit.transform.GetComponent<Outline>();
PlayerNavToEnumy(hit.collider.transform);
}
}
4. 플레이어를 Enumy에게 이동하게 SetDestination()을 사용하였으며 회전은 기존 함수를 그대로 사용해 주었습니다.
또한 가져온 outline이 null이 아닐 때, Outline의 enabled를 true로 변경해 주어 Outline이 보이게 해 주었습니다.
private void PlayerNavToEnumy(Transform getEnumyTr)
{
if (outline != null)
outline.enabled = true;
enumyGameobject = getEnumyTr;
playerNav.SetDestination(getEnumyTr.transform.position);
playerNav.stoppingDistance = stopDistance;
PlayerRotation(getEnumyTr.transform.position);
}
5. 레이에 감지된 콜라이더의 태그가 "Ground"일 땐 Outline을 꺼주었습니다.
outline!= null 일 때의 조건문을 붙여준 이유는
단순히 outline.enabled = false; 만 있을 때 게임 시작 시 Enumy를 선택하지 않고 Ground를 바로 선택한다면
NullReferenceExeption이 나왔기에 추가를 해주었습니다.
private void PlayerNavToMove(Vector3 getHitPoint)
{
if (outline != null)
outline.enabled = false;
playerNav.SetDestination(getHitPoint);
playerNav.stoppingDistance = 0f;
if (enumyGameobject != null)
{
enumyGameobject = null;
}
}
6. 다음은 레이에 감지된 콜라이더의 태그가 Enumy라면 선언해 준 enumyGameobject에 대입을 해줬었는데,
Ground로 다시 이동을 할 때는 enumyGameobject가 null이 아니라면 null이 되게 해 주었습니다.
이는 다음에 있을 Enumy가 이동하고 있을 때 목적지를 갱신하며 Enumy를 따라가는 로직을 끊어주기 위함입니다.
private void PlayerNavToMove(Vector3 getHitPoint)
{
if (outline != null)
outline.enabled = false;
playerNav.SetDestination(getHitPoint);
playerNav.stoppingDistance = 0f;
if (enumyGameobject != null)
{
enumyGameobject = null;
}
}
7. 마지막으로 Enumy가 이동하고 있을 때입니다.
단순히 현재 로직만 있을 때는 Enumy를 선택한 시점의 Position으로만 플레이어가 이동을 한 뒤 멈춥니다.
다음과 같이 선택된 Enumy가 있다면 목적지를 갱신시켜 주었습니다.
private void MoveController()
{
if (Input.GetMouseButtonDown(1))
{
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit, Mathf.Infinity))
{
if (hit.collider.tag == "Ground")
{
PlayerNavToMove(hit.point);
PlayerRotation(hit.point);
}
else if (hit.collider.tag == "Enumy")
{
if (outline != null)
outline.enabled = false;
outline = hit.transform.GetComponent<Outline>();
PlayerNavToEnumy(hit.collider.transform);
}
}
}
if (enumyGameobject != null)
{
if (Vector3.Distance(transform.position, enumyGameobject.position) > stopDistance)
{
playerNav.SetDestination(enumyGameobject.position);
}
}
}
요즘 집에 도착을 하면 평균 10시 ~ 11시가 되기에 프로젝트를 작업할 시간이 많지 않은 게 아쉬운데요..
시간이 되는 한! 꾸준히 작업을 진행하며 업로드하도록 하겠습니다.
감사합니다.😌