1. 오브젝트 풀링으로 몬스터 생성
오브젝트 풀링을 사용하는 이유?
: Instantiate 와 Destory를 자주 사용하면 메모리에 문제가 생길 수 있음
따라서 오브젝트들을 미리 생성해놓고 활성/비활성을 통해 게임신에 노출하는 것을 조절하자
01. 목표
Player 주변에 설정해둔 Point들 中 랜덤으로 1곳에서 0.2초 마다 3가지 몬스터 中 하나를 랜덤으로 생성하기
설정해둔 Point(몬스터 생성 위치) 들
몬스터 종류 3가지 - PoolManager에 넣어주기
[스크립트]
PoolManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PoolManager : MonoBehaviour
{
//프리팹 보관할 변수
public GameObject[] prefabs;
//pool 담당하는 리스트들
//prefab 종류에 따라 리스트 개수 생성 -> List도 배열화
List<GameObject>[] pools;
void Awake()
{
//List[] 초기화
pools = new List<GameObject>[prefabs.Length];
//for문 돌려서 배열 안의 각각의 List들 초기화
for(int i = 0;i< pools.Length; i++)
{
//List 초기화
pools[i] = new List<GameObject>();
}
Debug.Log(pools.Length);
}
//pool 안의 게임오브젝트(item) 반환(가져오는) 메서드
public GameObject Get(int i)
{
//게임오브젝트 중에 '선택'
GameObject select = null; //일단 비워둠
//... 선택한 pool의 놀고있는(비활성화된) 게임오브젝트 접근
foreach (GameObject item in pools[i])
{
//...비활성화 되었으면
if(!item.activeSelf)
{
//... 발견하면 -> select 변수에 할당 후 활성화
select = item;
select.SetActive(true);
break;
}
}
//... 못 찾았으면?
if(!select)
{
//...새롭게 생성하고 -> select 변수에 할당
select = Instantiate(prefabs[i],transform);
pools[i].Add(select);
}
return select; //선택한 것을 반환
}
}
Reposition
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Reposition : MonoBehaviour
{
//모든 class의 collider2D를 아우름
private Collider2D coll;
private void Awake()
{
this.coll = this.GetComponent<Collider2D>();
}
//Trigger가 체크된 Collier에서 나갔을 때
void OnTriggerExit2D(Collider2D collision)
{
if (!collision.CompareTag("Area"))
{
return;
}
//Player 포지션
Vector3 playerPos = GameManager.instance.player.transform.position;
//타일맵 포지션
Vector3 myPos = this.transform.position;
//둘 사이의 거리 측정(x,y), 절대값으로 반환
float disX = Mathf.Abs(playerPos.x - myPos.x);
float disY = Mathf.Abs(playerPos.y - myPos.y);
//Player 방향(x,y)
Vector3 playerDir = GameManager.instance.player.inputVec;
//대각선일 때는 nomalized에 의해 1보다 값이 작아짐
//연산자 (조건) ? (true일 때 값) : (false일 때 값)
float dirX = playerDir.x < 0 ? -1 : 1;
float dirY = playerDir.y < 0 ? -1 : 1;
switch (transform.tag)
{
case "Ground":
//좌우로 이동하면
if (disX > disY)
{
//dirX 방향으로 40칸 건너서 이동
this.transform.Translate(Vector3.right * dirX * 40);
}
//상하로 이동하면
else if (disX < disY)
{
//dirY 방향으로 40칸 건너서 이동
this.transform.Translate(Vector3.up * dirY * 40);
}
break;
case "Enemy":
//몬스터가 살아있으면
if (coll.enabled)
{
//Player 맞은편에서 몬스터 생성(화면 밖)
//화면 크기인 20 떨어진 거리에서 +-3만큼 랜덤으로 생성
transform.Translate(playerDir * 20 + new Vector3(Random.Range(-3f,3f),Random.Range(-3f,3f),0));
}
break;
}
}
}
GameManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public Player player;
public PoolManager pool;
//바로 메모리에 저장
public static GameManager instance;
void Awake()
{
instance = this;
}
}
Spawner
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class Spawner : MonoBehaviour
{
public Transform[] spawnPoint;
float timer;
int level;
void Start()
{
this.spawnPoint = this.GetComponentsInChildren<Transform>();
}
void Update()
{
timer += Time.deltaTime;
//0.2초 후 마다 몬스터 생성하기
if (timer > 0.2f)
{
timer = 0f;
this.Spawn();
}
}
void Spawn()
{
//pool들 중에 랜덤으로 가져옴
GameObject enemy = GameManager.instance.pool.Get(Random.Range(0,3));
//가져온 pool을 spawnPoint들 중에 랜덤으로 배치
enemy.transform.position
= this.spawnPoint[Random.Range(1, this.spawnPoint.Length)].transform.position;
}
}
Monster
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Monster : MonoBehaviour
{
[SerializeField]
private Player player;
private float speed =3f;
private Rigidbody2D rBody;
private int dirX;
private Animator anim;
private bool isLive = true;
void Start()
{
this.rBody = this.GetComponent<Rigidbody2D>();
this.anim = this.GetComponent<Animator>();
this.player = GameManager.instance.player.GetComponent<Player>();
}
void Update()
{
if (!this.isLive)
{
return;
}
Vector3 dir = this.player.transform.position - this.transform.position;
this.transform.Translate(dir.normalized * this.speed * Time.deltaTime);
rBody.velocity = Vector2.zero;
//바라보는 방향 설정
if (dir.x < 0)
{
dirX = -1; //왼쪽
}
else if (dir.x > 1)
{
dirX = 1; //오른쪽
}
//방향전환
if (dir != Vector3.zero)
{
this.transform.localScale = new Vector3(dirX, 1, 1);
}
}
private void OnCollisionEnter2D(Collision2D collision)
{
//"Player" 테그를 붙인 게임오브젝트와 충돌하면
if (collision.collider.CompareTag("Player"))
{
//현재 게임오브젝트(몬스터)를 비활성화
this.gameObject.SetActive(false);
}
}
}
결과
2. 몬스터 레벨 적용
01. 목표
: 플레이 시간에 따라 몬스터 종류를 구분하여 소환하기
결과
-시간에 따라 다른 종류의 몬스터 등장
02. 목표
: 몬스터 별로 레벨 정보(플레이 시간, 체력, 속도 등)를 다르게 하여
플레이 시간에 따라 해당되는 레벨의 몬스터 소환하기
※ 하나의 Script 내에 여러개의 Class 선언 가능!
=> 'Spawner' Script 안에 SpawnData 클래스 추가하기
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
public class Spawner : MonoBehaviour
{
//spawnPoint 배열
public Transform[] spawnPoint;
//spawnData 배열
public SpawnData[] spawnData;
float timer;
int level;
//...중략
void Spawn()
{
//pool들 중에 level에 따라 몬스터 호출
GameObject monster = GameManager.instance.pool.Get(0);
//가져온 pool을 spawnPoint들 중에 랜덤으로 배치
monster.transform.position
= this.spawnPoint[Random.Range(1, this.spawnPoint.Length)].transform.position;
Debug.LogFormat("level : {0}", level);
//몬스터 호출 및 초기화
monster.GetComponent<Monster>().Init(spawnData[level]); //
}
}
[System.Serializable] //직렬화 해주기 - 개체 저장 혹은 전송 가능
//하나의 스크립트 내에 여러개의 클래스 선언 가능
public class SpawnData
{
//소환시간, 스프라이트 타입, 체력, 속도
public float spawnTime;
public int spriteType;
public int health;
public float speed;
}
- 몬스터 종류(3가지) 별로 SpawnData 설정하기
- Monster 스크립트의 animCon에 몬스터 종류별 AnimatorController 넣어주기
나머지 Script 들도 잘 작성했는데....
오류 발생...
시간에 따라 몬스터 종류도 다르게 잘 나오는데 자꾸 아래와 같은 오류문구가 나왔다.
스크립트를 봐도 딱히 문제가 없는 것 같아 꽤 긴 시간 고민을 했다.
문득 해당 유튜브 댓글에 나와같은 문제를 겪는 분이 있지 않을까 해서 찾아본 결과
나와 비슷한 분들이 꽤 있었고 해결 방법을 찾을 수 있었다!
Monster 스크립트에 Start보다 Init이 더 빨리 호출돼서 문제가 발생한 것이다..
그래서 Monster 스크립트의 Start와 Monster 스크립트의 Init을 호출하고 있는 Spawner 스크립트 에 Log를 찍어보았더니
실제로 Monster 스크립트의 Start보다 Init이 더 먼저 호출되고 있었다.
아직 Monster에 컴포넌트 부착이 안 되어있는 상태에서 Init을 호출하고 있으니 문제가 발생했던 것이라고 생각한다.
Monster 스크립트의 Start -> Awake로 변경한 결과 아래와 같이 몬스터도 잘 호출되고 오류문도 없어졌다!
결과
+느낀점
: 새롭게 무언가를 할 때 마다 문제는 매번 발생하고 그게 참 답답하고 그렇다.
'개발자' 는 문제를 해결하는 사람이라고 선생님께서 말씀해주셨는데 이제는 이런 문제가 발생하는 것은 일상이라고 생각하고 적응하며 문제 해결을 위해 검색하는 능력과 생각하는 능력을 더 키워야겠다고 생각했다..
'2D 콘텐츠 제작 > [언데드 서바이벌] 제작 일지' 카테고리의 다른 글
[언데드 서바이벌 05] HUD 구현 & 피격 액션 추가 & 능력 업그레이드 구현 (0) | 2023.09.18 |
---|---|
[언데드 서바이벌 04] 공격 구현(근거리, 원거리)&무기 장착 (0) | 2023.09.16 |
[언데드 서바이벌 02] 무한맵 생성 & 몬스터 생성 (0) | 2023.09.14 |
[언데드 서바이벌 01] 게임오브젝트 생성, 애니메이션 제작, 이동 구현 (1) | 2023.09.13 |
골드메탈 -언데드 서바이버(Undead Survivor) 제작 일정 (0) | 2023.09.13 |