[XR 전문 인력 과정] 유니티 타워디펜스 만들기(4) -적 공격하기, 적 폭발, 적 생성

2023. 12. 11. 09:31Unity

728x90
반응형

적이 맞으면 Damage 상태로 전환, Coroutine 적용

EnemyAI 스크립트 수정

적 체력 변수 생성

//적 체력
public int enemyHp = 3;

 

DamageProcess() 함수 생성

데미지를 맞으면 적의 체력이 깎임 PlayerFire 스크립트에서 접근 가능하도록 public 함수로 생성

public void DamageProcess()
{
    //데미지를 맞으면 적의 체력이 깎임 PlayerFire 스크립트에서 접근 가능하도록 public 함수로 생성
    enemyHp--;
    Debug.Log("적 체력: " + enemyHp);
    if(enemyHp > 0) //적의 체력이 양수라면,
    {
        state = EnemyState.Damage;
    }
}

 

Damage() 코루틴 생성

//코루틴 생성
IEnumerator Damage()
{
    //피격되면 잠깐 Idle 상태로 전환 했다가 다시 원래대로 전환
    agent.enabled = false; //길찾기 중지
    yield return new WaitForSeconds(0.1f);
    state = EnemyState.Idle;
    currentTime = 0; //맞았으니까 시간 초기화
}

 

DamageProcess 함수에서 밑에서 만든 Damage() 코루틴 호출

public void DamageProcess()
{
    //데미지를 맞으면 적의 체력이 깎임 PlayerFire 스크립트에서 접근 가능하도록 public 함수로 생성
    enemyHp--;
    Debug.Log("적 체력: " + enemyHp);
    if(enemyHp > 0) //적의 체력이 양수라면,
    {
        state = EnemyState.Damage;
        //밑에서 만든 코루틴 호출
        StopAllCoroutines(); //현재 재생 중인 모든 Coroutine 중지 초기화 느낌
        StartCoroutine(Damage());
    }
}

 

현재까지 EnemyScript 전체 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;

public class EnemyAI : MonoBehaviour
{
    //상태 상수 정의
    enum EnemyState
    {
        Idle, Move, Attack, Damage, Die
    }
    //초기 시작 상태 설정
    EnemyState state = EnemyState.Idle;
    //대기 지속 시간
    public float idleDelayTime = 2f;
    //경과 시간
    float currentTime = 0;
    //이동 속도
    public float moveSpeed = 5f;
    //타워 위치
    Transform tower;
    //네비게이션 메시 에이전트 가져오기
    NavMeshAgent agent;
    //공격 범위
    public float attackRange = 30f;
    //공격 지연 시간
    public float attackDelayTime = 2f;
    //적 체력
    public int enemyHp = 3;
    void Start()
    {
        //타워를 이름으로 가져오기
        tower = GameObject.Find("TowerObject").transform;
        agent = GetComponent<NavMeshAgent>();
        //처음 상태에서는 비활성화 되어야한다.
        agent.enabled = false;
        agent.speed = moveSpeed;
    }
    void Update()
    {
        switch (state)
        {
            case EnemyState.Idle:
                Idle();
                break;
            case EnemyState.Move:
                Movement();
                break;
            case EnemyState.Attack:
                Attack();
                break;
        }
    }
    void Idle()
    {
        //시간의 경과
        currentTime += Time.deltaTime;
        if(currentTime > idleDelayTime)
        {
            //일정 시간 이상 지나면 이동 상태로 전환
            state = EnemyState.Move;
            print("이동 상태");
            //에이전트 활성화
            agent.enabled = true;
        }
    }
    void Movement()
    {
        agent.SetDestination(tower.position);

        //공격 범위 안에 들어오면 공격 상태로 전환
        if(Vector3.Distance(transform.position, tower.position) < attackRange)
        {
            state = EnemyState.Attack;
            //공격 상태일 때는 NavMesh 비활성화
            agent.enabled = false;
        }
    }
    void Attack()
    {
        currentTime += Time.deltaTime;
        if(currentTime > attackDelayTime)
        {
            Debug.Log("공격!");
            currentTime = 0;
        }
    }
    public void DamageProcess()
    {
        //데미지를 맞으면 적의 체력이 깎임 PlayerFire 스크립트에서 접근 가능하도록 public 함수로 생성
        enemyHp--;
        Debug.Log("적 체력: " + enemyHp);
        if(enemyHp > 0) //적의 체력이 양수라면,
        {
            state = EnemyState.Damage;
            //밑에서 만든 코루틴 호출
            StopAllCoroutines(); //현재 재생 중인 모든 Coroutine 중지 초기화 느낌
            StartCoroutine(Damage());
        }
    }
    //코루틴 생성
    IEnumerator Damage()
    {
        //피격되면 잠깐 Idle 상태로 전환 했다가 다시 원래대로 전환
        agent.enabled = false; //길찾기 중지
        yield return new WaitForSeconds(0.1f);
        state = EnemyState.Idle;
        currentTime = 0; //맞았으니까 시간 초기화
    }
}

 

 


 

 

