Unity 게임 개발 숙련주차 / 3D게임 플레이어 및 적 구현
2023. 9. 25. 21:00ㆍUnity 게임 개발 입문
1. 플레이어 구현
: Player Controls이라는 기능을 통해 캐릭터 컨트롤을 구현했다.
: 다양한 기능을 코드 없이 간단하게 구현 가능해 편리했다.

2. Enemy 구현
: 선인장 몬스터 에셋에 애니메이션을 추가했다.

: 몬스터에 적용되는 코드는 강의에서 배운 것을 사용했다.
: 몬스터는 주변을 돌아다니다 플레이어가 가까워지면 플레이어에게 달려와 공격한다.
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.AI;
public enum AIState
{
Idle,
Wandering,
Attacking,
Fleeing
}
public class NPC : MonoBehaviour /*, IDamagable*/
{
[Header("Stats")]
public int health;
public float walkSpeed;
public float runSpeed;
//public ItemData[] dropOnDeath; // related with item
[Header("AI")]
private AIState aiState;
public float detectDistance;
public float safeDistance;
[Header("Wandering")]
public float minWanderDistance;
public float maxWanderDistance;
public float minWanderWaitTime;
public float maxWanderWaitTime;
[Header("Combat")]
public int damage;
public float attackRate;
private float lastAttackTime;
public float attackDistance;
private float playerDistance;
public float fieldOfView = 120f;
private NavMeshAgent agent;
private Animator animator;
private SkinnedMeshRenderer[] meshRenderers;
private void Awake()
{
agent = GetComponent<NavMeshAgent>();
animator = GetComponentInChildren<Animator>();
meshRenderers = GetComponentsInChildren<SkinnedMeshRenderer>();
}
private void Start()
{
SetState(AIState.Wandering);
}
private void Update()
{
playerDistance = Vector3.Distance(transform.position, PlayerController.instance.transform.position);
animator.SetBool("Moving", aiState != AIState.Idle);
switch (aiState)
{
case AIState.Idle: PassiveUpdate(); break;
case AIState.Wandering: PassiveUpdate(); break;
case AIState.Attacking: AttackingUpdate(); break;
case AIState.Fleeing: FleeingUpdate(); break;
}
}
private void FleeingUpdate()
{
if (agent.remainingDistance < 0.1f)
{
agent.SetDestination(GetFleeLocation());
}
else
{
SetState(AIState.Wandering);
}
}
private void AttackingUpdate()
{
if (playerDistance > attackDistance || !IsPlaterInFireldOfView())
{
agent.isStopped = false;
NavMeshPath path = new NavMeshPath();
if (agent.CalculatePath(PlayerController.instance.transform.position, path))
{
agent.SetDestination(PlayerController.instance.transform.position);
}
else
{
SetState(AIState.Fleeing);
}
}
else
{
agent.isStopped = true;
if (Time.time - lastAttackTime > attackRate)
{
lastAttackTime = Time.time;
//PlayerController.instance.GetComponent<IDamagable>().TakePhysicalDamage(damage);
animator.speed = 1;
animator.SetTrigger("Attack");
}
}
}
private void PassiveUpdate()
{
if (aiState == AIState.Wandering && agent.remainingDistance < 0.1f)
{
SetState(AIState.Idle);
Invoke("WanderToNewLocation", Random.Range(minWanderWaitTime, maxWanderWaitTime));
}
if (playerDistance < detectDistance)
{
SetState(AIState.Attacking);
}
}
bool IsPlaterInFireldOfView()
{
Vector3 directionToPlayer = PlayerController.instance.transform.position - transform.position;
float angle = Vector3.Angle(transform.forward, directionToPlayer);
return angle < fieldOfView * 0.5f;
}
private void SetState(AIState newState)
{
aiState = newState;
switch (aiState)
{
case AIState.Idle:
{
agent.speed = walkSpeed;
agent.isStopped = true;
}
break;
case AIState.Wandering:
{
agent.speed = walkSpeed;
agent.isStopped = false;
}
break;
case AIState.Attacking:
{
agent.speed = runSpeed;
agent.isStopped = false;
}
break;
case AIState.Fleeing:
{
agent.speed = runSpeed;
agent.isStopped = false;
}
break;
}
animator.speed = agent.speed / walkSpeed;
}
void WanderToNewLocation()
{
if (aiState != AIState.Idle)
{
return;
}
SetState(AIState.Wandering);
agent.SetDestination(GetWanderLocation());
}
Vector3 GetWanderLocation()
{
NavMeshHit hit;
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(minWanderDistance, maxWanderDistance)), out hit, maxWanderDistance, NavMesh.AllAreas);
int i = 0;
while (Vector3.Distance(transform.position, hit.position) < detectDistance)
{
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * Random.Range(minWanderDistance, maxWanderDistance)), out hit, maxWanderDistance, NavMesh.AllAreas);
i++;
if (i == 30)
break;
}
return hit.position;
}
Vector3 GetFleeLocation()
{
NavMeshHit hit;
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
int i = 0;
while (GetDestinationAngle(hit.position) > 90 || playerDistance < safeDistance)
{
NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * safeDistance), out hit, maxWanderDistance, NavMesh.AllAreas);
i++;
if (i == 30)
break;
}
return hit.position;
}
float GetDestinationAngle(Vector3 targetPos)
{
return Vector3.Angle(transform.position - PlayerController.instance.transform.position, transform.position + targetPos);
}
public void TakePhysicalDamage(int damageAmount)
{
health -= damageAmount;
if (health <= 0)
Die();
StartCoroutine(DamageFlash());
}
void Die()
{
// related with item
//for (int x = 0; x < dropOnDeath.Length; x++)
//{
// Instantiate(dropOnDeath[x].dropPrefab, transform.position + Vector3.up * 2, Quaternion.identity);
//}
Destroy(gameObject);
}
IEnumerator DamageFlash()
{
for (int x = 0; x < meshRenderers.Length; x++)
meshRenderers[x].material.color = new Color(1.0f, 0.6f, 0.6f);
yield return new WaitForSeconds(0.1f);
for (int x = 0; x < meshRenderers.Length; x++)
meshRenderers[x].material.color = Color.white;
}
}
'Unity 게임 개발 입문' 카테고리의 다른 글
Unity 게임 개발 입문 심화 / 개인 프로젝트 / 플랫폼 게임 (0) | 2023.10.06 |
---|---|
Unity 게임 개발 숙련주차 / 3D게임 Enemy AI 구현 (0) | 2023.10.02 |
Unity 게임 개발 / 개인 프로젝트 / ATM / UI (0) | 2023.09.19 |
팀프로젝트 / 똥피하기 / 아이템 Ice (0) | 2023.09.13 |
팀프로젝트 / 똥피하기 / Item & Enemy 설정 (0) | 2023.09.11 |