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 | 31 |
Tags
- 4월
- 백준
- 자료 구조
- 5월
- 단계별로 풀어보기
- 1월
- C/C++
- 2025년
- 3월
- 2024년
- 2월
- 입문
- 2023년
- 10월
- 코딩 테스트
- 다이나믹 프로그래밍
- 유니티 심화과정
- 프로그래머스
- 게임 엔진 공부
- 2022년
- c++
- 수학
- 개인 프로젝트
- 유니티
- 골드메탈
- 7월
- 기초
- 개인 프로젝트 - 런앤건
- 코딩 기초 트레이닝
- todolist
Archives
- Today
- Total
기록 보관소
[Unity/유니티] 기초-물리 퍼즐게임: 쉽게 구현해보는 오브젝트풀링[B60] 본문
개요
유니티 입문과 독학을 위해서 아래 링크의 골드메탈님의 영상들을 보며 진행 상황 사진 또는 캡처를 올리고 배웠던 점을 요약해서 적는다.
현재는 영상들을 보고 따라하고 배우는 것에 집중할 것이며, 영상을 모두 보고 따라한 후에는 개인 프로젝트를 설계하고 직접 만드는 것이 목표다.
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
물리 퍼즐게임: 쉽게 구현해보는 오브젝트풀링[B60]
1. 오브젝트풀링?
- 내용이 2D 종스크롤 슈팅의 오브젝트 풀링의 첫 파트 내용과 같으므로 생략한다.
2. 풀 생성
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour {
public GameObject donglePrefab;
public Transform dongleGroup;
public List<Dongle> donglePool;
public GameObject effectPrefab;
public Transform effectGroup;
public List<ParticleSystem> effectPool;
[Range(1, 30)] //Inspector 창에서 poolSize가 1~30까지 스크롤형태로 표현됨
public int poolSize;
public int poolCursor;
public Dongle lastDongle;
public AudioSource bgmPlayer;
public AudioSource[] sfxPlayer;
public AudioClip[] sfxClip; //효과음
public enum Sfx { LevelUp, Next, Attach, Button, Over };
int sfxCursor;
public int score;
public int maxLevel;
public bool isOver;
void Awake() {
Application.targetFrameRate = 60; //60프레임 이하로 유지
donglePool = new List<Dongle>();
effectPool = new List<ParticleSystem>();
for (int index = 0; index < poolSize; index++)
MakeDongle();
}
void Start() {
bgmPlayer.Play();
NextDongle();
}
Dongle MakeDongle() {
//이펙트 생성
GameObject instantEffectObj = Instantiate(effectPrefab, effectGroup);
instantEffectObj.name = "Effect " + effectPool.Count;
ParticleSystem instantEffect = instantEffectObj.GetComponent<ParticleSystem>();
effectPool.Add(instantEffect);
//동글 생성
GameObject instantDongleObj = Instantiate(donglePrefab, dongleGroup);
instantDongleObj.name = "Dongle " + donglePool.Count;
Dongle instantDongle = instantDongleObj.GetComponent<Dongle>();
instantDongle.manager = this;
instantDongle.effect = instantEffect; //이펙트 전달
donglePool.Add(instantDongle);
return instantDongle;
}
Dongle GetDongle() {
return null; //임시
}
void NextDongle() {
if (isOver)
return;
Dongle newDongle = GetDongle();
lastDongle = newDongle;
lastDongle.level = Random.Range(0, maxLevel);
lastDongle.gameObject.SetActive(true);
SfxPlay(Sfx.Next);
StartCoroutine(WaitNext());
}
IEnumerator WaitNext() {
while (lastDongle != null) {
yield return null;
}
yield return new WaitForSeconds(2.5f);
NextDongle();
}
public void TouchDown() {
if (lastDongle == null)
return;
lastDongle.Drag();
}
public void TouchUp() {
if (lastDongle == null)
return;
lastDongle.Drop();
lastDongle = null; //드랍 후 조종 불가로 만들기
}
public void GameOver() {
if (isOver)
return;
isOver = true;
StartCoroutine("GameOverRoutine");
}
IEnumerator GameOverRoutine() {
// 1. 장면 안에 활성화 되어있는 모든 동글 가져오기
Dongle[] dongles = FindObjectsOfType<Dongle>(); //앞에 GameObject.FindObjectsOfType으로도 가능
// 2. 지우기 전에 모든 동글의 물리효과 비활성화
for (int index = 0; index < dongles.Length; index++) {
dongles[index].rigid.simulated = false;
}
// 3. 1번의 목록을 하나씩 접근해 지우기
for (int index = 0; index < dongles.Length; index++) {
dongles[index].Hide(Vector3.up * 100);
yield return new WaitForSeconds(0.1f);
}
yield return new WaitForSeconds(1f);
SfxPlay(Sfx.Over);
}
public void SfxPlay(Sfx type) {
switch(type) {
case Sfx.LevelUp:
sfxPlayer[sfxCursor].clip = sfxClip[Random.Range(0, 3)];
break;
case Sfx.Next:
sfxPlayer[sfxCursor].clip = sfxClip[3];
break;
case Sfx.Attach:
sfxPlayer[sfxCursor].clip = sfxClip[4];
break;
case Sfx.Button:
sfxPlayer[sfxCursor].clip = sfxClip[5];
break;
case Sfx.Over:
sfxPlayer[sfxCursor].clip = sfxClip[6];
break;
}
sfxPlayer[sfxCursor].Play();
sfxCursor = (sfxCursor + 1) % sfxPlayer.Length; //값이 범위를 넘지 않도록 나머지 연산 사용
}
}
- 오브젝트 풀을 위해서 동글과 이펙트를 저장할 List 변수 두개와 풀 크기와 풀 위치를 가르킬 int형 변수 2개를 만들고, 기존의 동글 생성까지 처리하던 GetDongle() 대신 MakeDongle()을 통해서 생성하고, 앞의 변수를 활용해 이름을 변경하고 list에 할당한다. 또한 NextDongle에서 GameManager를 넘겨주던 부분을 빼고 MakeDongle에서 같이 넘겨주도록 변경했다. 남은 GetDongle()은 일단 임시로 null을 넘겨주도록 했다.
3. 풀 사용
//GameManager 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameManager : MonoBehaviour {
public GameObject donglePrefab;
public Transform dongleGroup;
public List<Dongle> donglePool;
public GameObject effectPrefab;
public Transform effectGroup;
public List<ParticleSystem> effectPool;
[Range(1, 30)] //Inspector 창에서 poolSize가 1~30까지 스크롤형태로 표현됨
public int poolSize;
public int poolCursor;
public Dongle lastDongle;
public AudioSource bgmPlayer;
public AudioSource[] sfxPlayer;
public AudioClip[] sfxClip; //효과음
public enum Sfx { LevelUp, Next, Attach, Button, Over };
int sfxCursor;
public int score;
public int maxLevel;
public bool isOver;
void Awake() {
Application.targetFrameRate = 60; //60프레임 이하로 유지
donglePool = new List<Dongle>();
effectPool = new List<ParticleSystem>();
for (int index = 0; index < poolSize; index++)
MakeDongle();
}
void Start() {
bgmPlayer.Play();
NextDongle();
}
Dongle MakeDongle() {
//이펙트 생성
GameObject instantEffectObj = Instantiate(effectPrefab, effectGroup);
instantEffectObj.name = "Effect " + effectPool.Count;
ParticleSystem instantEffect = instantEffectObj.GetComponent<ParticleSystem>();
effectPool.Add(instantEffect);
//동글 생성
GameObject instantDongleObj = Instantiate(donglePrefab, dongleGroup);
instantDongleObj.name = "Dongle " + donglePool.Count;
Dongle instantDongle = instantDongleObj.GetComponent<Dongle>();
instantDongle.manager = this;
instantDongle.effect = instantEffect; //이펙트 전달
donglePool.Add(instantDongle);
return instantDongle;
}
Dongle GetDongle() {
for (int index = 0; index < donglePool.Count; index++) {
poolCursor = (poolCursor + 1) % donglePool.Count;
if (!donglePool[poolCursor].gameObject.activeSelf) //비활성화일때
return donglePool[poolCursor];
}
return MakeDongle();
}
void NextDongle() {
if (isOver)
return;
lastDongle = GetDongle();
lastDongle.level = Random.Range(0, maxLevel);
lastDongle.gameObject.SetActive(true);
SfxPlay(Sfx.Next);
StartCoroutine(WaitNext());
}
IEnumerator WaitNext() {
while (lastDongle != null) {
yield return null;
}
yield return new WaitForSeconds(2.5f);
NextDongle();
}
public void TouchDown() {
if (lastDongle == null)
return;
lastDongle.Drag();
}
public void TouchUp() {
if (lastDongle == null)
return;
lastDongle.Drop();
lastDongle = null; //드랍 후 조종 불가로 만들기
}
public void GameOver() {
if (isOver)
return;
isOver = true;
StartCoroutine("GameOverRoutine");
}
IEnumerator GameOverRoutine() {
// 1. 장면 안에 활성화 되어있는 모든 동글 가져오기
Dongle[] dongles = FindObjectsOfType<Dongle>(); //앞에 GameObject.FindObjectsOfType으로도 가능
// 2. 지우기 전에 모든 동글의 물리효과 비활성화
for (int index = 0; index < dongles.Length; index++) {
dongles[index].rigid.simulated = false;
}
// 3. 1번의 목록을 하나씩 접근해 지우기
for (int index = 0; index < dongles.Length; index++) {
dongles[index].Hide(Vector3.up * 100);
yield return new WaitForSeconds(0.1f);
}
yield return new WaitForSeconds(1f);
SfxPlay(Sfx.Over);
}
public void SfxPlay(Sfx type) {
switch(type) {
case Sfx.LevelUp:
sfxPlayer[sfxCursor].clip = sfxClip[Random.Range(0, 3)];
break;
case Sfx.Next:
sfxPlayer[sfxCursor].clip = sfxClip[3];
break;
case Sfx.Attach:
sfxPlayer[sfxCursor].clip = sfxClip[4];
break;
case Sfx.Button:
sfxPlayer[sfxCursor].clip = sfxClip[5];
break;
case Sfx.Over:
sfxPlayer[sfxCursor].clip = sfxClip[6];
break;
}
sfxPlayer[sfxCursor].Play();
sfxCursor = (sfxCursor + 1) % sfxPlayer.Length; //값이 범위를 넘지 않도록 나머지 연산 사용
}
}
- 앞에서 비웠던 GetDongle()은 풀에 생성된 동글 중 비활성화된, 즉 아직 풀에 남아있거나 사용하지 않았던 동글을 넘겨주거나, 아니면 새로운 동글을 생성해서 넘겨주는 역할을 하도록 만들었다.
4. 재사용 로직
//Dongle 스크립트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Dongle : MonoBehaviour {
public GameManager manager;
public ParticleSystem effect;
public int level;
public bool isDrag;
public bool isMerge;
public bool isAttach;
public Rigidbody2D rigid;
CircleCollider2D circle;
Animator anim;
SpriteRenderer spriteRenderer;
float deadTime;
void Awake() {
rigid = GetComponent<Rigidbody2D>();
circle = GetComponent<CircleCollider2D>();
anim = GetComponent<Animator>();
spriteRenderer = GetComponent<SpriteRenderer>();
}
void OnEnable() {
anim.SetInteger("Level", level);
}
void OnDisable() {
//동글 속성 초기화
level = 0;
isDrag = false;
isMerge = false;
isAttach = false;
//동글 위치 초기화
transform.localPosition = Vector3.zero;
transform.localRotation = Quaternion.identity;
transform.localScale = Vector3.zero;
//동글 물리 초기화
rigid.simulated = false;
rigid.velocity = Vector2.zero;
rigid.angularVelocity = 0;
circle.enabled = true;
}
void Update() {
if (isDrag) {
Vector3 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
float leftBorder = -4.2f + transform.localScale.x / 2f; //좌측 벽 경계 설정
float rightBorder = 4.2f - transform.localScale.x / 2f; //우측 벽 경계 설정
if (mousePos.x < leftBorder) { //벽에 x축 접근 제한
mousePos.x = leftBorder;
}
else if (mousePos.x > rightBorder) {
mousePos.x = rightBorder;
}
mousePos.y = 8; //Y축 고정해서 경계선 밑으로 내려가지 않도록 설정
mousePos.z = 0; //Z축 고정해서 맵 밖으로 나가지 않도록 설정
transform.position = Vector3.Lerp(transform.position, mousePos, 0.2f);
}
}
public void Drag() {
isDrag = true;
}
public void Drop() {
isDrag = false;
rigid.simulated = true;
}
void OnCollisionEnter2D(Collision2D collision) {
StartCoroutine("AttachRoutine");
}
IEnumerator AttachRoutine() {
if (isAttach) {
yield break;
}
isAttach = true;
manager.SfxPlay(GameManager.Sfx.Attach);
yield return new WaitForSeconds(0.2f);
isAttach = false;
}
void OnCollisionStay2D(Collision2D collision) {
if (collision.gameObject.tag == "Dongle") {
Dongle other = collision.gameObject.GetComponent<Dongle>();
if (level == other.level && !isMerge && !other.isMerge && level < 7) {
//상대편 위치 가져오기
float meX = transform.position.x;
float meY = transform.position.y;
float otherX = other.transform.position.x;
float otherY = other.transform.position.y;
//1. 내가 아래에 있을 때
//2. 동일한 높이 or 내가 오른쪽에 있을 때
if (meY < otherY || (meY == otherY && meX > otherX)) {
//상대방 숨기기
other.Hide(transform.position);
//나는 레벨 업
LevelUp();
}
}
}
}
public void Hide(Vector3 targetPos) {
isMerge = true;
rigid.simulated = false;
circle.enabled = false;
if (targetPos == Vector3.up * 100)
EffectPlay();
StartCoroutine(HideRoutine(targetPos));
}
IEnumerator HideRoutine(Vector3 targetPos) {
int frameCount = 0;
while(frameCount < 20) {
frameCount++;
if (targetPos != Vector3.up * 100) {
transform.position = Vector3.Lerp(transform.position, targetPos, 0.5f);
}
else if (targetPos == Vector3.up * 100) {
transform.localScale = Vector3.Lerp(transform.localScale, Vector3.zero, 0.2f);
}
yield return null;
}
manager.score += (int)Mathf.Pow(2, level);
isMerge = false;
gameObject.SetActive(false);
}
void LevelUp() {
isMerge = true;
rigid.velocity = Vector2.zero;
rigid.angularVelocity = 0; //회전 속도
StartCoroutine(LevelUpRoutine());
}
IEnumerator LevelUpRoutine() {
yield return new WaitForSeconds(0.2f);
anim.SetInteger("Level", level + 1); //애니메이션 실행
EffectPlay(); //이펙트 실행
manager.SfxPlay(GameManager.Sfx.LevelUp);
yield return new WaitForSeconds(0.3f);
level++; //변수 값 증가
manager.maxLevel = Mathf.Max(level, manager.maxLevel);
isMerge = false;
}
void OnTriggerStay2D(Collider2D collision) {
if (collision.tag == "Finish") {
deadTime += Time.deltaTime;
if (deadTime > 2) {
spriteRenderer.color = new Color(0.9f, 0.2f, 0.2f); //Color.red; 도 가능
}
if (deadTime > 5) {
manager.GameOver();
}
}
}
void OnTriggerExit2D(Collider2D collision) {
if (collision.tag == "Finish") {
deadTime = 0;
spriteRenderer.color = Color.white;
}
}
void EffectPlay() {
effect.transform.position = transform.position;
effect.transform.localScale = transform.localScale;
effect.Play();
}
}
- OnDisable() : 비활성화시 호출되는 이벤트 함수
- 앞에서 생긴 문제는 동글이 합쳐지면서 기존 동글 비활성화 - 다음 레벨 동글 활성화로 이어지면서 위치나 크기 등이 변경되지 않고 활성화되는 것때문이다. 그래서 동글이 합쳐지면서 비활성화 될때 각종 변수들과 위치, 크기, 속도, 콜라이더 등을 초기화해주도록 OnEnable()의 반대인 OnDisable() 함수에서 이를 처리해주도록 만들었다.
'유니티 프로젝트 > 물리 퍼즐게임' 카테고리의 다른 글
[Unity/유니티] 기초-물리 퍼즐게임: 모바일 게임으로 완성하기[BE6] (0) | 2022.04.29 |
---|---|
[Unity/유니티] 기초-물리 퍼즐게임: 채널링이 포함된 사운드 시스템[B59] (0) | 2022.04.24 |
[Unity/유니티] 기초-물리 퍼즐게임: 게임 오버 구현하기[B58] (0) | 2022.04.23 |
[Unity/유니티] 기초-물리 퍼즐게임: 멋진 이펙트 만들기[B57] (0) | 2022.04.22 |
[Unity/유니티] 기초-물리 퍼즐게임: 물리 이벤트로 동글 합치기[B56] (0) | 2022.04.17 |