티스토리 뷰
💻 Unity 디자인 패턴
Singleton
public class GameManager : MonoBehaviour
{
private static GameManager _instance;
public static GameManager Instance
{
get
{
if (_instance == null)
{
_instance = FindFirstObjectByType<GameManager>();
if (_instance == null)
{
GameObject gameObject = new GameObject("GameManager");
_instance = gameObject.AddComponent<GameManager>();
}
}
return _instance;
}
}
private int _score = 0;
public int Score
{
get { return _score; }
}
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
}
public void AddScore(int point)
{
_score += point;
Debug.Log($"Current Score : {Score}");
}
}
GameManager 오브젝트를 생성하지도 않아도 됨
Observer
public class EventManager : MonoBehaviour
{
private static EventManager _instance;
public static EventManager Instance
{
get
{
if (_instance == null)
{
_instance = FindFirstObjectByType<EventManager>();
if (_instance == null)
{
GameObject gameObject = new GameObject("EventManager");
_instance = gameObject.AddComponent<EventManager>();
}
}
return _instance;
}
}
private Dictionary<string, Action<object>> _eventDictionary = new Dictionary<string, Action<object>>();
public void AddListsner(string eventName, Action<object> listener)
{
if (_eventDictionary.TryGetValue(eventName, out Action<object> thisEvent))
{
thisEvent += listener;
_eventDictionary[eventName] = thisEvent;
}
else
{
_eventDictionary.Add(eventName, listener);
}
}
public void RemoveListener(string eventName, Action<object> listener)
{
if (_eventDictionary.TryGetValue(eventName, out Action<object> thisEvent))
{
thisEvent -= listener;
_eventDictionary[eventName] = thisEvent;
}
}
public void TriggerEvent(string eventName, object data = null)
{
if (_eventDictionary.TryGetValue(eventName, out Action<object> thisEvent))
{
thisEvent?.Invoke(data);
}
}
}
public class UIHealthDisplay : MonoBehaviour
{
void Start()
{
EventManager.Instance.AddListsner("PlayerHealthChanged", OnPlayHealthChanged);
EventManager.Instance.AddListsner("PlayerDied", OnPlayerDied);
}
private void OnPlayHealthChanged(object data)
{
int health = (int)data;
Debug.Log($"UI Update : Player hp is changed to {health}.");
// UI 업데이트
}
private void OnPlayerDied(object data)
{
Debug.Log("UI Update : Player is died!");
// 게임 오버 화면 표시
}
private void OnDestroy()
{
EventManager.Instance.RemoveListener("PlayerHealthChanged", OnPlayHealthChanged);
EventManager.Instance.RemoveListener("PlayerDied", OnPlayerDied);
}
}
public class Player : MonoBehaviour
{
private int _health = 100;
public int Health
{
get { return _health; }
set
{
_health = value;
EventManager.Instance.TriggerEvent("PlayerHealthChanged", _health);
if (_health <= 0)
{
EventManager.Instance.TriggerEvent("PlayerDied");
}
}
}
private void TakeDamage(int damage)
{
Health -= damage;
}
void Update()
{
//테스트용: 스페이스 키를 누르면 데미지 받기
if (Input.GetKeyDown(KeyCode.Space))
{
if (Health > 0)
{
TakeDamage(10);
}
}
}
}
Factory
// EnemyFactory.cs
public class EnemyFactory : MonoBehaviour
{
private static EnemyFactory _instance;
public static EnemyFactory Instance
{
get
{
if (_instance == null)
{
_instance = FindFirstObjectByType<EnemyFactory>();
if (_instance == null)
{
GameObject gameObject = new GameObject("EnemyFactory");
_instance = gameObject.AddComponent<EnemyFactory>();
}
}
return _instance;
}
}
public GameObject GruntPrefab;
public GameObject RunnerPrefab;
public GameObject TankPrefab;
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
}
public IEnemy CreateEnemy(EnemyType type, Vector3 position)
{
GameObject enemyObject = null;
switch (type)
{
case EnemyType.Grunt:
enemyObject = Instantiate(GruntPrefab);
break;
case EnemyType.Runner:
enemyObject = Instantiate(RunnerPrefab);
break;
case EnemyType.Tank:
enemyObject = Instantiate(TankPrefab);
break;
default:
Debug.LogError($"Unknown enemy type : {type}");
return null;
}
IEnemy enemy = enemyObject.GetComponent<IEnemy>();
enemy.Initialize(position);
return enemy;
}
}
// EnemyBase.cs
public enum EnemyType
{
Grunt,
Runner,
Tank,
Boss
}
public interface IEnemy
{
void Initialize(Vector3 position);
void Attack();
void TakeDamage(float damage);
}
public abstract class EnemyBase : MonoBehaviour, IEnemy
{
public float Health;
public float Speed;
public float Damage;
public virtual void Initialize(Vector3 position)
{
transform.position = position;
}
public abstract void Attack();
public virtual void TakeDamage(float damage)
{
Health -= damage;
if (Health <= 0)
{
Die();
}
}
protected virtual void Die()
{
Destroy(gameObject);
}
}
// EnemySpawner.cs
public class EnemySpawner : MonoBehaviour
{
[SerializeField]
private float spawnInterval = 5f;
private float _timer;
void Update()
{
_timer += Time.deltaTime;
if (_timer >= spawnInterval)
{
SpawnRandomEnemy();
_timer = 0;
}
}
private void SpawnRandomEnemy()
{
Vector3 spawnPosition = new Vector3(Random.Range(-10f, 10f), 0, Random.Range(-10f, 10f));
EnemyType randomgType = (EnemyType)Random.Range(0, 3);
IEnemy enemy = EnemyFactory.Instance.CreateEnemy(randomgType, spawnPosition);
Debug.Log($"{randomgType}가 {spawnPosition}에 생성되었습니다.");
}
}
🕹️ 실습 (2D 횡스크롤)
슬로우모션
// EnemyMissile.cs
void Update()
{
float timeScale = TimeController.Instance.GetTimeScale();
transform.Translate(direction * speed * Time.deltaTime * timeScale);
}
// Player.cs
void Update()
{
// 시간조절 입력 체크 (왼쪽 시프트키를 누르면 슬로우 모션 시작)
if (Input.GetKeyDown(KeyCode.LeftShift))
{
// 포스트프로세싱 화면효과
TimeController.Instance.SetSlowMotion(true);
}
}
적 사망
// ShootingEnemy.cs
public void PlayDeathAnimation()
{
animator.SetBool("Death", true);
Destroy(gameObject, animator.GetCurrentAnimatorStateInfo(0).length * 2); // 애니메이션 종료 후 오브젝트 제거
}
// Slash.cs
private void OnTriggerEnter2D(Collider2D collision)
{
// 적 처리
if (collision.CompareTag("Enemy"))
{
// 적 죽음 애니메이션 실행
ShootingEnemy enemy = collision.GetComponent<ShootingEnemy>();
if (enemy != null)
{
enemy.PlayDeathAnimation();
}
}
}
// EnemyMissile.cs
private void OnTriggerEnter2D(Collider2D other)
{
if(other.CompareTag("Player"))
{
// ...
}
else if (other.CompareTag("Enemy"))
{
ShootingEnemy enemy = other.GetComponent<ShootingEnemy>();
if (enemy != null)
{
enemy.PlayDeathAnimation();
}
Destroy(gameObject);
}
}
슬로우모션시 화면 효과 (Post Processing)


