1. 몬스터 생성하기
에셋들에서 Monster로 사용할 프리팹을 하나 가져온 후
기존의 애니메이션을 필요한 부분만 골라 재생성해준 후 Animator에 넣어준다.
현재로는 걷는 동작만 필요하므로 Walk만 남겨두고 다 삭제
Monster(몬스터의 동작 관리) 스크립트를 작성후 컴포넌트로 부착
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public float speed = 1f;
[SerializeField]
private Player player;
void Awake()
{
this.player = this.GetComponent<Player>();
}
//Player를 향해 이동
void Update()
{
//방향 설정
Vector3 dir = this.player.transform.position - this.transform.position;
//player 바라보기
this.transform.LookAt(this.player.transform.position);
//이동하기
this.transform.Translate(Vector3.forward * this.speed * Time.deltaTime);
}
//몬스터가 마법진에 닿으면 사라지게 하기
private void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Weapon"))
{
Debug.Log("Destroy");
//pool에 다시 넣어주기
PoolManager.instance.Release(this.gameObject);
}
}
}
**오류 발생
이렇게 설정한 후 프리팹화 시켰더니...
프리팹화 하기 전에 잘만 인식하던 Player가 'None'이라고 나온다.
무슨 이유인가 하고 봤더니
.
.
.
prefab에 연결되는 객체는 반드시 프리팹 내부에 있는 오브젝트여야 함
즉, prefab은 다른 scene의 오브젝트와 달리 public GameObject를 선언해주어도
오브젝트를 외부에서 드래그 & 드랍으로 할당해줄 수 없다!
=> 따라서 GameManager스크립트를 생성해 싱글톤으로 만들어주고,
GameManager에 Player를 넣어주고 GameManager에서 가져오는 방식으로 스크립트를 수정해줬다.
GameManager 스크립트
수정한 Monster 스크립트
2. 한 가지 종류의 몬스터만 풀링으로 생성해보기
01. 풀링을 관리할 PoolManager 스크립트 생성
: 오브젝트 활성, 비활성화 ->몬스터 생성 및 제거를 관리할 것임.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
//싱글톤
public static PoolManager instance;
[Header("#Prefabs")]
[SerializeField]
private GameObject monsterPrefab;
//풀 리스트
private List<GameObject> pool = new List<GameObject>();
public int maxMonsters = 30;
void Awake()
{
instance = this;
DontDestroyOnLoad(gameObject);
}
void Start()
{
for(int i = 0; i < maxMonsters; i++)
{
//몬스터 프리팹(게임오브젝트) 생성
GameObject monsterGo = Instantiate(this.monsterPrefab);
//비활성화 시키기
monsterGo.SetActive(false);
//현재 transform을 부모로 설정
monsterGo.transform.SetParent(this.transform);
//pool List에 추가
this.pool.Add(monsterGo);
}
}
//monster 가져오기(pool에서 빼기)
public GameObject GetMonster()
{
foreach(GameObject monsterGo in pool)
{
//monsterGo가 비활성화 되어있으면
if(monsterGo.activeSelf == false)
{
//pool에서 monsterGo 꺼내기
monsterGo.transform.SetParent(null);
//몬스터 활성화
monsterGo.SetActive(true);
//monsterGo 반환
return monsterGo;
}
}
return null;
}
//풀에 반환하기
public void Release(GameObject monsterGo)
{
monsterGo.SetActive(false);
monsterGo.transform.SetParent(this.transform);
}
}
02. 몬스터를 생성할 위치들(Point) 설정
: 추가한 Point들 중에 랜덤으로 몬스터를 일정시간이 지난 후(1초) 마다 생성하기
Player에 빈 오브젝트 'Spawn'을 추가하고 그 자식들로 'Point' 들을 추가
Spawn 스크립트 작성후 Spawn에 컴포넌트로 추가
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Spawner : MonoBehaviour
{
//Point들의 transform
[Header("#Prefabs")]
[SerializeField]
private Transform[] pointTrans;
private float timer;
void Start()
{
//현재 transform의 자식으로 붙여주기
this.pointTrans = this.GetComponentsInChildren<Transform>();
}
void Update()
{
//...1초마다 몬스터 생산하기
//타이머 설정
this.timer += Time.deltaTime;
if (this.timer > 1f)
{
//타이머 초기화
this.timer = 0f;
this.Spawn();
}
}
//...몬스터 생성 후 위치 할당 메서드
public void Spawn()
{
//PoolManager에서 가져온 몬스터에 변수 할당
GameObject monsterGo = GameManager.instance.pool.GetMonster();
//가져온 몬스터의 위치를 pointTrans[]에서 랜덤 배치
monsterGo.transform.position
= this.pointTrans[Random.Range(1, this.pointTrans.Length)].transform.position;
}
}
실행 결과
3. 몬스터 레벨 적용하기
일정 시간에 따라 몬스터를 레벨별로 나누어 소환하기
이전 2D 프로젝트때와 마찬가지로 Animator를 변경하여 몬스터를 소환하려고 했으나...
(오류발생)
2D와 3D의 근본적인 차이를 간과하였다..
골드메탈님(2D) : Sprite로 Animation 생성 O, Animator 변경시 게임오브젝트의 Sprite도 변경 O
현재 에셋(3D) : Sprite로 Animation 생성 X, Animator 변경시 게임오브젝트의 Sprite도 변경 X
현재 에셋(3D)의 경우 Animation이 Sprite로 구성되지 않아서
Animator를 바꿔준다고 해도 몬스터 게임오브젝트가 변하지 않는다.
따라서 몬스터를 종류별로 프리팹화 시킨 후 그것을 소환하는 방식으로 변경.
몬스터들을 각각 프리팹화 시킨 후 PoolManager에 넣어준다.
(**이 때 넣어준 순서 = 레벨 순서)
Spawner 스크립트에 아래와 같이 SpawnData를 추가하고 다음과 같이 설정해준다.
SpawnTime : 소환 시간(주기)
Type : 몬스터 타입
Health : 몬스터 체력
Speed : 몬스터 이동속도
스크립트 정리
Monster
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
public float speed = 1f;
[SerializeField]
private Player player;
//...몬스터 정보
private GameObject prefab;
private float health;
private float maxHealth;
[SerializeField]
private Bullet bullet;
private Animator anim;
void Awake()
{
this.anim = this.GetComponent<Animator>();
}
//Player를 향해 이동
void Update()
{
//방향 설정
Vector3 dir = this.player.transform.position - this.transform.position;
//player 바라보기
this.transform.LookAt(this.player.transform.position);
//이동하기
this.transform.Translate(Vector3.forward * this.speed * Time.deltaTime);
}
private void OnTriggerEnter(Collider other)
{
if (!other.CompareTag("Weapon"))
{
return;
}
this.health -= this.bullet.damage;
if(health > 0)
{
}
else
{
//죽음
Debug.Log("Destroy");
//코루틴 실행
this.StartCoroutine(CoDie());
}
}
private void OnEnable()
{
this.player = GameManager.instance.player.GetComponent<Player>();
this.health = maxHealth;
this.anim.SetBool("Dead", false);
}
//초기화
public void Init(SpawnData data)
{
this.speed = data.speed;
this.maxHealth = data.health;
this.health = data.health;
}
//죽음 코루틴
IEnumerator CoDie()
{
this.anim.SetBool("Dead", true);
yield return new WaitForSecondsRealtime(0.3f);
//pool에 다시 넣어주기
PoolManager.instance.Release(this.gameObject);
}
}
PoolManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
//싱글톤
public static PoolManager instance;
[Header("#Prefabs")]
[SerializeField]
private GameObject[] prefabs;
//pool 리스트(각 프리팹 종류에 따라 관리)
private List<GameObject>[] pools;
public int maxMonsters = 30;
void Awake()
{
instance = this;
//프리팹 종류만큼 List생성
this.pools = new List<GameObject>[prefabs.Length];
//for문 돌려서 배열 안의 각각의 List들 초기화
for(int i = 0; i < pools.Length; i++)
{
//List 초기화
pools[i] = new List<GameObject>();
}
}
//monster 가져오기(pool에서 빼기)
public GameObject GetMonster(int i)
{
//...게임오브젝트 중에 선택
GameObject select = null; //일단 비워둠
//...선택한 pool의 놀고 있는(비활성화된) 게임오브젝트 접근
foreach(GameObject monster in pools[i])
{
//...비활성화면
if (!monster.activeSelf)
{
//...발견하면 --> select 변수에 할당후 활성화
select = monster;
select.SetActive(true);
break;
}
}
//...못 찾았으면(전부 활성화상태면)
if (!select)
{
select = Instantiate(prefabs[i], transform);
pools[i].Add(select);
}
return select;
}
//풀에 반환하기
public void Release(GameObject monsterGo)
{
monsterGo.SetActive(false);
monsterGo.transform.SetParent(this.transform);
}
}
Spawner
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
public class Spawner : MonoBehaviour
{
//Point들의 transform
[Header("#Prefabs")]
//spawnPoint 배열
[SerializeField]
private Transform[] pointTrans;
//spawnData 배열
public SpawnData[] spawnData;
//..타이머
private float timer;
//..레벨
private int level;
void Start()
{
//현재 transform의 자식으로 붙여주기
this.pointTrans = this.GetComponentsInChildren<Transform>();
}
void Update()
{
//타이머 설정
this.timer += Time.deltaTime;
//레벨 : 게임시간/10
//level -> int 형식, 나눈 나머지를 버리고 몫만 int로 변환
this.level = Mathf.FloorToInt(GameManager.instance.gameTime / 10f);
//spawnData[]에서 레벨에 해당하는 spawnTime이 되면 몬스터 소환
if(timer > spawnData[level].spawnTime)
{
timer = 0f;
this.Spawn();
}
}
//몬스터 생성 후 위치 할당 메서드
public void Spawn()
{
//pool들 중에서 level에 따라 몬스터 호출
GameObject monster = GameManager.instance.pool.GetMonster(level);
//가져온 몬스터의 위치를 pointTrans[]에서 랜덤 배치
monster.transform.position
= this.pointTrans[Random.Range(1, this.pointTrans.Length)].transform.position;
Debug.LogFormat("<color=yellow>level : {0}</color>", level);
//몬스터 호출 및 초기화
monster.GetComponent<Monster>().Init(spawnData[level]);
}
}
[System.Serializable]
//...직렬화 해주기 - 개체 저장 또는 전송 가능
//하나의 스크립트 내에 여러개의 클래스 선언 가능
public class SpawnData
{
public float spawnTime;
public int Type;
public int health;
public float speed;
}
'3D 콘텐츠 제작 > [위저드 히어로] 제작 일지' 카테고리의 다른 글
[위저드 히어로05] 근거리 원거리 무기 및 플레이 영상 (0) | 2023.10.10 |
---|---|
[위저드 히어로 04] HUD 만들기 & 레벨업 시스템 (0) | 2023.10.05 |
[위저드 히어로 02] 무한맵 생성 (0) | 2023.09.25 |
[위저드 히어로 01] 플레이어 이동 및 카메라 조정 (0) | 2023.09.24 |
[위저드 히어로] 제작 일정 (0) | 2023.09.22 |