2023. 12. 11. 09:31ㆍUnity
적이 맞으면 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가 매우 떨어지는 것을 볼 수 있었다. 해결해야할 문제라고 생각한다. 다음 글에서 프로젝트 최적화와 추가 기능을 넣어볼 예정이다.