- Main Camera에 Post-process Layer 추가
- PostProcessVolume 오브젝트에 Post-process Volye 추가 하고 vignette, colo rgrading, bloom 추가 후 모두 체크
// TimeController.cs
[Header("Post Processing")]
public PostProcessVolume postProcessVolume;
private Vignette vignette;
private ColorGrading colorGrading;
public void SetSlowMotion(bool slow)
{
isSlowMotion = slow;
if (slow)
{
// 슬로우 모션 시작 시 효과 설정
slowMotionTimer = 0f;
vignette.intensity.value = 0.8f; // 비네트 강도 대폭 증가
colorGrading = postProcessVolume.profile.GetSetting<ColorGrading>();
colorGrading.saturation.value = -40f; // 채도 더욱 낮게
colorGrading.temperature.value = -25f; // 매우 차가운 색감
colorGrading.contrast.value = 20f; // 대비 더 강하게
colorGrading.postExposure.value = -1.0f; // 전체적으로 더 어둡게
colorGrading.tint.value = 10f; // 약간의 초록빛 추가
}
else
{
// 슬로우 모션 종료 시 효과 초기화
vignette.intensity.value = 0f;
colorGrading.saturation.value = 0f;
colorGrading.temperature.value = 0f;
colorGrading.contrast.value = 0f;
colorGrading.postExposure.value = 0f;
colorGrading.tint.value = 0f;
}
}
씬 전환
// Player.cs
private void OnTriggerEnter2D(Collider2D collision)
{
// 보스 씬 진입 포탈과 충돌 체크
if (collision.CompareTag("BossSceneTransition"))
{
// 보스 씬으로 전환
SceneManager.LoadScene("BossScene");
}
}
📝 과제
공부한 내용을 바탕으로 예제 만들어 보기
https://github.com/yh97yhyh/likelion-unity-study/tree/main/DesignPatternAssignment
'Unity > 멋쟁이사자처럼' 카테고리의 다른 글
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 29일차 회고 (0) | 2025.04.03 |
---|---|
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 28일차 회고 (0) | 2025.04.03 |
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 23일차 회고 (0) | 2025.04.03 |
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 22일차 회고 (0) | 2025.04.03 |
멋쟁이 사자처럼 부트캠프 유니티 게임 개발 4기 19일차 회고 (0) | 2025.04.03 |