플레이어가 적을 맞추면 적의 체력 감소

PlayerFire 스크립트 Ray 부분 수정. Enemy를 포함하는 이름 찾는 곳 부터.

//Ray 발사 괄호 안에 (ray 대상, 충돌 정보, 얼마만큼 뻗어나갈 것인지, )
if (Physics.Raycast(ray, out hitInfo, 1200, ~layerMask)) //~ 뜻은 ~빼고
{
    //이펙트 생성. 이펙트를 한 번 Stop하고 그 다음 Play
    bulletEffect.Stop();
    bulletEffect.Play();
    //파티클 이펙트를 맞은 위치에 순간이동
    bulletObj.position = hitInfo.point;
    //땅에 맞으면 해당 부분에 normal로 나타나게
    bulletObj.forward = hitInfo.normal;

    //Ray 충돌 정보가 Enemy 이름을 포함하고 있다면, (프리팹은 뒤에 괄호로 숫자도 들어감)
    if(hitInfo.transform.name.Contains("Enemy"))
    {
        EnemyAI enemy = hitInfo.transform.GetComponent<EnemyAI>(); //EnemyAI 스크립트 가져오기
        if (enemy) //만약 enemy가 들어왔다면,
        {
            enemy.DamageProcess(); //enemy 스크립트의 DamageProcess 함수를 불러오기
        }
    }
}

 

현재까지의 PlayerFire 스크립트 전체 코드

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerFire : MonoBehaviour
{
    //Transform 가져오기
    public Transform bulletObj;
    //bulletObj의 Particle System 가져오기
    ParticleSystem bulletEffect;
    void Start()
    {
        //bulletEffect 할당하기
        bulletEffect = bulletObj.GetComponent<ParticleSystem>();
    }

    void Update()
    {
        //버튼을 누르면 Ray를 생성
        if (XRInput.GetDown(XRInput.Button.IndexTrigger))
        {
            //레이 생성 구조체 변수로 가져오기. 괄호안에 시작점의 위치와 방향 넣기
            Ray ray = new Ray(XRInput.RHandPosition, XRInput.RHandDirection);
            //충돌 정보 저장(RaycastHit)
            RaycastHit hitInfo;
            //플레이어 레이어를 받아오기 레이어 번호 숫자 형태로 가져올 수 있다.
            int playerLayer = 1 << LayerMask.NameToLayer("Player"); ;  //꺾쇠는 레이어를 쭉 훑어보는 느낌
            //타워 레이어 받아오기
            int towerLayer = 1 << LayerMask.NameToLayer("Tower");
            int layerMask = playerLayer | towerLayer; //레이어 마스크에 저장

            //Ray 발사 괄호 안에 (ray 대상, 충돌 정보, 얼마만큼 뻗어나갈 것인지, )
            if (Physics.Raycast(ray, out hitInfo, 1200, ~layerMask)) //~ 뜻은 ~빼고
            {
                //이펙트 생성. 이펙트를 한 번 Stop하고 그 다음 Play
                bulletEffect.Stop();
                bulletEffect.Play();
                //파티클 이펙트를 맞은 위치에 순간이동
                bulletObj.position = hitInfo.point;
                //땅에 맞으면 해당 부분에 normal로 나타나게
                bulletObj.forward = hitInfo.normal;

                //Ray 충돌 정보가 Enemy 이름을 포함하고 있다면, (프리팹은 뒤에 괄호로 숫자도 들어감)
                if(hitInfo.transform.name.Contains("Enemy"))
                {
                    EnemyAI enemy = hitInfo.transform.GetComponent<EnemyAI>(); //EnemyAI 스크립트 가져오기
                    if (enemy) //만약 enemy가 들어왔다면,
                    {
                        enemy.DamageProcess(); //enemy 스크립트의 DamageProcess 함수를 불러오기
                    }
                }
            }
        }
    }
}

