Object Pooling으로 몬스터 생성
Object Pooling - 미리 비활성화 생성 후 쓸 때 활성화
-몬스터를 불규칙한 위치에 생성
spawnPointGroup을 만들고 다음과 같은 코드를 작성한다.
https://docs.unity3d.com/ScriptReference/GameObject-activeInHierarchy.html
-실행하면 자동으로 리스트에 객체가 추가된다.
GameObjt.activeInHierachy
-
https://docs.unity3d.com/ScriptReference/GameObject-activeInHierarchy.html
InvokeRepeating
-몬스터 풀에서 비활성화된 몬스터를 미리 만들어둔다.
-불규칙적인 위리츨 리스트로 저장하고, InvokeReapeating으로 CreateMonster() 메서드를 호출해 일정 시간 간격으로 몬스터를 호출한다.
-MonsterController의 start함수를 awake로 변경하고, 2개의 코루틴함수를 OnEnable로 옮긴다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.UIElements;
public class MonsterController : MonoBehaviour
{
public enum eState
{//몬스터 상태 정의
IDLE, TRACE, ATTACK, DIE
}
public eState state = eState.IDLE;//몬스터의 현재 상태
private bool isDie=false;//몬스터의 사망 여부
private NavMeshAgent agent;
private Transform playerTrans;
private Animator anim;
private readonly int hashTrace = Animator.StringToHash("IsTrace");//Animator 파라미터의 해시값 추출
private readonly int hashAttack = Animator.StringToHash("IsAttack");
private readonly int hashGotHit = Animator.StringToHash("GotHit");
private readonly int hashDance = Animator.StringToHash("Dance");
private readonly int hashSpeed = Animator.StringToHash("Speed");
private readonly int hashDie = Animator.StringToHash("Die");
private GameObject bloodEffectPrefab;//혈흔 효과 프리팹
[SerializeField]private float traceDistance = 10.0f;//추적 사거리
[SerializeField]private float attackDistance = 2f;//공격 사거리
//--Data----------------------------------------------------
private float initHp = 100f;
private float currHp;
void Awake()
{
this.currHp = this.initHp;
this.bloodEffectPrefab = Resources.Load<GameObject>("BloodSprayEffect");
this.anim = GetComponent<Animator>();
this.agent = this.GetComponent<NavMeshAgent>();//agent 컴포넌트 가져옴
GameObject playerGo = GameObject.FindGameObjectWithTag("Player");
this.playerTrans = playerGo.GetComponent<Transform>();
// Vector3 playerPosition = playerTrans.position;
// this.agent.destination = playerPosition;//destination 속성을 사용해 이동할 좌푯값 지정
}
// Update is called once per frame
void Update()
{
}
private IEnumerator CheckState()
{//상태 갱신 코루틴 함수: 일정 간격으로 몬스터의 행동상태 체크
while (!isDie)
{
yield return new WaitForSeconds(0.3f);
if (this.state == eState.DIE) yield break;
float distance = Vector3.Distance(this.transform.position, this.playerTrans.position);
// Debug.Log(distance);
//Debug.Log(this.state);
if (distance <= this.traceDistance)//추적 사거리에 들어왔다면
{
this.state = eState.TRACE;//추적상태로 변경
if (distance <= this.attackDistance) //공격 사거리에 들어왔다면
{
this.state = eState.IDLE;
yield return null;
this.state = eState.ATTACK;//공격상태로 변경
}
}
else//멈춤
{
this.state = eState.IDLE;
}
}
}
private IEnumerator MonsterAction()
{
while (!isDie)
{
switch (this.state)
{
case eState.IDLE:
this.agent.isStopped = true;//추적 중지
//this.anim.SetBool("IsTrace", false);
this.anim.SetBool(this.hashTrace, false);
this.anim.SetBool(this.hashAttack,false);
break;
case eState.TRACE:
this.agent.SetDestination(this.playerTrans.position);
this.agent.isStopped = false;
this.anim.SetBool(this.hashTrace, true);
this.anim.SetBool(this.hashAttack, false);
break;
case eState.ATTACK:
this.anim.SetBool(this.hashAttack, true);
this.anim.SetBool(this.hashTrace, false);
break;
case eState.DIE:
this.isDie = true;
this.agent.isStopped = true;
this.anim.SetTrigger(this.hashDie);
GetComponent<CapsuleCollider>().enabled = false;//몬스터의 collider 비활성화
yield return new WaitForSeconds(3.0f);
//몬스터 사망 시, 일정 시간 대기후 오브젝트 풀링으로 환원
this.currHp = 100;//사망 후 다시 사용을 위한 hp 초기화
this.isDie = false;
GetComponent<CapsuleCollider>().enabled = true;//몬스터 Collider 활성화
this.gameObject.SetActive(false);//몬스터 비활성화 - 오브젝트 풀에서 다시 추출가능한 상태로 만듦.
break;
}
yield return new WaitForSeconds(0.3f);
}
}
private void OnCollisionEnter(Collision collision)
{
if (collision.collider.CompareTag("Bullet"))
{
ContactPoint contactPoint = collision.GetContact(0);//첫 번째 충돌 지점
//DrawArrow.ForDebug(contactPoint.point, -contactPoint.normal, 5f, Color.white);
//point:위치, normal:법선, 법선 반대로 그려보기
Quaternion rotation = Quaternion.LookRotation(-contactPoint.normal);//바라볼 방향
//충돌 지점의 법선 벡터를 쿼터니언 타입으로 변환
GameObject go = Instantiate(this.bloodEffectPrefab,contactPoint.point,rotation,this.transform);
//go.transform.position = contactPoint.point;
//go.transform.rotation = rotation;//법선 벡터로 회전
// Debug.LogErrorFormat("{0}",contactPoint.point);
Destroy(collision.gameObject);//remove bullet
Destroy(go, 0.5f);//remove blood Effect
this.anim.SetTrigger(this.hashGotHit);
this.currHp -= 30.0f;
// Debug.Log(this.state);
if(this.currHp <= 0)
{//몬스터 사망
this.state = eState.DIE;
}
}
}
private void OnEnable()
{//스크립트가 활성화 될 때마다 호출되는 함수
PlayerController.OnPlayerDie += this.OnPlayerDie;//이벤트 발생 시 수행할 함수 연결
StartCoroutine(this.CheckState());//상태 갱신
StartCoroutine(this.MonsterAction());//상태에따라 몬스터 행동 수행
}
private void OnDisable()
{//기존에 연결된 함수 해제
PlayerController.OnPlayerDie -= this.OnPlayerDie;
}
void OnPlayerDie()
{
StopAllCoroutines();//몬스터 상태체크 중지
this.agent.isStopped = true;//추적을 정지하고 애니메이션 수행
this.anim.SetFloat(this.hashSpeed, Random.Range(0.8f, 1.2f));
this.anim.SetTrigger(this.hashDance);
}
private void OnDrawGizmos()
{
if(this.state == eState.TRACE)
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(this.transform.position, traceDistance);
}
else if(this.state == eState.ATTACK)
{
Gizmos.color= Color.green;
Gizmos.DrawWireSphere(this.transform.position,attackDistance);
}
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
[SerializeField] GameObject monsterPrefab;
public List<Transform> points = new List<Transform>();//몬스터가 출현할 위치를 저장할 List 변수
public List<GameObject> monsterPools = new List<GameObject>();//몬스터를 미리 생성해 저장
public int maxMonsters = 10; //오브젝트 풀에 생성할 몬스터 최대 개수
public static GameManager instance = null;//싱글톤 인스턴스 생성
private float createTime = 3.0f;
private bool isGameOver; //게임 종류 여부
public bool IsGameOver
{
get { return this.isGameOver; }
set
{
this.isGameOver = value;
if (this.isGameOver)
{
CancelInvoke("CreateMonster");
}
}
}
private void Awake()
{
if(instance == null)
{//인스터스가 할당되지 않았을경우
instance = this;
}
else if(instance != this)
{//인스턴스에 할당된 클래스의 인스턴스가 다르게 새로 생성된 클래스
Destroy(this.gameObject);
}
DontDestroyOnLoad(this.gameObject);//다른 씬으로 넘어가더라도 삭제하지 않고 유지
}
// Start is called before the first frame update
void Start()
{
CreateMonsterPool();
Transform spawnPointGroup = GameObject.Find("SpawnPointGroup").transform;
//
foreach(Transform point in spawnPointGroup)
{
this.points.Add(point);//parent 제외하고 child의 컴포넌트만 추출
}
InvokeRepeating("CreateMonster", 2.0f, this.createTime);//일정한 시간 간격으로 함수 호출
}
// Update is called once per frame
void Update()
{
}
private void CreateMonster()
{
int idx = Random.Range(0, this.points.Count);//몬스터의 불규칙한 생성 위치 산출
//풀에서 비활성화된 오브젝트 가져오기
GameObject monster = this.GetMonsterInPool();
monster?.transform.SetPositionAndRotation(points[idx].position, points[idx].rotation);
monster?.SetActive(true);
}
public GameObject GetMonsterInPool()
{
//오브젝트 풀의 처음부터 끝까지 순회
foreach(var monster in monsterPools)
{
if(monster.activeSelf == false)
{
return monster;//비활성화 여부로 사용 가능한 몬스터 판단
}
}
return null;
}
private void CreateMonsterPool()
{
for(int i= 0; i < this.maxMonsters; i++)
{
GameObject monster = Instantiate<GameObject>(monsterPrefab);
monster.name = $"Monster_{i:00}";//몬스터의 이름 지정
monster.SetActive(false);//몬스터 비활성화
monsterPools.Add(monster);//생성한 몬스터를 오브젝트 풀에 추가
}
}
}
'유니티 심화' 카테고리의 다른 글
(UI)생명 게이지 점수 구현 - PlayerPrefs로 저장 및 불러오기 (0) | 2023.08.29 |
---|---|
Object Pooling 연습 - 총알 생성 (0) | 2023.08.28 |
Unity UI-버튼-애니메이션, Play씬에서 HpBar 생성 (0) | 2023.08.25 |
Unity-UI 시스템-IMGUI, UI Toolkit,Unity UI(UGUI) (0) | 2023.08.25 |
Player 사망-Event 처리, 몬스터 사망 (0) | 2023.08.24 |