Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

Luna

[TIL Day 51] 3D 팀 프로젝트 시작 본문

🎮 Unity_7 TIL WIL

[TIL Day 51] 3D 팀 프로젝트 시작

fociend 2025. 3. 13. 16:08

오늘의 학습 키워드

- 프로젝트 내 역할분담

- 개인 작업


공부한 내용 본인의 언어로 정리하기

적과의 전투와 적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로 변환시켜 해결이 가능하다.