<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>김치킨 님의 블로그</title>
    <link>https://mauveman.tistory.com/</link>
    <description>유니티 프로그래머 김치킨 입니다.</description>
    <language>ko</language>
    <pubDate>Tue, 19 May 2026 01:40:12 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>김치킨.</managingEditor>
    <image>
      <title>김치킨 님의 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/8002402/attach/461d914de60f4b90bc012caeb0eb1d29</url>
      <link>https://mauveman.tistory.com</link>
    </image>
    <item>
      <title>[Unity/C#] 리플렉션은 왜 느리고, 왜 IL2CPP에서 위험한가</title>
      <link>https://mauveman.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;유니티 프로젝트에서 &lt;b&gt;이름(문자열)으로 메서드/필드/프로퍼티를 찾거나 호출&lt;/b&gt;하는 순간, 대부분 &amp;ldquo;리플렉션(Reflection) 계열&amp;rdquo; 문제(성능/안정성/스트리핑)를 만나게 됩니다.&lt;br /&gt;이 글은 &lt;b&gt;Unity + IL2CPP(AOT)&lt;/b&gt; 환경을 기준으로, &amp;ldquo;언제 써도 되는지/언제 피해야 하는지/피할 수 없으면 어떻게 안전장치를 거는지&amp;rdquo;를 정리합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0) 한 줄 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;link.xml&lt;/code&gt; / &lt;code&gt;[Preserve]&lt;/code&gt;는 &lt;b&gt;코드가 빌드에서 삭제(스트리핑)되는 문제&lt;/b&gt;를 막는 장치입니다.&lt;/li&gt;
&lt;li&gt;하지만 &lt;b&gt;리플렉션 호출이 느린 것(탐색/메타데이터 접근 비용)&lt;/b&gt; 자체를 빠르게 만들진 못합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;런타임(특히 매 프레임)&lt;/b&gt; 에서 리플렉션을 쓰면 성능이 무너질 수 있고, &lt;b&gt;IL2CPP(AOT)&lt;/b&gt; 에서는 &amp;ldquo;에디터에서는 OK, 기기에서는 실패&amp;rdquo; 패턴이 쉽게 나옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 유니티에서 흔히 등장하는 &amp;ldquo;리플렉션/문자열 호출&amp;rdquo; 패턴들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 유니티 개발에서 많이 보이는 &lt;b&gt;대표적인 문자열 기반 호출&lt;/b&gt;입니다. (직접 Reflection API를 쓰지 않더라도 결과적으로 비슷한 위험을 가집니다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-1. SendMessage / BroadcastMessage&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;gameObject.SendMessage(&quot;OnDamage&quot;, 10f, SendMessageOptions.DontRequireReceiver);
gameObject.BroadcastMessage(&quot;OnReset&quot;);&lt;/code&gt;&lt;/pre&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;합니다.&lt;/li&gt;
&lt;li&gt;컴파일 타임 체크가 없어서 오타가 런타임에야 터집니다.&lt;/li&gt;
&lt;li&gt;성능상 비추천(특히 빈번 호출).&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-2. Invoke / InvokeRepeating&lt;/h3&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;Invoke(&quot;SpawnEnemy&quot;, 2f);
InvokeRepeating(&quot;Tick&quot;, 1f, 0.2f);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부적으로 문자열 기반으로 메서드를 찾습니다.&lt;/li&gt;
&lt;li&gt;IL2CPP/스트리핑 상황에서 &amp;ldquo;찾을 메서드가 없어짐&amp;rdquo; 류 문제가 날 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-3. StartCoroutine(&quot;MethodName&quot;) / StopCoroutine(&quot;MethodName&quot;)&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;StartCoroutine(&quot;MyRoutine&quot;);
StopCoroutine(&quot;MyRoutine&quot;);&lt;/code&gt;&lt;/pre&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;보다 안전성과 유지보수성이 떨어집니다.&lt;/li&gt;
&lt;li&gt;가능하면 아래처럼 바꾸는 게 정석입니다:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StartCoroutine(MyRoutine());&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1-4. GetComponent(&quot;TypeName&quot;)&lt;/h3&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;var rb = GetComponent(&quot;Rigidbody&quot;); // 문자열로 타입 탐색&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;GetComponent&amp;lt;Rigidbody&amp;gt;()&lt;/code&gt;가 가능하면 항상 그쪽이 낫습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) &amp;ldquo;정통 리플렉션&amp;rdquo; API: GetField/GetProperty/Invoke&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에서 직접 &lt;code&gt;System.Reflection&lt;/code&gt;을 써야 하는 경우도 있습니다(툴, 디버깅, 플러그인). 가장 흔한 형태는 아래입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-1. 필드(Field) 접근&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System.Reflection;

var flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
FieldInfo fi = target.GetType().GetField(&quot;hp&quot;, flags);
int hp = (int)fi.GetValue(target);
fi.SetValue(target, hp + 10);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-2. 프로퍼티(Property) 접근&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System.Reflection;

var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
PropertyInfo pi = target.GetType().GetProperty(&quot;Speed&quot;, flags);
float speed = (float)pi.GetValue(target);
pi.SetValue(target, speed * 1.1f);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2-3. 메서드(Method) 호출&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System.Reflection;

var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
MethodInfo mi = target.GetType().GetMethod(&quot;Recalculate&quot;, flags);
mi.Invoke(target, null);&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 왜 IL2CPP(AOT)에서 문제가 더 자주 터지나?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-1. 코드 스트리핑(Managed Stripping / Linker)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IL2CPP 빌드(특히 iOS)는 용량/로딩/보안을 위해 &lt;b&gt;사용되지 않는 관리 코드&lt;/b&gt;를 제거합니다.&lt;br /&gt;문제는 &amp;ldquo;문자열로만 호출되는 코드&amp;rdquo;는 정적 분석에서 &lt;b&gt;사용된다고 확신할 수 없어서&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;에디터/Mono에서는 잘 되는데&lt;/li&gt;
&lt;li&gt;기기(IL2CPP)에서만 &lt;code&gt;MissingMethodException&lt;/code&gt;, 호출 실패, 동작 누락 같은 현상이 나옵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3-2. 제네릭(generic)과 AOT 제약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IL2CPP는 &lt;b&gt;런타임에 새 제네릭 인스턴스를 JIT로 만들 수 없습니다.&lt;/b&gt;&lt;br /&gt;리플렉션으로 예측 불가능한 제네릭 조합을 만들거나 호출하려 하면, 특정 케이스에서 런타임 실패를 유발할 수 있습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4) link.xml / [Preserve]는 &amp;ldquo;성능 최적화&amp;rdquo;가 아니라 &amp;ldquo;생존 장치&amp;rdquo;다&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. link.xml이 하는 일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;link.xml&lt;/code&gt;은 빌드 단계에서 &lt;b&gt;링커에게 &amp;ldquo;이 타입/메서드는 삭제하지 말라&amp;rdquo;&lt;/b&gt;고 지시합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시(&lt;code&gt;Assets/link.xml&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;linker&amp;gt;
  &amp;lt;assembly fullname=&quot;Assembly-CSharp&quot;&amp;gt;
    &amp;lt;type fullname=&quot;MyNamespace.MyBehaviour&quot; preserve=&quot;all&quot;/&amp;gt;
  &amp;lt;/assembly&amp;gt;
&amp;lt;/linker&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;✅ 해결: 스트리핑으로 인해 &amp;ldquo;메서드를 못 찾는&amp;rdquo; 문제&lt;/li&gt;
&lt;li&gt;❌ 미해결: 리플렉션 호출이 느린 문제(탐색/메타데이터 접근 비용은 그대로)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. [Preserve]가 하는 일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에 직접 보존 표시를 할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using UnityEngine.Scripting;

public class MyBehaviour : MonoBehaviour
{
    [Preserve]
    private void SpawnEnemy()
    {
        // Invoke(&quot;SpawnEnemy&quot;) 같은 문자열 호출을 쓴다면 보존 표시가 도움이 됩니다.
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5) 성능 관점: 리플렉션을 &amp;ldquo;안 느리게&amp;rdquo; 쓰는 방법(캐싱/델리게이트)&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;code&gt;GetMethod&lt;/code&gt;, &lt;code&gt;GetProperty&lt;/code&gt; 등)&lt;/li&gt;
&lt;li&gt;접근 제한/메타데이터 확인&lt;/li&gt;
&lt;li&gt;박싱/언박싱&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Invoke&lt;/code&gt;의 간접 호출 오버헤드&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-1. 멤버 탐색 결과를 캐싱하기&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using System.Reflection;

public static class ReflectionCache
{
    private static readonly Dictionary&amp;lt;(Type, string), MethodInfo&amp;gt; _methodCache = new();

    public static MethodInfo GetMethodCached(Type t, string name, BindingFlags flags)
    {
        var key = (t, name);
        if (_methodCache.TryGetValue(key, out var mi)) return mi;

        mi = t.GetMethod(name, flags);
        _methodCache[key] = mi;
        return mi;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;매번 찾는 비용&amp;rdquo;을 한 번으로 줄입니다.&lt;/li&gt;
&lt;li&gt;그래도 &lt;code&gt;Invoke&lt;/code&gt; 자체는 느립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5-2. Delegate로 변환(가능하면 가장 효과적)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플렉션으로 한 번 찾고, 이후부터는 델리게이트로 호출하면 오버헤드가 크게 줄어듭니다.&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using System;
using System.Reflection;

public static class DelegateFactory
{
    public static Action CreateAction(object target, string methodName)
    {
        var flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        MethodInfo mi = target.GetType().GetMethod(methodName, flags);
        return (Action)Delegate.CreateDelegate(typeof(Action), target, mi);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;private Action _tick;

void Awake()
{
    _tick = DelegateFactory.CreateAction(this, &quot;Tick&quot;);
}

void Update()
{
    _tick?.Invoke(); // 리플렉션 Invoke보다 훨씬 유리
}

private void Tick()
{
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의: IL2CPP/AOT 환경에서는 &lt;code&gt;CreateDelegate&lt;/code&gt; 조합이나 시그니처가 복잡해질수록 보존/제네릭 문제가 얽힐 수 있습니다.&lt;br /&gt;&amp;ldquo;정말 매 프레임 호출해야 한다&amp;rdquo;면 구조적으로 리플렉션을 제거하는 쪽이 최선입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6) 실무 규칙(추천)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6-1. 절대(에 가까운) 금지 구역&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;Update()&lt;/code&gt; / &lt;code&gt;LateUpdate()&lt;/code&gt; / &lt;code&gt;FixedUpdate()&lt;/code&gt;에서의 리플렉션/문자열 호출&lt;/li&gt;
&lt;li&gt;대량 반복(수백~수천 회) 루프 안에서의 &lt;code&gt;GetMethod/GetProperty/Invoke&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SendMessage/BroadcastMessage&lt;/code&gt; 남발&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6-2. 조건부 허용 구역(&amp;ldquo;가끔&amp;rdquo;이면 가능)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI 버튼 클릭, 메뉴 열기, 설정 적용 같은 &lt;b&gt;낮은 빈도 이벤트&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;디버그/치트/툴용 코드(릴리스에서 제외하거나 빈도 제한)&lt;/li&gt;
&lt;li&gt;에디터 전용 도구(런타임이 아니라면 비교적 안전)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6-3. 모바일(IL2CPP)에서 안전장치 체크리스트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열/리플렉션으로만 호출되는 메서드/타입이 있나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;있다면 &lt;code&gt;link.xml&lt;/code&gt; 또는 &lt;code&gt;[Preserve]&lt;/code&gt; 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Managed Stripping Level이 Medium/High인가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 기기에서 반드시 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;제네릭/런타임 타입 생성이 있나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IL2CPP 제약을 고려해 설계 변경 검토&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7) 결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;link.xml&lt;/code&gt; / &lt;code&gt;[Preserve]&lt;/code&gt;는 &amp;ldquo;코드가 사라지는 문제&amp;rdquo;를 막는 &lt;b&gt;안전망&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;성능을 지키려면 &lt;b&gt;리플렉션 자체를 빈도 높은 경로에서 제거&lt;/b&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;ldquo;탐색/Invoke 비용&amp;rdquo;을 낮추는 전략이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&amp;ldquo;에디터 OK / 모바일만 Fail&amp;rdquo;은 대부분 &lt;b&gt;스트리핑/IL2CPP 제약&lt;/b&gt;부터 의심하는 게 정석입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부록) 빠른 치환 표&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;문제 코드&lt;/th&gt;
&lt;th&gt;추천 대안&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;StartCoroutine(&quot;MyRoutine&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StartCoroutine(MyRoutine())&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Invoke(&quot;Spawn&quot;, 1f)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;StartCoroutine(SpawnAfter(1f))&lt;/code&gt; 또는 타이머/스케줄러&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GetComponent(&quot;Rigidbody&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GetComponent&amp;lt;Rigidbody&amp;gt;()&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SendMessage(&quot;OnDamage&quot;)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;인터페이스/이벤트/델리게이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;반복적으로 &lt;code&gt;GetMethod(...).Invoke(...)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1회 탐색 후 캐싱/델리게이트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>Public</category>
      <category>#Unity #CSharp #Reflection #IL2CPP #AOT #CodeStripping #linkxml #Preserve #모바일최적화 #성능최적화</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/34</guid>
      <comments>https://mauveman.tistory.com/34#entry34comment</comments>
      <pubDate>Sat, 31 Jan 2026 16:22:19 +0900</pubDate>
    </item>
    <item>
      <title>[Unity\] On-Screen Stick 으로 모바일 조이스틱 구현하기</title>
      <link>https://mauveman.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모바일 게임을 개발할 때 가장 먼저 부딪히는 난관 중 하나는 바로 &lt;b&gt;이동 컨트롤&lt;/b&gt;입니다. 과거에는 &lt;code&gt;Joystick Pack&lt;/code&gt; 같은 외부 에셋을 다운로드하거나, UI 터치 좌표를 계산하는 복잡한 코드를 직접 짜야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 유니티의 &lt;b&gt;New Input System&lt;/b&gt;이 등장하면서, 이제는 &lt;b&gt;&lt;code&gt;On-Screen Stick&lt;/code&gt;&lt;/b&gt; 컴포넌트 하나로 아주 손쉽게 가상 조이스틱을 구현할 수 있게 되었습니다.&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;h2 data-ke-size=&quot;size26&quot;&gt;1. 사전 준비 (Package 설치)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 프로젝트에 &lt;b&gt;Input System&lt;/b&gt; 패키지가 설치되어 있어야 합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Window &amp;gt; Package Manager&lt;/b&gt;를 엽니다.&lt;/li&gt;
&lt;li&gt;Packages를 &lt;b&gt;Unity Registry&lt;/b&gt;로 변경합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Input System&lt;/b&gt;을 검색하여 Install 합니다.&lt;/li&gt;
&lt;li&gt;(설치 후 에디터가 재시작될 수 있습니다. 경고창이 뜨면 'Yes'를 눌러주세요.)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. UI 구성하기 (비주얼 만들기)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조이스틱은 눈에 보여야 하니 UI 이미지가 필요합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Hierarchy&lt;/b&gt; 창에서 우클릭 &amp;gt; &lt;b&gt;UI &amp;gt; Canvas&lt;/b&gt;를 생성합니다. (Canvas Scaler는 &lt;code&gt;Scale With Screen Size&lt;/code&gt;로 설정하는 것을 추천합니다.)&lt;/li&gt;
&lt;li&gt;Canvas 아래에 &lt;b&gt;Empty Object&lt;/b&gt;를 만들고 이름을 &lt;code&gt;Joystick&lt;/code&gt;이라고 짓습니다. (위치 잡기 용도)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Joystick&lt;/code&gt; 아래에 &lt;b&gt;Image&lt;/b&gt;를 만들고 &lt;code&gt;Background&lt;/code&gt;라고 합니다. (조이스틱 배경 원)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Background&lt;/code&gt; 아래에 &lt;b&gt;Image&lt;/b&gt;를 만들고 &lt;code&gt;Handle&lt;/code&gt;이라고 합니다. (움직이는 손잡이)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Tip:&lt;/b&gt; &lt;code&gt;Background&lt;/code&gt;는 조금 크게, &lt;code&gt;Handle&lt;/code&gt;은 그보다 작게 사이즈를 조절하고, 앵커(Anchor)를 활용하여 화면 좌측 하단에 적절히 배치하세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. On-Screen Stick 컴포넌트 추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마법을 부릴 차례입니다. 실제로 움직이는 &lt;b&gt;&lt;code&gt;Handle&lt;/code&gt;&lt;/b&gt; 오브젝트에 기능을 부여합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Hierarchy에서 &lt;b&gt;&lt;code&gt;Handle&lt;/code&gt;&lt;/b&gt; 이미지를 선택합니다.&lt;/li&gt;
&lt;li&gt;Inspector에서 &lt;b&gt;Add Component&lt;/b&gt; &amp;gt; &lt;b&gt;&lt;code&gt;On-Screen Stick&lt;/code&gt;&lt;/b&gt;을 검색해 추가합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Movement Range&lt;/b&gt;를 조절합니다. (보통 50~100 사이가 적당합니다. Handle이 배경을 벗어나지 않게 설정하세요.)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Input Action 연결하기 (핵심!)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;On-Screen Stick&lt;/code&gt;의 원리는 &lt;b&gt;&quot;화면의 터치 입력을 마치 게임패드의 스틱 입력인 것처럼 속여서 Input System에 전달하는 것&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Control Path 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Handle&lt;/code&gt; 오브젝트의 &lt;b&gt;On-Screen Stick&lt;/b&gt; 컴포넌트로 돌아가서 &lt;b&gt;Control Path&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;Control Path:&lt;/b&gt; &lt;code&gt;Gamepad&lt;/code&gt; &amp;gt; &lt;code&gt;Left Stick&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) Input Actions 생성 및 연결&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Project 창에서 우클릭 &amp;gt; Create &amp;gt; &lt;b&gt;Input Actions&lt;/b&gt;를 생성합니다. (이름 예시: &lt;code&gt;GameControls&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;생성된 에셋을 더블 클릭하여 편집 창을 엽니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Action&lt;/b&gt;을 하나 만들고 이름을 &lt;code&gt;Move&lt;/code&gt;라고 짓습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Action Type&lt;/b&gt;은 &lt;code&gt;Value&lt;/code&gt;, &lt;b&gt;Control Type&lt;/b&gt;은 &lt;code&gt;Vector 2&lt;/code&gt;로 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Move&lt;/code&gt; 아래의 Binding(No Binding)을 지우고, &lt;code&gt;+&lt;/code&gt;를 눌러 &lt;b&gt;Add Binding&lt;/b&gt;을 선택합니다.&lt;/li&gt;
&lt;li&gt;Binding의 Path를 &lt;b&gt;Gamepad &amp;gt; Left Stick&lt;/b&gt;으로 설정합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;중요:&lt;/b&gt; On-Screen Stick 컴포넌트에서 설정한 Path와 Input Action의 Binding Path가 &lt;b&gt;반드시 일치&lt;/b&gt;해야 작동합니다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Input System이 모바일 화면의 터치 조작을 게임패드의 왼쪽 스틱 입력으로 인식하게 됩니다. 기존에 게임패드 지원이 구현되어 있다면 코드를 수정할 필요 없이 즉시 작동합니다.&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;이제 입력값을 받아 캐릭터를 움직이는 간단한 스크립트를 작성해 봅시다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;using UnityEngine;
using UnityEngine.InputSystem; // 네임스페이스 필수

public class PlayerController : MonoBehaviour
{
    public float moveSpeed = 5f;
    private Vector2 moveInput;
    private Rigidbody2D rb;

    // Input Action 레퍼런스 (인스펙터에서 할당 필요)
    public InputActionReference moveAction;

    void Awake()
    {
        rb = GetComponent&amp;lt;Rigidbody2D&amp;gt;();
    }

    void OnEnable()
    {
        // 액션 활성화
        moveAction.action.Enable();
    }

    void OnDisable()
    {
        moveAction.action.Disable();
    }

    void Update()
    {
        // 입력값 읽어오기
        moveInput = moveAction.action.ReadValue&amp;lt;Vector2&amp;gt;();
    }

    void FixedUpdate()
    {
        // 물리 이동 처리
        rb.velocity = moveInput * moveSpeed;
    }
}&lt;/code&gt;&lt;/pre&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;인스펙터의 Move Action 빈칸에, 아까 만든 Input Action Asset(GameControls) 내부의 Move 액션을 드래그해서 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임을 실행하고 마우스(또는 터치)로 UI 핸들을 움직여 보세요!&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며: 왜 On-Screen Stick인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 터치 좌표를 계산하는 방식(&lt;code&gt;IDragHandler&lt;/code&gt; 등)도 좋지만, &lt;b&gt;On-Screen Stick&lt;/b&gt;을 사용하면 다음과 같은 장점이 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;통합 관리:&lt;/b&gt; PC(키보드/패드)와 모바일(터치) 입력을 &lt;b&gt;Input System&lt;/b&gt; 하나로 통합하여 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;간편함:&lt;/b&gt; 복잡한 UI 좌표 계산 로직을 작성할 필요가 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유연함:&lt;/b&gt; 나중에 물리 컨트롤러(Xbox 패드 등)를 연결해도 별도의 코드 수정 없이 그대로 호환됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 복잡한 구현은 엔진에 맡기고, 여러분은 더 재미있는 콘텐츠를 만드는 데 집중하세요!  ️`&lt;br /&gt;```&lt;/p&gt;</description>
      <category>Public</category>
      <category>#Unity #유니티 #GameDev #게임개발 #InputSystem #NewInputSystem #OnScreenStick #모바일게임 #VirtualJoystick #UnityTips</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/33</guid>
      <comments>https://mauveman.tistory.com/33#entry33comment</comments>
      <pubDate>Sat, 20 Dec 2025 19:53:24 +0900</pubDate>
    </item>
    <item>
      <title>[Unity] while 문 그만! Coroutine에서 '우아하게' 기다리기 : WaitUntil, WaitWhile</title>
      <link>https://mauveman.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;게임 로직을 짜다 보면 &lt;b&gt;&quot;특정 조건이 만족될 때까지 대기&quot;&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;플레이어의 HP가 0이 될 때까지 대기&lt;/li&gt;
&lt;li&gt;스페이스 바를 누를 때까지 대기&lt;/li&gt;
&lt;li&gt;적들이 모두 사라질 때까지 대기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 우리는 Coroutine(코루틴)을 사용해 이 문제를 해결하는데요. 여러분은 이 코드를 어떻게 작성하고 계신가요? 오늘은 고전적인 방식과 유니티가 제공하는 조금 더 '우아한' 방식을 비교해 보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 고전적인 방식: &lt;code&gt;while&lt;/code&gt; 루프와 &lt;code&gt;yield return null&lt;/code&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 흔하게 사용되는 패턴입니다. &lt;code&gt;while&lt;/code&gt; 문으로 조건을 검사하고, 조건이 만족되지 않으면 &lt;code&gt;yield return null&lt;/code&gt;을 통해 1 프레임을 쉬는 방식이죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 플레이어의 HP가 0 이하가 될 때까지 기다리는 코드는 보통 이렇게 작성합니다.&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;IEnumerator WaitForDeath_Classic()
{
    Debug.Log(&quot;전투 시작! 죽을 때까지 기다립니다...&quot;);

    // HP가 0보다 큰 동안 계속 루프를 돕니다.
    while (playerHP &amp;gt; 0)
    {
        // 1 프레임 대기 후 다시 조건을 검사합니다.
        yield return null; 
    }

    Debug.Log(&quot;플레이어 사망! 게임 오버 처리를 시작합니다.&quot;);
}&lt;/code&gt;&lt;/pre&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;가독성:&lt;/b&gt; &lt;code&gt;while&lt;/code&gt; 문 안에 &lt;code&gt;yield return null&lt;/code&gt;이 들어가는 구조가 매번 반복되어 코드가 길어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의도 파악:&lt;/b&gt; 코드를 읽을 때 &quot;이 루프가 도대체 무엇을 기다리는 것인가?&quot;를 파악하기 위해 루프 내부 로직을 해석해야 할 때가 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 우아한 방식: &lt;code&gt;WaitUntil&lt;/code&gt;과 &lt;code&gt;WaitWhile&lt;/code&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티는 이러한 패턴을 더 직관적으로 작성할 수 있도록 &lt;code&gt;WaitUntil&lt;/code&gt;과 &lt;code&gt;WaitWhile&lt;/code&gt;이라는 클래스를 제공합니다. 이들은 &lt;b&gt;델리게이트(Delegate)&lt;/b&gt; 를 인자로 받아 조건을 판단합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) &lt;code&gt;WaitUntil&lt;/code&gt;: 조건이 True가 될 때까지 대기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말 그대로 &lt;b&gt;&quot;~할 때까지(Until)&quot;&lt;/b&gt; 기다리는 것입니다. 괄호 안의 조건이 &lt;code&gt;true&lt;/code&gt;가 되면 대기를 멈추고 다음 줄로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 &lt;code&gt;while&lt;/code&gt; 문 코드를 리팩토링해 볼까요?&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;IEnumerator WaitForDeath_Elegant()
{
    Debug.Log(&quot;전투 시작! 죽을 때까지 기다립니다...&quot;);

    // 플레이어의 HP가 0 이하가 될 때(True)까지 대기합니다.
    yield return new WaitUntil(() =&amp;gt; playerHP &amp;lt;= 0);

    Debug.Log(&quot;플레이어 사망! 게임 오버 처리를 시작합니다.&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 한 줄로 줄어들었습니다!&lt;br /&gt;&lt;code&gt;() =&amp;gt; playerHP &amp;lt;= 0&lt;/code&gt; 은 람다식(Lambda Expression)으로, &quot;HP가 0 이하인가?&quot;라는 조건을 실시간으로 체크합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) &lt;code&gt;WaitWhile&lt;/code&gt;: 조건이 True인 동안 대기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건 반대입니다. &lt;b&gt;&quot;~하는 동안(While)&quot;&lt;/b&gt; 계속 기다리는 것입니다. 즉, 괄호 안의 조건이 &lt;code&gt;false&lt;/code&gt;가 되어야 탈출합니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;IEnumerator WaitForInput_Elegant()
{
    Debug.Log(&quot;키 입력을 기다리는 중...&quot;);

    // 스페이스 바를 누르지 않고 있는 동안(True) 계속 대기합니다.
    // 즉, 스페이스 바를 누르면(False) 대기가 끝납니다.
    yield return new WaitWhile(() =&amp;gt; !Input.GetKeyDown(KeyCode.Space));

    Debug.Log(&quot;스페이스 바 입력 확인!&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 비교 및 요약&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;특징&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;고전 방식 (&lt;code&gt;while&lt;/code&gt;)&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;우아한 방식 (&lt;code&gt;WaitUntil&lt;/code&gt; / &lt;code&gt;WaitWhile&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;구문&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;while(조건) { yield return null; }&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;yield return new WaitUntil(() =&amp;gt; 조건);&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;가독성&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;루프 구조로 인해 다소 김&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;영어 문장처럼 읽혀 직관적임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;유연성&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;루프 내부에 추가 로직 넣기 쉬움&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;오직 '대기' 목적에 특화됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;성능&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;가비지 생성 없음&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;new&lt;/code&gt; 키워드로 인한 미세한 가비지 생성 (무시할 수준)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;언제 무엇을 써야 할까?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순히 &lt;b&gt;&quot;특정 상태가 변하길 기다리는 것&quot;&lt;/b&gt;이 목적이라면 &lt;b&gt;&lt;code&gt;WaitUntil&lt;/code&gt; / &lt;code&gt;WaitWhile&lt;/code&gt;&lt;/b&gt;을 쓰세요. 코드가 훨씬 깔끔해지고 &quot;나는 지금 기다리는 중이다&quot;라는 의도가 명확해집니다.&lt;/li&gt;
&lt;li&gt;기다리는 동안 &lt;b&gt;매 프레임 다른 연산(예: 타이머 카운트 다운, 로그 출력 등)을 같이 해야 한다면&lt;/b&gt; 기존의 &lt;b&gt;&lt;code&gt;while&lt;/code&gt;&lt;/b&gt; 문을 쓰는 것이 낫습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 컴퓨터가 읽는 글이기도 하지만, 동료(혹은 미래의 나)가 읽는 글이기도 합니다.&lt;br /&gt;똑같은 기능을 하더라도 &lt;code&gt;yield return new WaitUntil(() =&amp;gt; isFinished);&lt;/code&gt;라고 적혀 있다면, 누구나 직관적으로 &quot;아, 끝날 때까지 기다리는구나!&quot;라고 이해할 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 작성 중인 코루틴에 불필요한 &lt;code&gt;while&lt;/code&gt; 루프가 있다면, 오늘 한 번 리팩토링해 보는 건 어떨까요?&lt;/p&gt;</description>
      <category>Public</category>
      <category>#Unity #Unity3D #GameDev #CSharp #Coroutine #WaitUntil #WaitWhile #Refactoring #유니티 #게임개발 #코루틴 #프로그래밍 #개발자</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/32</guid>
      <comments>https://mauveman.tistory.com/32#entry32comment</comments>
      <pubDate>Sat, 20 Dec 2025 00:36:27 +0900</pubDate>
    </item>
    <item>
      <title>[Unity] Prefab Variant vs Nested Prefab</title>
      <link>https://mauveman.tistory.com/31</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 한눈에 보는 차이점: 포함 vs 상속&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많이 하는 실수가 이 둘의 용도를 혼동하는 것입니다. 아래 표로 확실히 정리해 드립니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;핵심 개념&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;포함 (Composition)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;상속 (Inheritance)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;관계&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&quot;A &lt;b&gt;Has a&lt;/b&gt; B&quot; (A는 B를 가지고 있다)&lt;/td&gt;
&lt;td&gt;&quot;B &lt;b&gt;Is a&lt;/b&gt; A&quot; (B는 A의 일종이다)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자동차는 바퀴를 가지고 있다.&lt;/td&gt;
&lt;td&gt;스포츠카는 자동차의 일종이다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;아이콘&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;일반 파란색 큐브&lt;/td&gt;
&lt;td&gt;화살표가 있는 큐브 아이콘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;주 사용처&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;씬 구성, 복합 오브젝트 조립 (건물, UI 패널)&lt;/td&gt;
&lt;td&gt;데이터 변형이 필요한 개체 (적 캐릭터, 아이템)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 실전 전략: 두 기능을 섞어 쓰기 (The Killer Combo) ⚔️&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고수들은 이 두 가지를 따로 쓰지 않고 &lt;b&gt;함께 사용&lt;/b&gt;합니다. RPG 게임의 아이템 시스템을 예로 들어보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계 1: Nested Prefab으로 '부품' 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 게임에 쓰일 시각적 이펙트나 공통 UI를 만듭니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;SparkleEffect&lt;/code&gt; (프리팹)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ItemLabelUI&lt;/code&gt; (프리팹)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계 2: Base Prefab 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 아이템의 조상이 될 기본 프리팹을 만듭니다. 여기에 단계 1의 프리팹을 &lt;b&gt;Nested&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;code&gt;Item_Base&lt;/code&gt; (프리팹)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델링 (Placeholder)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SparkleEffect&lt;/code&gt; (Nested Prefab)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ItemLabelUI&lt;/code&gt; (Nested Prefab)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계 3: Prefab Variant로 '파생' 상품 만들기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Item_Base&lt;/code&gt;를 우클릭하여 &lt;b&gt;Create &amp;gt; Prefab Variant&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;code&gt;Sword_Fire&lt;/code&gt; (Variant)&lt;/b&gt;: &lt;code&gt;Item_Base&lt;/code&gt; 상속 + 빨간색 모델링 교체 + 공격력 100&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;Shield_Ice&lt;/code&gt; (Variant)&lt;/b&gt;: &lt;code&gt;Item_Base&lt;/code&gt; 상속 + 파란색 모델링 교체 + 방어력 50&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  결과적으로 얻는 이득&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나중에 기획자가 *&quot;모든 아이템 먹을 때 반짝이는 효과(&lt;code&gt;SparkleEffect&lt;/code&gt;)를 좀 더 화려하게 바꿔주세요!&quot;* 라고 요청한다면?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;SparkleEffect&lt;/code&gt; 프리팹 하나만 수정합니다.&lt;/li&gt;
&lt;li&gt;이것이 포함된 &lt;code&gt;Item_Base&lt;/code&gt;에 반영됩니다. (Nested 덕분)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Item_Base&lt;/code&gt;를 상속받은 &lt;code&gt;Sword_Fire&lt;/code&gt;, &lt;code&gt;Shield_Ice&lt;/code&gt; 등 수백 개의 아이템이 &lt;b&gt;동시에 수정&lt;/b&gt;됩니다. (Variant 덕분)&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 주의할 점 (Best Practices)&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Variant의 Variant는 신중하게:&lt;/b&gt;&lt;br /&gt;&lt;code&gt;Base -&amp;gt; Variant A -&amp;gt; Variant A-1&lt;/code&gt; 처럼 상속이 너무 깊어지면, 나중에 수정 사항이 어디서 덮어씌워졌는지(Override) 파악하기 힘듭니다. 1~2단계 상속을 권장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이름 규칙 정하기:&lt;/b&gt;&lt;br /&gt;프로젝트 뷰에서 헷갈리지 않도록 Base 프리팹에는 &lt;code&gt;_Base&lt;/code&gt;나 &lt;code&gt;_Master&lt;/code&gt; 같은 접미사를 붙여 원본임을 명시하는 것이 좋습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Nested Prefab&lt;/b&gt;은 &quot;조립&quot;을 위해,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Prefab Variant&lt;/b&gt;는 &quot;변형&quot;을 위해 사용하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 가지 무기를 적재적소에 활용한다면, 수정 사항이 발생했을 때 야근하지 않고 &quot;클릭 한 번&quot;으로 해결하는 쾌거를 맛보실 수 있을 것입니다..&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;태그: #Unity #PrefabVariant #NestedPrefab #유니티강좌 #객체지향&lt;/i&gt;&lt;/p&gt;</description>
      <category>Public</category>
      <category>#Unity #유니티 #PrefabVariant #프리팹변형 #NestedPrefab #객체지향 #게임아키텍처 #리팩토링</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/31</guid>
      <comments>https://mauveman.tistory.com/31#entry31comment</comments>
      <pubDate>Thu, 18 Dec 2025 21:46:21 +0900</pubDate>
    </item>
    <item>
      <title>[Unity] 프리팹 안의 프리팹 : Nested Prefab (중첩 프리팹)</title>
      <link>https://mauveman.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 유니티로 게임을 개발하다 보면 &lt;b&gt;'프리팹(Prefab)'&lt;/b&gt;은 떼려야 뗄 수 없는 존재입니다. 하지만 혹시, 프리팹 안에 다른 프리팹을 넣었다가 수정 사항이 반영되지 않아 고생했던 기억, 없으신가요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거(Unity 2018.3 이전)에는 프리팹 안에 다른 프리팹을 넣으면 그 연결 고리가 끊어져 버리는 악몽 같은 문제가 있었습니다. 하지만 &lt;b&gt;Nested Prefab(중첩 프리팹)&lt;/b&gt; 시스템이 도입되면서 개발 환경은 완전히 달라졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 유니티 개발의 생산성을 200% 올려주는 &lt;b&gt;Nested Prefab&lt;/b&gt;에 대해 아주 쉽게 정리해 드립니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Nested Prefab이란 무엇인가요?&lt;/h2&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;과거에는 '집(House)' 프리팹 안에 '의자(Chair)' 프리팹을 넣는 순간, '의자'는 더 이상 독립적인 프리팹이 아니게 되었습니다. 단순히 복사된 데이터 덩어리가 되어버렸죠. 그래서 원본 '의자' 프리팹을 수정해도 '집' 안에 있는 의자는 바뀌지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;Nested Prefab&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;&lt;/li&gt;
&lt;li&gt;원본 '의자' 디자인을 바꾸면, '집' 안에 있는 의자도 자동으로 바뀝니다.&lt;/li&gt;
&lt;/ul&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;Nested Prefab을 잘 활용하면 게임 구조가 레고 블록처럼 &lt;b&gt;모듈화(Modular)&lt;/b&gt;됩니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 모듈형 작업 가능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거대한 맵이나 UI를 하나의 통짜 프리팹으로 만들지 않아도 됩니다.&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;UI:&lt;/b&gt; 버튼 프리팹 -&amp;gt; 팝업창 프리팹 -&amp;gt; HUD 프리팹&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경:&lt;/b&gt; 나무 프리팹 -&amp;gt; 숲 프리팹 -&amp;gt; 전체 맵 씬&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 작은 부품(프리팹)만 수정하면, 이를 포함하는 모든 상위 프리팹에 자동으로 변경 사항이 전파됩니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;아티스트 A:&lt;/b&gt; '무기' 모델링과 이펙트를 수정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기획자 B:&lt;/b&gt; 해당 무기를 들고 있는 '캐릭터'의 스탯을 조정합니다.&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. 핵심 기능: Overrides (오버라이드)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nested Prefab의 꽃은 바로 &lt;b&gt;Overrides&lt;/b&gt; 기능입니다.&lt;br /&gt;상위 프리팹(Parent) 안에서 하위 프리팹(Child)의 일부 속성만 '살짝' 바꾸고 싶을 때 사용합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시:&lt;/b&gt;&lt;br /&gt;기본 '병사' 프리팹이 있습니다. 이 병사를 '부대' 프리팹 안에 10명 배치했습니다. 대장 병사 한 명만 빨간색 투구를 씌우고 싶다면?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;'부대' 프리팹 편집 모드로 들어갑니다.&lt;/li&gt;
&lt;li&gt;대장 병사의 투구 색을 빨간색으로 변경합니다.&lt;/li&gt;
&lt;li&gt;이 변경 사항은 &lt;b&gt;이 특정 병사에게만 적용(Override)&lt;/b&gt;되며, 원본 '병사' 프리팹은 그대로 유지됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인스펙터 창에서 변수 이름이 &lt;b&gt;굵은 글씨(Bold)&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;Revert:&lt;/b&gt; 오버라이드한 내용을 취소하고 원본 프리팹 값으로 되돌립니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Apply:&lt;/b&gt; 변경한 내용을 원본 프리팹에 덮어씌웁니다. (주의: 이 경우 모든 병사의 투구가 빨간색이 됩니다!)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 실무 활용 팁 &amp;amp; 주의사항  &lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) Prefab Mode(프리팹 모드)를 적극 활용하세요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이어라키(Hierarchy)에서 프리팹 옆의 작은 화살표(&lt;code&gt;&amp;gt;&lt;/code&gt;)를 누르면 해당 프리팹만 독립적으로 편집할 수 있는 &lt;b&gt;Prefab Mode&lt;/b&gt;로 진입합니다. 씬이 복잡할 때 이 모드에서 작업하면 실수로 다른 오브젝트를 건드리는 일을 방지할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 너무 깊은 중첩(Deep Nesting)은 피하세요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;나사 &amp;lt; 바퀴 &amp;lt; 자동차 &amp;lt; 차고 &amp;lt; 마을 &amp;lt; 도시&lt;/code&gt; 처럼 너무 깊게 중첩하면, 나중에 수정 사항이 어디서 오버라이드 되었는지 추적하기 힘들어질 수 있습니다. 3~4단계 정도의 깊이를 유지하는 것이 정신건강에 좋습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) Prefab Variant(프리팹 변형)와 함께 쓰세요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nested Prefab과 짝꿍인 기능이 &lt;b&gt;Prefab Variant&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;Nested:&lt;/b&gt; 포함 관계 (A 안에 B가 있다)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Variant:&lt;/b&gt; 상속 관계 (A를 기반으로 만든 A')&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 '몬스터' 프리팹을 만들고, 이를 상속받아 '불 몬스터', '얼음 몬스터' Variant를 만드세요. 그리고 이들을 '던전' 프리팹 안에 Nested 시키면 완벽한 구조가 됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Nested Prefab은 유니티가 제공하는 가장 강력한 워크플로우 개선 기능 중 하나입니다. 이 기능을 제대로 이해하고 구조를 짠다면, 나중에 게임의 규모가 커져도 유지 보수가 훨씬 쉬워질 것입니다.&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;i&gt;태그: #Unity #유니티 #게임개발 #NestedPrefab #프리팹 #개발꿀팁&lt;/i&gt;&lt;/p&gt;</description>
      <category>Public</category>
      <category>#Unity #유니티 #게임개발 #NestedPrefab #중첩프리팹 #프리팹 #UnityTips #인디게임개발</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/30</guid>
      <comments>https://mauveman.tistory.com/30#entry30comment</comments>
      <pubDate>Thu, 18 Dec 2025 21:44:52 +0900</pubDate>
    </item>
    <item>
      <title>[Unity] 프리팹 간에 상속이 가능하다고? Prefab Variant 활용법</title>
      <link>https://mauveman.tistory.com/29</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;객체 지향 프로그래밍(OOP)에서 &lt;b&gt;클래스 상속(Inheritance)&lt;/b&gt;은 코드의 재사용성을 높이는 핵심 개념입니다. 유니티의 &lt;b&gt;Prefab Variant(프리팹 바리언트)&lt;/b&gt;를 사용하면, 게임 오브젝트(Prefab) 단계에서도 이 상속의 이점을 그대로 누릴 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순 복제(&lt;code&gt;Ctrl+D&lt;/code&gt;)로 인해 발생하는 유지보수의 문제를 해결하고, 효율적으로 프리팹을 계층화하는 방법을 소개합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 프리팹의 상속 개념 (Duplicate vs Variant)&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;단순 복제 (Duplicate, &lt;code&gt;Ctrl+D&lt;/code&gt;)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본과 완전히 독립된 개체가 됩니다.&lt;/li&gt;
&lt;li&gt;원본이 수정되어도 복제본에는 아무런 영향을 주지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비유:&lt;/b&gt; 코드를 복사해서 새 파일(&lt;code&gt;ClassB.cs&lt;/code&gt;)을 만드는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프리팹 바리언트 (Prefab Variant)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본(Base)을 부모로 하는 자식 프리팹을 생성합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;부모가 수정되면 자식에게도 변경 사항이 자동 반영됩니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비유:&lt;/b&gt; &lt;code&gt;class Child : Parent&lt;/code&gt; 처럼 상속받는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 구현 시나리오: 몬스터 시스템&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 기본형 몬스터(&lt;code&gt;Zombie_Base&lt;/code&gt;)를 만들고, 이를 상속받아 화염 속성(&lt;code&gt;Zombie_Red&lt;/code&gt;)과 빙결 속성(&lt;code&gt;Zombie_Blue&lt;/code&gt;) 몬스터를 구현한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 방식 (Duplicate)의 문제점&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;Zombie_Base&lt;/code&gt;를 복사해서 &lt;code&gt;Zombie_Red&lt;/code&gt;를 만듦.&lt;/li&gt;
&lt;li&gt;개발 후반부에 좀비의 기본 이동 속도가 너무 느려 &lt;code&gt;Zombie_Base&lt;/code&gt;를 수정함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; &lt;code&gt;Zombie_Red&lt;/code&gt;는 업데이트되지 않음. 수십 종류의 파생 좀비들을 일일이 찾아가서 똑같은 값을 수정해야 함.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상속 방식 (Variant)의 해결책&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트 창에서 &lt;code&gt;Zombie_Base&lt;/code&gt; 우클릭 -&amp;gt; &lt;b&gt;Create &amp;gt; Prefab Variant&lt;/b&gt; 선택.&lt;/li&gt;
&lt;li&gt;생성된 &lt;code&gt;Zombie_Red&lt;/code&gt;는 &lt;code&gt;Base&lt;/code&gt;의 모든 컴포넌트와 설정을 상속받음.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Zombie_Red&lt;/code&gt;에만 화염 공격 스크립트를 추가하거나 외형을 변경.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; 추후 &lt;code&gt;Zombie_Base&lt;/code&gt;의 이동 속도를 수정하면, &lt;b&gt;모든 파생 좀비(Variant)들의 속도가 자동으로 일괄 변경됨.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 설계 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프리팹 상속을 활용하면 다음과 같은 구조로 데이터를 관리할 수 있습니다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;계층&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;역할&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;포함 요소 (컴포넌트)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Parent (Base)&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;공통 로직 정의&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;&lt;code&gt;MonsterAI&lt;/code&gt;, &lt;code&gt;Animator&lt;/code&gt;, &lt;code&gt;Collider&lt;/code&gt;, &lt;code&gt;Rigidbody&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Child (Variant)&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;개별 기능 확장&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;부모 요소 + &lt;b&gt;&lt;code&gt;FireAttack&lt;/code&gt;, &lt;code&gt;IceEffect&lt;/code&gt; (추가 컴포넌트)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;&lt;b&gt;Instance&lt;/b&gt;&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;개별 수치 조정&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;변수 값 변경 (HP, Speed 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 핵심 기능: 오버라이드(Override)와 리버트(Revert)&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;Override (재정의):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Variant에서 특정 변수(예: &lt;code&gt;Max HP&lt;/code&gt;) 값을 변경하면, 해당 속성은 부모와의 연결이 끊깁니다.&lt;/li&gt;
&lt;li&gt;인스펙터에서 &lt;b&gt;굵은 글씨(Bold)&lt;/b&gt;나 파란색 띠로 표시됩니다.&lt;/li&gt;
&lt;li&gt;이후 부모의 HP가 변경되어도, 이 Variant는 재정의된 독자적인 값을 유지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Revert (되돌리기):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재정의했던 값을 다시 부모의 설정과 동기화하고 싶을 때 사용합니다.&lt;/li&gt;
&lt;li&gt;변경된 변수 우클릭 -&amp;gt; &lt;b&gt;&lt;code&gt;Revert&lt;/code&gt;&lt;/b&gt; 선택.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;팀 프로젝트 진행 시, 프리팹 아이콘과 인스펙터만 확인하면 상속 관계를 파악할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 아이콘 확인&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;226&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi8kOR/dJMcabCIL2b/MBW0K7tj9yP03NwqHJaZRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi8kOR/dJMcabCIL2b/MBW0K7tj9yP03NwqHJaZRk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi8kOR/dJMcabCIL2b/MBW0K7tj9yP03NwqHJaZRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi8kOR%2FdJMcabCIL2b%2FMBW0K7tj9yP03NwqHJaZRk%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;401&quot; height=&quot;226&quot; data-origin-width=&quot;401&quot; data-origin-height=&quot;226&quot;/&gt;&lt;/span&gt;&lt;/figure&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;  일반 프리팹 (Parent):&lt;/b&gt; 파란색 육면체.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;  프리팹 바리언트 (Child):&lt;/b&gt; 아이콘 한쪽 면이 회색.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 인스펙터 확인&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Variant를 선택하면 인스펙터 최상단 헤더에 &lt;b&gt;&lt;code&gt;Prefab Variant&lt;/code&gt;&lt;/b&gt;라고 명시되어 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Open&lt;/b&gt; 버튼 옆 계층 구조를 통해 부모가 누구인지 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) 부모 수정 시 주의사항 (Find References)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반 프리팹처럼 보여도 다른 프리팹의 부모일 수 있습니다.&lt;/li&gt;
&lt;li&gt;수정 전 &lt;b&gt;우클릭 -&amp;gt; &lt;code&gt;Find References In Project&lt;/code&gt;&lt;/b&gt;를 실행하여, 이를 상속받고 있는 다른 프리팹이 있는지 확인하는 습관이 중요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;반복되는 구조는 상속하라:&lt;/b&gt; 유사한 오브젝트는 복사(Duplicate)하지 말고 &lt;b&gt;Variant&lt;/b&gt;를 사용하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층적 설계:&lt;/b&gt; 공통 기능은 부모 프리팹에, 특수 기능은 자식 프리팹에 구현하세요.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유연한 변경:&lt;/b&gt; 부모를 수정하여 전체를 제어하고, 자식에서 &lt;code&gt;Override&lt;/code&gt;하여 개성을 부여하세요.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Public</category>
      <category>#Unity #유니티 #프리팹바리언트 #PrefabVariant #게임개발 #유니티팁 #객체지향 #생산성 #협업</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/29</guid>
      <comments>https://mauveman.tistory.com/29#entry29comment</comments>
      <pubDate>Thu, 11 Dec 2025 10:54:00 +0900</pubDate>
    </item>
    <item>
      <title>Unity에서 `MonoBehaviour`를 `new`로 만들면 무슨 일이 일어날까?</title>
      <link>https://mauveman.tistory.com/28</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Unity 개발 중 &lt;code&gt;MonoBehaviour&lt;/code&gt;를 만들 때, 보통은 &lt;code&gt;AddComponent&amp;lt;T&amp;gt;()&lt;/code&gt;를 사용합니다.&lt;br /&gt;그런데 혹시 &lt;code&gt;new&lt;/code&gt; 키워드로 &lt;code&gt;MonoBehaviour&lt;/code&gt;를 직접 생성하면 어떤 일이 일어날까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 &lt;code&gt;MonoBehaviour&lt;/code&gt;를 &lt;b&gt;&lt;code&gt;AddComponent&lt;/code&gt;로 만든 경우와 &lt;code&gt;new&lt;/code&gt;로 만든 경우의 차이점&lt;/b&gt;을&lt;br /&gt;동작, 구조, 런타임 관점에서 자세히 살펴보겠습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  1. &lt;code&gt;MonoBehaviour&lt;/code&gt;는 일반 C# 클래스가 아니다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;MonoBehaviour&lt;/code&gt;는 단순한 C# 객체가 아니라,&lt;br /&gt;&lt;b&gt;Unity 엔진(C++) 쪽 객체와 C# 객체가 연결된 하이브리드 구조&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;C# 객체&lt;/b&gt;: 우리가 코드에서 접근하고 조작하는 부분&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Unity 네이티브 객체&lt;/b&gt;: Unity 엔진 내부에서 실제로 관리되는 GameObject 컴포넌트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;code&gt;new&lt;/code&gt; 키워드로 생성하면 &lt;b&gt;엔진 쪽 네이티브 객체가 전혀 생성되지 않습니다.&lt;/b&gt;&lt;br /&gt;이게 핵심입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  2. 잘못된 생성 방식 &amp;mdash; &lt;code&gt;new&lt;/code&gt;&lt;/h2&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;TempObj obj = new TempObj(); // ❌ 잘못된 방식&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 생성하면 C# 입장에서는 객체가 생기지만,&lt;br /&gt;Unity는 그 존재를 전혀 모릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 객체는 &lt;b&gt;C# 힙에만 존재하는 유령 객체&lt;/b&gt;이며,&lt;br /&gt;Unity 씬 어디에도 연결되지 않습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 3. 올바른 생성 방식 &amp;mdash; &lt;code&gt;AddComponent&lt;/code&gt;&lt;/h2&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;TempObj obj = gameObject.AddComponent&amp;lt;TempObj&amp;gt;(); // ✅ 올바른 방식&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AddComponent&lt;/code&gt;를 사용하면 Unity가 다음 과정을 자동으로 수행합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;C# 객체를 생성&lt;/li&gt;
&lt;li&gt;Unity 네이티브 객체를 생성&lt;/li&gt;
&lt;li&gt;둘을 연결하여 관리 시작&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Awake()&lt;/code&gt;, &lt;code&gt;Start()&lt;/code&gt;, &lt;code&gt;Update()&lt;/code&gt; 등의 생명주기 함수를 호출&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이 방법만이 &lt;code&gt;MonoBehaviour&lt;/code&gt;를 &lt;b&gt;Unity 엔진이 인식할 수 있는 상태로 등록하는 방법&lt;/b&gt;입니다.&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;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;&lt;code&gt;AddComponent&lt;/code&gt;로 생성&lt;/th&gt;
&lt;th&gt;&lt;code&gt;new&lt;/code&gt;로 생성&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GameObject 연결&lt;/td&gt;
&lt;td&gt;✅ 연결됨&lt;/td&gt;
&lt;td&gt;❌ 연결 안 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unity 네이티브 객체&lt;/td&gt;
&lt;td&gt;✅ 존재함&lt;/td&gt;
&lt;td&gt;❌ 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gameObject&lt;/code&gt;, &lt;code&gt;transform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ 유효&lt;/td&gt;
&lt;td&gt;❌ null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Awake()&lt;/code&gt;, &lt;code&gt;Start()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ 정상 호출&lt;/td&gt;
&lt;td&gt;❌ 호출 안 됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hierarchy 창 표시&lt;/td&gt;
&lt;td&gt;✅ 표시됨&lt;/td&gt;
&lt;td&gt;❌ 안 보임&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Inspector 표시&lt;/td&gt;
&lt;td&gt;✅ 가능&lt;/td&gt;
&lt;td&gt;❌ 불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Destroy()&lt;/code&gt; 작동&lt;/td&gt;
&lt;td&gt;✅ 작동함&lt;/td&gt;
&lt;td&gt;❌ 무효&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 관리&lt;/td&gt;
&lt;td&gt;Unity + GC&lt;/td&gt;
&lt;td&gt;C# GC만 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실질적 유효성&lt;/td&gt;
&lt;td&gt;엔진 관리 중인 정상 객체&lt;/td&gt;
&lt;td&gt;엔진에 등록되지 않은 유령 객체&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  5. 예시: &lt;code&gt;new&lt;/code&gt;로 만든 객체의 동작&lt;/h2&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;TempObj obj = new TempObj();

if (obj != null)
{
    Debug.Log(&quot;obj는 null이 아닙니다.&quot;); // ✅ 출력됨
    Debug.Log(obj.transform.position);   // ❌ NullReferenceException 발생
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;obj != null&lt;/code&gt; 은 참입니다.&lt;br /&gt;(C# 상에서 분명히 객체가 존재하기 때문입니다.)&lt;/li&gt;
&lt;li&gt;하지만 &lt;code&gt;obj.transform&lt;/code&gt; 은 Unity 네이티브 객체가 없기 때문에 &lt;b&gt;null&lt;/b&gt;이며,&lt;br /&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  6. &lt;code&gt;AddComponent&lt;/code&gt;로 생성된 객체인지 구분하는 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity 엔진에 실제로 등록된 컴포넌트인지 확인하려면&lt;br /&gt;아래와 같이 검사하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;if (obj != null &amp;amp;&amp;amp; obj.gameObject != null)
{
    Debug.Log(&quot;✅ Unity에 등록된 실제 컴포넌트입니다.&quot;);
}
else
{
    Debug.Log(&quot;❌ new로 만들어진 유령 객체입니다.&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;gameObject&lt;/code&gt; 프로퍼티는 &lt;b&gt;Unity 네이티브 객체가 연결될 때 자동으로 채워지는 필드&lt;/b&gt;이므로,&lt;br /&gt;이 값이 &lt;code&gt;null&lt;/code&gt;이라면 &lt;code&gt;AddComponent&lt;/code&gt;로 생성된 객체가 아닙니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  7. 요약&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;new&lt;/code&gt; 로 생성&lt;/td&gt;
&lt;td&gt;C# 객체만 존재. Unity는 모름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AddComponent&lt;/code&gt; 로 생성&lt;/td&gt;
&lt;td&gt;Unity 네이티브 객체와 연결되어 엔진이 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gameObject&lt;/code&gt;, &lt;code&gt;transform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;AddComponent 객체에서만 유효&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Destroy()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Unity 관리 객체에서만 작동&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;판별 코드&lt;/td&gt;
&lt;td&gt;&lt;code&gt;if (obj != null &amp;amp;&amp;amp; obj.gameObject != null)&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  결론&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;MonoBehaviour&lt;/code&gt;는 단순한 C# 클래스가 아니라&lt;br /&gt;Unity 엔진이 관리하는 &lt;b&gt;컴포넌트&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;new&lt;/code&gt;로 생성하면 Unity 세계에서는 존재하지 않는 &lt;b&gt;유령 객체&lt;/b&gt;가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ 따라서 &lt;code&gt;MonoBehaviour&lt;/code&gt;를 만들 때는 반드시 &lt;code&gt;AddComponent&amp;lt;T&amp;gt;()&lt;/code&gt;를 사용해야 합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 순수 데이터나 로직만 다루는 클래스라면&lt;br /&gt;&lt;code&gt;MonoBehaviour&lt;/code&gt; 대신 그냥 일반 클래스나 &lt;code&gt;ScriptableObject&lt;/code&gt;를 사용하는 것이 적절합니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class TempData // MonoBehaviour 제거
{
    public int hp;
}

// 이렇게 안전하게 생성 가능
TempData data = new TempData();&lt;/code&gt;&lt;/pre&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;MonoBehaviour&lt;/code&gt;는 Unity 엔진이 관리하는 컴포넌트다.&lt;br /&gt;&lt;code&gt;new&lt;/code&gt;로 만들면 유령 객체가 되고, &lt;code&gt;AddComponent()&lt;/code&gt;로만 정상적으로 작동한다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>Public</category>
      <category>#Unity #CSharp #MonoBehaviour #AddComponent #Unity개발 #유니티팁 #UnityScript #게임개발 #Unity엔진 #Unity프로그래밍</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/28</guid>
      <comments>https://mauveman.tistory.com/28#entry28comment</comments>
      <pubDate>Tue, 21 Oct 2025 17:11:53 +0900</pubDate>
    </item>
    <item>
      <title>Unity Vector2.Reflect 함수 완벽 가이드</title>
      <link>https://mauveman.tistory.com/27</link>
      <description>&lt;p&gt;
게임 속 물리 계산이나 충돌 처리에서 중요한 개념 중 하나가 바로 &lt;b&gt;&quot;반사&quot;&lt;/b&gt;입니다.  
예를 들어, 공이 벽에 튕겨 나가거나, 레이캐스트가 표면에 맞고 반사되는 경우를 구현할 때 유용합니다.  
Unity에서는 이런 상황을 쉽게 처리할 수 있도록 &lt;code&gt;Vector2.Reflect&lt;/code&gt; 함수를 제공합니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;1. 기본 정의&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
public static Vector2 Reflect(Vector2 inDirection, Vector2 inNormal);
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;inDirection&lt;/b&gt; : 진행 중인 벡터(예: 공의 이동 방향)&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;inNormal&lt;/b&gt; : 맞닿은 표면의 법선 벡터(normal)&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;반환값&lt;/b&gt; : 반사된 새로운 방향 벡터&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
즉, 특정 표면에 어떤 방향 벡터가 부딪혔을 때, 그 표면을 기준으로 &lt;b&gt;대칭된 방향&lt;/b&gt;을 계산해서 돌려줍니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;2. 방향 벡터와 법선 벡터란?&lt;/h2&gt;

&lt;p&gt;
&lt;code&gt;Vector2.Reflect&lt;/code&gt;를 이해하려면 두 가지 개념을 알아야 합니다.
&lt;/p&gt;

&lt;h3&gt;(1) 방향 벡터 (Direction Vector)&lt;/h3&gt;
&lt;p&gt;
객체가 &lt;b&gt;어느 쪽으로 움직이고 있는지&lt;/b&gt;를 나타내는 벡터입니다.  
예를 들어, 공이 오른쪽 아래로 굴러가고 있다면 방향 벡터는 &lt;code&gt;(1, -1)&lt;/code&gt;이 됩니다.
&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
Vector2 dir = new Vector2(1, -1); // 오른쪽 아래 방향
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;(2) 법선 벡터 (Normal Vector)&lt;/h3&gt;
&lt;p&gt;
&lt;b&gt;표면에 수직으로 뻗은 벡터&lt;/b&gt;를 의미합니다.  
- 바닥(수평면)의 법선은 &lt;code&gt;(0, 1)&lt;/code&gt; (위쪽)  
- 오른쪽 벽(수직면)의 법선은 &lt;code&gt;(-1, 0)&lt;/code&gt; (왼쪽)  
&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
Vector2 normal = Vector2.up; // 바닥 표면의 법선
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
즉, &lt;b&gt;방향 벡터&lt;/b&gt;는 “움직이는 방향”, &lt;b&gt;법선 벡터&lt;/b&gt;는 “표면이 바라보는 방향”이라고 이해하면 쉽습니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;3. 수학적 원리&lt;/h2&gt;

&lt;p&gt;
벡터 반사는 수학적으로 이렇게 정의됩니다:
&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;
R = D - 2 * (D · N) * N
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;D&lt;/b&gt; : 진행 방향 벡터 (inDirection)&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;N&lt;/b&gt; : 단위(normalized) 법선 벡터 (inNormal)&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;R&lt;/b&gt; : 반사된 결과 벡터&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;
즉, 법선 방향으로의 성분을 빼서, 반대쪽으로 튕겨낸 것이 반사 벡터입니다.  
Unity의 &lt;code&gt;Vector2.Reflect&lt;/code&gt;는 이 계산을 대신 해줍니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;4. 간단한 예시&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
Vector2 dir = new Vector2(1, -1).normalized; // 오른쪽 아래로 이동
Vector2 normal = Vector2.up;                 // 위쪽(0,1) 법선

Vector2 reflected = Vector2.Reflect(dir, normal);
// 결과: (1, 1) → 오른쪽 위 방향
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;
아래쪽으로 향하던 벡터가 바닥에 맞아, 위쪽으로 튀어 나가는 결과가 계산됩니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;5. 활용 사례&lt;/h2&gt;

&lt;h3&gt;(1) 공 튀기기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
void OnCollisionEnter2D(Collision2D collision) {
    Vector2 inDir = rigidbody2D.velocity;
    Vector2 normal = collision.contacts[0].normal;
    rigidbody2D.velocity = Vector2.Reflect(inDir, normal);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
공이 벽에 부딪힐 때 속도를 반사시켜, 튕기는 효과를 줍니다.
&lt;/p&gt;

&lt;h3&gt;(2) 레이 반사&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
RaycastHit2D hit = Physics2D.Raycast(origin, direction);
if (hit.collider != null) {
    Vector2 reflectedDir = Vector2.Reflect(direction, hit.normal);
    Debug.DrawRay(hit.point, reflectedDir * 5, Color.red);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
레이가 벽에 맞으면 반사된 방향을 계산하고, 그 결과를 디버그로 시각화할 수 있습니다.
&lt;/p&gt;

&lt;h3&gt;(3) 탄알 반사&lt;/h3&gt;
&lt;p&gt;
총알이나 빛줄기가 벽에 맞고 튀는 효과를 구현할 때 간단히 쓸 수 있습니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;6. 주의할 점&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;법선 벡터는 반드시 단위 벡터(normalized)&lt;/b&gt;여야 올바른 결과가 나옵니다.&lt;/li&gt;
  &lt;li&gt;Unity의 충돌 정보 &lt;code&gt;collision.contacts[0].normal&lt;/code&gt;은 이미 단위 벡터라 바로 사용할 수 있습니다.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;Vector2.Reflect&lt;/code&gt;는 방향만 계산하므로, 속도의 크기(스피드)는 별도로 유지하거나 조절해야 합니다.&lt;/li&gt;
&lt;/ul&gt;

&lt;hr/&gt;

&lt;h2&gt;7. 결론&lt;/h2&gt;
&lt;p&gt;
&lt;code&gt;Vector2.Reflect&lt;/code&gt;는 2D 게임에서 벽 튕김, 레이 반사, 시야 연출 등 
다양한 곳에서 활용할 수 있는 간단하면서도 강력한 함수입니다.  
핵심은 &lt;b&gt;방향 벡터 = 움직이는 방향&lt;/b&gt;, &lt;b&gt;법선 벡터 = 표면이 바라보는 방향&lt;/b&gt;이라는 개념을 이해하는 것입니다.  
이 원리만 알면, 반사 효과를 아주 쉽게 구현할 수 있습니다.
&lt;/p&gt;

&lt;hr/&gt;</description>
      <category>Public</category>
      <category>#Unity #유니티 #Vector2 #Reflect #벡터연산 #법선벡터 #방향벡터 #게임개발 #UnityTips #CSharp #프로그래밍</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/27</guid>
      <comments>https://mauveman.tistory.com/27#entry27comment</comments>
      <pubDate>Sun, 24 Aug 2025 02:15:35 +0900</pubDate>
    </item>
    <item>
      <title>Unity Mathf.Lerp 함수 완벽 가이드</title>
      <link>https://mauveman.tistory.com/26</link>
      <description>&lt;p&gt;게임에서 자연스럽게 값이 변하거나 점점 다가가는 느낌을 줄 때 자주 사용하는 함수가 있습니다. 
바로 &lt;code&gt;Mathf.Lerp&lt;/code&gt; 입니다. 예를 들어, 카메라가 부드럽게 이동하거나 UI 값이 서서히 변할 때 
이 함수를 활용하면 간단하게 구현할 수 있습니다.&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;1. 기본 정의&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Mathf.Lerp&lt;/code&gt; 함수는 다음과 같이 정의됩니다:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
public static float Lerp(float a, float b, float t);
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;a&lt;/b&gt; : 시작 값&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;b&lt;/b&gt; : 목표 값&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;t&lt;/b&gt; : 보간 계수(0 ~ 1 사이의 값 권장)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;t&lt;/code&gt;가 0일 때는 &lt;code&gt;a&lt;/code&gt;, 1일 때는 &lt;code&gt;b&lt;/code&gt;를 반환합니다. 
0과 1 사이 값에서는 a와 b 사이의 중간 값을 계산해 줍니다.&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;2. 동작 예시&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
// 0과 10 사이에서 절반 지점
float value = Mathf.Lerp(0f, 10f, 0.5f); 
// 결과: 5f
&lt;/code&gt;&lt;/pre&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
// 속도 값 보간
float speed = Mathf.Lerp(5f, 20f, 0.25f); 
// 결과: 8.75f
&lt;/code&gt;&lt;/pre&gt;

&lt;hr/&gt;

&lt;h2&gt;보간 계수 t의 의미와 변화&lt;/h2&gt;

&lt;p&gt;
&lt;code&gt;Mathf.Lerp(a, b, t)&lt;/code&gt;에서 &lt;b&gt;t&lt;/b&gt;는 두 값 사이에서 몇 퍼센트나 이동했는지를 나타냅니다.
&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;b&gt;t = 0&lt;/b&gt; → 결과는 시작값 &lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;t = 1&lt;/b&gt; → 결과는 목표값 &lt;code&gt;b&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;b&gt;0 &amp;lt; t &amp;lt; 1&lt;/b&gt; → a와 b 사이의 중간 값&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;예: a=0, b=10일 때&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;&lt;th&gt;t 값&lt;/th&gt;&lt;th&gt;결과&lt;/th&gt;&lt;th&gt;설명&lt;/th&gt;&lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;&lt;td&gt;0.0&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;시작점 그대로&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;0.25&lt;/td&gt;&lt;td&gt;2.5&lt;/td&gt;&lt;td&gt;25% 이동&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;0.5&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;절반 지점&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;0.75&lt;/td&gt;&lt;td&gt;7.5&lt;/td&gt;&lt;td&gt;75% 이동&lt;/td&gt;&lt;/tr&gt;
    &lt;tr&gt;&lt;td&gt;1.0&lt;/td&gt;&lt;td&gt;10&lt;/td&gt;&lt;td&gt;목표값&lt;/td&gt;&lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;
즉, t가 조금씩 커질수록 결과 값도 일정한 속도로 바뀌게 됩니다.  
그래서 UI, 카메라, 색상 변화 등에서 부드럽고 예측 가능한 전환을 구현할 수 있습니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;3. 왜 부드럽게 변화할까?&lt;/h2&gt;

&lt;p&gt;
사실 &lt;code&gt;Mathf.Lerp&lt;/code&gt; 자체는 단순히 &lt;b&gt;“t에 따라 하나의 값”&lt;/b&gt;을 계산해 주는 함수일 뿐입니다.  
그런데 게임에서는 이 함수를 &lt;b&gt;매 프레임마다&lt;/b&gt; 계속 호출하면서 t를 바꿔주기 때문에,
값이 조금씩 이어지면서 변화하고, 그 결과 화면에는 부드럽게 움직이는 것처럼 보입니다.
&lt;/p&gt;

&lt;h3&gt;(1) 일정 시간 동안 점점 이동&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
float elapsed = 0f;
float duration = 2f;
float start = 0f, end = 10f;

void Update() {
    elapsed += Time.deltaTime;
    float t = Mathf.Clamp01(elapsed / duration);
    float value = Mathf.Lerp(start, end, t);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
- 처음에는 0에서 시작해, 2초 동안 점점 커지다가 끝에서 10에 도달합니다.  
- 이렇게 시간에 따라 t를 계산하면, 값이 서서히 바뀌어 보이게 됩니다.
&lt;/p&gt;

&lt;h3&gt;(2) 목표 값에 천천히 다가가기&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
current = Mathf.Lerp(current, target, Time.deltaTime * speed);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;
- 여기서는 매 프레임마다 현재 값이 목표 값 쪽으로 조금씩 이동합니다.  
- 처음에는 빠르게 움직이다가 목표에 가까워질수록 속도가 줄어들면서 “서서히 멈추는 듯한” 느낌을 줍니다.  
- 이 패턴 덕분에 체력바, UI 슬라이더, 카메라 이동 등에서 자연스럽게 다가가는 효과를 쉽게 만들 수 있습니다.
&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;4. 주요 활용 사례&lt;/h2&gt;

&lt;h3&gt;(1) 부드러운 이동&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
transform.position = new Vector3(
    Mathf.Lerp(transform.position.x, target.x, 0.1f),
    Mathf.Lerp(transform.position.y, target.y, 0.1f),
    Mathf.Lerp(transform.position.z, target.z, 0.1f)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;- 매 프레임마다 조금씩 움직여 자연스러운 이동을 구현합니다.&lt;/p&gt;

&lt;h3&gt;(2) UI 애니메이션&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
float alpha = Mathf.Lerp(0f, 1f, t);
canvasGroup.alpha = alpha;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;- UI가 점점 투명 → 불투명으로 변하도록 할 수 있습니다.&lt;/p&gt;

&lt;h3&gt;(3) 체력바 게이지 변화&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
hpBar.fillAmount = Mathf.Lerp(hpBar.fillAmount, targetHP, Time.deltaTime * speed);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;- 체력 게이지가 순간적으로 튀지 않고 부드럽게 줄거나 차오릅니다.&lt;/p&gt;

&lt;hr/&gt;

&lt;h2&gt;5. Lerp와 Time.deltaTime&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Lerp&lt;/code&gt;를 사용할 때 자주 쓰이는 방식은 &lt;code&gt;Time.deltaTime&lt;/code&gt;을 곱하는 것입니다. 
이렇게 하면 프레임 속도와 관계없이 비슷한 체감 속도로 움직이는 효과를 줄 수 있습니다.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
currentValue = Mathf.Lerp(currentValue, targetValue, Time.deltaTime * 5f);
&lt;/code&gt;&lt;/pre&gt;

&lt;hr/&gt;

&lt;h2&gt;6. 주의할 점&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;Lerp&lt;/code&gt;는 내부적으로 t 값을 0~1 범위 안에 맞추는 것이 기본입니다.&lt;/li&gt;
  &lt;li&gt;만약 그 범위를 넘어선 값까지 계산하고 싶다면 &lt;code&gt;Mathf.LerpUnclamped&lt;/code&gt;를 사용하세요.&lt;/li&gt;
&lt;/ul&gt;

&lt;pre&gt;&lt;code class=&quot;language-csharp&quot;&gt;
float safe = Mathf.Lerp(a, b, Mathf.Clamp01(t));     // 안전하게 0~1
float over = Mathf.LerpUnclamped(a, b, 1.2f);        // 목표를 넘어선 값도 계산
&lt;/code&gt;&lt;/pre&gt;

&lt;hr/&gt;

&lt;h2&gt;7. 결론&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Mathf.Lerp&lt;/code&gt;는 Unity에서 &lt;b&gt;값을 자연스럽게 변화&lt;/b&gt;시키는 가장 기본적인 도구입니다.  
핵심은 함수 자체가 아니라, &lt;b&gt;시간의 흐름에 맞춰 t를 바꿔주고 반복 호출&lt;/b한다는 점입니다.  
이 원리만 이해하면 카메라 이동, UI 전환, 체력 게이지 등 다양한 상황에서 손쉽게 응용할 수 있습니다.&lt;/p&gt;

&lt;hr/&gt;</description>
      <category>Public</category>
      <category>#Unity #유니티 #Mathf #Lerp #보간 #부드러운이동 #Unity개발 #게임개발 #UnityTips #CSharp #프로그래밍</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/26</guid>
      <comments>https://mauveman.tistory.com/26#entry26comment</comments>
      <pubDate>Sat, 23 Aug 2025 16:54:29 +0900</pubDate>
    </item>
    <item>
      <title>Unity Mathf.Clamp 함수 완벽 가이드</title>
      <link>https://mauveman.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;게임 개발을 하다 보면, 값이 특정 범위를 벗어나지 않도록 제한해야 할 때가 자주 있습니다. 예를 들어 &lt;b&gt;플레이어 체력(HP)&lt;/b&gt; 은 0보다 작아지면 안 되고, 최대 체력을 넘어가서도 안 됩니다. 이럴 때 바로 유용하게 쓰이는 함수가 &lt;code&gt;Mathf.Clamp&lt;/code&gt; 입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 기본 정의&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Mathf.Clamp&lt;/code&gt; 함수는 다음과 같이 정의됩니다:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;
public static float Clamp(float value, float min, float max);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;value&lt;/b&gt; : 제한할 대상 값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;min&lt;/b&gt; : 최소 허용 값&lt;/li&gt;
&lt;li&gt;&lt;b&gt;max&lt;/b&gt; : 최대 허용 값&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, value가 min보다 작으면 min을 반환하고, max보다 크면 max를 반환합니다. 범위 안에 있다면 그대로 반환합니다.&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;
float hp = 120f;
hp = Mathf.Clamp(hp, 0f, 100f); 
// 결과: 100f
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;
float speed = -5f;
speed = Mathf.Clamp(speed, 0f, 10f); 
// 결과: 0f
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;rsl&quot;&gt;&lt;code&gt;
float volume = 0.7f;
volume = Mathf.Clamp(volume, 0f, 1f); 
// 결과: 0.7f (변화 없음)
&lt;/code&gt;&lt;/pre&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;&lt;code&gt;Mathf.Clamp&lt;/code&gt;는 &lt;code&gt;float&lt;/code&gt;뿐만 아니라 &lt;code&gt;int&lt;/code&gt; 버전도 있습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;
public static int Clamp(int value, int min, int max);
&lt;/code&gt;&lt;/pre&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;4. 활용 사례&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) 플레이어 체력 관리&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;
currentHP = Mathf.Clamp(currentHP, 0, maxHP);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;체력이 0 미만으로 내려가도 자동으로 0에 맞춰짐&lt;/li&gt;
&lt;li&gt;회복 아이템을 사용해도 maxHP를 넘지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 이동 속도 제한&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;
moveSpeed = Mathf.Clamp(moveSpeed, minSpeed, maxSpeed);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 캐릭터가 지나치게 느려지거나 빨라지는 현상 방지&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) UI 슬라이더 값 제한&lt;/h3&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;
sliderValue = Mathf.Clamp(sliderValue, 0f, 1f);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 항상 0~1 범위 내 값만 유지 (볼륨, 밝기 등)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Mathf.Clamp01 와의 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity는 자주 쓰이는 &lt;b&gt;0~1 범위&lt;/b&gt; 제한을 위한 별도 함수도 제공합니다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;
float normalized = Mathf.Clamp01(value);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;value가 0 미만 &amp;rarr; 0 반환&lt;/li&gt;
&lt;li&gt;value가 1 초과 &amp;rarr; 1 반환&lt;/li&gt;
&lt;li&gt;0~1 사이 값은 그대로 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 &lt;b&gt;정규화(normalization)&lt;/b&gt; 나 &lt;b&gt;Lerp 보간 값&lt;/b&gt;을 처리할 때 자주 사용됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 주의할 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;min 값이 max보다 크면&lt;/b&gt; 모든 경우에 max를 반환합니다.&lt;/li&gt;
&lt;li&gt;항상 min &amp;le; max 관계가 보장되도록 코드를 짜는 습관이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Mathf.Clamp&lt;/code&gt;는 간단하지만, 게임 개발에서 굉장히 자주 사용되는 &lt;b&gt;안전장치 함수&lt;/b&gt;입니다. 체력, 속도, 카메라 이동, UI 값 등 &lt;b&gt;범위를 제한해야 하는 모든 상황&lt;/b&gt;에 적용할 수 있으니, 습관적으로 활용하는 것이 좋습니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>Public</category>
      <category>#Unity #유니티 #Mathf #Clamp #Unity개발 #게임개발 #UnityTips #CSharp #코딩 #프로그래밍</category>
      <author>김치킨.</author>
      <guid isPermaLink="true">https://mauveman.tistory.com/22</guid>
      <comments>https://mauveman.tistory.com/22#entry22comment</comments>
      <pubDate>Sat, 23 Aug 2025 16:37:21 +0900</pubDate>
    </item>
  </channel>
</rss>