<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>ZoNaDak 의 생각</title>
    <link>https://zonadak.tistory.com/</link>
    <description>돌고 돌아 게임개발자</description>
    <language>ko</language>
    <pubDate>Sat, 23 May 2026 01:53:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ZoNaDak</managingEditor>
    <item>
      <title>EditorWindow 안에 독립적인 SceneView 띄우기</title>
      <link>https://zonadak.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;에디터 툴을 만들다 보면 Prefab View와 같이 메인 SceneView와 별개로 특정 오브젝트만 보여주는 독립적인 씬 뷰가 있다면 좋겠다는 생각이 들 때가 있습니다. 하지만 기존 SceneView를 EditorWindow 안에 직접 임베드하기는 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 UI Toolkit 기반 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;EditorWindow&lt;/span&gt;&lt;/b&gt;에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IMGUIContainer&lt;/b&gt;&lt;/span&gt;와 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;PreviewRenderUtility&lt;/span&gt;&lt;/b&gt;를 조합해 독립적인 SceneView를 구현하고, 오브젝트 스폰과 카메라 조작 이벤트 등록하는 방법을 단계별로 정리해 보겠습니다.&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;1. 구조 설계 및 각 요소 역할&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현에 들어가기 전에 전체 구조를 먼저 잡아보겠습니다. 만들려고 하는 독립 SceneView는 아래와 같은 흐름으로 구성됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;제목 없는 다이어그램.drawio.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;520&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFXPvT/dJMcagkPIEf/Z1LTApNJNWIrUqHERuoxqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFXPvT/dJMcagkPIEf/Z1LTApNJNWIrUqHERuoxqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFXPvT/dJMcagkPIEf/Z1LTApNJNWIrUqHERuoxqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFXPvT%2FdJMcagkPIEf%2FZ1LTApNJNWIrUqHERuoxqk%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;382&quot; height=&quot;414&quot; data-filename=&quot;제목 없는 다이어그램.drawio.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;520&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;EditorWindow&lt;/span&gt;:&lt;/b&gt; UI Toolkit 기반 에디터 창의 진입점입니다. &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;CreateGUI&lt;/span&gt;&lt;/b&gt;에서 UI 구성을 시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;VisualElement&lt;/span&gt;:&lt;/b&gt; UI Toolkit의 기본 레이아웃 단위로, 여기에 자식 요소들을 배치합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;IMGUIContainer&lt;/span&gt;: &lt;span style=&quot;color: #ef5369;&quot;&gt;VisualElement&lt;/span&gt; &lt;/b&gt;트리 안에서 IMGUI 코드를 실행할 수 있게 해주는 브릿지 역할을 합니다. UI Toolkit은 직접적인 씬 렌더링 요소를 제공하지 않기 때문에, IMGUI를 통해 렌더링 결과를 그려주는 이 컨테이너가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;PreviewRenderUtility&lt;/span&gt;:&lt;/b&gt; 메인 씬과 분리된 독립 렌더링 환경을 제공합니다. 이 안에서 카메라, 라이트, 오브젝트를 세팅하고 결과를 텍스처로 뽑아냅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;PreviewRenderUtility&lt;/span&gt;&lt;/b&gt;가 렌더링한 텍스처를, &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;IMGUIContainer&lt;/span&gt;&lt;/b&gt;의 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;onGUIHandler&lt;/span&gt;&lt;/b&gt;에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GUI.DrawTexture&lt;/b&gt;&lt;/span&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;2. 독립 SceneView 구현&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 정리한 구조를 코드로 구현해 보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 EditorWindow 베이스 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 SceneView를 띄울 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;를 만듭니다. 기본 창 크기를 가로 800, 세로 600으로 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;class ToolSceneViewWindow : EditorWindow
{
    [MenuItem(&quot;ToolSceneView/Open ToolSceneViewWindow&quot;)]
    public static void ShowWindow()
    {
        var window = GetWindow&amp;lt;ToolSceneViewWindow&amp;gt;(&quot;Tool Scene View&quot;);
        window.minSize = new Vector2(800, 600);
    }

    private void CreateGUI()
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 &lt;b&gt;ToolSceneView &amp;rarr; Open ToolSceneViewWindow &lt;/b&gt;를&amp;nbsp;클릭하면 빈 EditorWindow가 열립니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-13 오후 10.29.28.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zqpbv/dJMcaf0weJw/Fj3SRrMZ1P7kIn0WubtL0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zqpbv/dJMcaf0weJw/Fj3SRrMZ1P7kIn0WubtL0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zqpbv/dJMcaf0weJw/Fj3SRrMZ1P7kIn0WubtL0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZqpbv%2FdJMcaf0weJw%2FFj3SRrMZ1P7kIn0WubtL0K%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;470&quot; height=&quot;395&quot; data-filename=&quot;스크린샷 2026-04-13 오후 10.29.28.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 CustomSceneView 클래스 생성 및 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;VisualElement&lt;/b&gt;&lt;/span&gt;를 상속받는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt; 클래스를 만들고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;에 연결합니다. 아직 내부 구현은 비어 있지만, 먼저 뼈대를 잡아둡니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class CustomSceneView : VisualElement
{
    public CustomSceneView()
    {
        style.flexGrow = 1;
        RegisterCallback&amp;lt;DetachFromPanelEvent&amp;gt;(OnDetachFromPanel);
    }
    
    private void OnDetachFromPanel(DetachFromPanelEvent evt)
    {
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;의 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CreateGUI&lt;/b&gt;&lt;/span&gt;에서 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;CustomSceneView&lt;/span&gt;&lt;/b&gt; 를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private void CreateGUI()
    {
        var customSceneView = new CustomSceneView();
    	rootVisualElement.Add(customSceneView);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 분리하면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;는 깔끔하게 유지되고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt;는 다른 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;에서도 재사용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 PreviewRenderUtility 세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt; 안에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;를 추가합니다. 독립적인 렌더링 환경을 제공하는 핵심 요소입니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private PreviewRenderUtility m_previewRenderUtility;

    public CustomSceneView()
    {
        style.flexGrow = 1;

        InitPreviewRenderUtility();

        RegisterCallback&amp;lt;DetachFromPanelEvent&amp;gt;(OnDetachFromPanel);
    }

    private void InitPreviewRenderUtility()
    {
        m_previewRenderUtility = new PreviewRenderUtility();

        //Camera
        var camera = m_previewRenderUtility.camera;
        camera.fieldOfView = 30f;
        camera.nearClipPlane = 0.01f;
        camera.farClipPlane = 100f;
        camera.transform.position = new Vector3(0f, 2f, -5f);
        camera.transform.LookAt(Vector3.zero);

        //Light
        m_previewRenderUtility.lights[0].intensity = 1.4f;
        m_previewRenderUtility.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0f);
    }

    private void OnDetachFromPanel(DetachFromPanelEvent evt)
    {
        m_previewRenderUtility?.Cleanup();
        m_previewRenderUtility = null;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;InitPreviewRenderUtility&lt;/b&gt;&lt;/span&gt;에서 카메라의 시야각, 클리핑 범위, 위치를 설정하고 라이트를 세팅합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DetachFromPanelEvent&lt;/b&gt;&lt;/span&gt;에서 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;Cleanup&lt;/span&gt;&lt;/b&gt;을 호출하여, 이 요소가 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;VisualElement&lt;/span&gt;&lt;/b&gt; 트리에서 제거될 때 리소스를 정리합니다. 이 부분을 빠뜨리면 메모리 누수가 발생할 수 있으므로 주의해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.4 IMGUIContainer 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IMGUIContainer&lt;/b&gt;&lt;/span&gt;를 추가하여 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;의 렌더링 결과를 화면에 그립니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private IMGUIContainer m_imguiContainer;

    public CustomSceneView()
    {
        style.flexGrow = 1;

        InitPreviewRenderUtility();
        InitIMGUIContainer();

        RegisterCallback&amp;lt;DetachFromPanelEvent&amp;gt;(OnDetachFromPanel);
    }

    private void InitIMGUIContainer()
    {
        m_imguiContainer = new IMGUIContainer(OnRenderGUI);
        m_imguiContainer.style.flexGrow = 1;
        Add(m_imguiContainer);
    }

    private void OnRenderGUI()
    {
        if (m_previewRenderUtility == null) return;

        var rect = GUILayoutUtility.GetRect(0, 0, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
        if (rect.width &amp;lt;= 0 || rect.height &amp;lt;= 0) return;

        m_previewRenderUtility.BeginPreview(rect, GUIStyle.none);
        m_previewRenderUtility.camera.Render();
        var texture = m_previewRenderUtility.EndPreview();

        GUI.DrawTexture(rect, texture, ScaleMode.StretchToFill, false);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;OnRenderGUI&lt;/b&gt;&lt;/span&gt;의 흐름을 정리하면 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GUILayoutUtility.GetRect&lt;/b&gt;&lt;/span&gt;로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IMGUIContainer&lt;/b&gt;&lt;/span&gt;가 차지하는 영역을 가져옵니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;BeginPreview&lt;/b&gt;&lt;/span&gt;로 해당 영역 크기에 맞는 렌더 타겟을 준비하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;camera.Render&lt;/b&gt;&lt;/span&gt;로 프리뷰 씬을 렌더링합니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EndPreview&lt;/b&gt;&lt;/span&gt;로 렌더링 결과를 텍스처로 받아온 뒤, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GUI.DrawTexture&lt;/b&gt;&lt;/span&gt;로 화면에 그립니다.&lt;/p&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-filename=&quot;스크린샷 2026-04-13 오후 10.55.38.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw1vqk/dJMcacW2k9w/sTYTviIqM3lsWJ8wvBd5I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw1vqk/dJMcacW2k9w/sTYTviIqM3lsWJ8wvBd5I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw1vqk/dJMcacW2k9w/sTYTviIqM3lsWJ8wvBd5I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw1vqk%2FdJMcacW2k9w%2FsTYTviIqM3lsWJ8wvBd5I1%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;469&quot; height=&quot;394&quot; data-filename=&quot;스크린샷 2026-04-13 오후 10.55.38.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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-filename=&quot;스크린샷 2026-04-13 오후 10.56.02.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u4Dxb/dJMcajoglUl/KPBBZOBuKKKpuBR6Xr3au0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u4Dxb/dJMcajoglUl/KPBBZOBuKKKpuBR6Xr3au0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u4Dxb/dJMcajoglUl/KPBBZOBuKKKpuBR6Xr3au0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu4Dxb%2FdJMcajoglUl%2FKPBBZOBuKKKpuBR6Xr3au0%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;470&quot; height=&quot;395&quot; data-filename=&quot;스크린샷 2026-04-13 오후 10.56.02.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&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;h2 data-ke-size=&quot;size26&quot;&gt;3. 오브젝트 스폰으로 그리드 구성 및 단일 큐브 스폰&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 만든 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt;는 아직 빈 화면입니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;로 구성한 독립 씬은 기존 SceneView와 달리 기즈모나 핸들 같은 에디터 기능을 사용할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 먼저 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;GL&lt;/span&gt; 드로잉&lt;/b&gt;으로 기즈모 대용 그리드를 그려 위치 확인을 편하게 한 뒤, 단일 큐브를 스폰해 보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 그리드 렌더링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위치 확인용 그리드를 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;GL&lt;/span&gt; 클래스&lt;/b&gt;로 직접 그려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt;은 Unity에서 GPU에 직접 그리기 명령을 내리는 &lt;b&gt;즉시 모드 렌더링(Immediate Mode Rendering)&lt;/b&gt; 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;씬에 오브젝트로 존재하지 않고, Unity의 기즈모 시스템을 거치지도 않지만, 렌더 버퍼에 선을 직접 그려주기 때문에 기즈모와 유사한 역할을 할 수 있습니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt; 환경에서는 기즈모를 사용할 수 없으므로, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt;을 활용하여 그리드를 그려줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt;에 아래 코드를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private Material m_gridMaterial;

    private void InitGridMaterial()
    {
        m_gridMaterial = new Material(Shader.Find(&quot;Hidden/Internal-Colored&quot;));
        m_gridMaterial.SetInt(&quot;_ZWrite&quot;, 0);
        m_gridMaterial.SetInt(&quot;_Cull&quot;, (int)CullMode.Off);
    }

    private void DrawGrid(int gridSize = 50, float spacing = 1f)
    {
        if (m_gridMaterial == null) return;

        var camera = m_previewRenderUtility.camera;

        GL.PushMatrix();
        GL.LoadProjectionMatrix(camera.projectionMatrix);
        GL.modelview = camera.worldToCameraMatrix;

        m_gridMaterial.SetPass(0);

        GL.Begin(GL.LINES);
        GL.Color(new Color(0.5f, 0.5f, 0.5f, 0.3f));

        float half = gridSize * spacing;

        for (int i = -gridSize; i &amp;lt;= gridSize; i++)
        {
            float pos = i * spacing;

            // X축 방향 선
            GL.Vertex3(-half, 0f, pos);
            GL.Vertex3(half, 0f, pos);

            // Z축 방향 선
            GL.Vertex3(pos, 0f, -half);
            GL.Vertex3(pos, 0f, half);
        }

        GL.End();
        GL.PopMatrix();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;InitGridMaterial&lt;/b&gt;&lt;/span&gt;을 호출하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;OnRenderGUI&lt;/b&gt;&lt;/span&gt;에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;camera.Render&lt;/b&gt;&lt;/span&gt; 직후에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DrawGrid&lt;/b&gt;&lt;/span&gt;를 호출합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    public CustomSceneView()
    {
        style.flexGrow = 1;

        InitPreviewRenderUtility();
        InitGridMaterial();
        InitIMGUIContainer();

        RegisterCallback&amp;lt;DetachFromPanelEvent&amp;gt;(OnDetachFromPanel);
    }

    private void OnRenderGUI()
    {
        if (m_previewRenderUtility == null) return;

        var rect = GUILayoutUtility.GetRect(0, 0, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
        if (rect.width &amp;lt;= 0 || rect.height &amp;lt;= 0) return;

        m_previewRenderUtility.BeginPreview(rect, GUIStyle.none);
        m_previewRenderUtility.camera.Render();
        DrawGrid();
        var texture = m_previewRenderUtility.EndPreview();

        GUI.DrawTexture(rect, texture, ScaleMode.StretchToFill, false);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Hidden/Internal-Colored&lt;/b&gt;&lt;/span&gt; 셰이더는 Unity에 내장된 단색 렌더링용 셰이더로, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt; 라인 드로잉에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;GL.PushMatrix&lt;/span&gt; / &lt;span style=&quot;color: #ef5369;&quot;&gt;GL.PopMatrix&lt;/span&gt;&lt;/b&gt;로 현재 매트릭스 상태를 저장&amp;middot;복원합니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt;로 직접 도형을 그릴 때는 현재 설정된 매트릭스를 기준으로 좌표가 변환되는데, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;camera.Render&lt;/b&gt;&lt;/span&gt; 이후에는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt;의 매트릭스가 프리뷰 카메라의 시점과 일치한다는 보장이 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL.LoadProjectionMatrix&lt;/b&gt;&lt;/span&gt;로 카메라의 투영 매트릭스를, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL.modelview&lt;/b&gt;&lt;/span&gt;로 카메라의 뷰 매트릭스를 직접 설정해야 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt;로 그리는 선이 카메라 시점에 맞게 올바르게 렌더링됩니다. 이 설정이 없으면 그리드가 엉뚱한 위치에 표시되거나 아예 보이지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;GL.Begin&lt;/span&gt;&lt;/b&gt;와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL.End&lt;/b&gt;&amp;nbsp;&lt;/span&gt;사이에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL.Vertex3&lt;/b&gt;&lt;/span&gt;로 격자선의 시작점과 끝점을 지정합니다. X축과 Z축 방향으로 각각 선을 그려 바닥면 격자를 구성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DrawGrid&lt;/b&gt;&lt;/span&gt;는 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;camera.Render&lt;/span&gt;&lt;/b&gt;와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EndPreview&lt;/b&gt;&lt;/span&gt; 사이에 호출해야 합니다. 이 시점에 그려야 프리뷰 렌더 타겟에 함께 포함됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음엔 리소스 정리 코드도 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private void OnDetachFromPanel(DetachFromPanelEvent evt)
    {
        if (m_gridMaterial != null)
            Object.DestroyImmediate(m_gridMaterial);

        m_previewRenderUtility?.Cleanup();
        m_previewRenderUtility = null;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 그리드를 그려두면 아래와 같이 기즈모 없이도 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt;의 공간감과 오브젝트 위치를 한눈에 파악할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-04-14 오전 12.17.32.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daFZ11/dJMcacbGhEh/SunC09qV4kx4IjvGZq3WH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daFZ11/dJMcacbGhEh/SunC09qV4kx4IjvGZq3WH0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daFZ11/dJMcacbGhEh/SunC09qV4kx4IjvGZq3WH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaFZ11%2FdJMcacbGhEh%2FSunC09qV4kx4IjvGZq3WH0%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;471&quot; height=&quot;396&quot; data-filename=&quot;스크린샷 2026-04-14 오전 12.17.32.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 단일 큐브 스폰&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제로 활용할 큐브를 스폰해 보겠습니다. 외부에서 호출할 수 있도록 public 메서드로 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private List&amp;lt;GameObject&amp;gt; m_spawnedObjects = new List&amp;lt;GameObject&amp;gt;();

    public void SpawnCube(GameObject prefab)
    {
        var obj = Object.Instantiate(prefab, Vector3.zero, Quaternion.identity);
        m_previewRenderUtility.AddSingleGO(obj);
        m_spawnedObjects.Add(obj);
    }

    private void ClearSpawnedObjects()
    {
        foreach (var obj in m_spawnedObjects)
        {
            if (obj != null)
                Object.DestroyImmediate(obj);
        }
        m_spawnedObjects.Clear();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;SpawnCube&lt;/b&gt;&lt;/span&gt;는 전달받은 프리팹을 원점에 생성하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;AddSingleGO&lt;/b&gt;&lt;/span&gt;로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;의 독립 씬에 등록합니다. 이 메서드를 통해 등록해야만 프리뷰 카메라에 렌더링됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;ClearSpawnedObjects&lt;/b&gt;&lt;/span&gt;는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;SpawnCube&lt;/b&gt;&lt;/span&gt; 내부에서 자동으로 호출하지 않으므로, 필요한 시점에 외부에서 직접 호출하여 오브젝트를 정리할 수 있습니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DestroyImmediate&lt;/b&gt;&lt;/span&gt;를 사용하는 이유는 에디터 환경에서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Destroy&lt;/b&gt;&lt;/span&gt;가 즉시 실행되지 않기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;에서 아래와 같이 호출합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private void CreateGUI()
    {
        var customSceneView = new CustomSceneView();
        rootVisualElement.Add(customSceneView);

        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        customSceneView.SpawnCube(cube);
        Object.DestroyImmediate(cube);
    }&lt;/code&gt;&lt;/pre&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-filename=&quot;스크린샷 2026-04-14 오전 12.36.05.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bvkvc/dJMcaiprrJo/8ZRW21uxrHASpkRybv2Ma0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bvkvc/dJMcaiprrJo/8ZRW21uxrHASpkRybv2Ma0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bvkvc/dJMcaiprrJo/8ZRW21uxrHASpkRybv2Ma0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBvkvc%2FdJMcaiprrJo%2F8ZRW21uxrHASpkRybv2Ma0%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;472&quot; height=&quot;396&quot; data-filename=&quot;스크린샷 2026-04-14 오전 12.36.05.png&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;1532&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오브젝트 스폰 기능을 추가했으므로, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DetachFromPanelEvent&lt;/b&gt;&lt;/span&gt;에서 스폰된 오브젝트도 함께 정리해야 합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private void OnDetachFromPanel(DetachFromPanelEvent evt)
    {
        ClearSpawnedObjects();

        if (m_gridMaterial != null)
            Object.DestroyImmediate(m_gridMaterial);

        m_previewRenderUtility?.Cleanup();
        m_previewRenderUtility = null;
    }&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 카메라 조작 이벤트 등록&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt;에 오브젝트를 배치했지만, 카메라가 고정되어 있으면 다양한 각도에서 확인하기 어렵습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 마우스 스크롤로 줌인 &amp;middot; 줌아웃, 마우스 오른쪽 클릭 드래그로 중심 기준 공전 회전을 구현합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 카메라 변수 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;CustomSceneView&lt;/span&gt;&lt;/b&gt;에 카메라 조작에 필요한 상태 변수를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private Vector3 m_cameraTarget = Vector3.zero;
    private float m_cameraDistance = 5.385f;
    private float m_cameraYaw = 0f;
    private float m_cameraPitch = -21.8f;

    private const float MinDistance = 1f;
    private const float MaxDistance = 50f;
    private const float RotationSpeed = 0.3f;
    private const float ZoomSpeed = 0.5f;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;m_cameraTarget&lt;/b&gt;&lt;/span&gt;은 카메라가 바라보는 중심점입니다. 카메라는 이 지점을 기준으로 공전합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;m_cameraDistance&lt;/b&gt;&lt;/span&gt;는 중심점으로부터 카메라까지의 거리이며, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;MinDistance&lt;/b&gt;&lt;/span&gt;로 줌인 최소 거리를 제한합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;m_cameraYaw&lt;/span&gt; / &lt;span style=&quot;color: #ef5369;&quot;&gt;m_cameraPitch&lt;/span&gt;&lt;/b&gt;는 중심점 기준 좌우&amp;middot;상하 회전 각도입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 카메라 위치 업데이트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상태 변수를 기반으로 카메라의 위치와 방향을 갱신하는 메서드를 추가합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private void UpdateCameraTransform()
    {
        var rotation = Quaternion.Euler(m_cameraPitch, m_cameraYaw, 0f);
        var direction = rotation * Vector3.back;
        var position = m_cameraTarget - direction * m_cameraDistance;

        var camera = m_previewRenderUtility.camera;
        camera.transform.position = position;
        camera.transform.LookAt(m_cameraTarget);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Yaw&lt;/b&gt;와 &lt;b&gt;Pitch&lt;/b&gt; 값으로 회전을 만들고, 중심점에서 해당 방향의 반대로 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;m_cameraDistance&lt;/span&gt;&lt;/b&gt;만큼 떨어진 위치에 카메라를 배치합니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;LookAt&lt;/b&gt;&lt;/span&gt;으로 항상 중심점을 바라보게 합니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;InitPreviewRenderUtility&lt;/b&gt;&lt;/span&gt;에서 기존 카메라 위치 설정 코드를 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;UpdateCameraTransform&lt;/b&gt;&lt;/span&gt; 호출로 교체합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private void InitPreviewRenderUtility()
    {
        m_previewRenderUtility = new PreviewRenderUtility();

        //Camera
        var camera = m_previewRenderUtility.camera;
        camera.fieldOfView = 30f;
        camera.nearClipPlane = 0.01f;
        camera.farClipPlane = 100f;
        camera.clearFlags = CameraClearFlags.Skybox;
        UpdateCameraTransform();

        //Light
        m_previewRenderUtility.lights[0].intensity = 1.4f;
        m_previewRenderUtility.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0f);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 마우스 이벤트 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;OnRenderGUI&lt;/b&gt;&lt;/span&gt; 안에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Event.current&lt;/b&gt;&lt;/span&gt;를 사용하여 마우스 입력을 처리합니다. 커서가 프리뷰 영역 안에 있을 때만 조작이 동작하도록 구현합니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;    private void OnRenderGUI()
    {
        if (m_previewRenderUtility == null) return;

        var rect = GUILayoutUtility.GetRect(0, 0, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
        if (rect.width &amp;lt;= 0 || rect.height &amp;lt;= 0) return;

        HandleMouseInput(rect);

        m_previewRenderUtility.BeginPreview(rect, GUIStyle.none);
        m_previewRenderUtility.camera.Render();
        DrawGrid();
        var texture = m_previewRenderUtility.EndPreview();

        GUI.DrawTexture(rect, texture, ScaleMode.StretchToFill, false);
    }

    private void HandleMouseInput(Rect rect)
    {
        var evt = Event.current;

        if (!rect.Contains(evt.mousePosition)) return;

        switch (evt.type)
        {
            // 마우스 오른쪽 클릭 드래그 &amp;rarr; 공전 회전
            case EventType.MouseDrag when evt.button == 1:
                m_cameraYaw += evt.delta.x * RotationSpeed;
                m_cameraPitch -= evt.delta.y * RotationSpeed;
                m_cameraPitch = Mathf.Clamp(m_cameraPitch, -89f, 89f);
                UpdateCameraTransform();
                evt.Use();
                break;

            // 마우스 스크롤 &amp;rarr; 줌인&amp;middot;줌아웃
            case EventType.ScrollWheel:
                m_cameraDistance += evt.delta.y * ZoomSpeed;
                m_cameraDistance = Mathf.Clamp(m_cameraDistance, MinDistance, MaxDistance);
                UpdateCameraTransform();
                evt.Use();
                break;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;rect.Contains&lt;/b&gt;&lt;/span&gt;으로 커서가 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CustomSceneView&lt;/b&gt;&lt;/span&gt;의 프리뷰 영역 안에 있을 때만 입력을 처리합니다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;에 버튼이나 슬라이더 같은 다른 UI 요소가 함께 배치되어 있을 경우, 이 체크가 없으면 프리뷰 영역 밖에서의 마우스 조작까지 카메라에 반영되어 의도치 않은 동작이 발생할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EventType.MouseDrag&lt;/b&gt;&lt;/span&gt;에서 &lt;b&gt;마우스 오른쪽 버튼(button == 1) 드래그 시 delta 값&lt;/b&gt;을 &lt;b&gt;Yaw&lt;/b&gt;와 &lt;b&gt;Pitch&lt;/b&gt;에 적용합니다. &lt;b&gt;Pitch는 -89도에서 89도로 제한&lt;/b&gt;하여 카메라가 뒤집히는 현상을 방지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EventType.ScrollWheel&lt;/b&gt;&lt;/span&gt;에서 &lt;b&gt;스크롤 delta&lt;/b&gt;를 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;m_cameraDistance&lt;/b&gt;&lt;/span&gt;에 적용하고, &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;MinDistance&lt;/span&gt;와 &lt;span style=&quot;color: #ef5369;&quot;&gt;MaxDistance&lt;/span&gt;로 클램프&lt;/b&gt;하여 너무 가까이 다가가거나 너무 멀어지는 것을 방지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;evt.Use&lt;/b&gt;&lt;/span&gt;를 호출하여 이벤트를 소비합니다. 이를 호출하지 않으면 다른 UI 요소에 이벤트가 전파되어 의도치 않은 동작이 발생할 수 있습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Apr-14-2026 01-13-26.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgvb4Q/dJMb99Tyjyc/MnflbyhuhdNaecTVTD2HB1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgvb4Q/dJMb99Tyjyc/MnflbyhuhdNaecTVTD2HB1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgvb4Q/dJMb99Tyjyc/MnflbyhuhdNaecTVTD2HB1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cgvb4Q/dJMb99Tyjyc/MnflbyhuhdNaecTVTD2HB1/img.gif&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;471&quot; height=&quot;385&quot; data-filename=&quot;Apr-14-2026 01-13-26.gif&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;654&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;h2 data-ke-size=&quot;size26&quot;&gt;5. 주의사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt; 기반의 독립 SceneView는 일반적인 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt; 개발과 다른 점이 많아, 처음 구현할 때 예상치 못한 문제를 만나기 쉽습니다. 아래에 구현 과정에서 주의해야 할 점들을 정리합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기존 SceneView 기능 사용 불가&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;로 구성한 독립 씬에서는 기존 SceneView에서 제공하는 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;Gizmos&lt;/span&gt;, &lt;span style=&quot;color: #ef5369;&quot;&gt;Handles&lt;/span&gt;, &lt;span style=&quot;color: #ef5369;&quot;&gt;OnSceneGUI&lt;/span&gt;&lt;/b&gt; 같은 에디터 기능을 사용할 수 없습니다. 이글에서 그리드를 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt;로 직접 그린 것처럼, 필요한 시각 보조 요소나 이벤트 처리는 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;GL&lt;/span&gt; 드로잉&lt;/b&gt;이나 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Event.current&lt;/b&gt;&lt;/span&gt;를 활용하여 직접 구현해야 합니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가비지 관련 주의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;OnPreviewGUI&lt;/b&gt;&lt;/span&gt;는 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;IMGUIContainer&lt;/span&gt;&lt;/b&gt;에 의해 매 프레임 호출됩니다. 이 안에서 new 키워드로 객체를 생성하거나, 매번 리스트를 할당하는 등의 코드가 있으면 가비지가 누적되어 &lt;b&gt;GC 스파이크&lt;/b&gt;를 유발할 수 있습니다. 상태 변수나 컬렉션은 필드로 선언하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;OnPreviewGUI&lt;/b&gt;&lt;/span&gt; 내부에서는 할당을 최소화해야 합니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GL.Lines 및 오브젝트 수에 따른 성능&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL.Lines&lt;/b&gt;&lt;/span&gt;로 그리는 격자선의 수와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;AddSingleGO&lt;/b&gt;&lt;/span&gt;로 등록한 오브젝트 수가 많아질수록 렌더링 부하가 증가합니다. 그리드의 gridSize나 스폰 오브젝트 수는 실제 용도에 맞게 적절히 조절해야 합니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Repaint 빈도 관리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IMGUIContainer&lt;/b&gt;&lt;/span&gt;는 기본적으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;가 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Repaint&lt;/b&gt;&lt;/span&gt;될 때마다 다시 그려집니다. 카메라 조작처럼 지속적인 갱신이 필요한 경우에는 문제가 없지만, 정적인 프리뷰를 보여줄 때도 불필요하게 자주 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Repaint&lt;/b&gt;&lt;/span&gt;되면 성능 낭비가 발생할 수 있습니다. 필요에 따라 변경이 있을 때만 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Repaint&lt;/b&gt;&lt;/span&gt;를 호출하도록 제어하는 것이 좋습니다.&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;리소스 정리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;는 반드시 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Cleanup&lt;/b&gt;&lt;/span&gt;을 호출하여 내부 리소스를 해제해야 합니다. 스폰된 오브젝트는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DestroyImmediate&lt;/b&gt;&lt;/span&gt;로, &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;GL&lt;/span&gt;용 머티리얼&lt;/b&gt;도 마찬가지로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DestroyImmediate&lt;/b&gt;&lt;/span&gt;로 정리해야 합니다. 이를 빠뜨리면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt;를 열고 닫을 때마다 메모리 누수가 누적될 수 있습니다. 본문에서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DetachFromPanelEvent&lt;/b&gt;&lt;/span&gt;에서 이를 처리했지만, 구조에 따라 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;OnDisable&lt;/b&gt;&lt;/span&gt;에서 정리하는 방식도 고려할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UI Toolkit 기반 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EditorWindow&lt;/b&gt;&lt;/span&gt; 안에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IMGUIContainer&lt;/b&gt;&lt;/span&gt;를 조합하여 독립적인 SceneView를 구현하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;GL&lt;/b&gt;&lt;/span&gt; 그리드 렌더링, 오브젝트 스폰, 카메라 조작까지 다뤄보았습니다. 기존 SceneView에 비해 기즈모나 핸들을 직접 구현해야 하는 번거로움은 있지만, 메인 씬과 완전히 분리된 렌더링 환경을 자유롭게 구성할 수 있다는 점에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;PreviewRenderUtility&lt;/b&gt;&lt;/span&gt;는 충분히 실용적인 선택지입니다.&lt;/p&gt;</description>
      <category>Unity</category>
      <category>Custom SceneView</category>
      <category>EditorWindow</category>
      <category>IMGUIContainer</category>
      <category>PreviewRenderUtility</category>
      <category>UI Toolkit</category>
      <category>Unity</category>
      <author>ZoNaDak</author>
      <guid isPermaLink="true">https://zonadak.tistory.com/17</guid>
      <comments>https://zonadak.tistory.com/17#entry17comment</comments>
      <pubDate>Tue, 14 Apr 2026 16:49:50 +0900</pubDate>
    </item>
    <item>
      <title>추상클래스 vs 인터페이스</title>
      <link>https://zonadak.tistory.com/16</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클래스를 설계하다 보면 &lt;b&gt;추상클래스&lt;/b&gt;와 &lt;b&gt;인터페이스&lt;/b&gt; 중 무엇을 써야 할지 막히는 순간이 옵니다. 이 글에서는 두 개념의 차이를 짚어보고, 어떤 상황에서 각각을 선택해야 하는지 알아보겠습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 추상클래스, 인터페이스란?&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;추상클래스&lt;/b&gt;는 일반 클래스와 비슷하지만, 구현이 없는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;abstract&lt;/b&gt;&lt;/span&gt; 메서드를 하나 이상 포함할 수 있습니다. 자식 클래스는 이 메서드를 반드시 구현해야 하며, 직접 인스턴스화할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인터페이스&lt;/b&gt;는 메서드 시그니처만 정의하는 순수한 틀입니다. 구현은 전혀 없고, 이를 채택한 클래스가 모든 메서드를 구현할 책임을 집니다. 마찬가지로 직접 인스턴스화할 수 없습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 판단 기준 - 구현을 공유할 것인가?&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&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;p data-ke-size=&quot;size16&quot;&gt;또한 추상클래스는 &lt;b&gt;다중 상속이 불가능&lt;/b&gt;하지만, 인터페이스는 &lt;b&gt;여러 개를 동시에 구현&lt;/b&gt;할 수 있습니다. 그래서 실전에서는 추상클래스로 공통 뼈대를 잡고, 인터페이스로 추가적인 역할을 부여하는 방식을 함께 사용하는 경우가 많습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 인터페이스에서 메서드 구현이 가능한 경우&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언어에 따라 인터페이스에서도 메서드 구현이 가능한 경우가 있습니다. C#은 8.0부터, Java는 8부터 지원하며, Unity의 경우 2021 버전 이상에서 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;default 구현&lt;/b&gt;이라고 부르며, 기존 인터페이스에 메서드를 추가할 때 기존 구현체를 모두 수정하지 않아도 되도록 하위 호환성을 유지하기 위해 도입된 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unity C# 예시는 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public interface IInterfaceA 
{
    public void MethodA();

    public void MethodB() 
    {
        Debug.Log(&quot;IInterfaceA MethodB&quot;);
    }

    public void MethodC() 
    {
        Debug.Log(&quot;IInterfaceA MethodC&quot;);
    }
}

public class TestClassA : IInterfaceA 
{
    public void MethodA()
    {
        Debug.Log(&quot;TestClassA MetoadA&quot;);
    }

    public void MethodC()
    {
        Debug.Log(&quot;TestClassA MethodC&quot;);
    }
}&lt;/code&gt;&lt;/pre&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;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;TestClassA testClassA = new TestClassA();
testClassA.MethodA();  // &quot;TestClassA MetoadA&quot;
testClassA.MethodB();  // Error
testClassA.MethodC();  // &quot;TestClassA MethodC&quot;

IInterfaceA interfaceA = testClassA;
interfaceA.MethodA();  // &quot;TestClassA MetoadA&quot;
interfaceA.MethodB();  // &quot;IInterfaceA MethodB&quot;
interfaceA.MethodC();  // &quot;TestClassA MethodC&quot;&lt;/code&gt;&lt;/pre&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;&lt;b&gt;첫째, &lt;span style=&quot;color: #ef5369;&quot;&gt;MethodB&lt;/span&gt;를 보면&lt;/b&gt; 클래스 참조(&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;testClassA&lt;/span&gt;&lt;/b&gt;)로는 인터페이스의 default 구현에 접근할 수 없습니다. 인터페이스 참조(&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;interfaceA&lt;/span&gt;&lt;/b&gt;)로만 접근이 가능합니다. 즉, 추상클래스처럼 구현을 자식과 공유하는 개념이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;둘째, &lt;span style=&quot;color: #ef5369;&quot;&gt;MethodC&lt;/span&gt;를 보면&lt;/b&gt; 자식 클래스가 동일한 메서드를 구현한 경우, 참조 타입에 관계없이 자식 클래스의 구현이 우선합니다. &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;override&lt;/span&gt;&lt;/b&gt;는 가능하지만, 클래스 참조와 인터페이스 참조 간에 동작 차이가 생길 수 있어 혼동을 줄 수 있으므로 주의해서 사용해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 결론&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&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;p data-ke-size=&quot;size16&quot;&gt;또한 인터페이스의 default 구현은 편리한 기능이지만, 추상클래스의 구현 공유와는 목적과 동작 방식이 다릅니다. 혼동을 줄이기 위해 꼭 필요한 경우에만 신중하게 사용하길 권장합니다.&lt;/p&gt;</description>
      <category>프로그래밍 일반</category>
      <category>abstract</category>
      <category>abstract class</category>
      <category>Interface</category>
      <category>인터페이스</category>
      <category>추상클래스</category>
      <author>ZoNaDak</author>
      <guid isPermaLink="true">https://zonadak.tistory.com/16</guid>
      <comments>https://zonadak.tistory.com/16#entry16comment</comments>
      <pubDate>Thu, 9 Apr 2026 02:22:40 +0900</pubDate>
    </item>
    <item>
      <title>Service Locator vs Dependency Injection</title>
      <link>https://zonadak.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; Service Locator&lt;/b&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Dependency Injection&lt;/b&gt;&lt;/span&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;1. IoC (Inversion of Control)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IoC&lt;/b&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;이 때문에 클래스 내에서 사용할 클래스 객체를 생성하면 안 되고, 외부에서 객체를 받아와서 사용해야 합니다.&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;p data-ke-size=&quot;size16&quot;&gt;이때, 외부에서 객체를 받아오는 방법으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Service Locator&lt;/b&gt;&lt;/span&gt;, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Dependency Injection&lt;/b&gt;&lt;/span&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;2. Service Locator&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt; Service Locator&lt;/span&gt;&lt;/b&gt; 는 다른 클래스에서 사용될 클래스 객체들을 전역 접근이 가능한 중앙 등록소에 미리 등록하여, 클래스가 해당 객체들을 사용할 때 중앙등록소에서 가져와서 사용하는 방법입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kwVet/btsPzUcLBOQ/AKBKYVbpWbLp3x1rCYJxX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kwVet/btsPzUcLBOQ/AKBKYVbpWbLp3x1rCYJxX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kwVet/btsPzUcLBOQ/AKBKYVbpWbLp3x1rCYJxX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkwVet%2FbtsPzUcLBOQ%2FAKBKYVbpWbLp3x1rCYJxX1%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;597&quot; height=&quot;314&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;597&quot; data-origin-height=&quot;314&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;h4 data-ke-size=&quot;size20&quot;&gt;장점&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Sevice Locator&lt;/b&gt;&lt;/span&gt; 에 접근할 수 있기 때문에 편리하게 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스가 어떤 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Service Locator&lt;/b&gt;&lt;/span&gt;의 객체들을 사용하는지 의존성이 명확하게 드러나지 않습니다. &lt;br /&gt;(구현부를 들여다 봐야지 확인이 가능합니다.)&lt;/li&gt;
&lt;li&gt;사용될 클래스가 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Service Locator&lt;/b&gt;&lt;/span&gt; 에 모두 등록되므로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;ServiceLocator&lt;/b&gt;&lt;/span&gt; 가 너무 비대해질 수 있습니다.&lt;br /&gt;또한, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Service Locator&lt;/b&gt;&lt;/span&gt; 와의 의존성은 강하게 결합되는 문제가 있습니다.&lt;/li&gt;
&lt;li&gt;테스트 환경에서도 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Service Locator&lt;/b&gt;&lt;/span&gt; 가 문제없이 동작하도록 신경 써야 하는 문제가 있습니다.&lt;br /&gt;(&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Dependency Injection&lt;/b&gt;&lt;/span&gt;보다 테스트 환경을 구축하기 어렵습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Dependency Injection&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;DI(Dependency Injection)&lt;/span&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kOOQI/btsPAkoGaWg/b8GhHUBLKQt3KwVPVWt030/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kOOQI/btsPAkoGaWg/b8GhHUBLKQt3KwVPVWt030/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kOOQI/btsPAkoGaWg/b8GhHUBLKQt3KwVPVWt030/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkOOQI%2FbtsPAkoGaWg%2Fb8GhHUBLKQt3KwVPVWt030%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;480&quot; height=&quot;320&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;320&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;h4 data-ke-size=&quot;size20&quot;&gt;장점&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;/li&gt;
&lt;li&gt;테스트환경에서 모킹 객체들을 쉽게 주입할 수 있어 단위테스트 작성이 용이합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;단점&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라이브러리를 사용하지 않을 경우 초기 설정 구축이 어렵습니다.&lt;br /&gt;또한, &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;DI&lt;/span&gt;&lt;/b&gt; 프레임워크를 도입하더라도 러닝커브가 좀 있습니다.&lt;/li&gt;
&lt;li&gt;의존성 주입을 작성할 때마다 꽤 긴 코드를 작성해야 할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 결론&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Service Locator&lt;/b&gt;&lt;/span&gt; 의 경우 사용하기 간단하고 쉽지만, 테스트 환경을 구축하기가 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Dependency Injection&lt;/b&gt;&lt;/span&gt; 보다는 어렵기 때문에 간단하게 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IoC&lt;/b&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;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Dependnecy Injection&lt;/b&gt;&lt;/span&gt; 의 경우 초기 구축이 난이도가 있고 사용하기가 약간 복잡하다는 단점이 있지만, 테스트 환경 구축이 용이하며, 유지보수성이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Service Locator&lt;/b&gt;&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://martinfowler.com/bliki/InversionOfControl.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://martinfowler.com/bliki/InversionOfControl.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develogs.tistory.com/19&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://develogs.tistory.com/19&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sanichdaniel.tistory.com/25&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sanichdaniel.tistory.com/25&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍 일반</category>
      <category>Dependency Injection</category>
      <category>DI</category>
      <category>Inversion of Control</category>
      <category>IOC</category>
      <category>Service Locator</category>
      <author>ZoNaDak</author>
      <guid isPermaLink="true">https://zonadak.tistory.com/15</guid>
      <comments>https://zonadak.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 27 Jul 2025 22:17:51 +0900</pubDate>
    </item>
    <item>
      <title>6 Hidden C# Features Every Unity Developer Should Know 영상 내용 정리</title>
      <link>https://zonadak.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; git-amend&lt;/b&gt; 유튜버의 &lt;b&gt;&lt;span data-token-index=&quot;1&quot;&gt;6 Hidden C# Features Every Unity Developer Should Know &lt;/span&gt;&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;해당 영상은 아래 링크를 통해 확인 가능합니다.&lt;/p&gt;
&lt;div id=&quot;code_1751907254034&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;iframe width=&amp;quot;560&amp;quot; height=&amp;quot;315&amp;quot; src=&amp;quot;https://www.youtube.com/embed/ToWaslg-Vws?si=Hz3eN89u9gB3ljbE&amp;quot; title=&amp;quot;YouTube video player&amp;quot; frameborder=&amp;quot;0&amp;quot; allow=&amp;quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&amp;quot; referrerpolicy=&amp;quot;strict-origin-when-cross-origin&amp;quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/ToWaslg-Vws?si=Hz3eN89u9gB3ljbE&quot; width=&quot;560&quot; height=&quot;315&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Intelligent Logging&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출자 정보 어트리뷰트 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;CallerFilePath&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;, &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;CallerLineNumber&lt;/span&gt;&lt;/b&gt;, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CallerMemberName&lt;/b&gt;&lt;/span&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;&lt;span style=&quot;color: #ef5369;&quot;&gt;CallerFilePath&lt;/span&gt;&lt;/b&gt; : 호출자의 소스 파일 경로&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CallerLineNumber&lt;/b&gt;&lt;/span&gt;: 호출자의 소스 파일 행 번호&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CallerMemberName&lt;/b&gt;&lt;/span&gt;: 호출자의 메소드 또는 프로퍼티 이름&lt;/li&gt;
&lt;/ul&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-filename=&quot;1-1.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl3898/btsO7PCn6kl/05Y0Bu27SYFdHOEKPE26J0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl3898/btsO7PCn6kl/05Y0Bu27SYFdHOEKPE26J0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl3898/btsO7PCn6kl/05Y0Bu27SYFdHOEKPE26J0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl3898%2FbtsO7PCn6kl%2F05Y0Bu27SYFdHOEKPE26J0%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;738&quot; height=&quot;52&quot; data-filename=&quot;1-1.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;52&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;예시 코드는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751907511900&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TestIntelligentLog
{
    public void StartTest(string testMessage)
    {
        Log(testMessage);
    }

    public void Log(
        string message,
        [CallerFilePath] string file = &quot;&quot;,
        [CallerLineNumber] int line = 0,
        [CallerMemberName] string member = &quot;&quot;)
    {
        Debug.Log($&quot;&amp;lt;color=green&amp;gt;[{Path.GetFileName(file)}:{line} - {member}]&amp;lt;/color&amp;gt; {message}&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Default Interface Methods&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C# 8.0부터 생긴 기능으로 인터페이스에서 Default 함수를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 클래스는 Default 함수를 override 할지 선택해서 할 수 있으며, 하지 않았을 경우 interface 의 함수를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 해당 인터페이스의 타입으로 선언했을 때만 사용할 수 있으며, 자식 클래스 타입으로 선언했을 때는 Default 함수를 호출하지 못합니다.&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;pre id=&quot;code_1751907601576&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TestDefaultInterfaceMethod
{
    public void StartTest()
    {
        //LoggerA
        {
            LoggerA loggerA = new LoggerA();
            //loggerA.Log(&quot;Halo 0&quot;);  Compile Error
            loggerA.LogWarning(&quot;Halo 1&quot;);
            loggerA.LogError(&quot;Halo 2&quot;);
        }
            
        //LoggerA as ILooger
        {
            ILogger loggerA = new LoggerA();
            loggerA.Log(&quot;Halo 0&quot;);
            loggerA.LogWarning(&quot;Halo 1&quot;);
            loggerA.LogError(&quot;Halo 2&quot;);
        }
            
        //LoggerB
        {
            LoggerB loggerB = new LoggerB();
            loggerB.Log(&quot;Halo 0&quot;);
            loggerB.LogWarning(&quot;Halo 1&quot;);
            loggerB.LogError(&quot;Halo 2&quot;);
        }
            
        //LoggerB as ILooger
        {
            ILogger loggerB = new LoggerB();
            loggerB.Log(&quot;Halo 0&quot;);
            loggerB.LogWarning(&quot;Halo 1&quot;);
            loggerB.LogError(&quot;Halo 2&quot;);
        }
    }
        
    public interface ILogger
    {
        void Log(string message)
        {
            Debug.Log(message);
        }

        void LogWarning(string message);
        void LogError(string message);
    }

    public class LoggerA : ILogger
    {
        public void LogWarning(string message)
        {
            Debug.LogWarning($&quot;LoggerA : {message}&quot;);
        }
            
        public void LogError(string message)
        {
            Debug.LogError($&quot;LoggerA : {message}&quot;);
        }
    }
        
    public class LoggerB : ILogger
    {
        public void Log(string message)
        {
            Debug.Log($&quot;LoggerB : {message}&quot;);
        }
            
        public void LogWarning(string message)
        {
            Debug.LogWarning($&quot;LoggerB : {message}&quot;);
        }
            
        public void LogError(string message)
        {
            Debug.LogError($&quot;LoggerB : {message}&quot;);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Custom Numeric Format Strings&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;숫자를 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;ToString&lt;/b&gt;&lt;/span&gt;을 사용해서 문자열로 리턴 할 때 인자로 포맷을 넣어서 서식을 적용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;;&lt;/span&gt;&lt;/b&gt;(세미콜론)을 이용하여 양수, 음수, 0에 따라 출력되는 문자열에 서식을 적용할 수 있습니다.&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;pre id=&quot;code_1751907641488&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class TestCustomNumericFormat
{
    public void StartTest()
    {
        Log(123);
        Log(-456);
        Log(0);
    }

    public void Log(int value)
    {
        var format = &quot;##;**##**;This is Zero!&quot;;
        var formattedValue = value.ToString(format);
        Debug.Log($&quot;Value : {value} -&amp;gt; FormattedValue: {formattedValue}&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. Deferred Enumeration&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 컬렉션에서 조건에 맞게 필터링하여 값들을 가져오고 싶을 때가 있는데 그때 값들을 컬렉션에 추가해서 바로 가져오는 대신 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;yield&lt;/b&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;IEnumerable&lt;/b&gt;&lt;/span&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;pre id=&quot;code_1751907706001&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void StartTest0()
{
    var scores = new List&amp;lt;int&amp;gt; { 50, 125, 300, 80, 170 };
            
    //Origin
    {
        Debug.Log(&quot;Origin-------&quot;);

        foreach (var score in GetBigValues(scores))
        {
            Debug.Log($&quot;High score : {score}&quot;);
        }
    }
            
    //Deferred Enumeration 0
    {
        Debug.Log(&quot;Deferred Enumeration 0-------&quot;);

        foreach (var score in GetBigValuesWithDeferredEnumeration0(scores))
        {
            Debug.Log($&quot;High score : {score}&quot;);
        }
    }
            
    //Deferred Enumeration 1
    {
        Debug.Log(&quot;Deferred Enumeration 1-------&quot;);

        foreach (var score in GetBigValuesWithDeferredEnumeration1(scores))
        {
            Debug.Log($&quot;High score : {score}&quot;);
        }
    }
}

public List&amp;lt;int&amp;gt; GetBigValues(List&amp;lt;int&amp;gt; input)
{
    var result = new List&amp;lt;int&amp;gt;();
    foreach (var val in input)
    {
        if (val &amp;gt; 100)
        {
            result.Add(val);
        }
    }

    return result;
}
        
public IEnumerable&amp;lt;int&amp;gt; GetBigValuesWithDeferredEnumeration0(List&amp;lt;int&amp;gt; input)
{
    foreach (var val in input)
    {
        if (val &amp;gt; 100)
        {
            yield return val;
        }
    }
}
        
public IEnumerable&amp;lt;int&amp;gt; GetBigValuesWithDeferredEnumeration1(List&amp;lt;int&amp;gt; input)
{
    return input.Where(val =&amp;gt; val &amp;gt; 100);
}&lt;/code&gt;&lt;/pre&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;아래와 같이 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;fitlered&lt;/b&gt;&lt;/span&gt; 변수에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;linq&lt;/b&gt;&lt;/span&gt;를 사용하여 값을 넣어 코드를 작성하면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;fitlered&lt;/b&gt;&lt;/span&gt; 가 호출될 때마다 컬렉션을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751907776984&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void StartTest1()
{
    Debug.Log(&quot;StartTest1 ========================&quot;);
    var scores = new List&amp;lt;int&amp;gt; { 50, 125, 300, 80, 170 };
         
    // 호출할때마다 생성함
    var filtered = scores.Where(val =&amp;gt;
    {
        Debug.Log($&quot;Filtering: {val}&quot;);
        return val &amp;gt; 100;
    });
            
    Debug.Log($&quot;Count of big scores: {filtered.Count()}&quot;);
    Debug.Log($&quot;Average of big scores: {filtered.Average()}&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4-1.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zGosR/btsO7zT5EQ3/01wzG0clE5csY3jIW1WGlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zGosR/btsO7zT5EQ3/01wzG0clE5csY3jIW1WGlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zGosR/btsO7zT5EQ3/01wzG0clE5csY3jIW1WGlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzGosR%2FbtsO7zT5EQ3%2F01wzG0clE5csY3jIW1WGlK%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;726&quot; height=&quot;495&quot; data-filename=&quot;4-1.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;495&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;때문에 아래와 같이 캐싱해서 사용해야 컬렉션을 중복해서 생성하지 않습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751907838515&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void StartTest1()
{
    Debug.Log(&quot;StartTest1 ========================&quot;);
    var scores = new List&amp;lt;int&amp;gt; { 50, 125, 300, 80, 170 };

		//캐싱됨
    var filtered = scores.Where(val =&amp;gt;
    {
        Debug.Log($&quot;Filtering: {val}&quot;);
        return val &amp;gt; 100;
    }).ToList();
            
    Debug.Log($&quot;Count of big scores: {filtered.Count()}&quot;);
    Debug.Log($&quot;Average of big scores: {filtered.Average()}&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4-2.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MVlh4/btsO9seDY1B/oYlI7yZHBNByoBAAnfU9O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MVlh4/btsO9seDY1B/oYlI7yZHBNByoBAAnfU9O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MVlh4/btsO9seDY1B/oYlI7yZHBNByoBAAnfU9O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMVlh4%2FbtsO9seDY1B%2FoYlI7yZHBNByoBAAnfU9O1%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;725&quot; height=&quot;304&quot; data-filename=&quot;4-2.png&quot; data-origin-width=&quot;725&quot; data-origin-height=&quot;304&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;h2 data-ke-size=&quot;size26&quot;&gt;5. Implicit and Explicit Operators&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 정의한 클래스나 구조체를 다른 타입으로 형 변환하도록 구현해야 할 때가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C# 은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;implicit&lt;/b&gt;&lt;/span&gt;(암시적), &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;explicit&lt;/b&gt;&lt;/span&gt;(명시적) 변환 연산자를 통해 해당 기능을 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;implicit&lt;/b&gt;&lt;/span&gt; 은 자동으로 형변환 해주는 연산자이며, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;explicit&lt;/b&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;예시 코드는 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1751907893992&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void StartTest()
{
            //explicit
    HealthForExplicit health1 = (HealthForExplicit)100;
    int hp1 = (int)health1;

            //implicit
    HealthForImplicit health2 = 100;
    int hp2 = health2;
}

struct HealthForExplicit
{
    public int value;

    public HealthForExplicit(int v)
    {
        value = v;
    }

    public static explicit operator int(HealthForExplicit health)
    {
        return health.value;
    }

    public static explicit operator HealthForExplicit(int v)
    {
        return new HealthForExplicit(v);
    }
}
        
struct HealthForImplicit
{
    public int value;

    public HealthForImplicit(int v)
    {
        value = v;
    }

    public static implicit operator int(HealthForImplicit health)
    {
        return health.value;
    }

    public static implicit operator HealthForImplicit(int v)
    {
        return new HealthForImplicit(v);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Array Slicing&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C# 8.0부터는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;..&lt;/b&gt;&lt;/span&gt; 연산자와 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;^&lt;/b&gt;&lt;/span&gt;연산자를 사용하여 배열의 특정 부분을 잘라낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;..&lt;/b&gt;&lt;/span&gt; 연산자 : 범위를 가져올 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #333333; color: #dddddd;&quot;&gt;ex) 1..4 : 인덱스 1부터 3까지&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;^&lt;/b&gt;&lt;/span&gt; 연산자: 배열의 끝에서 특정 인덱스까지 요소를 가져올 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #333333; color: #dddddd;&quot;&gt;ex) ^2 : 끝에서 두 번째 인덱스 요소&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;예시 코드는 아래와 같으며,&lt;/p&gt;
&lt;pre id=&quot;code_1751908048866&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void StartTest()
{
    int[] scores = { 10, 20, 30, 40, 50 };

    int last = scores[^1];
    Debug.Log($&quot;last : {last}&quot;);
            
    int[] last3 = scores[^3..];
    Debug.Log($&quot;last3 : {last3}&quot;);
            
    int[] first2 = scores[..2];
    Debug.Log($&quot;first2 : {first2}&quot;);
            
    int[] mid = scores[1..4];
    Debug.Log($&quot;mid : {mid}&quot;);
}&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;6-1.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byGMI9/btsO8SEKfaV/oz0mw0GqDUg8z18LYAH7ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byGMI9/btsO8SEKfaV/oz0mw0GqDUg8z18LYAH7ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byGMI9/btsO8SEKfaV/oz0mw0GqDUg8z18LYAH7ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyGMI9%2FbtsO8SEKfaV%2Foz0mw0GqDUg8z18LYAH7ck%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;726&quot; height=&quot;150&quot; data-filename=&quot;6-1.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;150&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;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ToWaslg-Vws&quot;&gt;https://www.youtube.com/watch?v=ToWaslg-Vws&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/attributes/caller-information&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/attributes/caller-information&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mentum.tistory.com/484&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mentum.tistory.com/484&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://tech-interview.tistory.com/250&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://tech-interview.tistory.com/250&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings#the--section-separator&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings#the--section-separator&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/user-defined-conversion-operators&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/operators/user-defined-conversion-operators&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.csharpstudy.com/latest/CS8-indexing-slicing.aspx&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.csharpstudy.com/latest/CS8-indexing-slicing.aspx&lt;/a&gt;&lt;/p&gt;</description>
      <category>Unity</category>
      <category>C#</category>
      <category>Unity</category>
      <author>ZoNaDak</author>
      <guid isPermaLink="true">https://zonadak.tistory.com/14</guid>
      <comments>https://zonadak.tistory.com/14#entry14comment</comments>
      <pubDate>Tue, 8 Jul 2025 02:09:40 +0900</pubDate>
    </item>
    <item>
      <title>Unity Awaitable 을 Coroutine, UniTask 와 비교</title>
      <link>https://zonadak.tistory.com/13</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Unity6에서 비동기 프로그래밍을 지원하기 위해 Awaitable이 추가되었습니다. 이를 기존에 사용했던 Coroutine과 외부 라이브러리인 UniTask와 비교해 보면서 알아보려 합니다.&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;1. Coroutine이란?&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에서 기존에 제공해 주었던 비동기 처리 방식입니다. IEnumerator와 yield return, yield break을 통해 구현합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1735971240521&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class CoroutineTest : MonoBehaviour
{
    private void Awake()
    {
        StartCoroutine(AsyncTest());
    }

    private IEnumerator AsyncTest()
    {
        Debug.LogWarning(&quot;Halo 0&quot;);

        yield return new WaitForSeconds(2f);
        
        Debug.LogWarning(&quot;Halo 1&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. UniTask란?&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니티에서 비동기 처리방식을 구현하기 위해 Cygames에서 개발한 외부 라이브러리입니다. async와 await와 같이 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1735971301140&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class UniTaskTest : MonoBehaviour
{
    private async void Awake()
    {
        await AsyncTest();
    }

    private async UniTask AsyncTest()
    {
        Debug.LogWarning(&quot;Halo 0&quot;);

        await UniTask.Delay(2000);
        
        Debug.LogWarning(&quot;Halo 1&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Awaitable 이란?&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Awaitable 은 유니티에서 Coroutine 이외에 새롭게 제공해준 비동기 처리 방식입니다. Task를 대체하여 비동기 작업을 표현하는 타입으로, 유니티가 추적할 수 있는 타입입니다. C#의 Task처럼 async와 await와 같이 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1735971394250&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class AwaitableTest : MonoBehaviour
{
    private async void Awake()
    {
        await AsyncTest();
    }

    private async Awaitable AsyncTest()
    {
        Debug.LogWarning(&quot;Halo 0&quot;);
        
        await Awaitable.WaitForSecondsAsync(2f);
        
        Debug.LogWarning(&quot;Halo 1&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 사용성 비교&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Corouitne&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유니티에서 제공해주는 기능이어서 특별한 설치 없이 사용 가능하고, MonoBehaviour에서도 쉽게 사용가능합니다.&lt;/li&gt;
&lt;li&gt;꽤 오래전부터 기능이 있었기 때문에, 레퍼런스가 많이 있습니다.&lt;/li&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;반환값을 다루기 난해한 부분이 있습니다.&lt;/li&gt;
&lt;li&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;호출과 동시에 가비지를 생성합니다.&lt;/li&gt;
&lt;li&gt;코루틴으로 루프 로직을 짤 때 yield return으로 null 이 아닌 반환값을 넣을 때 계속 가비지를 생성합니다. (ex. WaitForSeconds, WaitUntil&amp;hellip;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메인스레드에서만 사용 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UniTask&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;async, await 구문을 활용해서 더 직관적으로 로직을 짤 수 있습니다.&lt;/li&gt;
&lt;li&gt;라이브러리를 만들고 꽤 오랜 시간이 지나 레퍼런스가 많이 있습니다.&lt;/li&gt;
&lt;li&gt;반환값 처리가 쉽습니다.&lt;/li&gt;
&lt;li&gt;복잡한 비동기 로직을 짜기 비교적 쉽습니다. (WhenAll, WhenAny 제공)&lt;/li&gt;
&lt;li&gt;구조체 기반 및 복잡한 비동기 작업은 상태머신을 통해 관리되도록 설계되어 가비지 할당이 거의 없고 코루틴과 비교해 성능적 이점이 있습니다.&lt;/li&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;외부 라이브러리여서 별도 설치가 필요합니다.&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;CancellationToken 파라미터를 넘겨줘야 하고, 직접 작성한 로직은 토큰을 직접 체크해서 작성해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Awaitable&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유니티에서 제공해 주는 기능이어서 특별한 설치 없이 사용 가능하고, MonoBehaviour에서도 쉽게 사용가능합니다.&lt;/li&gt;
&lt;li&gt;async, await 구문을 활용해서 더 직관적으로 로직을 짤 수 있습니다.&lt;/li&gt;
&lt;li&gt;반환값 처리가 쉽습니다.&lt;/li&gt;
&lt;li&gt;기존 C# Task와 달리 풀로 관리되어 메모리 할당이 발생하는 문제를 해결해 코루틴과 비교해 성능적 이점이 있습니다.&lt;/li&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;특정 유니티 버전 이상에서만 사용 가능합니다.(2023.2 ~ )&lt;/li&gt;
&lt;li&gt;생긴 지 얼마 되지 않아 레퍼런스가 부족하고, 에러가 발생해도 노티가 되지 않는 등 개선해야 할 점이 꽤 있습니다.&lt;/li&gt;
&lt;li&gt;UniTask와 비교해 유틸기능이 부족합니다.&lt;/li&gt;
&lt;li&gt;동일한 Awaitable 인스턴스를 여러 번 await 할 수 없습니다. (처음 await으로 완료될 때 풀로 돌아갑니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 결론&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Awaitable을 쓰기에는 기능적인 개선도 필요해 보이고, 아직 레퍼런스가 더 쌓여야지 안정적으로 사용할 수 있어 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 UniTask가 성능과 기능이 더 강력하고, Coroutine도 나름의 장점이 살아있어서 아직은 실무에서 사용하기에는 큰 메리트가 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 Unity6부터 본격적인 업데이트를 하고 있어 UGUI처럼 차후에는 비동기 로직 처리에서 주류로 사용할 가능성이 있기 때문에 앞으로의 업데이트를 주의 깊게 지켜볼 필요가 있을 것으로 보입니다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.unitysquare.co.kr/growwith/unityblog/webinarView?id=566&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.unitysquare.co.kr/growwith/unityblog/webinarView?id=566&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/unity/comments/1elkqsb/choose_your_poison/?rdt=63173&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.reddit.com/r/unity/comments/1elkqsb/choose_your_poison/?rdt=63173&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Cysharp/UniTask&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Cysharp/UniTask&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://neuecc.medium.com/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://neuecc.medium.com/unitask-v2-zero-allocation-async-await-for-unity-with-asynchronous-linq-1aa9c96aa7dd&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://discussions.unity.com/t/on-async-await-with-awaitables-as-a-coroutine-replacement/1554090&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://discussions.unity.com/t/on-async-await-with-awaitables-as-a-coroutine-replacement/1554090&lt;/a&gt;&lt;/p&gt;</description>
      <category>Unity</category>
      <author>ZoNaDak</author>
      <guid isPermaLink="true">https://zonadak.tistory.com/13</guid>
      <comments>https://zonadak.tistory.com/13#entry13comment</comments>
      <pubDate>Sat, 4 Jan 2025 15:26:09 +0900</pubDate>
    </item>
    <item>
      <title>SOLID 원칙 정리</title>
      <link>https://zonadak.tistory.com/12</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 계속하다 보니 SOLID 원칙의 필요성을 체감함과 동시에, 내가 제대로 알고 있나라는 생각이 들어 정리하려고 합니다.&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 엔진 환경에서 게임 개발을 하고 있어 해당 환경 기반으로 정리하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 각 원칙마다 제 경험도 같이 덧붙이려고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SOLID 원칙이란&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소프트웨어 설계의 5가지 핵심 기본 사항을 나타내는 약어입니다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SRP : 단일 책임 원칙 (Single Responsibility Principle)&lt;/li&gt;
&lt;li&gt;OCP : 개방 - 폐쇄 원칙 (Open / Closed Principle)&lt;/li&gt;
&lt;li&gt;LSP : 리스코프 치환 원칙 (Liskov Substitution Principle)&lt;/li&gt;
&lt;li&gt;ISP : 인터페이스 분리 원칙 (Interface Segregation Principle)&lt;/li&gt;
&lt;li&gt;DIP : 의존관계 역전 원칙 (Dependency Inversion Principle)&lt;/li&gt;
&lt;/ol&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;1. SRP : 단일 책임 원칙 (Single Responsibility Principle)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;code&gt;Player&lt;/code&gt;에 &lt;code&gt;Audio&lt;/code&gt;, &lt;code&gt;Input&lt;/code&gt;, &lt;code&gt;Movement&lt;/code&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;code&gt;Player&lt;/code&gt; 클래스는 아래 그림과 같이 하나의 클래스로 &lt;code&gt;Audio&lt;/code&gt;, &lt;code&gt;Input&lt;/code&gt;, &lt;code&gt;Movement&lt;/code&gt; 각 기능을 구현합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_1-0.png&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;254&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgm57r/btsKtK62yFM/oxfblBzB7Gm4KLSOlF2ADK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgm57r/btsKtK62yFM/oxfblBzB7Gm4KLSOlF2ADK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgm57r/btsKtK62yFM/oxfblBzB7Gm4KLSOlF2ADK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbgm57r%2FbtsKtK62yFM%2FoxfblBzB7Gm4KLSOlF2ADK%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;426&quot; height=&quot;254&quot; data-filename=&quot;solid_1-0.png&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;254&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;코드로 작성할 경우 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class Player : MonoBehaviour
{
    [SerializeField] 
    private string inputAxisName;
    [SerializeField] 
    private float positionMultiplier;

    private float yPosition;
    private AudioSource bounceSfx;

    private void Start()
    {
        bounceSfx = GetComponent&amp;lt;AudioSource&amp;gt;();
    }

    private void Update()
    {
        float delta = Input.GetAxis(inputAxisName) * Time.deltaTime;
        yPosition = Mathf.Clamp(yPosition + delta, -1, 1);
        transform.position = 
            new Vector3(transform.position.x, yPosition * positionMultiplier, transform.position.z);
    }

    private void OnTriggerEnter(Collider other)
    {
        bounceSfx.Play();
    }
}&lt;/code&gt;&lt;/pre&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;code&gt;Player&lt;/code&gt;를 단일 책임 원칙을 지키면서 구현한다면 아래 그림과 같이 &lt;code&gt;Audio&lt;/code&gt;, &lt;code&gt;Input&lt;/code&gt;, &lt;code&gt;Movement&lt;/code&gt; 각 기능 단위로 클래스로 만들고, Player는 해당 클래스들을 사용하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_1-1.png&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wfIhY/btsKuS3TbEh/axwKFj7zbSSXPw0ywM3ptk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wfIhY/btsKuS3TbEh/axwKFj7zbSSXPw0ywM3ptk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wfIhY/btsKuS3TbEh/axwKFj7zbSSXPw0ywM3ptk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwfIhY%2FbtsKuS3TbEh%2FaxwKFj7zbSSXPw0ywM3ptk%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;426&quot; height=&quot;188&quot; data-filename=&quot;solid_1-1.png&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;188&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;코드로 구현한다면 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class Player : MonoBehaviour
{
    [SerializeField] 
    private PlayerInput playerInput;
    [SerializeField]
    private PlayerMovement playerMovement;
    [SerializeField] 
    private PlayerAudio playerAudio;

    private void Start()
    {
        playerAudio = GetComponent&amp;lt;PlayerAudio&amp;gt;();
        playerInput = GetComponent&amp;lt;PlayerInput&amp;gt;();
        playerMovement = GetComponent&amp;lt;PlayerMovement&amp;gt;();
    }

    private void Update() 
    {
        var deltaTime = Time.deltaTime;

        var moveDelta = playerInput.GetMoveDelta(deltaTime);
        playerMovement.Move(deltaTime);
    }

    private void OnTriggerEnter(Collider other)
    {
        playerAudio.Play();
    }
}

public class PlayerInput : MonoBehaviour
{
    [SerializeField] 
    private string inputAxisName;

    public float GetMoveDelta(float deltaTime) 
    {
        return Input.GetAxis(inputAxisName) * deltaTime;
    }
}

public class PlayerMovement : MonoBehaviour
{
    [SerializeField] 
    private float positionMultiplier;

    private float yPosition;

    public void Move(float deltaTime) 
    {
        yPosition = Mathf.Clamp(yPosition + deltaTime, -1, 1);
        transform.position = 
            new Vector3(transform.position.x, yPosition * positionMultiplier, transform.position.z);
    }
}

public class PlayerAudio : MonoBehaviour
{
    [SerializeField]
    private AudioSource bounceSfx;

    public void Play() 
    {
        bounceSfx.Play();
    }
}&lt;/code&gt;&lt;/pre&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;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;blockquote data-ke-style=&quot;style2&quot;&gt;  내 생각&lt;br /&gt;개인적으로 다섯 원칙 중 가장 중요하다고 생각하는 원칙이다.&lt;br /&gt;프로젝트가 커지면서 개발 속도는 점점 느려지기 마련인데, 해당 원칙을 잘 지킬수록 느려지는 정도가 많이 줄어들고 특히 팀원과 협업할 때 서로의 영역을 침범하지 않아 더 원활하게 작업이 진행되었다.&lt;br /&gt;다만, 쪼개는 단위가 너무 작을 경우 작업 속도가 너무 느려지고 굳이 클래스나 함수가 나눠지지 않아도 되는데 나누게 되면, 한눈에 들어오는 코드 정보가 너무 작아져 가독성을 많이 해치게 되어 적당히가 참 중요한 것 같다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. OCP : 개방 - 폐쇄 원칙 (Open / Closed Principle)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;code&gt;AreaCalculator&lt;/code&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;code&gt;AreaCalculator&lt;/code&gt; 클래스 안에 각 영역을 구하는 함수를 구현합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_2-0.PNG&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T97z2/btsKuOUMTdY/KO8vOCAgIIkl3mTI5ecsl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T97z2/btsKuOUMTdY/KO8vOCAgIIkl3mTI5ecsl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T97z2/btsKuOUMTdY/KO8vOCAgIIkl3mTI5ecsl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT97z2%2FbtsKuOUMTdY%2FKO8vOCAgIIkl3mTI5ecsl0%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;534&quot; height=&quot;386&quot; data-filename=&quot;solid_2-0.PNG&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;386&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;코드로 작성할 경우 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class AreaCalculator
{
    public float GetRectangleArea(Rectangle rectangle)
    {
        return rectangle.width * rectangle.height;
    }

    public float GetCircleArea(Circle circle)
    {
        return circle.radius * circle.radius * Mathf.PI;
    }
}

public class Rectangle
{
    public float width;
    public float height;
}

public class Circle
{
    public float radius;
}&lt;/code&gt;&lt;/pre&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;code&gt;AreaCalculator&lt;/code&gt;를 개방 폐쇄 원칙을 지키면서 구현한다면 아래 그림과 같이 &lt;code&gt;Shape&lt;/code&gt; 라는 추상클래스와 그 안에 &lt;code&gt;CalculateArea&lt;/code&gt; 추상 함수를 구현하고 자식인 각 영역 클래스에 &lt;code&gt;CalculateArea&lt;/code&gt; 함수를 구현하게 됩니다. 그리고 &lt;code&gt;AreaCalculator&lt;/code&gt; 는 해당 함수를 사용하여 각 영역을 계산합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_2-1.PNG&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TqMxr/btsKubQMZN3/EVa41o04Xs8ie4XFYOBEo1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TqMxr/btsKubQMZN3/EVa41o04Xs8ie4XFYOBEo1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TqMxr/btsKubQMZN3/EVa41o04Xs8ie4XFYOBEo1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTqMxr%2FbtsKubQMZN3%2FEVa41o04Xs8ie4XFYOBEo1%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;532&quot; height=&quot;435&quot; data-filename=&quot;solid_2-1.PNG&quot; data-origin-width=&quot;532&quot; data-origin-height=&quot;435&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;코드로 구현한다면 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class AreaCalculator
{
    public float GetArea(Shape shape)
    {
        return shape.CalculateArea();
    }
}

public abstract class Shape
{
    public abstract float CalculateArea();
} 

public class Rectangle : Shape
{
    public float width;
    public float height;
    public override float CalculateArea()
    {
        return width * height;
    }
}

public class Circle : Shape
{
    public float radius;
    public override float CalculateArea()
    {
        return radius * radius * Mathf.PI;
    }
}&lt;/code&gt;&lt;/pre&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;blockquote data-ke-style=&quot;style2&quot;&gt;  내 생각&lt;br /&gt;이 원칙은 코드가 쌓이면 쌓일수록 안 지킬 때보다 지킬 때 확장하기가 훨씬 쉬워, 빛을 발하는 원칙이라 생각한다.&lt;br /&gt;다만, 처음 기능을 추가할 때 확장성이 없는 기능인 경우에 해당 원칙을 지키게 되면, 작업 속도도 많이 느리고, 추후에 기능을 확장할 때 설계를 갈아엎는 경우도 빈번해서 기능의 종류가 두세 가지인 경우부터 원칙을 지키는 게 제일 효율적이라고 생각한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. LSP : 리스코프 치환 원칙 (Liskov Substitution Principle)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;code&gt;Vehicle&lt;/code&gt; 부모 클래스로, &lt;code&gt;Car&lt;/code&gt; 와 &lt;code&gt;Train&lt;/code&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;code&gt;Vehicle&lt;/code&gt; 을 구현했을 때 아래 그림과 같이 상단 함수(`GoForward`, `Reverse`)는 자식클래스와 호환이 되지만, 하단 함수(`TurnRight`, `TurnLeft`)는 기차에서는 구현할 수 없습니다. (기차는 철로를 따라가기 때문에 좌회전, 우회전을 자유롭게 못하기 때문에) 때문에 기차에서는 동작하지 않도록 둬야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_3-1.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HuAeQ/btsKtH3Kwk4/CwynkaK88LYxM1MQhMnX9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HuAeQ/btsKtH3Kwk4/CwynkaK88LYxM1MQhMnX9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HuAeQ/btsKtH3Kwk4/CwynkaK88LYxM1MQhMnX9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHuAeQ%2FbtsKtH3Kwk4%2FCwynkaK88LYxM1MQhMnX9k%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;450&quot; height=&quot;333&quot; data-filename=&quot;solid_3-1.png&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_3-2.png&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xPdxH/btsKucWktUt/HxxKYnQ0bZ296SxYDgvJA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xPdxH/btsKucWktUt/HxxKYnQ0bZ296SxYDgvJA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xPdxH/btsKucWktUt/HxxKYnQ0bZ296SxYDgvJA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxPdxH%2FbtsKucWktUt%2FHxxKYnQ0bZ296SxYDgvJA1%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;453&quot; height=&quot;306&quot; data-filename=&quot;solid_3-2.png&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;306&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;코드로 구현한다면 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class Vehicle
{
    public float speed = 100;
    public Vector3 direction;

    public void GoForward()
    {
        //전진
    }

     public void Reverse()
    {
        //후진
    }

     public void TurnRight()
    {
        //우회전
    }

     public void TurnLeft()
    {
        //좌회전
    }
}

public class Car 
{
    //Vehicle 동작과 똑같으므로 그대로 두면 됨
}

public class Train
{
    //Vehicle 동작다르게 좌회전 우회전 안하므로 재정의

    public new void TurnRight()
    {
        //우회전 안함
    }

     public new void TurnLeft()
    {
        //좌회전 안함
    }
}&lt;/code&gt;&lt;/pre&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;code&gt;Vehicle&lt;/code&gt; 을 각 &lt;code&gt;Car&lt;/code&gt; 와 &lt;code&gt;Train&lt;/code&gt;에 맞게 &lt;code&gt;RoadVehicle&lt;/code&gt;, &lt;code&gt;RailVehicle&lt;/code&gt; 로 분리하는 게 좋으며, 인터페이스를 만들어 해당 기능들을 호출할 수 있게 해야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_3-3.png&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;458&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tehxc/btsKtVUTFg1/XglAlzODMVkxvSj7rywkk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tehxc/btsKtVUTFg1/XglAlzODMVkxvSj7rywkk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tehxc/btsKtVUTFg1/XglAlzODMVkxvSj7rywkk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftehxc%2FbtsKtVUTFg1%2FXglAlzODMVkxvSj7rywkk0%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;660&quot; height=&quot;458&quot; data-filename=&quot;solid_3-3.png&quot; data-origin-width=&quot;660&quot; data-origin-height=&quot;458&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;코드로 구현한다면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public interface ITurnable
{
    public void TurnRight();
    public void TurnLeft();
}

public interface IMovable
{
    public void GoForward();
    public void Reverse();
}

public class RoadVehicle : IMovable, ITurnable
{
    public float speed = 100f;
    public float turnSpeed = 5f;

    public virtual void GoForward()
    {
        //전진
    }

    public virtual void Reverse()
    {
        //후진
    }

    public virtual void TurnLeft()
    {
        //좌회전
    }

    public virtual void TurnRight()
    {
        //우회전
    }
}

public class RailVehicle : IMovable
{
    public float speed = 100;

    public virtual void GoForward()
    {
        //전진
    }

    public virtual void Reverse()
    {
        //후진
    }
}

public class Car : RoadVehicle
{
    //RoadVehicle 동작과 똑같으므로 그대로 두면 됨
}

public class Train : RailVehicle
{
    //RailVehicle 동작과 똑같으므로 그대로 두면 됨
}&lt;/code&gt;&lt;/pre&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;blockquote data-ke-style=&quot;style2&quot;&gt;  내 생각&lt;br /&gt;프로그래밍을 처음 배울 때 이 원칙을 이해하기 너무나 힘들었고, 때문에 의도적으로 이 원칙을 지켜야 한다고 생각하면서 개발을 하지는 않았었다.&lt;br /&gt;하지만 계속 프로그래밍을 하면서 상속을 남용하여 쓰기보다는 인터페이스를 활용하여 최대한 자제하면서 써야한다는 생각을 자연스럽게 하게 되었는데, 알고보니 이 생각이 리스코프 치환 원칙을 지키려고 노력하고 있다는 걸 깨닫게 되었다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. ISP : 인터페이스 분리 원칙 (Interface Segregation Principle)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;p data-ke-size=&quot;size16&quot;&gt;원칙을 지키지 않았을 때 유닛의 상태는 아래 그림과 같이 하나의 큰 &lt;code&gt;IUnitStats&lt;/code&gt; 로 구현합니다. 이렇게 구현하면 유닛은 해당 프로퍼티나 함수들을 대부분 사용하겠지만, 파괴가능한 프랍을 만들 경우 해당 인터페이스를 상속받으면 대부분의 프로퍼티나 함수들을 사용하지 않을겁니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_4_1.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;620&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eho4LG/btsKtJtJQAG/3lfP9WGhJBCzKaJrDLyjX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eho4LG/btsKtJtJQAG/3lfP9WGhJBCzKaJrDLyjX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eho4LG/btsKtJtJQAG/3lfP9WGhJBCzKaJrDLyjX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feho4LG%2FbtsKtJtJQAG%2F3lfP9WGhJBCzKaJrDLyjX0%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;316&quot; height=&quot;323&quot; data-filename=&quot;solid_4_1.png&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;620&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;코드로 구현한다면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public interface IUnitStats
{
     public float Health { get; set; }
     public int Defense { get; set; }

     public void Die();
     public void TakeDamage();
     public void RestoreHealth();

     public float MoveSpeed { get; set; }
     public float Acceleration { get; set; }

     public void GoForward();
     public void Reverse();
     public void TurnLeft();
     public void TurnRight();

     public int Strength { get; set; }
     public int Dexterity { get; set; }
     public int Endurance { get; set; }
} 

public class EnemyUnit : IUnitStats
{
    public float Health { get; set; }
    public int Defense { get; set; }

    public void Die() 
    {
        //죽음
    }

    public void TakeDamage()
    {
        //피해 입음
    }

    public void RestoreHealth() 
    {
        //체력 회복
    }

    public float MoveSpeed { get; set; }
    public float Acceleration { get; set; }

    public void GoForward() 
    {
        //전진
    }

    public void Reverse() 
    {
        //후진
    }

    public void TurnLeft()
    {
        //좌회전
    }

    public void TurnRight()
    {
        //우회전
    }

    public int Strength { get; set; }
    public int Dexterity { get; set; }
    public int Endurance { get; set; }
}

public class ExplodingBarrel : IUnitStats
{
#region 인터페이스 상속

#region 사용함

    public float Health { get; set; }
    public int Defense { get; set; }

    public void Die() 
    {
        //죽음
    }

    public void TakeDamage()
    {
        //피해 입음
    }

    public void RestoreHealth() 
    {
        //체력 회복
    }

#endregion

#region 사용 안함

    public float MoveSpeed { get; set; }
    public float Acceleration { get; set; }

    public void GoForward() 
    {
        //전진
    }

    public void Reverse() 
    {
        //후진
    }

    public void TurnLeft()
    {
        //좌회전
    }

    public void TurnRight()
    {
        //우회전
    }

    public int Strength { get; set; }
    public int Dexterity { get; set; }
    public int Endurance { get; set; }

#endregion

#endregion

#region 인터페이스 상속 안받음

    public float Mass { get; set; }
    public int ExplosiveForce { get; set; }
    public int FuseDelay { get; set; }

    public void Explode() 
    {
        //폭발
    }

#endregion
}&lt;/code&gt;&lt;/pre&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;code&gt;IUnitStat&lt;/code&gt; 인터페이스를 만드는 대신 필요한 액션들만 가진 여러 인터페이스(&lt;code&gt;IMoveable&lt;/code&gt;, &lt;code&gt;IUnitStats&lt;/code&gt;, &lt;code&gt;IDamageable&lt;/code&gt;)를 만들어서 상속받는 클래스가 필요없는 함수를 구현할 필요 없게 만들어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, &lt;code&gt;IExplodable&lt;/code&gt; 를 추가하여 &lt;code&gt;ExplodingBarrel&lt;/code&gt; 뿐만 아니라 다른 폭발하는 프랍들도 추가하기 용이하게 만듭니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_4_2.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n0bxz/btsKvgjaLtE/JGfKnQk8dmUMKFWTh20hr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n0bxz/btsKvgjaLtE/JGfKnQk8dmUMKFWTh20hr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n0bxz/btsKvgjaLtE/JGfKnQk8dmUMKFWTh20hr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn0bxz%2FbtsKvgjaLtE%2FJGfKnQk8dmUMKFWTh20hr1%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;738&quot; height=&quot;521&quot; data-filename=&quot;solid_4_2.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;521&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;코드로 작성하면 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public interface IMovable
{
    public float MoveSpeed { get; set; }
    public float Acceleration { get; set; }

    public void GoForward();
    public void Reverse();
    public void TurnLeft();
    public void TurnRight();
}

public interface IDamageable
{
    public float Health { get; set; }
    public int Defense { get; set; }

    public void Die();
    public void TakeDamage();
    public void RestoreHealth();
}

public interface IUnitStats
{
    public int Strength { get; set; }
    public int Dexterity { get; set; }
    public int Endurance { get; set; }
} 

public interface IExplodable
{
    public float Mass { get; set; }
    public float ExplosiveForce { get; set; }
    public float FuseDelay { get; set; }

    public void Explode();
} 

public class EnemyUnit : IMovable, IDamageable, IUnitStats
{
#region IMovable 인터페이스 상속

    public float MoveSpeed { get; set; }
    public float Acceleration { get; set; }

    public void GoForward() 
    {
        //전진
    }

    public void Reverse() 
    {
        //후진
    }

    public void TurnLeft()
    {
        //좌회전
    }

    public void TurnRight()
    {
        //우회전
    }

#endregion

#region IDamageable 인터페이스 상속

    public float Health { get; set; }
    public int Defense { get; set; }

    public void Die() 
    {
        //죽음
    }

    public void TakeDamage()
    {
        //피해 입음
    }

    public void RestoreHealth() 
    {
        //체력 회복
    }

#endregion

#region IUnitStats 인터페이스 상속

    public int Strength { get; set; }
    public int Dexterity { get; set; }
    public int Endurance { get; set; }

#endregion
}

public class ExplodingBarrel : IDamageable, IExplodable
{
#region IDamageable 인터페이스 상속

    public float Health { get; set; }
    public int Defense { get; set; }

    public void Die() 
    {
        //죽음
    }

    public void TakeDamage()
    {
        //피해 입음
    }

    public void RestoreHealth() 
    {
        //체력 회복
    }

#endregion

#region IExplodable 인터페이스 상속

    public float Mass { get; set; }
    public int ExplosiveForce { get; set; }
    public int FuseDelay { get; set; }

    public void Explode() 
    {
        //폭발
    }

#endregion
}&lt;/code&gt;&lt;/pre&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;blockquote data-ke-style=&quot;style2&quot;&gt;  내 생각&lt;br /&gt;인터페이스가 너무 클 경우 상속받은 클래스가 불필요한 함수를 가질 뿐만 아니라 잘못 설계된 인터페이스를 수정할 때도 꽤 리소스가 많이 들어가 불편함을 겪었었던 경험이 있다.&lt;br /&gt;하지만 이 원칙을 너무 신경쓰면서 구조를 짜다보면 인터페이스 구현 난이도가 너무 올라가서 개인적으로는 인터페이스 위주의 구조를 짜면서 좀 더 연습해야겠다 생각이 든 원칙이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. DIP : 의존관계 역전 원칙 (Dependency Inversion Principle)&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&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;code&gt;Switch&lt;/code&gt; 클래스와 &lt;code&gt;Door&lt;/code&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;code&gt;Switch&lt;/code&gt; 클래스는 &lt;code&gt;Door&lt;/code&gt; 클래스의 &lt;code&gt;Open&lt;/code&gt;, &lt;code&gt;Close&lt;/code&gt; 함수를 직접 사용하여 구현하게 되는데, &lt;code&gt;Door&lt;/code&gt; 클래스의 구현부가 바뀌거나 다른 하위 모듈을 추가하게 될 때 &lt;code&gt;Switch&lt;/code&gt; 클래스도 같이 수정하게 되므로 오류가 발생할 가능성이 커집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_5_1.png&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmVloy/btsKtKlQvpY/EfkSp1skJDpUqb2mamuan1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmVloy/btsKtKlQvpY/EfkSp1skJDpUqb2mamuan1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmVloy/btsKtKlQvpY/EfkSp1skJDpUqb2mamuan1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmVloy%2FbtsKtKlQvpY%2FEfkSp1skJDpUqb2mamuan1%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;534&quot; height=&quot;307&quot; data-filename=&quot;solid_5_1.png&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;307&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;코드로 구현한다면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class Switch : MonoBehaviour
{
     public Door door;
     public bool isActivated;

     public void Toggle()
     {
         if (isActivated)
         {
             isActivated = false;
             door.Close();
         }
         else
         {
             isActivated = true;
             door.Open();
         }
     }
}

public class Door : MonoBehaviour
{
     public void Open()
     {
         //문 열림
     }

     public void Close()
     {
         //문 닫힘
     }
}&lt;/code&gt;&lt;/pre&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;code&gt;Switch&lt;/code&gt; 클래스와 &lt;code&gt;Door&lt;/code&gt; 클래스의 연결을 &lt;code&gt;ISwichable&lt;/code&gt; 인터페이스를 이용해서 구현합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함으로써 &lt;code&gt;Door&lt;/code&gt; 클래스의 구현부가 바뀌어도 &lt;code&gt;ISwichable&lt;/code&gt; 가 바뀔 정도의 변화가 아니면 &lt;code&gt;Switch&lt;/code&gt; 클래스는 신경 안써도 되고, 다른 하위 모듈을 추가할 때도 &lt;code&gt;ISwitchable&lt;/code&gt; 을 통해 연결되므로 쉽게 추가가 가능합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;solid_5_2.png&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bncS2d/btsKtWlXl33/9AlD1IBXAkucYevmOS5lK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bncS2d/btsKtWlXl33/9AlD1IBXAkucYevmOS5lK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bncS2d/btsKtWlXl33/9AlD1IBXAkucYevmOS5lK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbncS2d%2FbtsKtWlXl33%2F9AlD1IBXAkucYevmOS5lK1%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;531&quot; height=&quot;410&quot; data-filename=&quot;solid_5_2.png&quot; data-origin-width=&quot;531&quot; data-origin-height=&quot;410&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;코드로 구현한다면 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot;&gt;&lt;code&gt;public class Switch : MonoBehaviour
{
    public ISwitchable client;
    
    public void Toggle()
    {
        if (client.IsActive)
        {
            client.Deactivate();
        }
        else
        {
            client.Activate();
        }
    }
}

public interface ISwitchable
{
    public bool IsActive { get; }
    
    public void Activate();
    public void Deactivate();
}

public class Door : MonoBehaviour, ISwitchable
{
    private bool isActive;
    public bool IsActive =&amp;gt; isActive;
    
    public void Activate()
    {
        isActive = true;
        //문 열림
    }

    public void Deactivate()
    {
        isActive = false;
        //문 닫힘
    }
}&lt;/code&gt;&lt;/pre&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;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;blockquote data-ke-style=&quot;style2&quot;&gt;  내 생각&lt;br /&gt;의존성 주입을 공부하고 사용해보면서 해당 원칙을 자연스럽게 알게된 것 같다. 확실히 코드를 짤 때 다른 코드에 영향을 덜 주는 코드를 강제하게 만들어 유지보수성이 많이 오른 경험이 있다.&lt;br /&gt;해당 원칙을 사용할 때 쓰는 방법이 인터페이스, 추상클래스 두가지가 있는데 인터페이스를 사용할 때는 수정 가능성이 거의 없고 간단할 때 많이 쓰는 편이고, 추상클래스는 수정가능성이 많거나, 공용으로 쓰는 함수들이 많아 부모클래스에 구현하는게 나을 때 또는 빈 가상함수를 만들어 몇몇 자식클래스만 구현부를 만들 때 사용한다.&lt;/blockquote&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/2479/&quot;&gt;https://yozm.wishket.com/magazine/detail/2479/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://unity.com/kr/blog/game-programming-patterns-update-ebook&quot;&gt;https://unity.com/kr/blog/game-programming-patterns-update-ebook&lt;/a&gt;&lt;/p&gt;</description>
      <category>프로그래밍 일반</category>
      <author>ZoNaDak</author>
      <guid isPermaLink="true">https://zonadak.tistory.com/12</guid>
      <comments>https://zonadak.tistory.com/12#entry12comment</comments>
      <pubDate>Sun, 3 Nov 2024 19:11:23 +0900</pubDate>
    </item>
  </channel>
</rss>