<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>규니티의 게임 개발소</title>
    <link>https://gyunity23.tistory.com/</link>
    <description>규니티의 게임 개발을 기록하는 곳입니다!</description>
    <language>ko</language>
    <pubDate>Fri, 21 Aug 2026 17:42:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>규니티</managingEditor>
    <image>
      <title>규니티의 게임 개발소</title>
      <url>https://tistory1.daumcdn.net/tistory/7845413/attach/dae614a4380b4dd9b21d6b0c9d4c076f</url>
      <link>https://gyunity23.tistory.com</link>
    </image>
    <item>
      <title>게임 최적화(3) - 초보들이 놓치기 쉬운 최적화!</title>
      <link>https://gyunity23.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 규니티입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적화 특집 시리즈 3편입니다. 오늘 주제는 &lt;b&gt;놓치기 쉬운 최적화&lt;/b&gt;예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편에서 Profiler 보는 법, 2편에서 Draw Call 줄이는 법을 다뤘는데, 이번 편은 조금 달라요. 코드가 돌아가긴 하는데 &lt;b&gt;&quot;이게 맞는 방식인가?&quot;&lt;/b&gt; 싶은 것들이에요. 초보 때 몰라서 가장 많이 저지르는 실수 세 가지를 정리해봤습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. String GC Alloc&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임이 별 이유 없이 뚝뚝 끊긴다면 첫 번째로 의심해야 할 게 &lt;b&gt;문자열 연산&lt;/b&gt;이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 string이 문제야?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C#에서 string은 &lt;b&gt;불변(Immutable) 객체&lt;/b&gt;예요. 한 번 만들어진 문자열은 수정이 안 되고, 뭔가 바꾸려면 무조건 &lt;b&gt;새 객체를 힙에 생성&lt;/b&gt;해야 해요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;cpp&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;string a = &quot;Score: &quot;;
string b = a + &quot;100&quot;;  // &quot;Score: 100&quot;이라는 새 객체 생성
                       // 기존 &quot;Score: &quot;은 버려짐 &amp;rarr; GC 대상&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Update()처럼 매 프레임 이게 반복되면 힙에 쓰레기가 쌓이다가 GC가 터지는 거예요. GC가 터지는 순간 게임이 잠깐 멈추고 프레임이 뚝 떨어지는 거고요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실수 패턴 ① UI 텍스트 매 프레임 갱신&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// X
