Unity/Dongle Family
인보크(Invoke)와 코루틴(Corutine) / 게임 매니저(Game Manager)
hwan91
2025. 3. 27. 20:06
Invoke
특정 메서드를 일정 시간 후에 한 번 실행할 때 사용한다.
(아주 간단하게 시간 지연 호출이 필요할 때)
사용 방법
Invoke("MethodName", 3f); // 3초 후 해당 메서드 실행
InvokeRepeating("MethodName", delayTime, repeatRate); // 일정 시간 뒤에 실행하고, 반복 실행
CancelInvoke("MethodName"); // 반복 중단
Corutine
코루틴(Corutine) / 적 생성(Enemy, Spawn)
CorutineIEnumerator는 **코루틴 전용 반환형(인터페이스)**이다.코루틴은 일반 함수와 다르게, 실행 도중 yield return을 만나면 일시 중지되었다가 나중에 다시 실행된다.이 방식 덕분에 시간이 걸리
hwan91.tistory.com
비교
Invoke | Coroutine | |
반복 실행 | ❌ (InvokeRepeating, 한 번만) | ⭕ 가능 |
조건부 실행 흐름 | ❌ 불가 | ⭕ 가능 |
매개변수 전달 | ❌ 불가 | ⭕ 가능 |
종료/중지 | 🔸 취소만 가능 (CancelInvoke) | ⭕ StopCoroutine()으로 중지 가능 |
코드 가독성 | ⭕ 단순 | 🔸 복잡하지만 유연 |
각각 언제 써야할까?
Invoke
- 그냥 몇 초 뒤에 한 번 실행만 하면 될 때
- 일정 시간 간격으로 반복해야 할 때 (InvokeRepeating)
Corutine
- 일정 시간 간격으로 반복해야할 때 (공통)
- 실행 중간에 조건 체크, 반복 루프 등 복잡한 제어가 필요할 때
- 여러 개의 지연 동작을 순서대로 하고 싶을 때
- 매개변수를 넘기며 유연한 실행을 원할 때
GameManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;
public class GameManager : MonoBehaviour
{
[Header("=== Core ===")]
public bool isOver; // 게임오버 상태
public int score; // 점수
public int maxLevel; // 동글 생성 최대 레벨
[Header("=== Object Pooling ===")]
public GameObject donglePrefab;
public Transform dongleGroup;
public List<Dongle> donglePool;
public GameObject effectPrefab;
public Transform effectGroup;
public List<ParticleSystem> effectPool;
[Range(1, 30)]
public int poolSize;
public int poolCursor;
public Dongle lastDongle;
[Header("=== Audio ===")]
public AudioSource bgmPlayer;
public AudioSource[] sfxPlayer;
public AudioClip[] sfxClip;
[Header("=== UI ===")]
public GameObject startGroup; // 시작 화면 UI
public GameObject endGroup; // 종료 화면 UI
public Text scoreText; // 현재 점수
public Text maxScoreText; // 최고 점수
public Text subScoreText; // 결과창 점수
[Header("=== ETC ===")]
public GameObject line; // 위쪽 경계선
public GameObject bottom; // 바닥 라인
public enum Sfx { LevelUp, Next, Attach, Button, Over }; // 효과음 종류
int sfxCursor; // 다음에 재생할 오디오 소스
void Awake()
{
Application.targetFrameRate = 60; // 60 프레임 고정
donglePool = new List<Dongle>();
effectPool = new List<ParticleSystem>();
// 풀 사이즈만큼 동글을 미리 생성해서 풀에 넣음
for (int index = 0; index < poolSize; index++) {
MakeDongle();
}
// 저장된 최고 점수가 없으면 0으로 초기화
if (!PlayerPrefs.HasKey("MaxScore")) PlayerPrefs.SetInt("MaxScore", 0); // 예외 처리
// 최고 점수 UI 텍스트에 저장된 최고 점수를 표시
maxScoreText.text = PlayerPrefs.GetInt("MaxScore").ToString();
}
public void GameStart()
{
// 오브젝트 활성화
line.SetActive(true);
bottom.SetActive(true);
scoreText.gameObject.SetActive(true);
maxScoreText.gameObject.SetActive(true);
startGroup.SetActive(false);
bgmPlayer.Play();
SfxPlay(Sfx.Button);
// Invoke("실행시킬 함수 이름", 딜레이 시간(초)) = 함수 호출에 딜레이를 주고 싶을 때 사용하는 함수
Invoke("NextDongle", 1.5f); // 1.5ch 후 해당 함수 실행
}
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;
// activeSelf = bool 형태의 오브젝트 활성화 속성
if (!donglePool[poolCursor].gameObject.activeSelf) { // 비활성화 상태인 오브젝트 가져오기
return donglePool[poolCursor];
}
}
return MakeDongle(); // 전부 사용 중이면 새로 하나 만들기
}
// 다음 동글 가져오기
void NextDongle()
{
if (isOver) return;
lastDongle = GetDongle();
lastDongle.level = Random.Range(0, maxLevel); // 최대값 포함 X
lastDongle.gameObject.SetActive(true);
SfxPlay(Sfx.Next);
StartCoroutine("WaitNext");
}
// 다음 동글 코루틴
IEnumerator WaitNext()
{
while (lastDongle != null) yield return null; // 동글이 빌 떄까지 계속 대기
yield return new WaitForSeconds(2f); // 2초 대기했다가 아래 로직 실행
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()
{
Dongle[] dongles = FindObjectsOfType<Dongle>(); // Dongle 스크립트를 컴포넌트로 가지고 있는 모든 오브젝트에 접근해서 배열에 저장
for (int index = 0; index < dongles.Length; index++) {
dongles[index].rigid.simulated = false; // 모든 동글의 물리 효과 비활성화
}
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);
// 최고 점수 갱신
int maxScore = Mathf.Max(score, PlayerPrefs.GetInt("MaxScore"));
PlayerPrefs.SetInt("MaxScore", maxScore);
// 게임 오버 UI 표시
subScoreText.text = "점수 : " + scoreText.text;
endGroup.SetActive(true);
bgmPlayer.Stop();
SfxPlay(Sfx.Over);
}
// 다시 시작
public void Reset()
{
SfxPlay(Sfx.Button);
StartCoroutine("ResetCorutine");
}
// 다시 시작 코루틴
IEnumerator ResetCorutine()
{
yield return new WaitForSeconds(0.5f);
SceneManager.LoadScene("Main");
}
// 효과음 재생
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;
}
void Update()
{
// 뒤로가기 버튼 누르면 앱 종료(안드로이드 등)
if (Input.GetButtonDown("Cancel")) {
Application.Quit();
}
}
// 현재 점수 갱신
void LateUpdate()
{
scoreText.text = score.ToString();
}
}