Ray로 맞추면 적 비행기가 Coroutine으로 잠시 멈춘다.

 

 


 

 

적 체력이 0이 되면 폭발하기

Asset 중에서 폭발 이펙트 찾아서 Hierarchy에 올리기

 

 

스크립팅으로 위해서 Hierarchy 상에서 "Explosion"으로 프리팹 이름 바꾸기

 

 

EnemyAI 스크립트에서 변수 생성

//폭발 효과
Transform explosion;
//폭발 파티클 시스템 변수 선언
ParticleSystem explosionEffect;

 

 

Hierarchy에서 "Explosion" 이름인 GameObject 가져오기, 파티클 시스템 GetComponent로 가져오기

void Start()
{
    //타워를 이름으로 가져오기
    tower = GameObject.Find("TowerObject").transform;
    agent = GetComponent<NavMeshAgent>();
    //처음 상태에서는 비활성화 되어야한다.
    agent.enabled = false;
    agent.speed = moveSpeed;
    //폭발 프리팹을 이름으로 가져오기
    explosion = GameObject.Find("Explosion").transform;
    //파티클 시스템 가져오기
    explosionEffect = explosion.GetComponent<ParticleSystem>();
}

 

 

DamageProcess() 함수의 if문에 else로 추가 (적의 체력이 0 이하일 때)

public void DamageProcess()
{
    //데미지를 맞으면 적의 체력이 깎임 PlayerFire 스크립트에서 접근 가능하도록 public 함수로 생성
    enemyHp--;
    Debug.Log("적 체력: " + enemyHp);
    if(enemyHp > 0) //적의 체력이 양수라면,
    {
        state = EnemyState.Damage;
        //밑에서 만든 코루틴 호출
        StopAllCoroutines(); //현재 재생 중인 모든 Coroutine 중지 초기화 느낌
        StartCoroutine(Damage());
    }
    else //적 체력이 0 이하일 때
    {
        //폭발 효과를 적의 현재 위치로 가져오기
        explosion.position = transform.position;
        explosionEffect.Play();
        //적 제거
        Destroy(gameObject);
    }
}

 

 

파티클 프리팹에서 원래 있던 스크립트 컴포넌트 제거

적이 폭발한다.

 

 


 

 

적 랜덤 생성하기

평지에 적이 생성되어야 한다.

빈 게임 오브젝트 생성 - "EnemyPoints"로 이름 변경, 그 아래에 빈 게임 오브젝트 생성 - "Point"로 이름 변경 후 처음 적이 생성되었던 좌표의 위치와 맞춘다. 다른 포인트도 더 생성해서 위치시킨다. 총 3개의 포인트를 만들었다.

일정 시간 이후에 세 곳 중 랜덤 자리에서 적이 생성 됨을 확인하였다.

 

 


 

 

하지만 프로젝트를 만들 수록 화면을 움직일 때마다 FPS가 매우 떨어지는 것을 볼 수 있었다. 해결해야할 문제라고 생각한다. 다음 글에서 프로젝트 최적화 추가 기능을 넣어볼 예정이다.

728x90
반응형