Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
Tags
- 입문
- 단계별로 풀어보기
- c++
- 다이나믹 프로그래밍
- 코딩 테스트
- 백준
- 1월
- 2022년
- 2025년
- 5월
- 수학
- C/C++
- 2023년
- 자료 구조
- 유니티 심화과정
- 2024년
- 프로그래머스
- 코딩 기초 트레이닝
- 게임 엔진 공부
- 6월
- 2월
- 유니티
- 개인 프로젝트 - 런앤건
- 4월
- 기초
- todolist
- 3월
- 개인 프로젝트
- 골드메탈
- 10월
Archives
- Today
- Total
기록 보관소
[Unity/유니티] 기초-2D 종스크롤 슈팅: 최적화의 기본, 오브젝트 풀링[B34] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
https://youtube.com/playlist?list=PLO-mt5Iu5TeYI4dbYwWP8JqZMC9iuUIW2
유니티 강좌 기초 채널 Basic
유니티 개발을 처음 시작하시는 입문자 분들을 위한 기초 채널. [ 프로젝트 ] B00 ~ B12 (BE1) : 유니티 필수 기초 B13 ~ B19 (BE2) : 2D 플랫포머 B20 ~ B26 (BE3) : 2D 탑다운 대화형 RPG B27 ~ B37 (BE4) : 2D 종스크롤
www.youtube.com
2D 종스크롤 슈팅: 최적화의 기본, 오브젝트 풀링[B34]
1. 오브젝트 풀링이란

//ObjectManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectManager : MonoBehaviour {
public GameObject enemyLPrefab;
public GameObject enemyMPrefab;
public GameObject enemySPrefab;
public GameObject itemCoinPrefab;
public GameObject itemPowerPrefab;
public GameObject itemBoomPrefab;
public GameObject bulletPlayerAPrefab;
public GameObject bulletPlayerBPrefab;
public GameObject bulletEnemyAPrefab;
public GameObject bulletEnemyBPrefab;
GameObject[] enemyL;
GameObject[] enemyM;
GameObject[] enemyS;
GameObject[] itemCoin;
GameObject[] itemPower;
GameObject[] itemBoom;
GameObject[] bulletPlayerA;
GameObject[] bulletPlayerB;
GameObject[] bulletEnemyA;
GameObject[] bulletEnemyB;
void Awake() {
enemyL = new GameObject[10];
enemyM = new GameObject[10];
enemyS = new GameObject[20];
itemCoin = new GameObject[20];
itemPower = new GameObject[10];
itemBoom = new GameObject[10];
bulletPlayerA = new GameObject[100];
bulletPlayerB = new GameObject[100];
bulletEnemyA = new GameObject[100];
bulletEnemyB = new GameObject[100];
Generate();
}
void Generate() {
//적 기체
for (int index = 0; index < enemyL.Length; index++) {
enemyL[index] = Instantiate(enemyLPrefab);
enemyL[index].SetActive(false);
}
for (int index = 0; index < enemyM.Length; index++) {
enemyM[index] = Instantiate(enemyMPrefab);
enemyM[index].SetActive(false);
}
for (int index = 0; index < enemyS.Length; index++) {
enemyS[index] = Instantiate(enemySPrefab);
enemyS[index].SetActive(false);
}
//아이템
for (int index = 0; index < itemCoin.Length; index++) {
itemCoin[index] = Instantiate(itemCoinPrefab);
itemCoin[index].SetActive(false);
}
for (int index = 0; index < itemPower.Length; index++) {
itemPower[index] = Instantiate(itemPowerPrefab);
itemPower[index].SetActive(false);
}
for (int index = 0; index < itemBoom.Length; index++) {
itemBoom[index] = Instantiate(itemBoomPrefab);
itemBoom[index].SetActive(false);
}
//총알
for (int index = 0; index < bulletPlayerA.Length; index++) {
bulletPlayerA[index] = Instantiate(bulletPlayerAPrefab);
bulletPlayerA[index].SetActive(false);
}
for (int index = 0; index < bulletPlayerB.Length; index++) {
bulletPlayerB[index] = Instantiate(bulletPlayerBPrefab);
bulletPlayerB[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyA.Length; index++) {
bulletEnemyA[index] = Instantiate(bulletEnemyAPrefab);
bulletEnemyA[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyB.Length; index++) {
bulletEnemyB[index] = Instantiate(bulletEnemyBPrefab);
bulletEnemyB[index].SetActive(false);
}
}
}


- 적 기체들과 총알 같은 프리펩들은 Instantiate와 Destroy를 반복하면 조각난 메모리가 계속해서 쌓이게 되어 유니티에서 가비지 컬렉트(GC)를 실행한다. 가비지 컬렉트가 실행되면 렉이 생길 수 있다. 따라서 오브젝트 풀링을 통해서 이를 해결해야한다.
- 가비지컬렉트(GC) : 쌓인 조각난 메모리를 비우는 기술
- 오브젝트 풀링 : 미리 생성해둔 풀에서 활성화/비활성화로 사용
- 오브젝트 풀로 사용할 ObjectManager를 통해 프리펩들을 미리 다 생성해서 비활성화 한다. 실행히 위 캡쳐처럼 Hierachy 창에 많은 수의 프리펩들이 생성되어 비활성화 되며, 최초 실행시 약간의 렉을 동반한다.
- 일반적인 게임에서 로딩 시간이 있는 이유는 장면 배치와 이 오브젝트 풀 생성때문이다.
2. 풀 사용하기
//ObjectManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectManager : MonoBehaviour {
public GameObject enemyLPrefab;
public GameObject enemyMPrefab;
public GameObject enemySPrefab;
public GameObject itemCoinPrefab;
public GameObject itemPowerPrefab;
public GameObject itemBoomPrefab;
public GameObject bulletPlayerAPrefab;
public GameObject bulletPlayerBPrefab;
public GameObject bulletEnemyAPrefab;
public GameObject bulletEnemyBPrefab;
GameObject[] enemyL;
GameObject[] enemyM;
GameObject[] enemyS;
GameObject[] itemCoin;
GameObject[] itemPower;
GameObject[] itemBoom;
GameObject[] bulletPlayerA;
GameObject[] bulletPlayerB;
GameObject[] bulletEnemyA;
GameObject[] bulletEnemyB;
GameObject[] targetPool;
void Awake() {
enemyL = new GameObject[10];
enemyM = new GameObject[10];
enemyS = new GameObject[20];
itemCoin = new GameObject[20];
itemPower = new GameObject[10];
itemBoom = new GameObject[10];
bulletPlayerA = new GameObject[100];
bulletPlayerB = new GameObject[100];
bulletEnemyA = new GameObject[100];
bulletEnemyB = new GameObject[100];
Generate();
}
void Generate() {
//적 기체
for (int index = 0; index < enemyL.Length; index++) {
enemyL[index] = Instantiate(enemyLPrefab);
enemyL[index].SetActive(false);
}
for (int index = 0; index < enemyM.Length; index++) {
enemyM[index] = Instantiate(enemyMPrefab);
enemyM[index].SetActive(false);
}
for (int index = 0; index < enemyS.Length; index++) {
enemyS[index] = Instantiate(enemySPrefab);
enemyS[index].SetActive(false);
}
//아이템
for (int index = 0; index < itemCoin.Length; index++) {
itemCoin[index] = Instantiate(itemCoinPrefab);
itemCoin[index].SetActive(false);
}
for (int index = 0; index < itemPower.Length; index++) {
itemPower[index] = Instantiate(itemPowerPrefab);
itemPower[index].SetActive(false);
}
for (int index = 0; index < itemBoom.Length; index++) {
itemBoom[index] = Instantiate(itemBoomPrefab);
itemBoom[index].SetActive(false);
}
//총알
for (int index = 0; index < bulletPlayerA.Length; index++) {
bulletPlayerA[index] = Instantiate(bulletPlayerAPrefab);
bulletPlayerA[index].SetActive(false);
}
for (int index = 0; index < bulletPlayerB.Length; index++) {
bulletPlayerB[index] = Instantiate(bulletPlayerBPrefab);
bulletPlayerB[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyA.Length; index++) {
bulletEnemyA[index] = Instantiate(bulletEnemyAPrefab);
bulletEnemyA[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyB.Length; index++) {
bulletEnemyB[index] = Instantiate(bulletEnemyBPrefab);
bulletEnemyB[index].SetActive(false);
}
}
public GameObject MakeObj(string type) {
switch (type) {
case "EnemyL":
targetPool = enemyL;
break;
case "EnemyM":
targetPool = enemyM;
break;
case "EnemyS":
targetPool = enemyS;
break;
case "ItemCoin":
targetPool = itemCoin;
break;
case "ItemPower":
targetPool = itemPower;
break;
case "ItemBoom":
targetPool = itemBoom;
break;
case "BulletPlayerA":
targetPool = bulletPlayerA;
break;
case "BulletPlayerB":
targetPool = bulletPlayerB;
break;
case "BulletEnemyA":
targetPool = bulletEnemyA;
break;
case "BulletEnemyB":
targetPool = bulletEnemyB;
break;
}
for (int index = 0; index < targetPool.Length; index++) {
if (!targetPool[index].activeSelf) { //비활성화된 오브젝트에 접근
targetPool[index].SetActive(true); //해당 오브젝트를 활성화 후
return targetPool[index]; //오브젝트 반환
}
}
return null;
}
}
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour {
string[] enemyObjs;
public Transform[] spawnPoints;
public GameObject player;
public Text scoreText;
public Image[] lifeImage;
public Image[] boomImage;
public GameObject gameOverSet;
public ObjectManager objectManager;
public float maxSpawnDelay;
public float curSpawnDelay;
void Awake() {
enemyObjs = new string[] { "EnemyS", "EnemyM", "EnemyL" };
}
void Update() {
curSpawnDelay += Time.deltaTime;
if (curSpawnDelay > maxSpawnDelay) {
SpawnEnemy();
maxSpawnDelay = Random.Range(0.5f, 3f);
curSpawnDelay = 0;
}
//UI 점수 업데이트
Player playerLogic = player.GetComponent<Player>();
scoreText.text = string.Format("{0:n0}", playerLogic.score);
}
void SpawnEnemy() {
int ranEnemy = Random.Range(0, 3);
int ranPoint = Random.Range(0, 9);
GameObject enemy = objectManager.MakeObj(enemyObjs[ranEnemy]);
enemy.transform.position = spawnPoints[ranPoint].position;
Rigidbody2D rigid = enemy.GetComponent<Rigidbody2D>();
Enemy enemyLogic = enemy.GetComponent<Enemy>();
enemyLogic.player = player;
enemyLogic.objectManager = objectManager;
if (ranPoint == 5 || ranPoint == 6) { //오른쪽 포인트
enemy.transform.Rotate(Vector3.back * 90); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed * (-1), -1);
}
else if (ranPoint == 7 || ranPoint == 8) { //왼쪽 포인트
enemy.transform.Rotate(Vector3.back * (-90)); //적 기체 스프라이트 돌리기
rigid.velocity = new Vector2(enemyLogic.speed, -1);
}
else { //중앙 앞쪽 포인트
rigid.velocity = new Vector2(0, enemyLogic.speed * (-1));
}
}
public void UpdateLifeIcon(int life) {
//UI 라이프 모두 투명화
for (int index = 0; index < 3; index++) {
lifeImage[index].color = new Color(1, 1, 1, 0);
}
//UI 남은 라이프 수만큼 활성화
for (int index = 0; index < life; index++) {
lifeImage[index].color = new Color(1, 1, 1, 1);
}
}
public void UpdateBoomIcon(int boom) {
//UI 붐 모두 투명화
for (int index = 0; index < 3; index++) {
boomImage[index].color = new Color(1, 1, 1, 0);
}
//UI 남은 붐 수만큼 활성화
for (int index = 0; index < boom; index++) {
boomImage[index].color = new Color(1, 1, 1, 1);
}
}
public void RespawnPlayer() {
Invoke("RespawnPlayerExe", 2f);
}
void RespawnPlayerExe() {
player.transform.position = Vector3.down * 3.5f; //플레이어 위치 초기화
player.SetActive(true);
Player playerLogic = player.GetComponent<Player>();
playerLogic.isHit = false;
}
public void GameOver() {
gameOverSet.SetActive(true);
}
public void GameRetry() {
SceneManager.LoadScene(0);
}
}
//Enemy 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
public string enemyName;
public int enemyScore;
public float speed;
public int health;
public float maxShotDelay;
public float curShotDelay;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject itemCoin;
public GameObject itemPower;
public GameObject itemBoom;
public GameObject player;
public ObjectManager objectManager;
public Sprite[] sprites;
SpriteRenderer spriteRenderer;
void Awake() {
spriteRenderer = GetComponent<SpriteRenderer>();
}
void Update(){
Fire();
Reload();
}
void Fire() {
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
if (enemyName == "S") {
GameObject bullet = objectManager.MakeObj("BulletEnemyA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector3 dirVec = player.transform.position - transform.position; //목표물로 방향 = 목표물 위치 - 자신의 위치
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
else if (enemyName == "L") {
GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
bulletR.transform.position = transform.position + Vector3.right * 0.3f;
GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
bulletL.transform.position = transform.position + Vector3.left * 0.3f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
public void onHit(int dmg) {
if (health <= 0) //이미 죽은 상태라면
return;
health -= dmg;
spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
Invoke("ReturnSprite", 0.1f);
if (health <= 0) {
Player playerLogic = player.GetComponent<Player>();
playerLogic.score += enemyScore;
//랜덤 아이템 드랍
int ran = Random.Range(0, 10);
if (ran < 3) { //드랍 없음 30%
Debug.Log("Not Item");
}
else if (ran < 6) { //동전 드랍 30%
GameObject itemCoin = objectManager.MakeObj("ItemCoin");
itemCoin.transform.position = transform.position;
}
else if (ran < 8) { //파워 드랍 20%
GameObject itemPower = objectManager.MakeObj("ItemPower");
itemPower.transform.position = transform.position;
}
else if (ran < 10) { //붐 드랍 20%
GameObject itemBoom = objectManager.MakeObj("ItemBoom");
itemBoom.transform.position = transform.position;
}
gameObject.SetActive(false);
}
}
void ReturnSprite() {
spriteRenderer.sprite = sprites[0];
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "BorderBullet") //맵의 경계로 가게되면
gameObject.SetActive(false);
else if (collision.gameObject.tag == "PlayerBullet") { //플레이어 총알에 닿으면
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
onHit(bullet.dmg);
collision.gameObject.SetActive(false); //플레이어 총알 삭제
}
}
}
//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public GameManager gameManager;
public ObjectManager objectManager;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject boomEffect;
Animator anim;
public int life;
public int score;
public float speed;
public int maxPower;
public int power;
public int maxBoom;
public int boom;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
public bool isHit;
public bool isBoomTime;
void Awake() {
anim = GetComponent<Animator>();
}
void Update() {
Move();
Fire();
Boom();
Reload();
}
void Move() { //플레이어 이동 함수
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
anim.SetInteger("Input", (int)h);
}
void Fire() { //플레이어 총알 발사 함수
if (!Input.GetButton("Fire1")) //Ctrl 키, 마우스 좌클릭
return;
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
switch(power) {
case 1:
GameObject bullet = objectManager.MakeObj("BulletPlayerA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = objectManager.MakeObj("BulletPlayerA");
bulletR.transform.position = transform.position + Vector3.right * 0.1f;
GameObject bulletL = objectManager.MakeObj("BulletPlayerA");
bulletL.transform.position = transform.position + Vector3.left * 0.1f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 3:
GameObject bulletRR = objectManager.MakeObj("BulletPlayerA");
bulletRR.transform.position = transform.position + Vector3.right * 0.35f;
GameObject bulletCC = objectManager.MakeObj("BulletPlayerB");
bulletCC.transform.position = transform.position;
GameObject bulletLL = objectManager.MakeObj("BulletPlayerA");
bulletLL.transform.position = transform.position + Vector3.left * 0.35f;
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
void Boom() {
if (!Input.GetButton("Fire2")) //Alt 키, 마우스 우클릭
return;
if (isBoomTime) //Boom 재사용 대기시간 중이라면
return;
if (boom == 0) //필살기가 없다면
return;
boom--;
isBoomTime = true;
gameManager.UpdateBoomIcon(boom);
//Boom 활성화
boomEffect.SetActive(true);
Invoke("OffBoomEffect", 4f);
//모든 적 처치
GameObject[] enemies = GameObject.FindGameObjectsWithTag("Enemy");
for (int index = 0; index < enemies.Length; index++)
{
Enemy enemyLogic = enemies[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
//모든 적 총알 제거
GameObject[] bullets = GameObject.FindGameObjectsWithTag("EnemyBullet");
for (int index = 0; index < bullets.Length; index++) {
bullets[index].SetActive(false);
}
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch(collision.gameObject.name) {
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Left":
isTouchLeft = true;
break;
case "Right":
isTouchRight = true;
break;
}
}
else if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet") {
if (isHit) //이미 맞으면 다시 맞지 않도록 return
return;
isHit = true;
life--;
gameManager.UpdateLifeIcon(life);
if (life == 0) {
gameManager.GameOver();
}
else {
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
collision.gameObject.SetActive(false);
}
else if (collision.gameObject.tag == "Item") {
Item item = collision.gameObject.GetComponent<Item>();
switch (item.type) {
case "Coin":
score += 1000;
break;
case "Power":
if (power == maxPower)
score += 500;
else
power++;
break;
case "Boom":
if (boom == maxBoom)
score += 500;
else {
boom++;
gameManager.UpdateBoomIcon(boom);
}
break;
}
collision.gameObject.SetActive(false);
}
}
void OffBoomEffect() {
boomEffect.SetActive(false);
isBoomTime = false;
}
void OnTriggerExit2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch (collision.gameObject.name) {
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Left":
isTouchLeft = false;
break;
case "Right":
isTouchRight = false;
break;
}
}
}
}
//Bullet 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Bullet : MonoBehaviour {
public int dmg;
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "BorderBullet") {
gameObject.SetActive(false);
}
}
}




- 앞에서 생성한 풀에서 이제 활성화/비활성화를 해야하므로 기존의 Instantiate와 Destroy 함수들은 모두 제거해서 ObjectManager에서 끌어와서 활성화/비활성화 하도록 수정한다.
3. 로직 정리


- 적 기체가 최초 생성시 왼쪽이나 오른쪽 포인트에서 생성되면, 이후 위쪽 포인트에서 내려옴에도 위 캡처처럼 옆 모습으로 내려오는 버그와 아이템이 생성되어도 이동하지 않는 버그가 있다.
- 추가로 캡처는 없지만 적 기체가 한번 처치되었을 때, 다시 활성화되면 공격을 해도 피해를 주지 못하는 버그도 있다. 이는 한번 죽은 개체가 다시 한번 활성화되면서 체력이 0 이하가 되어서 그런 것이다.
//ObjectManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ObjectManager : MonoBehaviour {
public GameObject enemyLPrefab;
public GameObject enemyMPrefab;
public GameObject enemySPrefab;
public GameObject itemCoinPrefab;
public GameObject itemPowerPrefab;
public GameObject itemBoomPrefab;
public GameObject bulletPlayerAPrefab;
public GameObject bulletPlayerBPrefab;
public GameObject bulletEnemyAPrefab;
public GameObject bulletEnemyBPrefab;
GameObject[] enemyL;
GameObject[] enemyM;
GameObject[] enemyS;
GameObject[] itemCoin;
GameObject[] itemPower;
GameObject[] itemBoom;
GameObject[] bulletPlayerA;
GameObject[] bulletPlayerB;
GameObject[] bulletEnemyA;
GameObject[] bulletEnemyB;
GameObject[] targetPool;
void Awake() {
enemyL = new GameObject[10];
enemyM = new GameObject[10];
enemyS = new GameObject[20];
itemCoin = new GameObject[20];
itemPower = new GameObject[10];
itemBoom = new GameObject[10];
bulletPlayerA = new GameObject[100];
bulletPlayerB = new GameObject[100];
bulletEnemyA = new GameObject[100];
bulletEnemyB = new GameObject[100];
Generate();
}
void Generate() {
//적 기체
for (int index = 0; index < enemyL.Length; index++) {
enemyL[index] = Instantiate(enemyLPrefab);
enemyL[index].SetActive(false);
}
for (int index = 0; index < enemyM.Length; index++) {
enemyM[index] = Instantiate(enemyMPrefab);
enemyM[index].SetActive(false);
}
for (int index = 0; index < enemyS.Length; index++) {
enemyS[index] = Instantiate(enemySPrefab);
enemyS[index].SetActive(false);
}
//아이템
for (int index = 0; index < itemCoin.Length; index++) {
itemCoin[index] = Instantiate(itemCoinPrefab);
itemCoin[index].SetActive(false);
}
for (int index = 0; index < itemPower.Length; index++) {
itemPower[index] = Instantiate(itemPowerPrefab);
itemPower[index].SetActive(false);
}
for (int index = 0; index < itemBoom.Length; index++) {
itemBoom[index] = Instantiate(itemBoomPrefab);
itemBoom[index].SetActive(false);
}
//총알
for (int index = 0; index < bulletPlayerA.Length; index++) {
bulletPlayerA[index] = Instantiate(bulletPlayerAPrefab);
bulletPlayerA[index].SetActive(false);
}
for (int index = 0; index < bulletPlayerB.Length; index++) {
bulletPlayerB[index] = Instantiate(bulletPlayerBPrefab);
bulletPlayerB[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyA.Length; index++) {
bulletEnemyA[index] = Instantiate(bulletEnemyAPrefab);
bulletEnemyA[index].SetActive(false);
}
for (int index = 0; index < bulletEnemyB.Length; index++) {
bulletEnemyB[index] = Instantiate(bulletEnemyBPrefab);
bulletEnemyB[index].SetActive(false);
}
}
public GameObject MakeObj(string type) {
switch (type) {
case "EnemyL":
targetPool = enemyL;
break;
case "EnemyM":
targetPool = enemyM;
break;
case "EnemyS":
targetPool = enemyS;
break;
case "ItemCoin":
targetPool = itemCoin;
break;
case "ItemPower":
targetPool = itemPower;
break;
case "ItemBoom":
targetPool = itemBoom;
break;
case "BulletPlayerA":
targetPool = bulletPlayerA;
break;
case "BulletPlayerB":
targetPool = bulletPlayerB;
break;
case "BulletEnemyA":
targetPool = bulletEnemyA;
break;
case "BulletEnemyB":
targetPool = bulletEnemyB;
break;
}
for (int index = 0; index < targetPool.Length; index++) {
if (!targetPool[index].activeSelf) { //비활성화된 오브젝트에 접근
targetPool[index].SetActive(true); //해당 오브젝트를 활성화 후
return targetPool[index]; //오브젝트 반환
}
}
return null;
}
public GameObject[] GetPool(string type) {
switch (type) {
case "EnemyL":
targetPool = enemyL;
break;
case "EnemyM":
targetPool = enemyM;
break;
case "EnemyS":
targetPool = enemyS;
break;
case "ItemCoin":
targetPool = itemCoin;
break;
case "ItemPower":
targetPool = itemPower;
break;
case "ItemBoom":
targetPool = itemBoom;
break;
case "BulletPlayerA":
targetPool = bulletPlayerA;
break;
case "BulletPlayerB":
targetPool = bulletPlayerB;
break;
case "BulletEnemyA":
targetPool = bulletEnemyA;
break;
case "BulletEnemyB":
targetPool = bulletEnemyB;
break;
}
return targetPool;
}
}
//Player 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour {
public GameManager gameManager;
public ObjectManager objectManager;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject boomEffect;
Animator anim;
public int life;
public int score;
public float speed;
public int maxPower;
public int power;
public int maxBoom;
public int boom;
public float maxShotDelay;
public float curShotDelay;
public bool isTouchTop;
public bool isTouchBottom;
public bool isTouchLeft;
public bool isTouchRight;
public bool isHit;
public bool isBoomTime;
void Awake() {
anim = GetComponent<Animator>();
}
void Update() {
Move();
Fire();
Boom();
Reload();
}
void Move() { //플레이어 이동 함수
float h = Input.GetAxisRaw("Horizontal");
if ((isTouchRight && h == 1) || (isTouchLeft && h == -1))
h = 0;
float v = Input.GetAxisRaw("Vertical");
if ((isTouchTop && v == 1) || (isTouchBottom && v == -1))
v = 0;
Vector3 curPos = transform.position;
Vector3 nextPos = new Vector3(h, v, 0) * speed * Time.deltaTime;
transform.position = curPos + nextPos;
if (Input.GetButtonDown("Horizontal") || Input.GetButtonUp("Horizontal"))
anim.SetInteger("Input", (int)h);
}
void Fire() { //플레이어 총알 발사 함수
if (!Input.GetButton("Fire1")) //Ctrl 키, 마우스 좌클릭
return;
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
switch(power) {
case 1:
GameObject bullet = objectManager.MakeObj("BulletPlayerA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
rigid.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 2:
GameObject bulletR = objectManager.MakeObj("BulletPlayerA");
bulletR.transform.position = transform.position + Vector3.right * 0.1f;
GameObject bulletL = objectManager.MakeObj("BulletPlayerA");
bulletL.transform.position = transform.position + Vector3.left * 0.1f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
rigidR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
case 3:
GameObject bulletRR = objectManager.MakeObj("BulletPlayerA");
bulletRR.transform.position = transform.position + Vector3.right * 0.35f;
GameObject bulletCC = objectManager.MakeObj("BulletPlayerB");
bulletCC.transform.position = transform.position;
GameObject bulletLL = objectManager.MakeObj("BulletPlayerA");
bulletLL.transform.position = transform.position + Vector3.left * 0.35f;
Rigidbody2D rigidRR = bulletRR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidCC = bulletCC.GetComponent<Rigidbody2D>();
Rigidbody2D rigidLL = bulletLL.GetComponent<Rigidbody2D>();
rigidRR.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidCC.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
rigidLL.AddForce(Vector2.up * 10, ForceMode2D.Impulse);
break;
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
void Boom() {
if (!Input.GetButton("Fire2")) //Alt 키, 마우스 우클릭
return;
if (isBoomTime) //Boom 재사용 대기시간 중이라면
return;
if (boom == 0) //필살기가 없다면
return;
boom--;
isBoomTime = true;
gameManager.UpdateBoomIcon(boom);
//Boom 활성화
boomEffect.SetActive(true);
Invoke("OffBoomEffect", 4f);
//모든 적 처치
GameObject[] enemiesL = objectManager.GetPool("EnemyL");
GameObject[] enemiesM = objectManager.GetPool("EnemyM");
GameObject[] enemiesS = objectManager.GetPool("EnemyS");
for (int index = 0; index < enemiesL.Length; index++)
if (enemiesL[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesL[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesM.Length; index++)
if (enemiesM[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesM[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
for (int index = 0; index < enemiesS.Length; index++)
if (enemiesS[index].activeSelf) { //활성화된 적만 처치
Enemy enemyLogic = enemiesS[index].GetComponent<Enemy>();
enemyLogic.onHit(1000);
}
//모든 적 총알 제거
GameObject[] bulletsA = objectManager.GetPool("BulletEnemyA");
GameObject[] bulletsB = objectManager.GetPool("BulletEnemyB");
for (int index = 0; index < bulletsA.Length; index++)
if (bulletsA[index].activeSelf) //활성화된 총알만 제거
bulletsA[index].SetActive(false);
for (int index = 0; index < bulletsB.Length; index++)
if (bulletsB[index].activeSelf) //활성화된 총알만 제거
bulletsB[index].SetActive(false);
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch(collision.gameObject.name) {
case "Top":
isTouchTop = true;
break;
case "Bottom":
isTouchBottom = true;
break;
case "Left":
isTouchLeft = true;
break;
case "Right":
isTouchRight = true;
break;
}
}
else if (collision.gameObject.tag == "Enemy" || collision.gameObject.tag == "EnemyBullet") {
if (isHit) //이미 맞으면 다시 맞지 않도록 return
return;
isHit = true;
life--;
gameManager.UpdateLifeIcon(life);
if (life == 0) {
gameManager.GameOver();
}
else {
gameManager.RespawnPlayer();
}
gameObject.SetActive(false);
collision.gameObject.SetActive(false);
}
else if (collision.gameObject.tag == "Item") {
Item item = collision.gameObject.GetComponent<Item>();
switch (item.type) {
case "Coin":
score += 1000;
break;
case "Power":
if (power == maxPower)
score += 500;
else
power++;
break;
case "Boom":
if (boom == maxBoom)
score += 500;
else {
boom++;
gameManager.UpdateBoomIcon(boom);
}
break;
}
collision.gameObject.SetActive(false);
}
}
void OffBoomEffect() {
boomEffect.SetActive(false);
isBoomTime = false;
}
void OnTriggerExit2D(Collider2D collision) {
if (collision.gameObject.tag == "Border") {
switch (collision.gameObject.name) {
case "Top":
isTouchTop = false;
break;
case "Bottom":
isTouchBottom = false;
break;
case "Left":
isTouchLeft = false;
break;
case "Right":
isTouchRight = false;
break;
}
}
}
}
//Enemy 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Enemy : MonoBehaviour {
public string enemyName;
public int enemyScore;
public float speed;
public int health;
public float maxShotDelay;
public float curShotDelay;
public GameObject bulletObjA;
public GameObject bulletObjB;
public GameObject itemCoin;
public GameObject itemPower;
public GameObject itemBoom;
public GameObject player;
public ObjectManager objectManager;
public Sprite[] sprites;
SpriteRenderer spriteRenderer;
void Awake() {
spriteRenderer = GetComponent<SpriteRenderer>();
}
void OnEnable() {
switch(enemyName) {
case "L":
health = 40;
break;
case "M":
health = 10;
break;
case "S":
health = 3;
break;
}
}
void Update(){
Fire();
Reload();
}
void Fire() {
if (curShotDelay < maxShotDelay) //장전 중이라면
return;
if (enemyName == "S") {
GameObject bullet = objectManager.MakeObj("BulletEnemyA");
bullet.transform.position = transform.position;
Rigidbody2D rigid = bullet.GetComponent<Rigidbody2D>();
Vector3 dirVec = player.transform.position - transform.position; //목표물로 방향 = 목표물 위치 - 자신의 위치
rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}
else if (enemyName == "L") {
GameObject bulletR = objectManager.MakeObj("BulletEnemyB");
bulletR.transform.position = transform.position + Vector3.right * 0.3f;
GameObject bulletL = objectManager.MakeObj("BulletEnemyB");
bulletL.transform.position = transform.position + Vector3.left * 0.3f;
Rigidbody2D rigidR = bulletR.GetComponent<Rigidbody2D>();
Rigidbody2D rigidL = bulletL.GetComponent<Rigidbody2D>();
Vector3 dirVecR = player.transform.position - (transform.position + Vector3.right * 0.3f);
Vector3 dirVecL = player.transform.position - (transform.position + Vector3.left * 0.3f);
rigidR.AddForce(dirVecR.normalized * 4, ForceMode2D.Impulse);
rigidL.AddForce(dirVecL.normalized * 4, ForceMode2D.Impulse);
}
curShotDelay = 0;
}
void Reload() {
curShotDelay += Time.deltaTime;
}
public void onHit(int dmg) {
if (health <= 0) //이미 죽은 상태라면
return;
health -= dmg;
spriteRenderer.sprite = sprites[1]; //평소 스프라이트 0, 피격시 스프라이트 1
Invoke("ReturnSprite", 0.1f);
if (health <= 0) {
Player playerLogic = player.GetComponent<Player>();
playerLogic.score += enemyScore;
//랜덤 아이템 드랍
int ran = Random.Range(0, 10);
if (ran < 3) { //드랍 없음 30%
Debug.Log("Not Item");
}
else if (ran < 6) { //동전 드랍 30%
GameObject itemCoin = objectManager.MakeObj("ItemCoin");
itemCoin.transform.position = transform.position;
}
else if (ran < 8) { //파워 드랍 20%
GameObject itemPower = objectManager.MakeObj("ItemPower");
itemPower.transform.position = transform.position;
}
else if (ran < 10) { //붐 드랍 20%
GameObject itemBoom = objectManager.MakeObj("ItemBoom");
itemBoom.transform.position = transform.position;
}
gameObject.SetActive(false);
transform.rotation = Quaternion.identity;
}
}
void ReturnSprite() {
spriteRenderer.sprite = sprites[0];
}
void OnTriggerEnter2D(Collider2D collision) {
if (collision.gameObject.tag == "BorderBullet") { //맵의 경계로 가게되면
gameObject.SetActive(false);
transform.rotation = Quaternion.identity;
}
else if (collision.gameObject.tag == "PlayerBullet")
{ //플레이어 총알에 닿으면
Bullet bullet = collision.gameObject.GetComponent<Bullet>();
onHit(bullet.dmg);
collision.gameObject.SetActive(false); //플레이어 총알 삭제
}
}
}
//Item 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Item : MonoBehaviour {
public string type;
Rigidbody2D rigid;
void Awake() {
rigid = GetComponent<Rigidbody2D>();
}
void OnEnable(){
rigid.velocity = Vector2.down * 1.5f;
}
}


- Quaternion.identity : 오브젝트 기본 회전값이 0이 되는 함수.
- OnEnable() : 컴포넌트가 활성화 될 때 호출되는 생명 주기 함수. 아이템 프리펩의 이동과 적 기체 프리펩의 체력 등을 이 함수 내에서 관리하면, 활성화될 때마다 발동되고 초기화된다.
- 추가로 오브젝트를 직접 찾는 Find 계열 함수(오브젝트 태그 "Enemy" 찾기)도 풀 범위 전부를 대상으로 검색하므로 성능 부하를 유발할 수 있다. 따라서 프리펩들을 관리하는 오브젝트 매니저를 통해서 지정한 범위 내에서만 처리하도록 변경한다.
'유니티 프로젝트 > 2D 종스크롤 슈팅' 카테고리의 다른 글
[Unity/유니티] 기초-2D 종스크롤 슈팅: 따라다니는 보조 무기 만들기[B36] (0) | 2022.03.09 |
---|---|
[Unity/유니티] 기초-2D 종스크롤 슈팅: 텍스트 파일을 이용한 커스텀 배치 구현[B35] (0) | 2022.03.08 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 원근감있는 무한 배경만들기[B33] (0) | 2022.03.05 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: 아이템과 필살기 구현하기[B32] (0) | 2022.03.04 |
[Unity/유니티] 기초-2D 종스크롤 슈팅: UI 간단하게 완성하기[B31] (0) | 2022.03.03 |