Luna
[TIL Day 51] 3D 팀 프로젝트 시작 본문
오늘의 학습 키워드
- 프로젝트 내 역할분담
- 개인 작업
공부한 내용 본인의 언어로 정리하기
적과의 전투와 적AI를 구현해보았따.
우선 Skeleton 에셋을 임포트해서 프리팹을 그대로 가지고 와봤더니
애니메이션이 이미 다 있었어서 연결만 시켜주면 됐기 때문에 아주 편해보였다. 짱
using UnityEngine;
using System.Collections;
public class EnemyManager : MonoBehaviour
{
public GameObject enemyPrefab; // 스켈레톤 프리팹
public int enemyCount = 2; // 스폰할 적 개수
public Vector3 spawnArea; // 스폰 범위
void Start()
{
SpawnEnemies();
}
void SpawnEnemies()
{
for (int i = 0; i < enemyCount; i++)
{
Vector3 spawnPos = new Vector3(
Random.Range(-spawnArea.x, spawnArea.x),
0,
Random.Range(-spawnArea.z, spawnArea.z)
);
Instantiate(enemyPrefab, spawnPos, Quaternion.identity);
}
}
}
우선 EnemyManager를 만들어두었다.
적을 관리해주기 위해서 (스폰도 할 겸)
EnemyManager에서 enemyPrefab을 연결시켜놓았다.
그리고 Skeleton 프리팹에 SkeletonAI 스크립트를 넣어주었다.
using UnityEngine;
using UnityEngine.AI;
public class SkeletonAI : MonoBehaviour
{
public NavMeshAgent agent;
public Transform player;
public LayerMask playerLayer, safeZoneLayer; //groundLayer
public Animator animator;
private enum State
{
Patrolling,
Chasing,
Attacking,
Safe,
Dead
}
private State currentState = State.Patrolling;
public float patrolRange;
public float sightRange;
public float attackRange;
public float safezoneRange;
public float maxChaseDistance;
public float health = 100;
private Vector3 patrolTarget;
private bool isPlayerInSight, isPlayerInAttackRange, isInSafeZone;
private ItemDropper itemDropper;
void Start()
{
agent = GetComponent<NavMeshAgent>();
animator = GetComponent<Animator>();
player = GameObject.FindGameObjectWithTag("Player").transform;
itemDropper = GetComponent<ItemDropper>();
SetNewPatrolPoint();
Die();
}
void Update()
{
if (currentState == State.Dead) return;
isPlayerInSight = Physics.CheckSphere(transform.position, sightRange, playerLayer);
isPlayerInAttackRange = Physics.CheckSphere(transform.position, attackRange, playerLayer);
isInSafeZone = Physics.CheckSphere(transform.position, safezoneRange, safeZoneLayer);
if (isInSafeZone)
{
currentState = State.Safe;
StopMoving();
}
else
{
switch (currentState)
{
case State.Patrolling:
Patrol();
if (isPlayerInSight) currentState = State.Chasing;
break;
case State.Chasing:
ChasePlayer();
if (isPlayerInAttackRange) currentState = State.Attacking;
break;
case State.Attacking:
AttackPlayer();
if (!isPlayerInAttackRange) currentState = State.Patrolling;
break;
}
}
}
void SetNewPatrolPoint()
{
Vector3 randomPoint = transform.position + new Vector3(
Random.Range(-patrolRange, patrolRange), 0, Random.Range(-patrolRange, patrolRange));
if (NavMesh.SamplePosition(randomPoint, out NavMeshHit hit, 2f, NavMesh.AllAreas))
{
patrolTarget = hit.position;
agent.SetDestination(patrolTarget);
animator.SetBool("IsMoving", true);
}
}
void Patrol()
{
if (Vector3.Distance(transform.position, patrolTarget) < 1f)
{
SetNewPatrolPoint();
}
}
void ChasePlayer()
{
float distanceToPlayer = Vector3.Distance(transform.position, player.position);
if (distanceToPlayer > maxChaseDistance)
{
currentState = State.Patrolling;
SetNewPatrolPoint();
}
else
{
agent.SetDestination(player.position);
animator.SetBool("IsMoving", true);
}
}
void AttackPlayer()
{
agent.SetDestination(transform.position); // 공격 시 멈춤
animator.SetBool("IsMoving", false);
animator.SetTrigger("Attack");
}
void StopMoving()
{
agent.SetDestination(transform.position);
animator.SetBool("IsMoving", false); // 안전구역에서는 멈춤
}
public void TakeDamage(int damage)
{
health -= damage;
if (health <= 0)
{
Die();
}
}
void Die()
{
currentState = State.Dead;
animator.SetTrigger("Die");
agent.isStopped = true; // 네비게이션 중지
GetComponentInChildren<Collider>().enabled = false;
if (itemDropper != null)
{
///아이템 드롭
//itemDropper.DropItem();
itemDropper.DropItemWithDelay(0.5f);
}
else if (itemDropper == null)
{
Debug.Log("itemDropper가 Null입니다.");
}
Destroy(gameObject, 3f);
}
}
기능이 좀 많은데 처음에 클래스를 미리 분리시켜서 상속받으면 좋을텐데 일단 이후에 수정해야겠다.
플레이어를 탐지하고, 상태를 추가해서 적의 상태를 case문으로 바뀔 수 있게 하였다.
(상태가 많아지면 관리하기가 힘들어져서 나중에 다시 또 생각을 해봐야겠다.)
아이템 드롭하는 것도 같이 작성하려다가
이 Skeleton만 아이템을 떨구는 건 아니기에 일단 스크립트를 다시 나눠서 작성했다.
using UnityEngine;
using System.Collections;
public class ItemDropper : MonoBehaviour
{
public GameObject dropItemPrefab;
public float dropForce = 2f; // 아이템이 살짝 튀어나가는 힘
//코루틴을 이용해서 아이템 드롭 시간 지연시키기
public void DropItemWithDelay(float delay)
{
StartCoroutine(DropItemCoroutine(delay));
}
private IEnumerator DropItemCoroutine(float delay)
{
yield return new WaitForSeconds(delay);
DropItem();
}
public void DropItem()
{
if (dropItemPrefab != null)
{
// 현재 위치에서 아이템 생성
GameObject droppedItem = Instantiate(dropItemPrefab, transform.position, Quaternion.identity);
Rigidbody rb = droppedItem.GetComponent<Rigidbody>();
if (rb != null)
{
Vector3 randomForce = new Vector3(Random.Range(-1f, 1f), 1f, Random.Range(-1f, 1f)) * dropForce;
rb.AddForce(randomForce, ForceMode.Impulse);
}
else if (rb == null)
{
Debug.Log("아이템프리팹 안에 RigidBody가 없습니다! (생성되는데에는 문제가 없습니다.)");
}
}
}
}
RigidBody가 있으면 좀 더 현실감 있지 않을까 해서 rb를 체크하는 조건문을 써보았다. 뭐 근데 굳이 없어도 괜찮긴 하다.
코루틴을 써서 죽고난 뒤 적 애니메이션을 조금 재생시키고 아이템이 튀어나오도록 지연시켰다.
에러사항
3D 오브젝트 머테리얼이 깨져서 분홍색으로 보이는 현상이 있었다. 해결방법은 간단했다.
1. 문제있는 머테리얼 선택
2. [Edit > Rendering > Materials > Convert Selected Built-in Materials to URP] 클릭
3. Proceed 클릭
끝이다. 아주 간단하게 URP로 변환시켜 해결이 가능하다.
'🎮 Unity_7 TIL WIL' 카테고리의 다른 글
[TIL Day44] Unity 3D 강의 ~ing (2) (0) | 2025.03.06 |
---|---|
[TIL Day43] Unity 3D 강의 ~ing (0) | 2025.03.05 |
[TIL Day42] 유니티 3D 공부시작! (0) | 2025.03.04 |
[TIL Day38] 팀 프로젝트 마무리 (0) | 2025.02.28 |
[TIL Day34] 팀 프로젝트 - 플레이어 구현 (0) | 2025.02.24 |