void Update()
{
    scoreText.text = &quot;Score: &quot; + score.ToString();
    hpText.text = &quot;HP: &quot; + hp + &quot; / &quot; + maxHp;  // 이건 객체가 3개 생성됨
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;HP: &quot; + hp + &quot; / &quot; + maxHp 이렇게 쓰면 중간 결합마다 새 객체가 생겨서 한 줄에서 객체가 3개 만들어져요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// StringBuilder로 해결
private StringBuilder _sb = new StringBuilder();

void UpdateUI()
{
    _sb.Clear();
    _sb.Append(&quot;HP: &quot;);
    _sb.Append(hp);
    _sb.Append(&quot; / &quot;);
    _sb.Append(maxHp);
    hpText.text = _sb.ToString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실수 패턴 ② 빈 문자열 체크&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// X &quot;&quot; 는 새 string 객체를 생성함
if (playerName == &quot;&quot;) { }

// O
if (string.IsNullOrEmpty(playerName)) { }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실수 패턴 ③ 태그 비교&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// CompareTag 대신 tag 프로퍼티 쓰면 매번 새 string 반환
if (other.gameObject.tag == &quot;Enemy&quot;) { }

// CompareTag는 string 생성 없이 비교
if (other.gameObject.CompareTag(&quot;Enemy&quot;)) { }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 모르는 분이 꽤 많아요. tag 프로퍼티 자체가 호출될 때마다 새 string을 반환하거든요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실수 패턴 ④ 숫자 반복 변환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;점수처럼 자주 바뀌는 숫자를 매 프레임 문자열로 변환하는 경우, 미리 캐싱해두는 것도 방법이에요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// 0~999 점수를 미리 캐싱
private static readonly string[] ScoreCache = new string[1000];

private void Awake()
{
    for (int i = 0; i &amp;lt; ScoreCache.Length; i++)
        ScoreCache[i] = i.ToString();
}

void UpdateScoreUI(int score)
{
    scoreText.text = score &amp;lt; ScoreCache.Length
        ? ScoreCache[score]
        : score.ToString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실수 패턴 ⑤ Debug.Log 남발&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// 빌드 후에도 살아있으면 매 프레임 string 연산 발생
void Update()
{
    Debug.Log(&quot;현재 HP: &quot; + hp);
}

// 에디터에서만 동작, 빌드에서는 코드 자체가 사라짐
[System.Diagnostics.Conditional(&quot;UNITY_EDITOR&quot;)]
private void Log(string message)
{
    Debug.Log(message);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실수 패턴 ⑥ string.Format&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// string.Format도 내부적으로 boxing + 힙 할당 발생
scoreText.text = string.Format(&quot;Score: {0}&quot;, score);

// ✅ StringBuilder 쓰거나, 값이 자주 안 바뀌면 갱신 시점 제한
// score가 바뀔 때만 업데이트
private int _lastScore = -1;

void Update()
{
    if (score == _lastScore) return;  // 값이 같으면 건너뜀
    _lastScore = score;

    _sb.Clear();
    _sb.Append(&quot;Score: &quot;);
    _sb.Append(score);
    scoreText.text = _sb.ToString();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 바뀔 때만 업데이트하는 패턴은 string뿐 아니라 UI 갱신 전반에 써먹을 수 있어요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Profiler에서 확인하는 법&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;CPU Usage &amp;rarr; Hierarchy 뷰 &amp;rarr; GC Alloc 컬럼 클릭 (내림차순 정렬)
&amp;rarr; BehaviourUpdate 하위에 string 관련 함수가 올라와 있으면 바로 이 케이스&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Timeline 뷰에서는 &lt;b&gt;분홍/마젠타색 마커&lt;/b&gt;가 GC 할당이 일어나는 지점이에요. Update() 구간에 분홍색이 촘촘하게 찍혀있으면 string 실수일 가능성이 높아요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Update() 남용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity 개발을 처음 배우면 뭐든 Update()에 넣게 돼요. 근데 Update()는 &lt;b&gt;매 프레임 호출&lt;/b&gt;되기 때문에, 굳이 매 프레임 실행할 필요 없는 로직이 들어있으면 CPU를 낭비하는 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;자주 보이는 실수들&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// 변하지 않는 값을 매 프레임 계산
void Update()
{
    float distance = Vector3.Distance(transform.position, target.position);
    if (distance &amp;lt; 5f) { ... }
}

// 매 프레임 오브젝트 탐색
void Update()
{
    GameObject player = GameObject.Find(&quot;Player&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법 ① 캐싱&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 쓰는 값이나 참조는 Awake() 또는 Start()에서 한 번만 가져와서 변수에 저장해두세요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;private Transform _target;
private float _checkInterval = 0.2f;
private float _timer;

private void Awake()
{
    _target = GameObject.Find(&quot;Player&quot;).transform;
}

private void Update()
{
    _timer += Time.deltaTime;
    if (_timer &amp;gt;= _checkInterval)
    {
        _timer = 0f;
        float distance = Vector3.Distance(transform.position, _target.position);
        if (distance &amp;lt; 5f) { ... }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법 ② 필요할 때만 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매 프레임 체크할 필요 없는 로직은 타이머나 이벤트 방식으로 바꿔요. 위 코드처럼 0.2초마다 한 번만 체크하면 Update() 호출 횟수는 같아도 실제 로직 실행 횟수는 확 줄어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법 ③ 아예 Update() 없애기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태가 바뀔 때만 동작해야 하는 로직이라면 이벤트나 콜백으로 대체하는 게 제일 좋아요. 이전 포스팅에서 다뤘던 옵저버 패턴이나 이벤트버스가 여기서 진가를 발휘해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Profiler CPU Usage에서 BehaviourUpdate가 전체 프레임의 큰 비중을 차지하고 있으면 Update() 남용을 의심해보세요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. GetComponent 반복 호출&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GetComponent&amp;lt;&amp;gt;()는 편리하지만 &lt;b&gt;매 프레임 호출하면 꽤 비싼 연산&lt;/b&gt;이에요. 컴포넌트를 찾기 위해 내부적으로 탐색이 일어나거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가장 흔한 실수&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;// 매 프레임 GetComponent 호출
void Update()
{
    GetComponent&amp;lt;Rigidbody&amp;gt;().AddForce(Vector3.up);
    GetComponent&amp;lt;Animator&amp;gt;().SetBool(&quot;IsWalking&quot;, true);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해결 방법 &amp;mdash; Awake()에서 캐싱&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;private Rigidbody _rb;
private Animator _animator;

private static readonly int IsWalking = Animator.StringToHash(&quot;IsWalking&quot;);

private void Awake()
{
    _rb = GetComponent&amp;lt;Rigidbody&amp;gt;();
    _animator = GetComponent&amp;lt;Animator&amp;gt;();
}

private void Update()
{
    _rb.AddForce(Vector3.up);
    _animator.SetBool(IsWalking, true);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Animator.StringToHash()도 같이 캐싱했는데, Animator 파라미터를 문자열로 매 프레임 비교하는 것도 GC Alloc을 일으키거든요. 해시값으로 바꿔두면 이것도 같이 잡혀요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameObject.Find(), FindObjectOfType&amp;lt;&amp;gt;() 도 마찬가지예요. Update() 안에서 절대 쓰면 안 되고 Awake()에서 한 번만 호출해서 캐싱해두세요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 다 공통점이 있어요. &lt;b&gt;&quot;매 프레임 반복되는 곳에서 비싼 연산을 하고 있다&quot;&lt;/b&gt; 는 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 돌아가니까 문제없다고 생각하기 쉬운데, Profiler 켜보면 이것들이 조용히 프레임을 갉아먹고 있는 경우가 많아요. 특히 모바일에서는 더 심하게 나타나고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편은 모바일 특화 최적화입니다. PC에서 멀쩡하던 게 모바일 빌드하면 터지는 이유, 텍스처 압축, 그림자 설정, Target Frame Rate까지 정리할게요.&lt;/p&gt;</description>
      <category>유니티</category>
      <category>c#</category>
      <category>유니티</category>
      <category>최적화</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/44</guid>
      <comments>https://gyunity23.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 10 Jun 2026 22:55:16 +0900</pubDate>
    </item>
    <item>
      <title>게임 최적화(2) - Draw Call 줄이기</title>
      <link>https://gyunity23.tistory.com/43</link>
      <description>&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적화 특집 시리즈 2편입니다. 오늘 주제는 &lt;b&gt;Draw Call 줄이기&lt;/b&gt;예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1편에서 Profiler 켜고 Rendering 탭 봤을 때 Batches 수치가 높게 나왔던 분들, 이번 편이 딱 맞는 내용이에요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 게임을 만들다 보면 한 번쯤은 Batches가 높게나오고 그걸 줄여야 최적화가 된다라는 말을 들어보셨을거에요!&lt;br /&gt;저도 그래서 최적화를 공부할 때 &lt;b&gt;Draw Call, Batches, Batching&lt;/b&gt;을 알게되고 그때부터 이해가 편해지더라구요.&lt;b&gt;&lt;br /&gt;그럼 개념부터 알아보겠습니다!!&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Draw Call이 뭔데 왜 문제야?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Draw Call은 CPU가 GPU한테 &quot;이거 그려줘&quot; 하고 명령을 보내는 것 자체예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 명령 하나하나가 &lt;b&gt;CPU 오버헤드&lt;/b&gt;를 발생시킨다는 거예요. 오브젝트 하나 그릴 때마다 CPU가 머티리얼, 셰이더, 텍스처 정보를 GPU에 넘겨야 하거든요. 오브젝트 100개면 명령도 100번, 1000개면 1000번이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;모바일에서는 이 비용이 PC보다 훨씬 커요.&lt;/b&gt; 그래서 Batching이 필요한 거예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Batching은 간단히 말하면 &lt;b&gt;&quot;여러 오브젝트를 하나로 묶어서 Draw Call 횟수 자체를 줄이는 것&quot;&lt;/b&gt; 이에요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Static Batching&lt;/h4&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;움직이지 않는 오브젝트들을 &lt;b&gt;에디터에서 미리 하나로 합쳐두는 방식&lt;/b&gt;이에요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배경, 건물, 바닥처럼 절대 안 움직이는 오브젝트에 적용해요. 런타임에 연산이 없어서 Batching 방식 중 성능이 가장 좋아요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적용 방법은 두 가지입니다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;1. &lt;span&gt;&lt;span&gt;인스펙터에서 오브젝트 선택 &amp;rarr; 우측 상단 Static 체크박스 체크 &lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;2. 코드로 적용 &lt;/span&gt;&lt;span&gt;StaticBatchingUtility.Combine(gameObject);&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조건이 하나 있어요. &lt;b&gt;같은 머티리얼을 쓰는 오브젝트끼리만 묶여요.&lt;/b&gt; 머티리얼이 다르면 Batching이 안 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점으로는, 합쳐진 메시 데이터를 메모리에 보관하기 때문에 메모리 사용량이 늘어납니다.&lt;br /&gt;오브젝투가 너무 많으면 오히려 역효과가 날 수 있어서 적용 전후로 Profiler Memory 탭에서 수치 꼭 확인해보세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. SRP Batcher (Unity 6 기준 핵심)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity 6에서는 &lt;b&gt;SRP Batcher&lt;/b&gt;가 기본으로 활성화되어 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Batching이 &quot;오브젝트를 하나로 합쳐서 Draw Call 수를 줄이는 것&quot;이라면, SRP Batcher는 조금 달라요. Draw Call 수 자체를 줄이는 게 아니라 &lt;b&gt;Draw Call 사이에 발생하는 GPU 셋업 비용을 줄이는 방식&lt;/b&gt;이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하면 이래요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기존&amp;nbsp;방식:&amp;nbsp;오브젝트마다&amp;nbsp;셰이더&amp;nbsp;정보를&amp;nbsp;매번&amp;nbsp;GPU에&amp;nbsp;올림 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;SRP Batcher: 셰이더 정보를 GPU 메모리에 캐싱해두고 재사용&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Profiler Rendering 탭에서 Batches 수치는 그대로인데 실제 렌더링 시간이 줄어드는 현상이 나타나요. Batches 숫자만 보고 &quot;왜 안 줄어들지?&quot; 싶었던 분들은 이 때문일 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;활성화 확인 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project Settings &amp;rarr; Graphics &amp;rarr; SRP Batcher 체크 여부 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity 6 URP 기준으로는 기본 활성화되어 있어요. 꺼져 있으면 켜두는 게 좋아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SRP Batcher가 동작하려면 셰이더가 호환되어야 해요.&lt;/b&gt; URP/HDRP 기본 셰이더는 대부분 호환되는데, 커스텀 셰이더를 쓴다면 호환 여부를 Frame Debugger에서 확인할 수 있어요.&lt;/p&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. Batching이 안 되는 이유 확인하는 법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Batching 설정을 했는데도 Draw Call이 안 줄어드는 경우가 있어요. 이럴 때 &lt;b&gt;Frame Debugger&lt;/b&gt;로 원인을 바로 찾을 수 있어요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;Window &amp;rarr; Analysis &amp;rarr; Frame Debugger &amp;rarr; Enable 클릭&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 목록에서 드로우 콜을 하나씩 클릭하면 오른쪽에 &lt;b&gt;왜 이전 드로우 콜과 Batch가 안 됐는지&lt;/b&gt; 이유가 텍스트로 나와요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 나오는 이유들이에요.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;Different materials&quot; &amp;rarr; 머티리얼이 달라서&lt;/li&gt;
&lt;li&gt;&quot;Renderer is not in static batch&quot; &amp;rarr; Static 체크 안 해서&lt;/li&gt;
&lt;li&gt;&quot;GameObject is not active&quot; &amp;rarr; 비활성 오브젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유 확인하고 하나씩 잡으면 Batches 수치가 눈에 띄게 줄어요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Sprite Atlas는 왜 같이 쓰나&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Batching 조건이 &lt;b&gt;같은 머티리얼&lt;/b&gt;이라고 했잖아요. 2D 게임에서 스프라이트를 각각 따로 임포트하면 스프라이트마다 머티리얼이 달라져서 Batching이 깨져요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sprite Atlas로 스프라이트를 한 장의 텍스처로 묶으면 머티리얼이 하나로 통일돼서 Batching이 자연스럽게 걸려요. 아틀라스 관련 자세한 내용은 &lt;a href=&quot;https://gyunity23.tistory.com/26&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전 포스팅&lt;/a&gt;에 정리해뒀으니 참고해주세요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Static Batching이랑 SRP Batcher만 챙겨도 웬만한 프로젝트는 커버가 되는데, 상황에 따라 이런 방식도 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;나무, 돌, 적처럼 완전히 동일한 오브젝트가 씬에 수십 개 이상 배치된다면&lt;/b&gt; &amp;rarr; GPU Instancing. 머티리얼에서 Enable GPU Instancing 체크 하나로 적용돼요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;움직이는 단순한 오브젝트(파티클, 간단한 2D 스프라이트)가 많다면&lt;/b&gt; &amp;rarr; Dynamic Batching. 근데 버텍스 300 이하 제한이 있고 Unity 6에서는 SRP Batcher가 대부분 대체하기 때문에, 실전에서 의도적으로 쓸 일은 많지 않아요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편은 초보 때 몰라서 가장 많이 저지르는 실수들, String GC Alloc / Update() 남용 / GetComponent 반복 호출을 한 번에 정리할게요!&lt;/p&gt;
&lt;/div&gt;</description>
      <category>유니티</category>
      <category>Batch</category>
      <category>draw call</category>
      <category>드로우콜</category>
      <category>유니티</category>
      <category>최적화</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/43</guid>
      <comments>https://gyunity23.tistory.com/43#entry43comment</comments>
      <pubDate>Thu, 28 May 2026 12:23:33 +0900</pubDate>
    </item>
    <item>
      <title>게임 최적화(1) -  Unity Profiler 읽는 법!</title>
      <link>https://gyunity23.tistory.com/42</link>
      <description>&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 규니티입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적화 특집 시리즈 1편입니다. 오늘 주제는 Profiler 읽는 법이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Profiler&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;는 프레임당 성능 지표를 캡처하여 병목 현상을 파악하는 데 도움을 줍니다. 프로파일러에 포함된 모듈을 사용하여 CPU 사용량, GPU, 렌더링, 메모리, 물리 등의 세부 정보를 상세하게 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최적화를 처음 시작할 때 가장 많이 하는 실수가 &quot;느린 것 같은 곳&quot;을 감으로 건드리는 거예요. 근데 대부분 범인은 예상 못한 곳에 있어요. 저도 렌더링 문제인 줄 알고 한참 씨름했는데 알고 보니 &lt;b&gt;GC Alloc&lt;/b&gt;이 원인이었던 적이 있었거든요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 최적화보다 Profiler 읽는 법이 먼저입니다!&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;i&gt;GC Alloc : 프로그램이 실행되는 동안 &lt;b&gt;관리 힙(Managed Heap) 메모리에 새로 할당된 메모리 용량(바이트)&lt;/b&gt; &lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 그 전에, FPS보다 ms를 봐야 하는 이유&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Profiler 열기 전에 하나만 짚고 넘어갈게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 &quot;60fps 나오면 되는 거 아니야?&quot; 싶지만, 개발자 입장에선 fps보다 &lt;b&gt;밀리초(ms)&lt;/b&gt; 단위로 보는 게 훨씬 중요해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 게임이 0.75초 동안 59프레임을 멀쩡하게 그리다가, 딱 한 프레임만 0.25초가 걸렸다고 해볼게요. 평균 내면 60fps인데, 그 한 프레임에서 플레이어는 끊김을 느낍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fps는 이런 순간적인 스파이크를 숨겨버려요. 그래서 Profiler에서는 프레임당 ms로 판단해야 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 fps에 따른 프레임 예산은 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;30fps&lt;/b&gt; &amp;rarr; 프레임당 33.33ms 이하&lt;/li&gt;
&lt;li&gt;&lt;b&gt;60fps&lt;/b&gt; &amp;rarr; 프레임당 16.66ms 이하&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 예산을 단 한 프레임이라도 넘으면 플레이어가 알아챕니다. 모바일은 더 빡센데, 발열과 배터리 때문에 실제 목표는 예산의 65% 수준으로 잡는 게 좋아요. 30fps 기준이면 &lt;b&gt;22ms&lt;/b&gt;, 60fps 기준이면 &lt;b&gt;11ms&lt;/b&gt;가 현실적인 목표예요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Profiler 여는 법&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Window &amp;rarr; Analysis &amp;rarr; Profiler&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;열고 나서 Play 버튼 누르면 실시간으로 데이터가 쌓이기 시작해요. 상단에 탭이 여러 개 있는데 오늘은 자주 쓰는 세 개만 다뤄보겠습니다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU Usage&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Rendering&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SbLvI/dJMcabRLDDc/w1azxUaaUCWrwcHRZB1Uo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SbLvI/dJMcabRLDDc/w1azxUaaUCWrwcHRZB1Uo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SbLvI/dJMcabRLDDc/w1azxUaaUCWrwcHRZB1Uo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSbLvI%2FdJMcabRLDDc%2Fw1azxUaaUCWrwcHRZB1Uo1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1834&quot; height=&quot;770&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;770&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 병목이 CPU인지 GPU인지 먼저 파악하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 탭을 보기 전에, 방향부터 잡아야 해요&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CPU 바운드&lt;/b&gt;란 CPU에서 처리할 게 너무 많은 상황이에요. 스크립트, 물리, 애니메이션 같은 것들이 주범이고, 이 경우엔 GPU 설정을 아무리 건드려도 프레임이 안 올라요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPU 바운드&lt;/b&gt;란 GPU 렌더링이 너무 오래 걸리는 상황이에요. 이 경우엔 코드 최적화보다 셰이더, 해상도, 드로우 콜 쪽을 봐야 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity에서는 Profiler 창 상단에 &lt;b&gt;Highlights 모듈&lt;/b&gt;이 있습니다! 타겟 프레임 시간을 설정하면 CPU랑 GPU 중 어디가 문제인지 바로 알려줘서 시작점 잡기가 훨씬 편해졌습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. CPU Usage &amp;mdash; 어디서 시간을 가장 많이 쓰나&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 먼저 봐야 하는 탭이에요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래프에서 프레임을 하나 클릭하면 아래에 해당 프레임에서 실행된 함수들이 계층 구조로 나와요. 여기서 봐야 할 컬럼은 두 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Total(%)&lt;/b&gt; : 전체 프레임 시간 중 이 함수가 차지하는 비율&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GC Alloc&lt;/b&gt; : 이 함수에서 힙 메모리에 새로 할당된 양&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1281&quot; data-origin-height=&quot;1136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mTC7G/dJMb99NbRKU/C2hT52HVoa0hstctKyKR60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mTC7G/dJMb99NbRKU/C2hT52HVoa0hstctKyKR60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mTC7G/dJMb99NbRKU/C2hT52HVoa0hstctKyKR60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmTC7G%2FdJMb99NbRKU%2FC2hT52HVoa0hstctKyKR60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1281&quot; height=&quot;1136&quot; data-origin-width=&quot;1281&quot; data-origin-height=&quot;1136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;GC Alloc이 매 프레임 &lt;b&gt;0B&lt;/b&gt; 면 이상적인 상태예요. 숫자가 계속 찍히고 있으면 어딘가에서 불필요한 할당이 일어나고 있다는 신호입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 &lt;b&gt;Update()&lt;/b&gt; 안에서 GC Alloc이 나오고 있으면 바로 의심해야 해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보기 방식도 두 가지예요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hierarchy 뷰&lt;/b&gt; : 함수별로 정렬된 목록. GC Alloc 컬럼 클릭하면 많이 할당되는 함수 순서로 정렬돼요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Timeline 뷰&lt;/b&gt; : 시간 흐름대로 시각화된 뷰. 분홍색/마젠타색으로 표시된 부분이 GC 할당이 일어나는 지점이에요. 보이면 바로 의심하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;mcp-app-modal-toolu_01SzJPthraiAgWx8Z2qjHgxM&quot;&gt;
&lt;div id=&quot;mcp-app-modal-frame-toolu_01SzJPthraiAgWx8Z2qjHgxM&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;mcp-app-container-toolu_01SzJPthraiAgWx8Z2qjHgxM&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Memory &amp;mdash; 메모리가 어떻게 쌓이나&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Memory 탭에서는 게임이 현재 얼마나 메모리를 쓰고 있는지 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주로 보는 항목이에요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Total Used Memory&lt;/b&gt; : 현재 총 메모리 사용량&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Texture Memory&lt;/b&gt; : 텍스처가 차지하는 메모리. 아틀라스 적용 전후로 비교해보면 차이가 바로 보여요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GC Allocated In Frame&lt;/b&gt; : 이 수치가 빨간 선으로 스파이크 치는 프레임이 있으면 그 프레임을 집중 분석해야 해요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 탭은 실시간 성능보다는 &lt;b&gt;누수 탐지&lt;/b&gt; 쪽에 더 유용해요. 게임 플레이 중에 이 수치가 계속 우상향한다면 어딘가에서 메모리가 제대로 해제되지 않고 있다는 신호입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 단위로 뜯어보고 싶으면 &lt;b&gt;Memory Profiler 패키지&lt;/b&gt;를 따로 설치해야 해요. Package Manager에서 &quot;Memory Profiler&quot; 검색하면 나와요. 씬 언로드 전후 스냅샷을 비교해서 누수를 잡는 데 아주 유용합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. Rendering &amp;mdash; Draw Call이 얼마나 나오나&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Rendering 탭에서는 렌더링 관련 수치를 볼 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 봐야 할 핵심 수치는 두 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Batches&lt;/b&gt; : 실제로 GPU에 넘어간 드로우 콜 횟수. 낮을수록 좋아요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SetPass Calls&lt;/b&gt; : 렌더링 상태가 바뀐 횟수. Batches보다 이 수치가 더 중요할 때도 있어요&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 기준으로 Batches가 &lt;b&gt;200을 넘기 시작하면&lt;/b&gt; 렌더링 병목을 의심해볼 만해요. 다음 편에서 다룰 Batching이랑 아틀라스가 이 수치를 낮추는 핵심 방법입니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. Profiler Workflow&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 유니티에서 제공하는 Profiler Workflow입니다!&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;897&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rLpBn/dJMcadBZ8TK/IfBkVzw84OnlNyyj9lPas1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rLpBn/dJMcadBZ8TK/IfBkVzw84OnlNyyj9lPas1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rLpBn/dJMcadBZ8TK/IfBkVzw84OnlNyyj9lPas1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrLpBn%2FdJMcadBZ8TK%2FIfBkVzw84OnlNyyj9lPas1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;930&quot; height=&quot;897&quot; data-origin-width=&quot;930&quot; data-origin-height=&quot;897&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로파일링의 핵심은 최적화의 대상이 되는 병목 현상을 식별하는 것입니다!&lt;br /&gt;추측에만 의존하게 된다면 게임의 일부분만 최적화하게 되어 성능이 개선되지 않을 수도 있습니다. 심지어 원래 의도와 달리 게임의 전체적인 성능에 악영향을 끼칠 수도 있습니다.&lt;br /&gt;&lt;br /&gt;이 플로 차트를 보고 실제로 원인을 빠르게 파악하여 투입한 시간 대비 최대한의 효과가 나타나도록 최적화해보세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;마무리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Profiler는 처음엔 뭔가 많아 보여서 겁먹기 쉬운데, 사실 보는 탭이 정해져 있어요. CPU Usage에서 GC Alloc 확인하고, Memory에서 누수 체크하고, Rendering에서 Batches 확인하는 것. 이 세 가지만 익혀도 웬만한 병목은 잡을 수 있어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편은 Rendering 탭에서 자주 보이는 Draw Call을 실제로 줄이는 방법입니다. Batching이랑 아틀라스를 한 번에 정리할게요.&lt;br /&gt;&lt;br /&gt;그리고 제가 이번 글을 작성하면서 참고한 유니티 가이드북입니다! 다들 한 번씩 보는걸 추천 드립니다&lt;br /&gt;&lt;a href=&quot;https://unity.com/kr/resources/ultimate-guide-to-profiling-unity-games-unity-6&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://unity.com/kr/resources/ultimate-guide-to-profiling-unity-games-unity-6&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;</description>
      <category>유니티</category>
      <category>Profiler</category>
      <category>unity</category>
      <category>유니티</category>
      <category>최적화</category>
      <category>프로파일러</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/42</guid>
      <comments>https://gyunity23.tistory.com/42#entry42comment</comments>
      <pubDate>Thu, 21 May 2026 11:11:57 +0900</pubDate>
    </item>
    <item>
      <title>최적화 - 유니티 최적화 시리즈 시작!</title>
      <link>https://gyunity23.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요, 규니티입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘부터 게임 개발에 빼놓을 수 없는 유니티 최적화 특집 시리즈를 시작해보려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행하다가 코드도 별로 없는데 프레임이 뚝뚝 끊기는 상황이 생긴적 다들 있을 겁니다ㅠ&lt;br /&gt;처음엔 뭐가 문제인지 고민만하고 그냥 오브젝트가 많아서 그런가 싶었는데 Profiler 열어보니까 전혀 예상 못한 곳에서 터지고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그때 최적화는 필수로 해야하는구나, 감으로 하는 게 아니라 수치로 보고 판단해야 하는 거구나 생각했습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 준비한 유니티 최적화 시리즈!!&lt;br /&gt;이번 시리즈는 개념 나열보다는 &quot;아 이래서 터지는 거구나&quot;가 느껴지는 글을 목표로 제가 직접 겪은 것들 위주로 정리해보려 합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시리즈 구성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Unity 최적화 특집 #1] 최적화 전에 Profiler부터 읽어라&lt;/b&gt; 뭐가 병목인지도 모르는 상태에서 최적화를 시작하면 엉뚱한 곳만 건드리게 돼요. Profiler 읽는 법을 제대로 한 번 짚고 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Unity 최적화 특집 #2] Draw Call 줄이기 (Batching + 아틀라스)&lt;/b&gt; 렌더링 병목의 주범인 Draw Call을 줄이는 방법. Static Batching, Dynamic Batching, Sprite Atlas까지 연관된 것들을 한 번에 정리합니다. 아틀라스는 예전에 단독으로 다룬 적 있는데 이번엔 Draw Call 흐름 안에서 다시 봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Unity 최적화 특집 #3] 놓치기 쉬운 최적화&lt;/b&gt; String GC Alloc, Update() 남용, GetComponent 반복 호출. 사실 초보 때 이런 거 몰라서 많이 틀려요. 저도 그랬고요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[Unity 최적화 특집 #4] 모바일 특화 최적화&lt;/b&gt; PC에서 멀쩡하던 게 모바일 빌드하면 터지는 경우가 꽤 있어요. 텍스처 압축, 그림자 설정, Target Frame Rate 등 모바일 빌드 전에 꼭 챙겨야 할 것들을 다룹니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 풀링은 예전에 단독으로 정리한 포스팅이 있어서 시리즈 중간중간 링크로 연결할 예정입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 분들한테 도움이 될 것 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프레임 드롭이 생겼는데 원인을 어디서 찾아야 할지 모르겠는 분, Profiler는 켜봤는데 뭘 봐야 할지 모르겠는 분, 코드는 돌아가는데 이게 맞는 방식인지 모르겠는 분.&lt;br /&gt;&lt;br /&gt;끝까지 따라와주세요~~~~~~~&lt;/p&gt;</description>
      <category>유니티</category>
      <category>unity</category>
      <category>유니티</category>
      <category>최적화</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/41</guid>
      <comments>https://gyunity23.tistory.com/41#entry41comment</comments>
      <pubDate>Fri, 8 May 2026 12:55:50 +0900</pubDate>
    </item>
    <item>
      <title>Unity 기능 리뷰 - Avatar Mask + Animator Layer</title>
      <link>https://gyunity23.tistory.com/40</link>
      <description>&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 프로젝트를 진행하다가 캐릭터가 &lt;b&gt;걷는 도중에 공격&lt;/b&gt;을 해야 하는 상황이 생겼는데요. 이동 애니메이션과 공격 애니메이션을 동시에 재생해야 했는데, 하나의 레이어로는 둘 중 하나만 재생이 되더라고요. 이때 사용했던 &lt;b&gt;애니메이터 마스크 레이어(Avatar Mask + Animator Layer)&lt;/b&gt; 를 포스팅해보려고 합니다! 게임 개발을 하다 보면 &quot;이동하면서 총 쏘기&quot;, &quot;걸으면서 손 흔들기&quot;처럼 상체와 하체가 &lt;b&gt;서로 다른 애니메이션을 동시에&lt;/b&gt; 재생해야 하는 상황이 자주 생기잖아요. 이걸 깔끔하게 해결해주는 게 바로 &lt;b&gt;Avatar Mask + Animator Layer&lt;/b&gt; 조합입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Avatar Mask란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Avatar Mask는 애니메이션이 &lt;b&gt;어느 뼈대(Bone)에만 적용될지&lt;/b&gt;를 정의하는 에셋입니다. 쉽게 말해, &quot;이 애니메이션은 상체 뼈대에만 써!&quot; 라고 Unity에게 알려주는 필터예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity의 Humanoid 캐릭터 기준으로, 신체를 다음과 같이 나눌 수 있어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;상체(Upper Body)&lt;/b&gt; : 척추, 양팔, 머리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하체(Lower Body)&lt;/b&gt; : 골반, 양 다리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Avatar Mask에서 초록색 부위는 적용, 빨간색 부위는 제외입니다. 상체 마스크를 만들면 해당 레이어의 애니메이션이 상체 뼈대에만 영향을 주게 돼요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Animator Layer란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Animator Controller 안에서 &lt;b&gt;여러 애니메이션 상태 머신을 독립적으로 실행&lt;/b&gt;할 수 있게 해주는 레이어 시스템입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포토샵의 레이어와 똑같아요. 아래 레이어는 이동, 위 레이어는 공격 &amp;mdash; 위 레이어가 아래 레이어를 덮어씌우는 방식으로 동작합니다. 여기에 Avatar Mask를 얹으면 &quot;위 레이어는 상체만 덮어써!&quot; 가 되는 거예요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구조를 한눈에 보면 이렇습니다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;784&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yLFmT/dJMcaduQDvx/HZDrwuvmOZXpfqgUTMT4y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yLFmT/dJMcaduQDvx/HZDrwuvmOZXpfqgUTMT4y0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yLFmT/dJMcaduQDvx/HZDrwuvmOZXpfqgUTMT4y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyLFmT%2FdJMcaduQDvx%2FHZDrwuvmOZXpfqgUTMT4y0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;784&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;784&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;mcp-app-modal-toolu_018Ve7QHREtJZ5FSA5Mv8uec&quot;&gt;
&lt;div id=&quot;mcp-app-modal-frame-toolu_018Ve7QHREtJZ5FSA5Mv8uec&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 설정 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① Avatar Mask 생성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Project 창에서 우클릭 &amp;rarr; Create &amp;rarr; Avatar Mask 를 선택합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 Avatar Mask를 클릭하면 인스펙터에서 Humanoid 신체 부위가 초록/빨간색으로 표시돼요. 상체 마스크를 만들려면 &lt;b&gt;척추(Spine), 양팔(Left/Right Arm), 머리(Head)만 초록색&lt;/b&gt;으로 켜두고, 하체는 모두 꺼주면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② Animator Controller에 레이어 추가&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Animator 창을 열고 왼쪽 상단 Layers 탭에서 + 버튼으로 새 레이어를 추가해요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이어 설정에서 중요한 항목은 두 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Weight&lt;/b&gt; : 1로 설정 (0이면 레이어가 아예 적용 안 됨!)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Mask&lt;/b&gt; : 방금 만든 Avatar Mask를 드래그해서 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ Blending 모드 선택&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이어 설정의 Blending 항목에서 두 가지 모드 중 선택합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Override&lt;/b&gt; : 아래 레이어를 완전히 덮어씀. 상체 공격 애니메이션처럼 &lt;b&gt;하체와 완전히 독립적으로 동작&lt;/b&gt;할 때 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Additive&lt;/b&gt; : 아래 레이어 위에 추가로 얹음. 숨쉬기, 고개 끄덕임처럼 &lt;b&gt;미세한 동작을 더할 때&lt;/b&gt; 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 케이스(걷기 + 공격 동시 재생)는 &lt;b&gt;Override&lt;/b&gt;가 정답이에요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;④ C# 코드로 레이어 제어&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;csharp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;public class PlayerAnimator : MonoBehaviour
{
    private Animator _animator;

    // Animator Parameter 해시 (문자열 비교보다 성능 우수)
    private static readonly int IsWalking = Animator.StringToHash(&quot;IsWalking&quot;);
    private static readonly int Attack    = Animator.StringToHash(&quot;Attack&quot;);

    // Upper Body 레이어 인덱스 (Base Layer = 0, 추가한 레이어 = 1)
    private const int UpperBodyLayer = 1;

    private void Awake()
    {
        _animator = GetComponent&amp;lt;Animator&amp;gt;();
    }

    private void Update()
    {
        // 하체: 이동 여부에 따라 걷기 애니메이션
        bool isMoving = Input.GetAxis(&quot;Horizontal&quot;) != 0 || Input.GetAxis(&quot;Vertical&quot;) != 0;
        _animator.SetBool(IsWalking, isMoving);

        // 상체: 공격 입력 시 Upper Body Layer에서만 Attack 트리거 발동
        if (Input.GetMouseButtonDown(0))
        {
            _animator.SetTrigger(Attack);
        }
    }

    // 공격 중 상체 레이어 가중치 조절 (필요 시)
    public void SetUpperBodyWeight(float weight)
    {
        _animator.SetLayerWeight(UpperBodyLayer, weight);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SetLayerWeight()를 활용하면 공격 중 상체 레이어의 가중치를 0~1 사이로 블렌딩하는 것도 가능해서, 더 자연스러운 전환 효과를 만들 수 있어요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실제 적용 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 프로젝트에서는 NPC가 &lt;b&gt;걷는 도중 공격&lt;/b&gt;을 해야 하는 상황이 있었어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적용 전&lt;/b&gt; : 공격 트리거를 발동하면 이동 애니메이션이 끊기고 전신이 공격 모션으로 덮어씌워짐 &amp;rarr; 캐릭터가 공격할 때마다 제자리에서 멈추는 어색한 연출&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적용 후&lt;/b&gt; : Base Layer는 계속 이동 애니메이션 재생, Upper Body Layer가 상체에만 공격 애니메이션을 Override &amp;rarr; 걸어가면서 자연스럽게 공격하는 연출 완성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 &quot;걷기 + 아이템 줍기&quot;, &quot;달리기 + 총기 재장전&quot;처럼 새로운 상황이 생겨도, Upper Body Layer의 애니메이션만 교체하면 되니까 &lt;b&gt;Base Layer를 전혀 건드릴 필요가 없어요!&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Avatar Mask + Animator Layer 조합은 단순히 &quot;상체 따로 하체 따로&quot; 그 이상으로, &lt;b&gt;애니메이션 시스템의 확장성과 유지보수성을 동시에 챙길 수 있는&lt;/b&gt; 중요한 기법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 액션 게임이나 RPG처럼 이동과 전투가 동시에 일어나는 장르에서는 사실상 필수에 가까우니, 꼭 한 번 직접 설정해보시길 추천드립니다!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>유니티</category>
      <category>unity</category>
      <category>모션</category>
      <category>애니메이터</category>
      <category>유니티</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/40</guid>
      <comments>https://gyunity23.tistory.com/40#entry40comment</comments>
      <pubDate>Thu, 16 Apr 2026 11:27:00 +0900</pubDate>
    </item>
    <item>
      <title>디자인 패턴 - 서비스 로케이터 패턴(Service Locator Pattern)</title>
      <link>https://gyunity23.tistory.com/39</link>
      <description>&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 포스팅 형식에 맞게 서비스 로케이터 패턴을 작성해드릴게요!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그 글 형식에 맞게 서비스 로케이터 패턴 포스팅을 작성해드릴게요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;서비스 로케이터 패턴(Service Locator Pattern)&lt;/b&gt; &amp;mdash; Unity 게임 개발에서의 활용&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 플레이어블 광고 게임 프로젝트를 진행하면서, SoundManager, ObjectPool, GameEventBus 등 여러 매니저 클래스들을 각 시스템이 직접 참조해야 하는 상황이 생겼는데요. 그때마다 FindObjectOfType&amp;lt;&amp;gt;()이나 싱글톤 남용으로 코드가 지저분해지는 걸 느꼈어요. 이 문제를 깔끔하게 해결해준 게 바로 &lt;b&gt;서비스 로케이터 패턴(Service Locator Pattern)&lt;/b&gt; 이었습니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 서비스 로케이터 패턴이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스(의존성)를 어디서든 등록하고 꺼내 쓸 수 있는 &lt;b&gt;중앙 레지스트리&lt;/b&gt;를 두는 디자인 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서 등장하는 세 가지 역할이 있어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Service Interface&lt;/b&gt; : 시스템이 사용할 기능의 추상 계약 (인터페이스 또는 추상 클래스)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ServiceLocator&lt;/b&gt; : 서비스를 등록하고, 요청하면 꺼내주는 중앙 레지스트리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Client&lt;/b&gt; : ServiceLocator에 서비스를 요청해서 사용하는 객체&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비유하자면, 호텔 컨시어지와 똑같아요. 손님(Client)은 컨시어지(ServiceLocator)에게 &quot;렌터카 업체 알려줘!&quot;라고 요청하면, 컨시어지가 적절한 업체(Service)를 연결해주는 구조입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 왜 서비스 로케이터 패턴을 쓸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 이 패턴을 쓰게 된 이유는 크게 네 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의존성 역전&lt;/b&gt; &amp;rarr; 클라이언트는 구체 구현체가 아닌 인터페이스만 알면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;교체 용이성&lt;/b&gt; &amp;rarr; 테스트 시 Mock 서비스로 교체하는 게 아주 간단해집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전역 접근 단순화&lt;/b&gt; &amp;rarr; 싱글톤처럼 FindObjectOfType&amp;lt;&amp;gt;() 없이 어디서든 서비스에 접근할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;런타임 바인딩&lt;/b&gt; &amp;rarr; 앱 실행 중에도 서비스를 교체하거나 재등록할 수 있어 유연한 구조를 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 의존성을 코드 안에 &quot;박아넣는&quot; 대신 &lt;b&gt;중앙에서 관리&lt;/b&gt;하게 되어, 규모가 커질수록 진가가 드러나는 패턴입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 사용 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity C# 기준으로 간단하게 구현하는 방법을 소개할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① 서비스 인터페이스 정의&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;csharp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;public interface IAudioService
{
    void Play(string clipName);
    void Stop();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② ServiceLocator 작성&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;csharp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;public static class ServiceLocator
{
    private static readonly Dictionary&amp;lt;Type, object&amp;gt; _services = new();

    public static void Register&amp;lt;T&amp;gt;(T service)
    {
        _services[typeof(T)] = service;
    }

    public static T Get&amp;lt;T&amp;gt;()
    {
        if (_services.TryGetValue(typeof(T), out var service))
            return (T)service;

        throw new Exception($&quot;Service {typeof(T).Name} not registered!&quot;);
    }

    public static void Unregister&amp;lt;T&amp;gt;()
    {
        _services.Remove(typeof(T));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ 구현체 작성&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;csharp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;public class SoundManager : MonoBehaviour, IAudioService
{
    public void Play(string clipName)
    {
        Debug.Log($&quot;사운드 재생: {clipName}&quot;);
        // 실제 AudioSource 처리
    }

    public void Stop()
    {
        Debug.Log(&quot;사운드 정지&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;④ 등록 및 사용&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;csharp&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;// 초기화 시점 (e.g. GameBootstrapper)
ServiceLocator.Register&amp;lt;IAudioService&amp;gt;(soundManager);

// 어느 클래스에서든
ServiceLocator.Get&amp;lt;IAudioService&amp;gt;().Play(&quot;bgm_main&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity에서는 Awake()나 별도의 Bootstrapper 스크립트에서 서비스를 등록해두면, 씬 어디서든 깔끔하게 꺼내 쓸 수 있어요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 실제 적용 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플레이어블 광고 게임에서 NPCStateMachine이 사운드와 이벤트 버스를 필요로 하는 상황을 가정해볼게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적용 전&lt;/b&gt; : NPCStateMachine 안에서 SoundManager.Instance, GameEventBus.Instance를 직접 참조 &amp;rarr; 싱글톤 남용, 테스트 불가, 교체 어려움&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;적용 후&lt;/b&gt; : ServiceLocator.Get&amp;lt;IAudioService&amp;gt;().Play(&quot;npc_idle&quot;) 처럼 인터페이스로만 접근 &amp;rarr; NPC 코드는 구현 세부사항을 전혀 모름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 사운드 시스템을 DummyAudioService로 교체해서 테스트할 때도, 등록만 바꾸면 NPC 코드는 한 줄도 건드릴 필요가 없어요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 옵저버 패턴과의 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘 다 &quot;결합도를 낮추는 패턴&quot;이지만 역할이 달라요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 다이어그램으로 한눈에 비교해볼게요.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otjVp/dJMcaf0rBn6/YmxxCXceUP3OKf2bG4MKOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otjVp/dJMcaf0rBn6/YmxxCXceUP3OKf2bG4MKOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otjVp/dJMcaf0rBn6/YmxxCXceUP3OKf2bG4MKOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FotjVp%2FdJMcaf0rBn6%2FYmxxCXceUP3OKf2bG4MKOK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1440&quot; height=&quot;848&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div id=&quot;mcp-app-modal-toolu_012FXri3HjQga1GKRC556dCr&quot;&gt;
&lt;div id=&quot;mcp-app-modal-frame-toolu_012FXri3HjQga1GKRC556dCr&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 주의할 점 &amp;mdash; 안티패턴이라는 오해&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로케이터는 종종 &lt;b&gt;안티패턴&lt;/b&gt;으로 불리기도 해요. 의존성이 숨겨져서 &quot;이 클래스가 뭘 필요로 하는지&quot; 겉으로 보이지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제 프로젝트에서 쓸 때는 이런 점을 챙기면 좋아요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인터페이스로만 등록&lt;/b&gt; &amp;rarr; 구현체를 직접 꺼내 쓰면 의존성 역전의 의미가 없어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Null Object 패턴 병행&lt;/b&gt; &amp;rarr; 등록되지 않은 서비스 요청 시 예외 대신 무동작 구현체를 반환하면 안전합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;씬 전환 시 재등록&lt;/b&gt; &amp;rarr; Unity에서는 씬이 바뀌면 등록된 서비스가 파괴될 수 있으니 DontDestroyOnLoad나 Bootstrapper로 관리해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 로케이터 패턴은 단순히 &quot;전역에서 뭔가를 꺼내 쓰는 패턴&quot;처럼 보이지만, 올바르게 쓰면 &lt;b&gt;의존성 역전 + 교체 가능성 + 테스트 용이성&lt;/b&gt; 세 가지를 동시에 챙길 수 있는 강력한 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 SoundManager, EventBus, ObjectPool처럼 여러 시스템에서 공유되는 서비스가 많은 Unity 게임 프로젝트에서는, 이 패턴을 익혀두면 싱글톤 지옥에서 벗어날 수 있으니 꼭 한 번 써보시길 추천드립니다!&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description>
      <category>유니티</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/39</guid>
      <comments>https://gyunity23.tistory.com/39#entry39comment</comments>
      <pubDate>Wed, 8 Apr 2026 12:26:06 +0900</pubDate>
    </item>
    <item>
      <title>디자인 패턴 - 옵저버 패턴(Observer Pattern)</title>
      <link>https://gyunity23.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 프로젝트를 진행하다가 이벤트 기반으로 여러 객체가 서로 상태를 감지해야 하는 상황이 생겼는데요. 이때 사용했던 &lt;b&gt;옵저버 패턴(Observer Pattern)&lt;/b&gt; 을 포스팅해보려고 합니다! 게임 개발을 하다 보면 &quot;적이 죽었을 때 UI 업데이트, 사운드 재생, 점수 추가가 동시에 일어나야 해!&quot; 같은 상황이 자주 생기잖아요. 이걸 깔끔하게 해결해주는 게 바로 옵저버 패턴입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 옵저버 패턴(Observer Pattern)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체들 사이에서 &lt;b&gt;하나의 객체 상태가 바뀌면, 그걸 지켜보고 있던 다른 객체들이 자동으로 알림을 받아 반응&lt;/b&gt;하는 디자인 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조에서 등장하는 두 가지 역할이 있어요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Subject(발행자)&lt;/b&gt; : 상태가 변하는 객체. 옵저버 목록을 갖고 있고, 변경이 생기면 알림을 보냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Observer(구독자)&lt;/b&gt; : Subject를 구독하고 있는 객체. 알림이 오면 자신의 동작을 실행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비유하자면, 유튜브 구독 알림과 똑같아요. 크리에이터(Subject)가 영상을 올리면(상태 변경), 구독자들(Observer)이 알림을 받는 구조입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 옵저버 패턴을 쓸까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 이 패턴을 쓰게 된 이유는 크게 네 가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;결합도 감소&lt;/b&gt; &amp;rarr; Subject는 Observer가 누구인지 구체적으로 알 필요가 없어요. 그냥 &quot;알림을 보낼 뿐&quot;이고, 누가 받는지는 신경 안 씁니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유지보수 편의성&lt;/b&gt; &amp;rarr; 새로운 Observer를 추가해도 Subject 코드를 전혀 수정하지 않아도 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 처리 간결화&lt;/b&gt; &amp;rarr; 여러 객체가 동시에 반응해야 하는 상황을 깔끔하게 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성&lt;/b&gt; &amp;rarr; 구독자를 언제든 자유롭게 추가하거나 제거할 수 있어서 유연한 구조를 만들 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 객체 간 의존성을 최소화하면서도 긴밀하게 연동할 수 있어서, 규모가 커질수록 진가가 드러나는 패턴입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 사용 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity C# 기준으로 간단하게 구현하는 방법을 소개할게요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① Observer 인터페이스 정의&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;routeros&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;public interface IObserver
{
    void OnNotify(string eventType);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② Subject 클래스 작성&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;cs&quot; style=&quot;color: #eaecf0;&quot;&gt;&lt;code&gt;public class Subject
{
    private List&amp;lt;IObserver&amp;gt; _observers = new List&amp;lt;IObserver&amp;gt;();

    public void Subscribe(IObserver observer)
    {
        _observers.Add(observer);
    }

    public void Unsubscribe(IObserver observer)
    {
        _observers.Remove(observer);
    }

    public void Notify(string eventType)
    {
        foreach (var observer in _observers)
        {
            observer.OnNotify(eventType);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ Observer 구현&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class UIManager : MonoBehaviour, IObserver
{
    public void OnNotify(string eventType)
    {
        if (eventType == &quot;EnemyDead&quot;)
        {
            Debug.Log(&quot;UI 업데이트: 적 처치 표시!&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;④ 실제 연결 및 사용&lt;/b&gt;&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;csharp&quot; style=&quot;color: #eaecf0;&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;Subject enemySubject = new Subject();
enemySubject.Subscribe(uiManager);  // 구독 등록

// 적이 죽었을 때
enemySubject.Notify(&quot;EnemyDead&quot;);   // 구독자 전체에게 알림 발송&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity에서는 C# event나 Action, 또는 UnityEvent를 활용하면 이 구조를 더 간결하게 쓸 수도 있어요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 실제 적용 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적(Enemy)이 사망했을 때 &lt;b&gt;UI 업데이트 / 사운드 재생 / 점수 추가&lt;/b&gt;가 동시에 일어나야 하는 상황을 가정해볼게요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;적용 전&lt;/b&gt; : Enemy 클래스 안에서 UIManager, SoundManager, ScoreManager를 직접 참조 &amp;rarr; 의존성 폭발, 코드 수정 시 연쇄 오류 위험&lt;/li&gt;
&lt;li&gt;&lt;b&gt;적용 후&lt;/b&gt; : Enemy가 Notify(&quot;EnemyDead&quot;)만 호출 &amp;rarr; 각 Manager가 알아서 반응 &amp;rarr; Enemy 코드는 단 한 줄도 건드릴 필요 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 훨씬 깔끔해지고, 나중에 &quot;적 처치 시 파티클 효과 추가&quot;가 필요해도 EffectManager만 구독시키면 끝입니다!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옵저버 패턴은 단순히 &quot;알림 보내는 패턴&quot; 같아 보이지만, 실제 프로젝트에서는 &lt;b&gt;객체 간 결합도 감소 + 유지보수 효율&lt;/b&gt; 두 가지를 동시에 챙길 수 있는 중요한 디자인 패턴입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 Unity처럼 다양한 시스템이 서로 맞물려 돌아가는 환경에서는, 이 패턴을 익혀두면 훨씬 체계적인 코드를 짤 수 있으니 꼭 한 번 써보시길 추천드립니다!&lt;/p&gt;</description>
      <category>유니티</category>
      <category>개발</category>
      <category>게임</category>
      <category>디자인패턴</category>
      <category>옵저버패턴</category>
      <category>유니티</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/38</guid>
      <comments>https://gyunity23.tistory.com/38#entry38comment</comments>
      <pubDate>Thu, 2 Apr 2026 11:34:57 +0900</pubDate>
    </item>
    <item>
      <title>[독후감] 라프코스터의 재미이론</title>
      <link>https://gyunity23.tistory.com/37</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;라프 코스터의 재미란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 펼치기 전, 나는 세 가지 질문을 가지고 있었다. 내가 생각하는 재미의 기준이 맞는가. 라프 코스터는 재미를 어떻게 정의하는가. 그리고 대중이 원하는 재미는 무엇인가.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라프 코스터는 재미의 본질을 &lt;b&gt;학습(learning)&lt;/b&gt; 으로 정의한다. 인간의 뇌는 패턴을 인식하고 그것을 습득하는 순간 쾌감을 느끼도록 설계되어 있으며, 게임은 그 학습 과정을 극도로 압축하여 제공하는 매체라는 것이다. 즉, 플레이어가 게임의 규칙과 패턴을 파악하고 숙달해 나가는 그 과정 자체가 재미의 원천이다. 반대로 패턴을 모두 익혀버려 더 이상 배울 것이 없어진 순간, 게임은 지루해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 논리는 단순하지만 강력하다. 재미는 감각적 자극이나 화려한 연출에서 오는 것이 아니라, 도전과 학습의 사이클에서 온다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내 기준과 비교하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;독전감에서 나는 재미의 기준을 세 가지로 정리했다. 차별성, 성취감, 유저 편의성. 책을 읽고 나서 이 기준들을 다시 바라보니, 라프 코스터의 틀과 맞닿는 부분이 있으면서도 다른 층위에서 이야기하고 있다는 것을 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;차별성&lt;/b&gt;은 재미 그 자체라기보다 재미를 경험하게 만드는 입구에 가깝다. 라프 코스터의 언어로 바꾸면, 플레이어에게 '아직 학습하지 않은 새로운 패턴'을 제공하는 것이다. 수많은 게임 속에서 유저의 눈길을 끄는 것은 결국 낯선 규칙과 낯선 구조다. 차별성은 곧 새로운 학습 기회의 제공이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;성취감&lt;/b&gt;은 라프 코스터의 이론과 가장 직접적으로 연결된다. 그가 말하는 '패턴 습득의 쾌감'이 바로 우리가 흔히 성취감이라고 부르는 것이다. 스테이지를 클리어하거나 상대를 이겼을 때 느끼는 짜릿함은, 뇌가 하나의 패턴을 완전히 이해했다는 신호다. 나는 이것이 재미의 원초적인 형태라고 생각해왔는데, 라프 코스터도 같은 지점을 가리키고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유저 편의성&lt;/b&gt;은 라프 코스터가 직접적으로 다루지 않는 영역이지만, 그의 이론 안에서 재해석할 수 있다. 불편한 UX와 소통 부재는 플레이어가 패턴을 학습하는 흐름을 끊어버린다. 배움의 리듬이 깨지면 재미도 사라진다. 유저 편의성은 학습 경험을 보호하는 환경 설계라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;새롭게 얻은 인사이트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 인상 깊었던 것은 &lt;b&gt;재미의 유통기한&lt;/b&gt;에 대한 통찰이다. 아무리 잘 만든 게임도 플레이어가 모든 패턴을 익혀버리면 재미를 잃는다. 이것은 게임의 실패가 아니라 설계상의 숙명이다. 그렇다면 기획자가 해야 할 일은 단순히 재미있는 패턴을 만드는 것이 아니라, 플레이어의 학습 속도에 맞춰 새로운 패턴을 지속적으로 열어주는 구조를 설계하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 내가 앞으로 게임을 기획할 때 반드시 고려해야 할 관점이다. 초반의 차별성으로 유저를 유입시키는 것에서 그치지 않고, 유저가 게임 안에서 얼마나 오래, 얼마나 깊게 학습할 수 있는가를 함께 설계해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 라프 코스터는 게임이 단순한 오락을 넘어 인간의 인지 능력을 훈련시키는 도구가 될 수 있다고 말한다. 독전감에서 내가 '성취감을 통해 일상생활에서도 잘 살 수 있는 동기가 될 수 있다'고 썼는데, 이것이 단순한 감상이 아니라 뇌과학적으로도 근거가 있는 이야기였다는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앞으로의 기준&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 읽고 나서 내 재미의 기준은 더 풍부해졌다. 차별성, 성취감, 유저 편의성이라는 세 축은 여전히 유효하지만, 이제 하나의 질문을 더 추가하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;이 게임은 플레이어에게 얼마나 오랫동안 무언가를 배우게 할 수 있는가?&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저를 처음 끌어들이는 차별성, 한 번 더 플레이하게 만드는 성취감, 충성 유저를 만드는 편의성. 여기에 플레이어의 학습 여정을 얼마나 길고 깊게 설계하는가가 더해질 때, 비로소 오래 사랑받는 게임이 만들어진다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라프 코스터가 이 책에서 전하고 싶었던 것은 결국 이것이 아닐까. 게임은 인간이 세상을 이해하는 방식 중 하나이며, 기획자는 그 이해의 경험을 설계하는 사람이라는 것. 그 무게를 다시 한번 느끼며 이 책을 닫는다.&lt;/p&gt;</description>
      <category>게임 기획/서적</category>
      <category>게임</category>
      <category>게임기획</category>
      <category>독후감</category>
      <category>라프코스터</category>
      <category>라프코스터의 재미이론</category>
      <category>재미</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/37</guid>
      <comments>https://gyunity23.tistory.com/37#entry37comment</comments>
      <pubDate>Wed, 1 Apr 2026 09:32:12 +0900</pubDate>
    </item>
    <item>
      <title>[패치 리뷰]Unity 6.3 주요 패치 내용!!</title>
      <link>https://gyunity23.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 패치된 Unity 6.3 패치 내용 중 제가 느낀 도움이 되거나 중요하다고 생각한 패치내용들을 소개하려고 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 내용은 Unity Documentation과 Youtube Unity Korea를 참고하였습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;상단 툴바 수정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단 툴바의 위치를 조정하거나 숨기기 기능이 가능해졌습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwHzfx/dJMb996m2as/ldf6N7EzTMT0uqeewVhVb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwHzfx/dJMb996m2as/ldf6N7EzTMT0uqeewVhVb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwHzfx/dJMb996m2as/ldf6N7EzTMT0uqeewVhVb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwHzfx%2FdJMb996m2as%2Fldf6N7EzTMT0uqeewVhVb1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;254&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;254&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVgpdj/dJMcagErqnX/GcOfzVwyjtOKbEk98d7M61/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVgpdj/dJMcagErqnX/GcOfzVwyjtOKbEk98d7M61/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVgpdj/dJMcagErqnX/GcOfzVwyjtOKbEk98d7M61/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVgpdj%2FdJMcagErqnX%2FGcOfzVwyjtOKbEk98d7M61%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;230&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;230&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;하이어라키 추가 기능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이어라키에서 Active, Static, Tag 등을 보고 편집할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biRcKE/dJMcaajUzw9/5d3vnhxUqo1ekgDrXSeTFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biRcKE/dJMcaajUzw9/5d3vnhxUqo1ekgDrXSeTFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biRcKE/dJMcaajUzw9/5d3vnhxUqo1ekgDrXSeTFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiRcKE%2FdJMcaajUzw9%2F5d3vnhxUqo1ekgDrXSeTFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;302&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Preferences &amp;rarr; General &amp;rarr; new Hierarchy(Use new Hiererchy window)체크 로 활성화한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dDN7ps/dJMcagLcA38/OM13SKpCTKioFnUNdSK3lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dDN7ps/dJMcagLcA38/OM13SKpCTKioFnUNdSK3lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dDN7ps/dJMcagLcA38/OM13SKpCTKioFnUNdSK3lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdDN7ps%2FdJMcagLcA38%2FOM13SKpCTKioFnUNdSK3lk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2048&quot; height=&quot;1175&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1175&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아트라스 분석기 (Atras Analyzer)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프라이트 아틀라스를 분석하고 문제 소지가 있는 에셋을 레포트해주는 기능이 추가 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;모든 스프라이트 아틀라스 &amp;ndash;&lt;/b&gt;&amp;nbsp;이 보고서는 프로젝트에서 사용 가능한 모든 스프라이트 아틀라스를 보여주고, 각 아틀라스와 연결된 스프라이트 데이터를 포함합니다. 이 데이터는 스프라이트 아틀라스 최적화 방안을 결정하는 데 활용될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프라이트 아틀라스의 압축된 소스 텍스처 -&lt;/b&gt;&amp;nbsp;스프라이트 아틀라스에서 압축된 모든 소스 텍스처를 나열합니다. 소스 텍스처가 압축되면 최종 텍스처가 두 번 압축되어 텍스처 품질이 저하될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아틀라스 페이지 수 &amp;ndash;&lt;/b&gt;&amp;nbsp;각 스프라이트 아틀라스의 페이지 수를 표시합니다. 스프라이트 아틀라스에 2페이지 이상이 있는 경우, 렌더링에 필요한 스프라이트가 서로 다른 페이지에 위치할 수 있으므로 렌더링 배치 수가 예측 불가능해질 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;텍스처 공간 낭비 &amp;ndash;&lt;/b&gt;&amp;nbsp;각 스프라이트 아틀라스에서 낭비되는 텍스처 공간의 양을 표시합니다. 낭비를 최소화하도록 최적화하면 장치 메모리를 효율적으로 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프라이트 개수 &amp;ndash;&lt;/b&gt;&amp;nbsp;각 스프라이트 아틀라스에 있는 스프라이트 수를 나열합니다. 스프라이트 아틀라스를 하나의 스프라이트에만 사용하는 것은 비효율적입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스프라이트 아틀라스에서 텍스처는 서로 다른 보조 텍스처 개수를 포함할 수 있습니다&lt;/b&gt;&amp;nbsp;. 이는 텍스처 공간 낭비로 이어질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m1uwz/dJMcafetNUL/rFE35OORK2MJNhKj1iWgok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m1uwz/dJMcafetNUL/rFE35OORK2MJNhKj1iWgok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m1uwz/dJMcafetNUL/rFE35OORK2MJNhKj1iWgok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm1uwz%2FdJMcafetNUL%2FrFE35OORK2MJNhKj1iWgok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;690&quot; height=&quot;458&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;458&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;*아트라스 분석기 내용은 &lt;a href=&quot;https://discussions.unity.com/t/sprite-atlas-analyzer-in-unity-6-3/1683242&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://discussions.unity.com/t/sprite-atlas-analyzer-in-unity-6-3/1683242&lt;/a&gt;를 참조했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수익화 API 설치 간편화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에서 광고를 도와주는 LevelPlay가 간단하게 인스톨이 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단창 Service &amp;rarr; Ads Mediation(levelPlay) &amp;rarr; Install 에서 바로 수익화 API추가 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;763&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mg7SQ/dJMcacB1wwe/4mDlJSqUGgK8u56K9k69i0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mg7SQ/dJMcacB1wwe/4mDlJSqUGgK8u56K9k69i0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mg7SQ/dJMcacB1wwe/4mDlJSqUGgK8u56K9k69i0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMg7SQ%2FdJMcacB1wwe%2F4mDlJSqUGgK8u56K9k69i0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;763&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;763&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Animator 및 AnimatorControllerPlayable에 새로운 ResetControllerState 메서드가 추가&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Animator의 풀링을 간소화하기 위해 `AnimatorControllerPlayable` 및 `AnimatorControllerState` 클래스 &lt;b&gt;ResetControllerState&lt;/b&gt;에&amp;nbsp;새로운 메서드를 추가했습니다&amp;nbsp;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메서드를 사용하여 애니메이터를 풀에 반환하기 전에 원래 상태로 되돌릴 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770701687046&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

// 플레이 모드에서 스페이스바를 누르면 AnimationController가 기본 상태로 재설정 됩니다.
[RequireComponent(typeof(Animator))]
public class AnimatorControllerPlayableResetExample : MonoBehaviour
{
    // Animator 컴포넌트 와 함께 사용할 AnimatorController 입니다. 
    public RuntimeAnimatorController m_Controller;

    Animator m_Animator;
    AnimatorControllerPlayable m_ControllerPlayable;

    void Start()
    {
        m_Animator = GetComponent&amp;lt;Animator&amp;gt;();

        //  PlayableGraph 와 AnimatorControllerPlayable을 설정하고 그래프를 재생합니다. 
        var graph = PlayableGraph.Create(nameof(AnimatorControllerPlayableResetExample));
        m_ControllerPlayable = AnimatorControllerPlayable.Create(graph, m_Controller);
        var output = AnimationPlayableOutput.Create(graph, nameof(AnimatorControllerPlayableResetExample), m_Animator);
        output.SetSourcePlayable(m_ControllerPlayable);
        graph.Play();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            //AnimatorController 를 기본 상태로  재설정합니다        
            m_ControllerPlayable.ResetControllerState();
        }
    }

    void OnDestroy()
    {
        // GameObject가 파괴될 때 PlayableGraph 를 파괴합니다 . 
        m_ControllerPlayable.GetGraph().Destroy();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Render 3D as 2D&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MeshRenderer가 SpriteRenderer로 전환 가능하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2D Universal Render Pipeline(URP)을 사용할 때, 2D 렌더러가 동일한 씬 안에서 2D 스프라이트와 함께 Mesh Renderer 및 Skinned Mesh Renderer를 렌더링하는 기능을 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;호환되는 셰이더를 사용하면, 이 렌더러들은 다음과 같은 작업이 가능해집니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;5&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2D Lights(2D 조명)의 영향을 받을 수 있습니다.&lt;/li&gt;
&lt;li&gt;'2D &amp;gt; Mask Interaction' 속성을 활성화하여 Sprite Masks(스프라이트 마스크)와 상호작용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Sorting Group에서 'Sort 3D As 2D' 옵션을 켜면 스프라이트들과 함께 레이어 정렬이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용은 꼭 아래 링크를 타고 들어가서 확인하면 좋을 거 같아요!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/B7IwpkK4iwg?si=8QZQFjRPw_LhNx_E&quot;&gt;https://youtu.be/B7IwpkK4iwg?si=8QZQFjRPw_LhNx_E&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=B7IwpkK4iwg&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/beOtqP/dJMb8RRNLiC/yuJwazkb6HDNfZQUiqaHHk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/bARlAz/dJMb83SeFGN/lhMbxkScjEkdrqIdOICkA0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;유니티 6.3 LTS 에디터는 어떻게 변했을까?&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/B7IwpkK4iwg&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;느낀점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 패치 내용을 보고 느낀점이 2D에 관한 편안한 패치가 많이 나온 것을 느꼈고 3D는 언리얼이라는 라이벌이 있으니 유니티가 2D 쪽에 힘을 쓰려는 것도 느껴졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 시중에 API를 받아서 사용하던 것들을 기능으로 넣음으로서 유니티 개발자의 편의성을 높히는게 좋았던 것 같습니다.&lt;/p&gt;</description>
      <category>유니티</category>
      <category>unity</category>
      <category>Unity 6.3</category>
      <category>유니티</category>
      <category>패치</category>
      <category>패치노트</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/36</guid>
      <comments>https://gyunity23.tistory.com/36#entry36comment</comments>
      <pubDate>Tue, 10 Feb 2026 14:42:15 +0900</pubDate>
    </item>
    <item>
      <title>[독전감] 라프 코스터의 재미이론 : 재미의 기준을 정하자</title>
      <link>https://gyunity23.tistory.com/35</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlFHUO/dJMcaaxqTBU/KRAQNSfDmOzFzqkwlVwXNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlFHUO/dJMcaaxqTBU/KRAQNSfDmOzFzqkwlVwXNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlFHUO/dJMcaaxqTBU/KRAQNSfDmOzFzqkwlVwXNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlFHUO%2FdJMcaaxqTBU%2FKRAQNSfDmOzFzqkwlVwXNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;427&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;Why? : 왜 이 책을 선택했는가?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;게임을 기획하다 보면 항상 이런 질문을 던집니다. &amp;rdquo;이게 &lt;b&gt;재미&lt;/b&gt;있을까?&amp;ldquo;&lt;br /&gt;재미라는 단어는 게임과 뗄수없는 단어이면서 제일 어려운 단어입니다.&lt;br /&gt;게임은 재미있어야 사람들이 플레이를 하는데 재미란 지극히 주관적인 단어라 모든 사람에게 맞출 수 없기 때문입니다.&lt;br /&gt;&lt;br /&gt;그래서 저는 저만의 재미의 기준을 만들었었습니다. 그리고 그 기준에 따라 게임을 기획하였습니다. 하지만 그러면서도 &amp;ldquo;이게 대중들이 원하는 재미가 맞을까?&amp;rdquo;란 질문은 사라지지 않았고 그 기준이 맞는지 확인을 할 필요가 있다고 생각했습니다.&lt;br /&gt;&lt;br /&gt;그러던 중 게임 기획의 바이블이라는 &lt;b&gt;라프 코스터의 재미이론&lt;/b&gt;이라는 책을 알게 되었고 이 책을 읽고 재미의 기준을 확인해보며 리마인드 해보고 싶어졌습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;내가 생각하는 '재미'란?&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 제가 생각하는 재미의 기준은 &lt;b&gt;차별성, 성취감, 유저 편의성&lt;/b&gt; 이 3가지의 요건이 충족 되었을때 나온다고 생각합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;차별성 : 스팀에서 2025년에만 약 2만개의 게임이 출시되었습니다. 이 수많은 게임 중 유저들의 눈에 들고 유저가 처음으로 플레이를 할 수 있게 하는 가장 큰 요소가 다른 게임들과의 차별성이라고 생각합니다.&lt;/li&gt;
&lt;li&gt;성취감 : 게임은 사람이 살면서 가장 쉽고 빠르게 성취감을 얻는 수단이라고 생각합니다. 직접 플레이를 하며 상대방을 이기거나 어떠한 스테이지를 깰 때 나오는 성취감이 유저가 재미있다는 원초적인 느낌을 받는 가장 큰 이유이며 한 번더 플레이를 하게 하는 이유라고 생각합니다. 이 성취감을 통해 게임을 넘어 일상생활에서도 잘 살 수 있는 큰 동기가 될 수도 있는 중요한 요소입니다.&lt;/li&gt;
&lt;li&gt;유저 편의성 : 제가 생각하는 유저편의성이란 유저가 게임에서 최대한 불편함을 느낄 수 없게(UX)만드는 것과 유저와 게임사와의 활발한 커뮤니케이션과 그에대한 피드백을 모두 포함한 것 입니다. 아무리 차별성으로 유저가 유입되고 좋은 성취감으로 재미를 느껴도, 유저가 지속적인 접속을 하며 충성고객으로 변하기 위해서는 유저편의성이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 유저 유입을 위한 차별성, 한 번더 플레이를 하게 하는 성취감, 유저를 충성고객으로 만들기 위한 유저편의성 이 세가지가 잘 이루어져야 게임에서의 좋은 재미가 나온다고 생각을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;첫인상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 재미를 나타내는 책이다보니 글로만 이루어진 것이 아니라 한 페이지의 글과 한 페이지의 삽화를 반복하며 이어진 것을 볼 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 읽을 때 이 형식이 나에게 어떤 재미를 줄 지 기대가 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 목차를 보니 라프 코스터가 지금까지 살면서 느낀 자신의 재미를 경험에 빗대어 쓴 책이라고 느꼈으며 과연 라프 코스터의 재미는 어떤 것인지 궁금해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 독서를 통해 나만의 재미, 라프 코스터의 재미, 대중들의 재미가 어떤지 보고싶고 앞으로 게임을 기획할 때 내가 재미를 어떠한 기준으로 어떠한 방향으로 나아갈지 정하는 의미있는 독서였으면 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책을 읽은 후 독후감으로 다시 찾아 뵙겠습니다. 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>게임 기획/서적</category>
      <category>게임</category>
      <category>게임 개발</category>
      <category>게임 기획</category>
      <category>독전감</category>
      <category>라프 코스터</category>
      <category>라프코스터의 재미이론</category>
      <author>규니티</author>
      <guid isPermaLink="true">https://gyunity23.tistory.com/35</guid>
      <comments>https://gyunity23.tistory.com/35#entry35comment</comments>
      <pubDate>Sun, 8 Feb 2026 12:46:44 +0900</pubDate>
    </item>
  </channel>
</rss>