[2025_11_12]MeshCollider와 EventBus패턴의 남용 반성

2025. 11. 12. 19:20TIL

디자인 패턴과 EventBus 남용 반성, 그리고 MeshCollider 최적화

1. 디자인 패턴 사용의 함정

"망치를 들면 모든 것이 못으로 보인다"

오늘 특강에서 가장 와닿았던 부분:

  • 디자인 패턴을 배웠다고 해서 무조건 적용하려고 하면 안 된다
  • 기본기(알고리즘, 자료구조, 베스트 프랙티스)가 먼저
  • 패턴을 봤을 때 "새롭지 않다"고 느껴질 때 사용해도 된다
    • 즉, 이미 비슷한 구조로 짜본 적이 있을 때

내 EventBus 패턴 남용 사례 반성

문제점 분석

 
csharp
// 나쁜 예: 1:1 관계인데 이벤트 사용
public static Action OnPlayerJumpRequested;
public static Action<bool> OnPlayerSprintRequested;

왜 문제인가?

  • PlayerFSM → PlayerController로 직접 호출 가능한 1:1 관계
  • 여러 곳에서 참조하지 않는데도 이벤트로 구현
  • "EventBus 패턴을 사용해보자!"는 강박으로 무분별하게 남발

실제로 겪은 문제들

  1. 디버깅 지옥 
    • 이벤트 호출 추적이 매우 어려움
    • 구독자가 어디 있는지 찾기 힘듦
  2. 코드 복잡도 증가
    • 단순한 호출을 이벤트로 감싸서 오히려 복잡해짐
    • 코드 찾는 데 시간이 오래 걸림
  3. 유지보수 어려움
    • 이벤트 구독/해제 관리 필요
    • 메모리 누수 위험 (Subject에 등록된 걸 해제 안 하면 GC 대상이 안 될 수 있음)

앞으로의 Event 패턴 사용 기준

Event를 사용해야 할 때 (1:N 관계)

 
 
csharp
/// <summary>
/// 플레이어 사망 시 발생 (UI, 적AI, 사운드, 카메라 등 여러 시스템에서 반응 필요)
/// 구독자: GameManager, UIManager, EnemyAI, AudioManager, CameraController...
/// </summary>
public static Action OnPlayerDead;

/// <summary>
/// 인벤토리 요청 시 발생
/// - 인벤토리 UI 로드
/// - 플레이어 골드 업데이트
/// - 추후 확장 가능성 높음 (퀘스트 시스템, 상점 시스템 등)
/// </summary>
public static Action OnInventoryRequested;

사용 기준:

  • 여러 시스템이 동시에 반응해야 할 때
  • 확장 가능성이 높을 때
  • Observer들이 Subject의 로직을 몰라도 될 때

Direct Call을 사용해야 할 때 (1:1 관계)

csharp
// 좋은 예: 직접 호출
public class PlayerFSM
{
    private PlayerController controller;
    
    private void HandleJumpInput()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            controller.Jump(); // 직접 호출!
        }
    }
}
```

사용 기준:
- 1:1 관계일 때
- 한 곳에서만 반응하면 될 때
- 직접 참조해도 결합도가 크게 높아지지 않을 때

다음 팀 프로젝트 개선 방향

Event 사용 전 체크리스트
□ 이 이벤트를 여러 시스템이 구독하는가?
□ 확장 가능성이 있는가?
□ 직접 호출하면 결합도가 너무 높아지는가?

 

모두 Yes → Event 사용
하나라도 No → Direct Call 고려

2. Event 주석 작성 규칙

 
csharp
/// <summary>
/// [이벤트 설명]
/// 구독자: [구독하는 클래스들 나열]
/// </summary>
public static Action OnEventName;

3. Event 카테고리 분리

 
csharp
[Header("플레이어 상태 이벤트 (1:N)")]
public static Action OnPlayerDead;
public static Action OnPlayerLevelUp;

[Header("UI 업데이트 이벤트 (데이터→UI)")]
public static Action OnSpChanged;
public static Action OnHpChanged;

MeshCollider 최적화

문제 상황

  • MeshCollider를 오브젝트에 사용
  • Unity Stats에서 Batches 수가 증가

해결 방법

기본 Collider 조합

Unity클라이언트 에서
변경 전 MeshColldier
 
변경 후 boxCollider조합
 
csharp
// 나쁜 예
gameObject.AddComponent<MeshCollider>();

// 좋은 예
// BoxCollider와 CapsuleCollider를 조합해서 복잡한 형태
BoxCollider box1 = gameObject.AddComponent<BoxCollider>();
BoxCollider box2 = child1.AddComponent<BoxCollider>();
CapsuleCollider capsule = child2.AddComponent<CapsuleCollider>();

이유:

  • MeshCollider는 연산 비용이 매우 높음
  • BoxCollider, SphereCollider, CapsuleCollider 조합으로도 대부분 커버 가능
  • Batches 증가 방지 → 성능 향상

결론

배운 교훈

  1. "필요악"으로서의 디자인 패턴
    • Event는 필요할 때만 사용
    • 무분별한 추상화는 독이 됨
  2. 기본기가 우선
    • 패턴보다 코드를 정확히 이해하는 게 먼저
    • 패턴이 "새롭지 않을 때" 사용
  3. 다음 프로젝트에서는
    • "꼭 필요한가?" 고민하고 적용
    • 1:1 관계는 직접 호출
    • 1:N 관계만 Event 사용

특강에서의 한 마디

"디자인 패턴을 배웠다고 바로 적용하려 하지 말자.
프로젝트하면서 적용해보고 연습하는 건 OK,
취업해서 기존 코드에 디자인 패턴 적용해보려는 건 X"

이번 개인 과제에서 EventBus 패턴을 강박적으로 사용했던 게 정말 반성된다...
다음엔 더 신중하게 판단해서 적용해야겠다! 

 

+오늘 싱글턴 패턴 수업을 들으며 하승우 튜터님께 했던 질문
이전 팀플에서 사용했던 ManagerRoot
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ManagerRoot : Singleton<ManagerRoot>
{
    [Header("직접 참조")]
    [SerializeField] private GameManager gameManager;
    [SerializeField] private UIManager uiManager;
    [SerializeField] private SceneController sceneController;
    [SerializeField] private AudioManager audioManager;
    

    public static GameManager GameManager { get; private set; }
    public static UIManager UIManager { get; private set; }
    public static SceneController SceneController { get; private set; }
    public static AudioManager AudioManager { get; private set; }

    protected override void Init()
    {
        GameManager = gameManager;
        UIManager = uiManager;
        SceneController = sceneController;
        AudioManager = audioManager;

        Screen.SetResolution(1920, 1080, true);
    }
}

매니저들을 한곳에서 관리하겠다는 목표는 OK 그러나, 매니저들의 초기화 순서를 수동으로 지정하겠다!는 No.

 

각 매니저의 Awake,Start등의 유니티 콜백함수를 모두 지우고 RootManger의Init클래스에서 각 매니저들의 Init함수를 호출 해 값들을 초기화하거나

하위 클래스들의 MonoBehaviour를 지우고 유니티에서 쓰는 방식의 싱글턴이 아닌 c#에서 사용하는 방식의 싱글턴을 사용해 싱글턴 구현 후 동적 할당(eventSystem이나 Canvas이 때 동적 생성)

 

예시로 UIManager에서 UIPrefab을 불러올 때 ResroucesManager에서 프리팹들 동적으로 할당,생성 해줌