<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>젊은 프로그래머의 슬픔</title>
    <link>https://hate-errorlog.tistory.com/</link>
    <description>유티니, C#과 관련한 여러 정보를 끄적여둔 블로그입니다.
Email : dkavmdk98@gmail.com</description>
    <language>ko</language>
    <pubDate>Fri, 12 Jun 2026 05:31:52 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>브라더스톤</managingEditor>
    <image>
      <title>젊은 프로그래머의 슬픔</title>
      <url>https://tistory1.daumcdn.net/tistory/6561675/attach/f3c6022c5875439aa4bb5bec617c1904</url>
      <link>https://hate-errorlog.tistory.com</link>
    </image>
    <item>
      <title>[Unity] 총알 시스템으로 알아보는 Object Pool</title>
      <link>https://hate-errorlog.tistory.com/59</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ Object Pool&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lzwvQ/dJMcahK6UqY/IthFVVe2cImJAtWalrPuyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lzwvQ/dJMcahK6UqY/IthFVVe2cImJAtWalrPuyK/img.png&quot; data-alt=&quot;[ObjectPool]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lzwvQ/dJMcahK6UqY/IthFVVe2cImJAtWalrPuyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlzwvQ%2FdJMcahK6UqY%2FIthFVVe2cImJAtWalrPuyK%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;730&quot; height=&quot;487&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[ObjectPool]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;게임 오브젝트를 생성하는 Instantiate() 메서드는 비용이 높은 연산이다. 이 메서드를 게임 플레이 중 반복적으로 호출하면 프레임 드롭이나 GC(Garbage Collection) 스파이크 등 다양한 성능 문제를 일으킬 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이를 해결하기 위해 &quot;필요할 때마다 생성하자&quot;가 아닌, &quot;미리 만들어 두고 꺼내 쓰자&quot;라는 접근 방식을 취하는 것이 바로 &lt;b&gt;Object Pool&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #f89009;&quot;&gt; Instantiate()가 비싼 이유&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Unity에서 우리가 작성하는 코드는 C#이지만, Unity 엔진의 내부(코어)는 C++로 작성되어 있다. 즉, 우리가 다루는 C# 코드는 C++ 코어를 조작하는 &lt;b&gt;리모컨&lt;/b&gt; 같은 역할을 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Instantiate()를 호출하면, Unity는 하나의 게임 오브젝트를 위해 다음 과정을 거친다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;C# 영역&lt;/b&gt;(매니지드 힙)에 C# 래퍼 객체를 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;C++ 영역&lt;/b&gt;(네이티브 힙)에 실제 엔진 객체를 생성한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;C# 래퍼 객체 안에 C++ 본체의 주소(핸들)를 저장해 둘을 연결한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;C++ 본체를 엔진 시스템(씬 그래프, Transform 트리 등)에 등록한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 핵심은, 이 과정이 &lt;b&gt;컴포넌트 하나당, 자식 오브젝트 하나당 반복된다&lt;/b&gt;는 점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;C#-C++ 한 쌍을 만드는 비용 자체는 크지 않지만, 프리팹에 포함된 모든 컴포넌트와 모든 자식 오브젝트에 대해 곱해지기 때문에 전체 비용이 급격히 증가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 간단한 게임 시스템 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Honeycam 2026-05-06 22-27-53.gif&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDRsGJ/dJMcaiQJRVB/YcWlZOuGRKfXCQkl43IIb1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDRsGJ/dJMcaiQJRVB/YcWlZOuGRKfXCQkl43IIb1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDRsGJ/dJMcaiQJRVB/YcWlZOuGRKfXCQkl43IIb1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bDRsGJ/dJMcaiQJRVB/YcWlZOuGRKfXCQkl43IIb1/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;730&quot; height=&quot;411&quot; data-filename=&quot;Honeycam 2026-05-06 22-27-53.gif&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 영상처럼 플레이어가 마우스 방향을 바라보고, 바라보는 방향으로 총알을 발사하는 간단한 시스템을 만들어 볼 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Object Pool을 직접 구현하는 대신, Unity에서 제공하는 UnityEngine.Pool을 활용하여 제작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. Player.cs&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System;
using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
    private Camera _camera;
    private Plane _aimPlane;

    private void Awake()
    {
        _camera = Camera.main;
        _aimPlane = new Plane(Vector3.up, transform.position.y);
    }

    private void Update()
    {
        var ray = _camera.ScreenPointToRay(Mouse.current.position.ReadValue());

        if (_aimPlane.Raycast(ray, out var dist) == true)
        {
            var hit = ray.GetPoint(dist);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;597&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T4218/dJMcacbW1NX/dBzGwIaiblO5NaHUKveSwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T4218/dJMcacbW1NX/dBzGwIaiblO5NaHUKveSwk/img.png&quot; data-alt=&quot;[마우스 위치 구하기]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T4218/dJMcacbW1NX/dBzGwIaiblO5NaHUKveSwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT4218%2FdJMcacbW1NX%2FdBzGwIaiblO5NaHUKveSwk%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;730&quot; height=&quot;478&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;597&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[마우스 위치 구하기]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마우스의 월드 좌표(World Position)를 구하기 위해, 플레이어 위치의 Y값을 기준으로 수평 평면을 생성한다. 탑다운 시점에서는 마우스가 가리키는 바닥 위의 좌표를 알아야 하므로, 노멀을 Vector3.up(Y축)으로 설정해 바닥과 나란한 XZ 평면을 만든다. 플레이어가 이동하지 않으므로 Awake()에서 미리 생성해 둔다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이후 카메라에서 마우스 방향으로 Ray를 발사하고, 이 Ray가 평면과 교차하는 지점을 마우스의 월드 좌표로 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private void Update()
{
	var ray = _camera.ScreenPointToRay(Mouse.current.position.ReadValue());
	
	if (_aimPlane.Raycast(ray, out var dist) == true)
	{
		var hit = ray.GetPoint(dist);
		var dir = hit - transform.position;
		dir.y = 0;
		
		if (dir.sqrMagnitude &amp;gt; 0.001f)
		{
			transform.rotation = Quaternion.LookRotation(dir);
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAPTqX/dJMcaf7yNpj/hKKTzVzYueKoi7vJGWamT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAPTqX/dJMcaf7yNpj/hKKTzVzYueKoi7vJGWamT0/img.png&quot; data-alt=&quot;[캐릭터가 바라볼 방향 구하기]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAPTqX/dJMcaf7yNpj/hKKTzVzYueKoi7vJGWamT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAPTqX%2FdJMcaf7yNpj%2FhKKTzVzYueKoi7vJGWamT0%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;730&quot; height=&quot;367&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[캐릭터가 바라볼 방향 구하기]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마우스의 월드 좌표(hit)를 구했으니, 여기서 플레이어의 위치를 빼 플레이어에서 마우스 방향으로 향하는 방향 벡터(dir)를 구한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때 마우스가 플레이어와 너무 가까우면 방향 벡터가 거의 0에 가까워져 회전이 불안정해질 수 있다. 이를 방지하기 위해 sqrMagnitude로 벡터의 제곱 크기를 검사하고, 일정 값 이상일 때만 Quaternion.LookRotation()으로 플레이어를 회전시켜 마우스 방향을 바라보게 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;sqrMagnitude는 벡터 크기의 제곱을 반환한다. magnitude와 달리 제곱근 연산을 생략하므로, 단순 대소 비교에서는 더 가볍다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 총과 총알(Weapon, Bullet)&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using System;
using UnityEngine;

public class Weapon : MonoBehaviour
{
    [SerializeField] private Bullet currentBullet;
    [SerializeField] private float fireDelay;
    
    private float _lastFireTime = float.NegativeInfinity;

    public void Fire()
    {
        if (Time.time - _lastFireTime &amp;lt; fireDelay) return;
        
        // TODO : Pool에서 총알을 얻어오기!
        _lastFireTime = Time.time;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Weapon 클래스는 총의 역할을 담당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;발사할 총알과 발사 딜레이를 직렬화 필드로 선언하고, Fire()가 호출되면 사격 딜레이가 경과했는지 확인한다. 딜레이가 지났다면 풀에서 총알을 가져온 뒤 마지막 사격 시점을 갱신한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;using UnityEngine;

public class Bullet : MonoBehaviour
{
    private Vector3 _dir;
    private float _velocity;

    private float _despawnTime;
    private const float LIFE_TIME = 5f;
    
    public void Init(Vector3 dir, Vector3 pos, float velocity)
    {
        _dir = dir.normalized;
        transform.position = pos;
        _velocity = velocity;

        _despawnTime = Time.time + LIFE_TIME;
    }
    
    private void Update()
    {
        transform.position += _dir * (_velocity * Time.deltaTime);

        if (Time.time &amp;gt;= _despawnTime)
        {
            // TODO : 사용한 총알을 다시 Pool로 돌려 재사용!
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Bullet 클래스는 총알의 이동과 수명을 관리한다. Init()에서 날아갈 방향, 발사 위치, 속도를 파라미터로 받아 초기화하고, Update()에서 매 프레임 지정된 방향으로 이동한다. 일정 시간(LIFE_TIME)이 지나면 총알을 풀로 반환하여 재사용할 수 있도록 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. 입력 처리(Input Action)&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 Input 클래스의 GetMouseButton() 대신, InputAction을 활용하여 마우스 좌클릭 입력 시 Weapon의 Fire()를 호출하도록 구현해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pwGbZ/dJMcahj4d4A/NgYxxh47O3d3efCSwIHKX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pwGbZ/dJMcahj4d4A/NgYxxh47O3d3efCSwIHKX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pwGbZ/dJMcahj4d4A/NgYxxh47O3d3efCSwIHKX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpwGbZ%2FdJMcahj4d4A%2FNgYxxh47O3d3efCSwIHKX0%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;730&quot; height=&quot;411&quot; data-origin-width=&quot;1672&quot; data-origin-height=&quot;941&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;using System;
using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
    [SerializeField] private Weapon equippedWeapon;

    private GameInput _gameInput;

    private void Awake()
    {
        _camera = Camera.main;
        _aimPlane = new Plane(Vector3.up, transform.position.y);
        
        _gameInput = new GameInput();
    }
    
    private void OnEnable()
    {
        _gameInput.Player.Enable();
       
        _gameInput.Player.Fire.performed += OnStartFire;
    }

    private void OnDisable()
    {
        _gameInput.Player.Disable();
        
        _gameInput.Player.Fire.performed -= OnStartFire;
    }

    private void OnDestroy()
    {
        _gameInput.Dispose();
    }

    private void OnStartFire(InputAction.CallbackContext _)
    {
        equippedWeapon.Fire();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Input Action 에셋을 생성했으면, GameInput 타입의 변수를 선언할 수 있다. Awake()에서 new GameInput()으로 인스턴스를 초기화해야 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OnEnable()에서 Player 액션 맵을 활성화하고, Fire 액션의 performed 이벤트에 OnStartFire()를 등록한다. OnDisable()에서는 액션 맵을 비활성화하고 이벤트를 해제하며, OnDestroy()에서 Dispose()를 호출해 리소스를 정리한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OnStartFire()는 InputAction.CallbackContext를 파라미터로 받아야 하므로, Weapon의 Fire()를 직접 등록하지 않고 별도 메서드로 래핑 하여 호출한다.&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ ObjectPool 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;930&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lj6y1/dJMcafT3XQi/CHqoTgrfQ0lOeTxc6HVYrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lj6y1/dJMcafT3XQi/CHqoTgrfQ0lOeTxc6HVYrk/img.png&quot; data-alt=&quot;[ObjectPool 흐름]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lj6y1/dJMcafT3XQi/CHqoTgrfQ0lOeTxc6HVYrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flj6y1%2FdJMcafT3XQi%2FCHqoTgrfQ0lOeTxc6HVYrk%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;730&quot; height=&quot;401&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;930&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[ObjectPool 흐름]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오브젝트 풀을 구현하기 위해선 먼저 풀을 관리할 Owner클래스를 만들어야 한다. 구현 사항마다 다르지만, 여기서는 풀에 쉽게 접근할 수 있도록 전역 클래스로 생성한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public class BulletSpawner : MonoBehaviour
{
    public static BulletSpawner Instance { get; private set; }
    public IObjectPool&amp;lt;Bullet&amp;gt; BulletPool =&amp;gt; _pool;
    
    private ObjectPool&amp;lt;Bullet&amp;gt; _pool;
		private Bullet _curBullet;
    
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(Instance.gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    public void AddBullet(Bullet bullet)
    {
        _curBullet = bullet;

        _pool = new ObjectPool&amp;lt;Bullet&amp;gt;(
            CreateBullet,
            OnGetBullet,
            OnReleaseBullet,
            OnDestroyBullet,
            collectionCheck: true,
            defaultCapacity: 20,
            maxSize: 100
            );
    }

    private Bullet CreateBullet()
    {
        var obj = Instantiate(_curBullet, transform);
        return obj;
    }

    private void OnGetBullet(Bullet bullet)
    {
        bullet.gameObject.SetActive(true);
    }

    private void OnReleaseBullet(Bullet bullet)
    {
        bullet.gameObject.SetActive(false);
    }

    private void OnDestroyBullet(Bullet bullet)
    {
        Destroy(bullet.gameObject);
    }
    
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클래스의 전체 형태는 위와 같다. 천천히 살펴보자.&lt;/span&gt;&lt;/h4&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;먼저 풀 역할을 할 변수를 선언한다. 제네릭 타입 T에는 풀에서 관리할 오브젝트의 타입을 넣어주면 된다.&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;// 외부에서 접근할 Pool
public IObjectPool&amp;lt;Bullet&amp;gt; BulletPool =&amp;gt; _pool;

// 내부에서 사용할 Pool    
private ObjectPool&amp;lt;Bullet&amp;gt; _pool;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1619&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RWJTI/dJMb99M1Woy/0YP9kKWQ6wEa2yxc6bGYS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RWJTI/dJMb99M1Woy/0YP9kKWQ6wEa2yxc6bGYS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RWJTI/dJMb99M1Woy/0YP9kKWQ6wEa2yxc6bGYS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRWJTI%2FdJMb99M1Woy%2F0YP9kKWQ6wEa2yxc6bGYS0%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;730&quot; height=&quot;438&quot; data-origin-width=&quot;1619&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;IObjectPool&amp;lt;T&amp;gt;는 Unity가 제공하는 오브젝트 풀의 &lt;b&gt;인터페이스&lt;/b&gt;로, Get()과 Release() 등 풀의 기본 동작을 정의한다. 외부에서는 이 인터페이스 타입으로 노출하여, 풀의 내부 구현을 감추고 꺼내기&amp;middot;반환하기만 가능하도록 제한한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ObjectPool&amp;lt;T&amp;gt;는 IObjectPool&amp;lt;T&amp;gt;를 구현한 구체 클래스다. Unity는 두 가지 구현체를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 147px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 14.4186%;&quot;&gt;클래스&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 14.1861%;&quot;&gt;내부 구조&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 71.279%;&quot;&gt;특징&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 14.4186%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ObjectPool&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 14.1861%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Stack (배열 기반)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 71.279%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가장 최근에 반환된 객체를 먼저 꺼낸다(LIFO). 메모리가 연속적이고 접근이 빠르다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 14.4186%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;LinkedPool&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 14.1861%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;LinkedList (노드 기반)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 71.279%;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노드 단위로 할당되어 확장&amp;middot;축소에 유연하지만, 메모리 오버헤드가 크다. &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: start;&quot;&gt;(자주 사용하지 않는 오브젝트를 풀링할 때 사용)&lt;/span&gt; &lt;br /&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;일반적으로는 ObjectPool&amp;lt;T&amp;gt;이 성능상 유리하므로, 이 글에서도 이를 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;public void AddBullet(Bullet bullet)
{
	_curBullet = bullet;

	_pool = new ObjectPool&amp;lt;Bullet&amp;gt;(
			CreateBullet,
			OnGetBullet,
			OnReleaseBullet,
			OnDestroyBullet,
			collectionCheck: true,
			defaultCapacity: 20,
			maxSize: 100
	);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;ObjectPool&amp;lt;T&amp;gt;을 생성할 때 다음 파라미터를 전달한다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 185px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;파라미터&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;타입&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;createFunc&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Func&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;풀에 여유 객체가 없을 때 새 객체를 생성하는 콜백.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;actionOnGet&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Action&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;풀에서 객체를 꺼낼 때 호출되는 콜백. 주로 SetActive(true) 처리.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;actionOnRelease&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Action&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;객체를 풀로 반환할 때 호출되는 콜백. 주로 SetActive(false) 처리.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;actionOnDestroy&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Action&amp;lt;T&amp;gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;풀이 maxSize를 초과하여 객체를 수용할 수 없을 때 완전히 제거하는 콜백.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;collectionCheck&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;bool&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;true로 설정하면 같은 객체를 중복 반환하는 실수를 방지한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;defaultCapacity&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;int&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;풀 내부 컬렉션(Stack)의 초기 할당 크기.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;maxSize&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;int&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;풀이 보관할 수 있는 최대 객체 수. 초과 시 actionOnDestroy가 호출된다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 중요한 점은, 이 파라미터들은 &lt;b&gt;후처리 콜백 함수&lt;/b&gt;라는 것이다. 즉, 객체를 &quot;어떻게 만들고, 어떻게 꺼내고, 어떻게 반환할지&quot;의 핵심 로직은 Pool이 내부적으로 처리하고, 우리는 각 시점에 &lt;b&gt;추가로 실행될 동작&lt;/b&gt;만 정의하면 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;collectionCheck, defaultCapacity, maxSize는 모두 선택적 파라미터로, 생략하면 기본값이 적용된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private Bullet CreateBullet()
{
	var obj = Instantiate(_curBullet, transform);
	return obj;
}

private void OnGetBullet(Bullet bullet)
{
	bullet.gameObject.SetActive(true);
}

private void OnReleaseBullet(Bullet bullet)
{
	bullet.gameObject.SetActive(false);
}

private void OnDestroyBullet(Bullet bullet)
{
	Destroy(bullet.gameObject);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Pool을 구현했으니, 이제 총알을 꺼내는 부분과 반환하는 부분을 마저 완성하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class Weapon : MonoBehaviour
{
    [SerializeField] private Bullet currentBullet;
    [SerializeField] private float fireDelay;
    
    private BulletSpawner _bulletSpawner;
    private float _lastFireTime = float.NegativeInfinity;

    private void Start()
    {
        _bulletSpawner = BulletSpawner.Instance;
        _bulletSpawner.AddBullet(currentBullet);
    }

    public void Fire()
    {
        if (Time.time - _lastFireTime &amp;lt; fireDelay) return;
        
        // Pool에서 총알을 얻어온 뒤, 초기화 함수 호출
        var bullet = _bulletSpawner.BulletPool.Get();
        bullet.Init(transform.forward, transform.position, 10f, 
	        _bulletSpawner.BulletPool);
        
        _lastFireTime = Time.time;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Weapon은 Start()에서 BulletSpawner 싱글톤에 접근하고, AddBullet()으로 풀을 초기화한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Fire()가 호출되면 BulletPool.Get()으로 풀에서 총알을 꺼낸 뒤, Init()을 통해 발사 방향, 위치, 속도, 그리고 &lt;b&gt;돌아갈 풀의 참조&lt;/b&gt;를 함께 전달한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;public class Bullet : MonoBehaviour
{
    private IObjectPool&amp;lt;Bullet&amp;gt; _pool;
    
    private Vector3 _dir;
    private float _velocity;

    private float _despawnTime;
    private const float LIFE_TIME = 5f;
    
    public void Init(Vector3 dir, Vector3 pos, float velocity, IObjectPool&amp;lt;Bullet&amp;gt; pool)
    {
        _dir = dir.normalized;
        transform.position = pos;
        _velocity = velocity;
        _pool = pool;

        _despawnTime = Time.time + LIFE_TIME;
    }
    
    private void Update()
    {
        transform.position += _dir * (_velocity * Time.deltaTime);

        if (Time.time &amp;gt;= _despawnTime)
        {
            _pool.Release(this);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;총알이 어느 풀로 돌아가야 하는지 알아야 하므로, Init() 시점에 돌아갈 풀의 참조를 IObjectPool&amp;lt;Bullet&amp;gt; 타입으로 저장한다. 이후 수명(LIFE_TIME)이 만료되면 _pool.Release(this)를 호출해 자기 자신을 풀로 반환한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 시스템 개선&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;총알이 한 종류뿐이라면 위 코드에 문제가 없지만, 여러 종류의 총알이 존재하면 문제점이 드러난다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 AddBullet()을 호출하면 기존 풀을 덮어쓰고 새 풀을 생성한다. 이 경우 기존 풀의 참조가 사라져, 이미 발사된 총알이 반환될 풀을 잃게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 총알을 꺼낼 때마다 Init()에 방향, 위치, 속도, 풀 참조 등 모든 정보를 매번 전달하고 있어 비효율적이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. BulletData&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using UnityEngine;

[CreateAssetMenu(fileName = &quot;BulletData&quot;, menuName = &quot;Weapon/BulletData&quot;)]
public class BulletData : ScriptableObject
{
    public float Damage =&amp;gt; damage;
    public float Velocity =&amp;gt; velocity;
    public float LifeTime =&amp;gt; lifeTime;
    public Bullet BulletPrefab =&amp;gt; bulletPrefab;

    [SerializeField] private float damage;
    [SerializeField] private float velocity;
    [SerializeField] private float lifeTime;
    [SerializeField] private Bullet bulletPrefab;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;총알마다 서로 다른 정보를 저장하기 위해 BulletData ScriptableObject를 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프리팹, 데미지, 속도, 수명 등 총알의 고정 데이터를 에셋으로 관리하면, 총알을 꺼낼 때마다 매번 값을 넘길 필요 없이 데이터 에셋 하나로 참조할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. Bullet&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;using UnityEngine;
using UnityEngine.Pool;

public abstract class Bullet : MonoBehaviour
{
    protected BulletData BulletData;
    
    private IObjectPool&amp;lt;Bullet&amp;gt; _pool;
    
    private Vector3 _dir;
    private float _curTime = 0f;

    protected abstract bool OnHit(GameObject other);
    
    public void Init(BulletData bulletData, Vector3 dir, Vector3 pos)
    {
        BulletData = bulletData;
        _dir = dir;
        _curTime = 0f;
        
        transform.position = pos;
        transform.rotation = Quaternion.LookRotation(dir);
    }

    public void SetPool(IObjectPool&amp;lt;Bullet&amp;gt; pool)
    {
        _pool = pool;
    }
    
    private void Update()
    {
        transform.position += _dir * (BulletData.Velocity * Time.deltaTime);
        _curTime += Time.deltaTime;

        if (_curTime &amp;gt;= BulletData.LifeTime)
        {
            _pool.Release(this);
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag(&quot;Enemy&quot;))
        {
            if(OnHit(other.gameObject)) _pool.Release(this);
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 Bullet을 추상 클래스로 변경하여, 다양한 종류의 총알(예: 폭발탄, 관통탄)을 상속으로 확장할 수 있도록 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OnHit()을 추상 메서드로 선언해 충돌 시 동작을 자식 클래스에서 정의하도록 한다. 반환값이 true이면 총알을 풀로 반환하고, false이면 반환하지 않아 관통 등의 동작을 구현할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 풀 참조를 Init()이 아닌 SetPool()로 분리했다. 풀 참조는 총알이 생성될 때 한 번만 설정하면 되지만, Init()은 풀에서 꺼낼 때마다 호출되므로 매번 풀을 다시 넘길 필요가 없기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;using UnityEngine;

public class NormalBullet : Bullet
{
    protected override bool OnHit(GameObject other)
    {
			  // TODO : 일반 총알이 충돌했을 때 동작 구현
        return true;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. BulletSpawner&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;

public class BulletSpawner : MonoBehaviour
{
    public static BulletSpawner Instance { get; private set; }
    private Dictionary&amp;lt;BulletData, IObjectPool&amp;lt;Bullet&amp;gt;&amp;gt; _pool;
    
    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            DontDestroyOnLoad(Instance.gameObject);
        }
        else
        {
            Destroy(gameObject);
            return;
        }
        
        _pool = new Dictionary&amp;lt;BulletData, IObjectPool&amp;lt;Bullet&amp;gt;&amp;gt;();
    }

    public Bullet GetBullet(BulletData data)
    {
        var pool = GetOrCreateBullet(data);
        return pool.Get();
    }
    
    private IObjectPool&amp;lt;Bullet&amp;gt; GetOrCreateBullet(BulletData data)
    {
        if(_pool.TryGetValue(data, out var bulletPool))
        {
            return bulletPool;
        }
        
        IObjectPool&amp;lt;Bullet&amp;gt; pool = null;
        
        pool = new ObjectPool&amp;lt;Bullet&amp;gt;(
            createFunc: () =&amp;gt; 
            {
                var obj = Instantiate(data.BulletPrefab, transform);
                obj.SetPool(pool);
                return obj;
            },
            OnGetBullet,
            OnReleaseBullet,
            OnDestroyBullet,
            defaultCapacity: 10,
            maxSize: 100
            );
        
        _pool.Add(data, pool);
        return pool;
    }
    
    private void OnGetBullet(Bullet bullet)
    {
        bullet.gameObject.SetActive(true);
    }

    private void OnReleaseBullet(Bullet bullet)
    {
        bullet.gameObject.SetActive(false);
    }

    private void OnDestroyBullet(Bullet bullet)
    {
        Destroy(bullet.gameObject);
    }
  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;BulletSpawner의 핵심 변경점은 풀의 자료구조다. 기존의 단일 ObjectPool&amp;lt;Bullet&amp;gt; 대신, Dictionary&amp;lt;BulletData, IObjectPool&amp;lt;Bullet&amp;gt;&amp;gt;로 변경하여 총알 종류별로 독립된 풀을 관리한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;GetBullet()이 호출되면 내부의 GetOrCreateBullet()에서 해당 BulletData를 Key로 딕셔너리를 조회한다. 이미 풀이 존재하면 그대로 반환하고, 없으면 새 풀을 생성한 뒤 딕셔너리에 등록한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;IObjectPool&amp;lt;Bullet&amp;gt; pool = null;
        
pool = new ObjectPool&amp;lt;Bullet&amp;gt;(
	createFunc: () =&amp;gt; 
	{
		var obj = Instantiate(data.BulletPrefab, transform);
		obj.SetPool(pool);
		return obj;
	},
	OnGetBullet,
	OnReleaseBullet,
	OnDestroyBullet,
	defaultCapacity: 10,
	maxSize: 100
	);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 주목할 부분은 createFunc 내부에서 pool 변수를 &lt;b&gt;클로저로 캡처&lt;/b&gt;하고 있다는 점이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;pool을 먼저 null로 선언한 뒤 ObjectPool을 생성하면, 람다가 pool 변수 자체를 캡처하므로 실제 총알이 생성되는 시점에는 이미 유효한 풀 참조를 갖게 된다. 이를 통해 Init()에서 매번 풀을 전달하는 대신, 생성 시 한 번만 SetPool()로 등록하여 불필요한 반복을 제거할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;4. Player &amp;amp; Weapon&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;using System;
using UnityEngine;

public class Weapon : MonoBehaviour
{
    [SerializeField] private float fireDelay;
    
    private BulletData _loadedBullet;
    private float _lastFireTime = float.NegativeInfinity;
    
    public void Fire()
    {
        if (Time.time - _lastFireTime &amp;lt; fireDelay) return;
        
        var bullet = BulletSpawner.Instance.GetBullet(_loadedBullet);
        bullet.Init(_loadedBullet, transform.forward, transform.position);
        
        _lastFireTime = Time.time;
    }

    public void SwapBullet(BulletData bullet)
    {
        _loadedBullet = bullet;
        
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class Player : MonoBehaviour
{
    [SerializeField] private Weapon equippedWeapon;
    [SerializeField] private List&amp;lt;BulletData&amp;gt; inventory;

    private int _curInventoryIndex = 0;
    private GameInput _gameInput;
    private Camera _camera;
    private Plane _aimPlane;

    private void Awake()
    {
        _camera = Camera.main;
        _aimPlane = new Plane(Vector3.up, transform.position.y);
        
        _gameInput = new GameInput();
        
        equippedWeapon.SwapBullet(inventory[_curInventoryIndex]);
    }

    private void Update()
    {
        var ray = _camera.ScreenPointToRay(Mouse.current.position.ReadValue());

        if (_aimPlane.Raycast(ray, out var dist) == true)
        {
            var hit = ray.GetPoint(dist);
            var dir = hit - transform.position;
            dir.y = 0;

            if (dir.sqrMagnitude &amp;gt; 0.001f)
            {
                transform.rotation = Quaternion.LookRotation(dir);
            }
        }
    }

    private void OnEnable()
    {
        _gameInput.Player.Enable();
        
        _gameInput.Player.Fire.performed += OnStartFire;
        _gameInput.Player.Reload.started += OnStartReload;
    }

    private void OnDisable()
    {
        _gameInput.Player.Disable();
        
        _gameInput.Player.Fire.performed -= OnStartFire;
        _gameInput.Player.Reload.started -= OnStartReload;
    }

    private void OnDestroy()
    {
        _gameInput.Dispose();
    }

    private void OnStartFire(InputAction.CallbackContext _)
    {
        equippedWeapon.Fire();
    }

    private void OnStartReload(InputAction.CallbackContext _)
    {
        _curInventoryIndex = (_curInventoryIndex + 1) % inventory.Count;
        equippedWeapon.SwapBullet(inventory[_curInventoryIndex]);
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존에는 Weapon이 직렬화된 Bullet 프리팹을 직접 들고 있었지만, 총알 종류가 여러 가지로 늘어나면서 구조를 변경했다. Player에 BulletData 리스트로 간단한 인벤토리를 구성하고, 장전 키(Reload)를 누르면 인벤토리의 다음 총알 데이터를 Weapon에 전달하도록 했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Weapon은 더 이상 특정 총알에 종속되지 않으며, SwapBullet()을 통해 언제든 장전된 총알을 교체할 수 있다. Fire() 호출 시에는 현재 장전된 BulletData를 기반으로 BulletSpawner에서 &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: start;&quot;&gt;해당 종류의 총알을 꺼내 발사한다.&lt;/span&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;Honeycam 2026-05-07 15-56-54.gif&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;510&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csnzKj/dJMcagrSCAb/37HAkolMRM1gK2KBmkzXr1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csnzKj/dJMcagrSCAb/37HAkolMRM1gK2KBmkzXr1/img.gif&quot; data-alt=&quot;최종 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csnzKj/dJMcagrSCAb/37HAkolMRM1gK2KBmkzXr1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/csnzKj/dJMcagrSCAb/37HAkolMRM1gK2KBmkzXr1/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;730&quot; height=&quot;411&quot; data-filename=&quot;Honeycam 2026-05-07 15-56-54.gif&quot; data-origin-width=&quot;905&quot; data-origin-height=&quot;510&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;최종 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unity,C#/Unity 정보</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/59</guid>
      <comments>https://hate-errorlog.tistory.com/59#entry59comment</comments>
      <pubDate>Thu, 7 May 2026 16:18:24 +0900</pubDate>
    </item>
    <item>
      <title>[Unity, C#] 델리게이트에 대한 모든것(Action, Func, Lambda)</title>
      <link>https://hate-errorlog.tistory.com/58</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 델리게이트(Delegate)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;게임 개발을 하다 보면 &amp;ldquo;어떤 일이 일어났을 때 이 코드를 실행해 줘&amp;rdquo;라는 패턴이 굉장히 많다. 예를 들어,&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private void OnTriggerEnter(Collider other)
{
	if(other.Tag(&quot;Wall&quot;) == true)
	{
		//충돌된 오브젝트의 태그가 Wall이라면 실행...
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[간단한 충돌 코드]&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 코드처럼 충돌 발생 시 실행할 코드를 작성한다. 하지만 충돌 시 실행할 동작이 상황에 따라 매번 달라져야 한다면 어떻게 될까?&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class Bullet
{
	private Player _player;
	private Enemy _enemy;
	private Manager _manager;
	
	private void OnTriggerEnter(Collider other)
	{
		if(other.CompareTag(&quot;Wall&quot;))
		{
			// 이런 상황일 땐...
			_player.DoSomething();
			
			// 저런 상황일 땐...
			_enemy.DoSomething();
			
			// 요런 상황일 땐...
			_manager.DoSomething();
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt; &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: center;&quot;&gt;[복잡해진 충돌 코드]&lt;/span&gt; &lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 Bullet이 Player, Enemy, Manager 등 모든 객체를 직접 참조하고 있으면 &lt;b&gt;결합도가 높아진다.&lt;/b&gt; 새로운 클래스가 추가될 때마다 Bullet 코드를 수정해야 하고, 어떤 상황에 어떤 메서드가 호출되는지 한눈에 파악하기도 어려워진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이런 문제를 해결하기 위해 등장한 것이 바로 &lt;b&gt;델리게이트(Delegate)&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 델리게이트(Delegate, 대리자)의 사용&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;변수에 숫자나 문자열을 담듯이, 델리게이트 변수에는 &lt;b&gt;메서드를 담을 수 있다.&lt;/b&gt; 메서드를 값처럼 저장하고, 전달하고, 원하는 시점에 호출할 수 있게 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// 1. 델리게이트 타입 선언 &amp;mdash; 반환형과 매개변수를 지정한다
public delegate void MyDelegate(string msg);

// 2. 델리게이트 변수 선언
public MyDelegate onEvent;

// 3. 메서드 등록
onEvent += SayHello;
onEvent += SayBye;

// 4. 호출 &amp;mdash; 등록된 메서드가 순서대로 실행된다
onEvent?.Invoke(&quot;Unity&quot;);

// 결과:
// &quot;안녕, Unity&quot;
// &quot;잘가, Unity&quot;

private void SayHello(string name)
{
	Debug.Log($&quot;안녕, {name}&quot;);
}

private void SayBye(string name)
{
	Debug.Log($&quot;잘가, {name}&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[델리게이트 기본 사용법]&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;델리게이트의 핵심을 정리하면 다음과 같다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;delegate 반환형 이름(매개변수) : 델리게이트 &lt;b&gt;타입&lt;/b&gt;을 선언한다. 어떤 형태의 메서드를 담을 수 있는지를 정의한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;델리게이트타입 변수명 : 델리게이트 &lt;b&gt;변수&lt;/b&gt;를 선언한다. 이 변수에 같은 시그니처(반환형, 매개변수)를 가진 메서드를 등록할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;+= : 메서드를 등록(구독)한다. 여러 개를 등록하면 &lt;b&gt;모두 순서대로 실행&lt;/b&gt;된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;-= : 등록된 메서드를 해제한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;?.Invoke() &amp;mdash; 등록된 메서드가 있을 때만 안전하게 호출한다. ?를 빼면 등록된 메서드가 없을 때 &lt;b&gt;NullReferenceException&lt;/b&gt;이 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 델리게이트를 사용하면 메서드를 변수처럼 사용할 수 있게 된다. 앞서 본 예제 코드를 델리게이트로 수정하면 아래와 같이 간략하게 정리된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class Bullet
{
	public delegate void OnHitWallDelegate();
	public OnHitWallDelegate OnHitWall;
	
	private void OnTriggerEnter(Collider other)
	{
		if(other.CompareTag(&quot;Wall&quot;))
		{
			OnHitWall?.Invoke();
		}
	}
}

public class Player : MonoBehaviour
{
	[SerializeField] private Bullet _bullet;
	
	private void Start()
	{
		_bullet.OnHitWall += HandleHitWall;
	}
	
	private void HandleHitWall()
	{
		Debug.Log(&quot;Player: 총알이 벽에 맞았다!&quot;);
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[외부에서 동작을 등록]&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이제 Bullet은 벽에 부딪혔다는 사실만 알리고, &lt;b&gt;실제로 무엇을 할지는 외부에서 결정한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Bullet이 Player, Enemy, Manager를 직접 알 필요가 없으므로 결합도가 낮아지고, 새로운 동작을 추가할 때도 Bullet 코드를 수정할 필요 없이 OnHitWall +=로 등록만 하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 매번 delegate void OnHitWallDelegate();처럼 델리게이트 타입을 직접 선언하는 것은 번거롭다. 시그니처가 달라질 때마다 새로운 델리게이트 타입을 만들어야 하기 때문이다. 이 불편함을 해결하기 위해 C#에서는 &lt;b&gt;Action&lt;/b&gt;과 &lt;b&gt;Func&lt;/b&gt;를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ Action&amp;lt;T&amp;gt;, Func&amp;lt;T, TResult&amp;gt;&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public delegate void OnHitWallDelegate();
public OnHitWallDelegate OnHitWall;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 본 것처럼 델리게이트를 사용하려면 먼저 타입을 선언하고, 그다음에 변수를 만들어야 했다. 델리게이트가 늘어날수록 타입 선언도 함께 늘어나고, 시그니처를 잘못 정의하는 등 실수가 발생할 가능성도 높아진다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;public class GameEvents
{
	public static Action OnGameOver;
	public static Action&amp;lt;int&amp;gt; OnScoreChanged;
}

// 구독
GameEvents.OnGameOver += DoSomething;

// 호출
GameEvents.OnGameOver?.Invoke();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 C#에서는 자주 쓰는 시그니처를 매번 선언할 필요 없도록, 제네릭 델리게이트인 &lt;b&gt;Action&lt;/b&gt;과 &lt;b&gt;Func&lt;/b&gt;를 미리 제공한다. 사용법은 델리게이트와 동일하지만, 타입 선언이 사라진 것을 알 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;무조건 델리게이트를 static으로 선언해야 하는 것은 아님.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;// 파라미터가 int, float 형식이고, 반환값이 없음
Action&amp;lt;int, float&amp;gt;
// 파라미터가 int 타입이고, 반환값 형식이 bool 타입.
Func&amp;lt;int, bool&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Action과 Func의 차이는 &lt;b&gt;반환값의 유무&lt;/b&gt;다. Action은 반환값이 없고(void), Func는 마지막 제네릭 파라미터가 반환 타입이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;// Action 내부 선언
public delegate void Action();
public delegate void Action&amp;lt;in T&amp;gt;(T obj);
public delegate void Action&amp;lt;in T1, in T2&amp;gt;(T1 arg1, T2 arg2);

// Func 내부 선언
public delegate TResult Func&amp;lt;out TResult&amp;gt;();
public delegate TResult Func&amp;lt;in T, out TResult&amp;gt;(T arg);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Action과 Func의 실제 선언부를 보면 결국 델리게이트라는 것을 알 수 있다. Action과 Func는 특별한 기능이 아니라, 우리가 직접 선언하던 델리게이트를 &lt;b&gt;C#이 미리 만들어둔 것&lt;/b&gt;에 불과하다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Action은 매개변수를 최대 16개까지 받을 수 있으며, Func 역시 매개변수 최대 16개 + 반환 타입 1개를 지원한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;콜백(Callback) 함수&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;지금 당장 실행하지 말고, 나중에 특정 조건이 되면 이 메서드를 대신 호출해 줘&quot;라는 의미다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;일반적인 메서드 호출은 내가 원하는 시점에 직접 호출한다. 하지만 콜백은 다르다. 메서드를 미리 등록해 두고, 어떤 일이 일어났을 때 상대 쪽에서 대신 호출해 주는 방식이다. 델리게이트는 이 콜백을 구현하는 대표적인 수단이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ UnityAction과 UnityEvent&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UnityAction은 UnityEvent에 메서드를 등록할 때 사용하는 델리게이트이며, 기능적으로는 Action과 동일하다. UnityEvent는 코드와 인스펙터에서 모두 이벤트를 연결할 수 있다는 것이 가장 큰 차이점이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;// UnityAction &amp;mdash; 코드에서만 등록 가능 (Action과 동일)
public UnityAction onDeathAction;

// UnityEvent &amp;mdash; 코드 + 인스펙터 둘 다 등록 가능
public UnityEvent onDeathEvent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjE4Ri/dJMcahEiWSY/9KvW0fAAltO4cnDgEYuSh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjE4Ri/dJMcahEiWSY/9KvW0fAAltO4cnDgEYuSh1/img.png&quot; data-alt=&quot;[인스펙터에 표시된 모습]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjE4Ri/dJMcahEiWSY/9KvW0fAAltO4cnDgEYuSh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjE4Ri%2FdJMcahEiWSY%2F9KvW0fAAltO4cnDgEYuSh1%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;103&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;103&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[인스펙터에 표시된 모습]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ event 키워드&lt;/span&gt;&lt;/h2&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;public Action OnDeath;

// 외부 클래스에서
player.OnDeath = DoSomething;   //기존 등록이 전부 삭제됨
player.OnDeath?.Invoke();       //아무 곳에서나 호출 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;델리게이트를 public으로 선언하면, 외부에서 두 가지 위험한 동작이 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;=로 덮어쓰기 : 다른 곳에서 등록한 메서드가 전부 날아간다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;외부에서 직접 Invoke() 호출 : 의도하지 않은 시점에 이벤트 발동.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;콜백의 핵심은 특정 상황이 발생했을 때 &lt;b&gt;선언한 쪽에서 대신 호출하는 것&lt;/b&gt;인데, 위처럼 외부에서 마음대로 덮어쓰거나 호출할 수 있다면 일반 메서드를 직접 호출하는 것과 다를 바가 없다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;public event Action OnDeath;

// 외부 클래스에서
player.OnDeath = DoSomething;   //컴파일 에러!
player.OnDeath?.Invoke();       //컴파일 에러!

player.OnDeath += DoSomething;  //외부에서 이벤트 구독과 해제만 가능
player.OnDeath -= DoSomething;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;델리게이트에 event 키워드를 붙이면, 외부에서는 +=(구독)과 -=(해제)만 가능해진다. Invoke()는 오직 &lt;b&gt;이벤트를 선언한 클래스 내부에서만&lt;/b&gt; 호출할 수 있으므로, &quot;언제 이벤트를 발동할지&quot;는 해당 클래스가 온전히 제어하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉 event 키워드는 델리게이트의 기능을 바꾸는 것이 아니라, &lt;b&gt;외부 접근을 제한하는 보호 장치&lt;/b&gt;라고 이해하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 람다식(Lambda Expression)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 델리게이트에 메서드를 등록할 때, 매번 별도의 메서드를 작성해야 했다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public event Action OnDeath;

private void Start()
{
	OnDeath += HandleDeath;
}

private void HandleDeath()
{
	Debug.Log(&quot;사망!&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;간단한 동작 하나를 등록하기 위해 메서드를 따로 만들고, 이름을 짓고, 등록하는 과정을 거쳐야 한다. 람다식을 사용하면 이 과정을 &lt;b&gt;한 줄로 줄일 수 있다.&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다식은 &amp;ldquo;익명 함수&amp;rdquo;를 간결하게 작성하는 문법이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;OnDeath += () =&amp;gt; Debug.Log(&quot;사망!&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다식은 &lt;b&gt;이름 없는 메서드를 그 자리에서 바로 작성하는 문법&lt;/b&gt;이다. =&amp;gt; 기호를 기준으로 왼쪽이 매개변수, 오른쪽이 실행할 코드다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 기본 문법&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 매개변수 없음
() =&amp;gt; Debug.Log(&quot;Hello&quot;);

// 매개변수 1개 &amp;mdash; 괄호 생략 가능
x =&amp;gt; Debug.Log(x);

// 매개변수 2개 이상 &amp;mdash; 괄호 필수
(x, y) =&amp;gt; Debug.Log(x + y);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;OnDeath += () =&amp;gt;
{
	Debug.Log(&quot;사망!&quot;);
	Debug.Log(&quot;게임 오버 화면 표시&quot;);
	GameManager.Instance.GameOver();
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실행할 코드가 여러 줄이면 {}로 감싸면 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. Action, Func와 사용하기&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Action &amp;mdash; 반환값 없음
Action&amp;lt;string&amp;gt; greet = (name) =&amp;gt; Debug.Log($&quot;안녕, {name}!&quot;);
greet(&quot;Unity&quot;);  // &quot;안녕, Unity!&quot;

// Func &amp;mdash; 반환값 있음
Func&amp;lt;int, int, int&amp;gt; add = (a, b) =&amp;gt; a + b;
int result = add(3, 5);  // 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다식은 델리게이트 타입 변수에 바로 대입할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// return 생략 가능
Func&amp;lt;int, int&amp;gt; double1 = x =&amp;gt; x * 2;

// {} 사용 시 return 필수
Func&amp;lt;int, int&amp;gt; double2 = x =&amp;gt;
{
	return x * 2;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Func에서 실행할 코드가 한 줄이면 return을 생략할 수 있다. 하지만 {}를 사용하면 return을 명시해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ 람다식은 언제 사용하면 좋을까?&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// 간단한 동작 &amp;mdash; 람다식이 적합
OnDeath += () =&amp;gt; Debug.Log(&quot;사망!&quot;);

// 복잡한 동작 &amp;mdash; 별도 메서드가 적합
OnDeath += HandleDeath;

private void HandleDeath()
{
	SavePlayerData();
	ShowGameOverUI();
	PlayDeathAnimation();
	ReportToServer();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;짧고 단순한 동작을 등록할 때는 람다식이 편리하다. 하지만 로직이 복잡하거나 여러 곳에서 재사용해야 한다면, 별도의 메서드로 분리하는 것이 가독성과 유지보수 면에서 더 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;// 해제 불가 &amp;mdash; 등록할 때와 해제할 때의 람다가 서로 다른 인스턴스
OnDeath += () =&amp;gt; Debug.Log(&quot;사망!&quot;);
OnDeath -= () =&amp;gt; Debug.Log(&quot;사망!&quot;);  // 해제되지 않음!

// 해제가 필요하면 변수에 담아두기
Action deathHandler = () =&amp;gt; Debug.Log(&quot;사망!&quot;);
OnDeath += deathHandler;
OnDeath -= deathHandler;  // 정상적으로 해제됨
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 람다식으로 등록한 메서드는 -=로 해제하기 어렵다는 점도 주의해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 캡쳐(Capture)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다식은 자신이 선언된 위치의 &lt;b&gt;외부 변수를 사용할 수 있다.&lt;/b&gt; 이때 람다식이 외부 변수를 기억하고 가져가는 것을 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;캡처(Capture)&lt;/b&gt;&lt;/span&gt;라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;private void Start()
{
	string playerName = &quot;용사&quot;;
	
	Action greet = () =&amp;gt; Debug.Log($&quot;안녕, {playerName}!&quot;);
	greet();  // &quot;안녕, 용사!&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;greet 람다식은 자신의 매개변수가 아닌 외부 변수 playerName을 사용하고 있다. 이것이 캡처다. 얼핏 보면 당연한 동작 같지만, 여기에는 주의해야 할 함정이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ 캡처는 값이 아니라 변수 자체를 기억&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;axapta&quot;&gt;&lt;code&gt;private void Start()
{
	int count = 0;
	
	Action increase = () =&amp;gt; count++;
	
	increase();
	increase();
	increase();
	
	Debug.Log(count);  // 3 &amp;mdash; 람다 안에서 수정한 값이 반영됨
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;람다식은 캡처 시점의 &lt;b&gt;값을 복사하는 것이 아니라&lt;/b&gt;, 변수 자체를 &lt;b&gt;참조&lt;/b&gt;한다. 따라서 변수의 값이 변하면 람다식의 결과도 달라진다. 이 개념을 모르면 아래와 같은 문제가 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;private void Start()
{
	Action[] actions = new Action[3];
	
	for (int i = 0; i &amp;lt; 3; i++)
	{
		actions[i] = () =&amp;gt; Debug.Log(i);
	}
	
	actions[0]();  // 3
	actions[1]();  // 3
	actions[2]();  // 3
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;0, 1, 2가 출력될 것 같지만, 실제로는 &lt;b&gt;전부 3이 출력된다.&lt;/b&gt; 세 개의 람다식이 모두 같은 변수 i를 캡처하고 있고, 반복문이 끝난 시점에 i의 값은 3이기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 문제를 해결하려면 &lt;b&gt;반복문 안에서 지역 변수에 값을 복사해 두면&lt;/b&gt; 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;private void Start()
{
	Action[] actions = new Action[3];
	
	for (int i = 0; i &amp;lt; 3; i++)
	{
		int captured = i;  // 값을 복사
		actions[i] = () =&amp;gt; Debug.Log(captured);
	}
	
	actions[0]();  // 0
	actions[1]();  // 1
	actions[2]();  // 2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;int captured = i;로 매 반복마다 새로운 지역 변수를 만들면, 각 람다식이 &lt;b&gt;서로 다른 변수&lt;/b&gt;를 캡처하게 되어 의도한 대로 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;private void Start()
{
	GameObject effect = Instantiate(effectPrefab);
	
	DOTween.Sequence()
		.AppendInterval(3f)
		.OnComplete(() =&amp;gt;
		{
			// 3초 후 실행 &amp;mdash; 이 시점에 effect가 이미 Destroy 되었다면?
			effect.SetActive(false);  // MissingReferenceException!
		});
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Unity에서 캡처를 사용할 땐 특히 더 조심해야 하는 점이 있는데, 람다식이 effect를 캡처한 뒤, 실제 호출 시점에 해당 오브젝트가 파괴되어 있으면 MissingReferenceException이 발생한다. 이런 경우 null 체크를 추가해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;.OnComplete(() =&amp;gt;
{
	if (effect != null)
	{
		effect.SetActive(false);
	}
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ 캡처를 활용한 매개변수 바인딩&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;캡처는 단순히 외부 변수를 읽는 것뿐만 아니라, 구독하는 시점에 데이터를 &lt;b&gt;미리 전달해 두고,&lt;/b&gt; Invoke 될 때 해당 데이터와 함께 실행되도록 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;public Action OnFire;

// 구독 시점 &amp;mdash; data를 캡처해서 미리 묶어둠
BulletData data = new BulletData(speed: 10, damage: 50);
OnFire += () =&amp;gt; CreateBullet(data);

// Invoke 시점 &amp;mdash; 캡처해둔 data와 함께 실행됨
OnFire?.Invoke();
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;OnFire는 매개변수가 없는 Action이지만, 람다식이 data를 캡처하고 있기 때문에 Invoke 시점에 CreateBullet에 데이터를 함께 넘길 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 방식은 시그니처가 맞지 않는 메서드를 델리게이트에 등록할 때도 활용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;public Action OnHit;

// PlaySound는 string 매개변수가 필요하지만, OnHit은 매개변수가 없다
string soundName = &quot;Hit_SFX&quot;;
OnHit += () =&amp;gt; PlaySound(soundName);  // 캡처로 해결
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단, 앞서 설명한 것처럼 캡처는 값이 아니라 &lt;b&gt;변수를 참조&lt;/b&gt;한다는 점을 잊지 말자. 캡처 이후 변수의 값이 바뀌면 Invoke 시점의 결과도 달라진다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>Unity,C#/Unity 정보</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/58</guid>
      <comments>https://hate-errorlog.tistory.com/58#entry58comment</comments>
      <pubDate>Thu, 7 May 2026 16:05:06 +0900</pubDate>
    </item>
    <item>
      <title>1. 리치마작을 배워보자! - 마작의 기본</title>
      <link>https://hate-errorlog.tistory.com/57</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 마작 패&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;868&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QSKyY/dJMcahw0ReM/cBUQBsD7Y1L1KauSUZkTT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QSKyY/dJMcahw0ReM/cBUQBsD7Y1L1KauSUZkTT1/img.png&quot; data-alt=&quot;마작 패&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QSKyY/dJMcahw0ReM/cBUQBsD7Y1L1KauSUZkTT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQSKyY%2FdJMcahw0ReM%2FcBUQBsD7Y1L1KauSUZkTT1%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;720&quot; height=&quot;407&quot; data-origin-width=&quot;1536&quot; data-origin-height=&quot;868&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마작 패&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마작의 패는 숫자가 적힌 수패(만수패, 통수패, 삭수패)와, 글자가 적힌 자패(동&amp;middot;남&amp;middot;서&amp;middot;북의 풍패와 백&amp;middot;발&amp;middot;중의 삼원패)로 구성된다. 각 패는 동일한 종류가 총 4장씩 존재한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수패 중에서 1과 9는 &lt;b&gt;노두패&lt;/b&gt;, 2~8은 &lt;b&gt;중장패&lt;/b&gt;라고 부른다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마작은 기본적으로 4명(또는 규칙에 따라 3명)이 참여하며, 각 플레이어는 처음에 13장의 패를 받는다. 자신의 턴이 되면 패산(쌓아놓은 패 더미)에서 패를 한 장 뽑고, 그 후 패 한 장을 버리는 방식으로 게임을 진행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 마작의 승리! 화료(和了)의 조건&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyfuSo/dJMcai3GuYC/kuVt9JM06wAshOEF5Umnjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyfuSo/dJMcai3GuYC/kuVt9JM06wAshOEF5Umnjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyfuSo/dJMcai3GuYC/kuVt9JM06wAshOEF5Umnjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyfuSo%2FdJMcai3GuYC%2FkuVt9JM06wAshOEF5Umnjk%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;720&quot; height=&quot;148&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마작은 4개의 몸통(멘츠)과 1개의 머리(또이츠)를 완성하면 화료(승리)할 수 있다. 머리는 동일한 패 2개로 이루어지며, 몸통은 &lt;b&gt;연속된 수패 3장(예: 2만&amp;middot;3만&amp;middot;4만)&lt;/b&gt; 또는 같은 패 3장(刻子-커쯔)을 모아 만들어진다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;연속된 수패 3장을 슌쯔(順子)라 부르고, 같은 패 3장을 &lt;b&gt;커쯔(刻子)&lt;/b&gt; 라 부른다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1497&quot; data-origin-height=&quot;681&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OTvq3/dJMcahKx8ZX/50KpeWK4ZkGOaT1eXCN9y0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OTvq3/dJMcahKx8ZX/50KpeWK4ZkGOaT1eXCN9y0/img.png&quot; data-alt=&quot;[화료의 방식, 쯔모와 론]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OTvq3/dJMcahKx8ZX/50KpeWK4ZkGOaT1eXCN9y0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOTvq3%2FdJMcahKx8ZX%2F50KpeWK4ZkGOaT1eXCN9y0%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;720&quot; height=&quot;328&quot; data-origin-width=&quot;1497&quot; data-origin-height=&quot;681&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[화료의 방식, 쯔모와 론]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;화료에는 두 가지 방식이 있다. 하나는 직접 패를 뽑아 완성하는 &lt;b&gt;&amp;ldquo;쯔모(和ホ)&amp;rdquo;&lt;/b&gt;와, 상대의 버림패로 완성하는 &lt;b&gt;&amp;ldquo;론(栄)&amp;rdquo;&lt;/b&gt;이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 모든 상황에서 쯔모와 론이 가능한 것은 아니다. 화료를 위해선 반드시 &lt;b&gt;한 판 이상의 역&lt;/b&gt;이 필요하며, 특정 조건에서는 론이 불가능한 경우도 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 상대의 버림패를 가져오는 치, 퐁&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;역을 배우기 전에 치와 퐁에 대해 알아보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마작에서는 패산에서 가져온 패만으로 패를 완성하기 어려운 경우가 많기 때문에, 자신의 패를 완성하기 위해 다른 플레이어의 버림패를 가져올 수 있다. 이러한 행동을 울기&lt;b&gt;(후로 - 鳴き)&lt;/b&gt;라 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;후로라는 단어는 역을 설명하면서 많이 나오는 단어니 꼭 기억해 두자.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;▶ 치(チー)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;842&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOix6Q/dJMcaa5IpJe/BCVWF7UFeBxEkTJ6npUwxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOix6Q/dJMcaa5IpJe/BCVWF7UFeBxEkTJ6npUwxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOix6Q/dJMcaa5IpJe/BCVWF7UFeBxEkTJ6npUwxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOix6Q%2FdJMcaa5IpJe%2FBCVWF7UFeBxEkTJ6npUwxk%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;720&quot; height=&quot;382&quot; data-origin-width=&quot;1585&quot; data-origin-height=&quot;842&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2삭과 3삭을 가지고 있을 때, 1삭이나 4삭이 들어오면 하나의 슌쯔를 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때, 내 왼쪽 플레이어가 슌쯔를 완성할 수 있는 패를 버리면 &lt;b&gt;치(チー)&lt;/b&gt;를 할 수 있다. 치는 반드시 &lt;b&gt;왼쪽 플레이어의 버림패에서만 가능하다.&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-origin-width=&quot;1579&quot; data-origin-height=&quot;853&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dpo7H/dJMcabQ4we6/EKA0qHe67y61GJLM8MsZ01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dpo7H/dJMcabQ4we6/EKA0qHe67y61GJLM8MsZ01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dpo7H/dJMcabQ4we6/EKA0qHe67y61GJLM8MsZ01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDpo7H%2FdJMcabQ4we6%2FEKA0qHe67y61GJLM8MsZ01%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;720&quot; height=&quot;389&quot; data-origin-width=&quot;1579&quot; data-origin-height=&quot;853&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;치를 하면 완성된 몸통(멘츠)을 한쪽에 따로 놓고 패를 공개한다. 이후 자신의 패에서 한 장을 버리며 턴이 종료된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단, 다음과 같은 경우는 주의해야 한다. 예를 들어 2삭&amp;middot;3삭&amp;middot;4삭으로 이미 슌쯔를 완성한 상태에서, 치를 통해 1삭&amp;middot;2삭&amp;middot;3삭이라는 새로운 슌쯔를 만들었다고 하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때 &lt;b&gt;4삭을 버리는 것은 금지된다.&lt;/b&gt; 이는 치를 통해 만든 슌쯔와 관련된 패를 바로 교체하는 행위이기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 행위를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;쿠이카에(食い替え, 울어 바꾸기)&lt;/b&gt;&lt;/span&gt;라고 하며, 일반적으로 금지되는 플레이이다. 대부분의 온라인 마작에서는 시스템적으로 제한되며, 오프라인 마작에서도 반칙으로 간주된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;▶ 퐁(ポン)&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/45xDh/dJMcajnYlqn/44rzRUTWgQSD6x9ZIQ7fk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/45xDh/dJMcajnYlqn/44rzRUTWgQSD6x9ZIQ7fk0/img.png&quot; data-alt=&quot;[퐁]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/45xDh/dJMcajnYlqn/44rzRUTWgQSD6x9ZIQ7fk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F45xDh%2FdJMcajnYlqn%2F44rzRUTWgQSD6x9ZIQ7fk0%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;720&quot; height=&quot;382&quot; data-origin-width=&quot;1590&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[퐁]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3통 두 장을 가지고 있는 상태에서 3통이 한 장 더 있으면 하나의 커쯔를 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때, 다른 플레이어가 3통을 버리면 플레이어의 위치와 관계없이 &lt;b&gt;퐁(ポン)&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-origin-width=&quot;1302&quot; data-origin-height=&quot;231&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btgySL/dJMcagLDSCU/M9KKW40DTveJvYy2CDqKD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btgySL/dJMcagLDSCU/M9KKW40DTveJvYy2CDqKD1/img.png&quot; data-alt=&quot;[퐁한 패를 공개하는 방식]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btgySL/dJMcagLDSCU/M9KKW40DTveJvYy2CDqKD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtgySL%2FdJMcagLDSCU%2FM9KKW40DTveJvYy2CDqKD1%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;730&quot; height=&quot;130&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;231&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[퐁한 패를 공개하는 방식]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;퐁은 치와 달리 모든 플레이어의 버림패를 가져올 수 있다. 따라서 어떤 플레이어로부터 패를 가져왔는지를 표시해야 한다. 일반적으로는 가져온 패의 위치에 따라 해당 패를 가로로 눕혀 놓는 방식으로 표현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를 들어, 내 기준 왼쪽 플레이어에게서 가져왔다면 왼쪽에 있는 패를 눕혀 놓는다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;온라인 마작들은 이를 알아서 표시해 준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;퐁을 하게 되면 커쯔를 공개하게 되는데, 이를 &lt;b&gt;밍커(明刻)&lt;/b&gt;라 하고 반대로 퐁 하지 않은 커쯔를 &lt;b&gt;안커(暗刻) = 숨겨진 커쯔&lt;/b&gt;라 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 4개의 패를 모아버렸다! 깡(カン)!&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/65BoF/dJMcajhey4E/GArgGXzf0cVlSRcQVIU4d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/65BoF/dJMcajhey4E/GArgGXzf0cVlSRcQVIU4d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/65BoF/dJMcajhey4E/GArgGXzf0cVlSRcQVIU4d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F65BoF%2FdJMcajhey4E%2FGArgGXzf0cVlSRcQVIU4d1%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;730&quot; height=&quot;389&quot; data-origin-width=&quot;1592&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 개의 커쯔(3개의 남)가 있는 상황에서, 상대가 남 한 장을 버릴 때 깡을 할 수 있다. 깡을 하게 되면 동시에 상대방의 버림패를 가져온 뒤, 패를 공개한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHxNsq/dJMcadBhUuW/KVKHcWOlnyzMoUkUUYbf0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHxNsq/dJMcadBhUuW/KVKHcWOlnyzMoUkUUYbf0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHxNsq/dJMcadBhUuW/KVKHcWOlnyzMoUkUUYbf0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHxNsq%2FdJMcadBhUuW%2FKVKHcWOlnyzMoUkUUYbf0k%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;730&quot; height=&quot;390&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;851&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;같은 패 3장(예: 남 3장)을 가지고 있는 상태에서, 다른 플레이어가 동일한 패를 버리면 깡(칸カン)을 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깡을 하면 상대의 버림패를 가져와 같은 패 4장을 만들고, 이를 공개하여 한쪽에 놓는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깡의 가장 큰 특징은 치나 퐁과 달리, 패를 공개한 후 바로 턴이 끝나는 것이 아니라 &lt;b&gt;영상(嶺上)&lt;/b&gt;에서 패를 한 장 더 가져온다는 점이다. 이 패를 &lt;b&gt;영상패(嶺上牌)&lt;/b&gt;라고 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;영상패에 대해서는 도라와 같이 설명하겠다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;영상패를 가져온 뒤에는, 다른 경우와 마찬가지로 패를 한 장 버리고 턴을 마친다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커쯔를 들고 있는 상태에서 상대의 버림패로 깡을 하는 경우를 &amp;ldquo;대명깡&amp;rdquo;이라 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;801&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GTNy6/dJMcagY81gM/GicJb8fqlGzPLAd4gcOWgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GTNy6/dJMcagY81gM/GicJb8fqlGzPLAd4gcOWgk/img.png&quot; data-alt=&quot;[전설의 스깡즈 자일색 영상 일부분]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GTNy6/dJMcagY81gM/GicJb8fqlGzPLAd4gcOWgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGTNy6%2FdJMcagY81gM%2FGicJb8fqlGzPLAd4gcOWgk%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;730&quot; height=&quot;413&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;801&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[전설의 스깡즈 자일색 영상 일부분]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;중 3장을 퐁한 상태에서, 동일한 패인 중을 한 장 더 뽑으면 &amp;ldquo;깡&amp;rdquo;을 할 수 있다. 이 경우, 기존에 공개된 퐁에 해당 패를 추가하여 4장을 만들고, 추가된 패는 일반적으로 위에 겹쳐 놓는 방식으로 표시한다. 이후 영상(嶺上)에서 패를 한 장 더 가져온다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이미 퐁을 한 상태에서는 상대의 버림패로 깡을 할 수 없으며, 반드시 자신이 직접 뽑은 패로만 가능하다. 이러한 깡을 &lt;b&gt;가깡(加槓)&lt;/b&gt;이라고 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1433&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tp0ir/dJMcagESKCN/DCEfNjB7MyBOtDWtS6VHyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tp0ir/dJMcagESKCN/DCEfNjB7MyBOtDWtS6VHyk/img.png&quot; data-alt=&quot;[암깡]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tp0ir/dJMcagESKCN/DCEfNjB7MyBOtDWtS6VHyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftp0ir%2FdJMcagESKCN%2FDCEfNjB7MyBOtDWtS6VHyk%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;730&quot; height=&quot;405&quot; data-origin-width=&quot;1433&quot; data-origin-height=&quot;796&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[암깡]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;같은 패 4장을 모두 자신의 손패로 가지고 있는 경우에도 깡을 할 수 있으며, 이를 &lt;b&gt;암깡(暗槓)&lt;/b&gt;이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;암깡은 다른 울기와 달리 외부에서 가져온 패가 없기 때문에, 패를 공개할 때 &lt;b&gt;양 끝의 패를 뒤집어 놓고 가운데 두 장만 보이도록 배치&lt;/b&gt;하는 방식으로 표시한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;암깡을 한 뒤에는 다른 깡과 마찬가지로 영상에서 패를 한 장 더 가져온다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: start;&quot;&gt;깡의 종류&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; &lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: start;&quot;&gt;명칭&lt;/span&gt; &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상대의 버림패로부터 깡&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대명깡(大明槓)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;퐁 후에 내가 뽑은 패로 깡&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가깡(加槓) 혹은 소명깡&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;손패 4장으로 깡&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;암깡(暗槓)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깡을 할 수 있다고 해서 항상 깡을 하는 것이 좋은 것은 아니다. 깡을 하면 새로운 &lt;b&gt;&amp;ldquo;도라&amp;rdquo; 라고 불리는 패&lt;/b&gt;가 공개되며, 이는 모든 플레이어의 점수를 높일 수 있는 요소가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 자신이 유리한 상황이 아니라면, 깡을 신중하게 선택하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. 점수를 올리는 가장 쉬운 방법 : 도라&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLH6i6/dJMcaf0eiEo/wk6UFz7mYpiM0v503UpB3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLH6i6/dJMcaf0eiEo/wk6UFz7mYpiM0v503UpB3k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLH6i6/dJMcaf0eiEo/wk6UFz7mYpiM0v503UpB3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLH6i6%2FdJMcaf0eiEo%2Fwk6UFz7mYpiM0v503UpB3k%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;730&quot; height=&quot;389&quot; data-origin-width=&quot;1596&quot; data-origin-height=&quot;851&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리치마작을 하다 보면(온라인) 화면의 한쪽에 여러 장의 패가 표시되는데, 이 부분이 바로 &lt;b&gt;도라 표시패&lt;/b&gt;이다. 표시된 패의 &lt;b&gt;다음 패&lt;/b&gt;가 도라가 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;8삭이 표시되어 있다면 9삭이 도라가 되며, 9만이 표시되어 있다면 1만이 도라가 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자패의 경우, 동 &amp;rarr; 남 &amp;rarr; 서 &amp;rarr; 북 순으로 이어지며, 동이 도라 표시패라면 남이 도라가 된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;백 &amp;rarr; 발 &amp;rarr; 중 순서를 가지며, 백이 표시되어 있다면 발이 도라가 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 외에도 5만, 5통, 5삭 중 일부는 &lt;b&gt;적도라(아카도라)&lt;/b&gt;로 존재한다. 이는 일반적으로 각 종류당 1장씩 포함되며, 해당 패를 가지고 있으면 도라와 동일하게 추가 점수를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;도라는 그 자체로는 역이 아니지만, 가지고 있는 개수만큼 점수가 추가되기 때문에 점수를 크게 올릴 수 있는 중요한 요소이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1435&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ZrgT/dJMcahRh0vA/O36w4hSU6ElKDAuUMJKvm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ZrgT/dJMcahRh0vA/O36w4hSU6ElKDAuUMJKvm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ZrgT/dJMcahRh0vA/O36w4hSU6ElKDAuUMJKvm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ZrgT%2FdJMcahRh0vA%2FO36w4hSU6ElKDAuUMJKvm1%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;730&quot; height=&quot;401&quot; data-origin-width=&quot;1435&quot; data-origin-height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깡을 하면 새로운 도라 표시패가 공개된다. 이로 인해 전체적으로 높은 타점이 나올 가능성이 커지므로, 상황에 따라 유리할 수도 있고 불리할 수도 있다. 따라서 깡은 신중하게 선택하는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;745&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/byUdKg/dJMb99Z31FY/G7tx2prhvyfx4c9Kc3PnKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/byUdKg/dJMb99Z31FY/G7tx2prhvyfx4c9Kc3PnKk/img.png&quot; data-alt=&quot;[뒷도라]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/byUdKg/dJMb99Z31FY/G7tx2prhvyfx4c9Kc3PnKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbyUdKg%2FdJMb99Z31FY%2FG7tx2prhvyfx4c9Kc3PnKk%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;730&quot; height=&quot;345&quot; data-origin-width=&quot;1575&quot; data-origin-height=&quot;745&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[뒷도라]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이후에 설명할 &amp;ldquo;리치&amp;rdquo;를 선언한 뒤 화료하게 되면, 추가로 &lt;b&gt;뒷도라(우라도라, 裏ドラ)&lt;/b&gt;를 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;온라인 마작에서는 패산의 모습이 보이지 않기 때문에 구조를 직접 확인하기 어렵지만, 실제 오프라인 마작 테이블에서는 다음과 같은 형태로 구성되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/84oe9/dJMcajhey6x/d4FbMJMVSlvlozD9sL22c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/84oe9/dJMcajhey6x/d4FbMJMVSlvlozD9sL22c1/img.png&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;519&quot; data-is-animation=&quot;false&quot; width=&quot;500&quot; height=&quot;331&quot; style=&quot;width: 44.746%; margin-right: 10px;&quot; data-widthpercent=&quot;45.27&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/84oe9/dJMcajhey6x/d4FbMJMVSlvlozD9sL22c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F84oe9%2FdJMcajhey6x%2Fd4FbMJMVSlvlozD9sL22c1%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;784&quot; height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lARsL/dJMcajhey6D/jVZgKYEKvmKj0s6WRgcVkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lARsL/dJMcajhey6D/jVZgKYEKvmKj0s6WRgcVkK/img.png&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;322&quot; data-is-animation=&quot;false&quot; width=&quot;500&quot; height=&quot;274&quot; style=&quot;width: 54.0912%;&quot; data-widthpercent=&quot;54.73&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lARsL/dJMcajhey6D/jVZgKYEKvmKj0s6WRgcVkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlARsL%2FdJMcajhey6D%2FjVZgKYEKvmKj0s6WRgcVkK%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;588&quot; height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;[마작 테이블과 왕패]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깡을 하면 영상패에서 패를 한 장 가져온다. 그 후 &lt;b&gt;깡도라 표시패&lt;/b&gt;를 뒤집어 새로운 도라 표시패를 추가로 공개한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리치를 선언한 뒤 화료하게 되면, 이미 공개된 도라 표시패의 개수만큼 뒷도라 표시패를 추가로 뒤집어 도라를 확인한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 패만 맞춘다고 끝이 아니다, &lt;b&gt;역(役)&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 4개의 몸통과 1개의 머리를 완성하더라도, 반드시 화료할 수 있는 것은 아니라고 설명했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;화료를 하기 위해서는 반드시 1판 이상의 &lt;b&gt;역(役)&lt;/b&gt;이 필요하며, 역이 없는 경우에는 패를 완성해도 화료할 수 없다. 또한 역의 종류와 난이도에 따라 1판, 2판, 3판, 6판, 역만 등으로 구분되며, 이에 따라 얻을 수 있는 점수가 크게 달라진다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 치, 퐁, 깡에 대해 설명했는데, 이러한 행동을 하면 &lt;b&gt;후로(副露, 흔히 &amp;lsquo;울었다&amp;rsquo;라고 표현)&lt;/b&gt; 상태에 들어가게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;역의 조건 중에는 후로 상태에서는 성립하지 않고, 멘젠(門前, 후로를 하지 않은 상태)일 때만 성립하는 역이 많다. 따라서 역에 익숙하지 않다면 &lt;b&gt;함부로 울지 않는 것이 중요하다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 리치마작의 꽃, 리치(立直, 판수1)&amp;lt;멘젠&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2543&quot; data-origin-height=&quot;1355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbuc3g/dJMcahX3Wug/9GmoaflKbpUG8nzDAeo2l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbuc3g/dJMcahX3Wug/9GmoaflKbpUG8nzDAeo2l1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbuc3g/dJMcahX3Wug/9GmoaflKbpUG8nzDAeo2l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbuc3g%2FdJMcahX3Wug%2F9GmoaflKbpUG8nzDAeo2l1%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;730&quot; height=&quot;389&quot; data-origin-width=&quot;2543&quot; data-origin-height=&quot;1355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lnWuh/dJMcagdNL8R/RL6tIfPR65E8aVjSXGBR71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lnWuh/dJMcagdNL8R/RL6tIfPR65E8aVjSXGBR71/img.png&quot; data-alt=&quot;[리치를 걸 수 있는 상황]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lnWuh/dJMcagdNL8R/RL6tIfPR65E8aVjSXGBR71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlnWuh%2FdJMcagdNL8R%2FRL6tIfPR65E8aVjSXGBR71%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;730&quot; height=&quot;84&quot; data-origin-width=&quot;1554&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[리치를 걸 수 있는 상황]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;먼저 위 패를 보자. 6삭을 뽑은 상태에서 어떤 패를 버리면 텐파이가 될 수 있을까?&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 개의 패를 쯔모(혹은 론)했을 때 화료할 수 있는 상태를 &lt;b&gt;텐파이(聴牌)&lt;/b&gt; 상태라고 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3만, 3만, 4만, 5만, 1삭, 2삭, 4삭, 5삭, 6삭, 6삭, 7삭, 8삭, 9삭이 있는 상태에서 6삭을 쯔모한 상황이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;507&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7xctz/dJMcacoVp4L/DK7EcgykwsKBb52cVEE75K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7xctz/dJMcacoVp4L/DK7EcgykwsKBb52cVEE75K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7xctz/dJMcacoVp4L/DK7EcgykwsKBb52cVEE75K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7xctz%2FdJMcacoVp4L%2FDK7EcgykwsKBb52cVEE75K%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;730&quot; height=&quot;239&quot; data-origin-width=&quot;1551&quot; data-origin-height=&quot;507&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3만을 버리면 3만&amp;middot;4만&amp;middot;5만으로 슌쯔를 만들 수 있고, 6삭 두 장으로 머리(또이츠)를 구성할 수 있다. 이 경우 &lt;b&gt;3삭을 기다리는 텐파이 상태&lt;/b&gt;가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 울지 않은 상태(멘젠)에서 텐파이가 되면 &lt;b&gt;리치(立直)&lt;/b&gt;를 선언할 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;온라인 마작에서는 리치 버튼을 누르면 어떤 패를 버려야 텐파이 상태로 들어가는지 알려주기 때문에 크게 부담 가지지 않아도 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZaSZW/dJMcahqeVqE/mM4Qj4T1ab4f9Zx1zI6bK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZaSZW/dJMcahqeVqE/mM4Qj4T1ab4f9Zx1zI6bK1/img.png&quot; data-alt=&quot;[리치를 걸고 난 뒤]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZaSZW/dJMcahqeVqE/mM4Qj4T1ab4f9Zx1zI6bK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZaSZW%2FdJMcahqeVqE%2FmM4Qj4T1ab4f9Zx1zI6bK1%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;730&quot; height=&quot;389&quot; data-origin-width=&quot;1591&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[리치를 걸고 난 뒤]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리치를 선언하면 1000점을 지불하고 자신의 앞에 리치봉을 놓는다. 리치를 한 이후에는 패의 형태를 변경할 수 없으며, 이후에는 쯔모한 패를 그대로 타패해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2543&quot; data-origin-height=&quot;1352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bf3izR/dJMcafFXnFj/8lr60nLTMuyKcPUhWENvXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bf3izR/dJMcafFXnFj/8lr60nLTMuyKcPUhWENvXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bf3izR/dJMcafFXnFj/8lr60nLTMuyKcPUhWENvXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbf3izR%2FdJMcafFXnFj%2F8lr60nLTMuyKcPUhWENvXk%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;730&quot; height=&quot;388&quot; data-origin-width=&quot;2543&quot; data-origin-height=&quot;1352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;특정 패를 기다리고 있는 상태라면, 그 패를 대기패(화료패)라고 한다. 예를 들어 3삭을 기다리고 있는 상태에서 상대가 3삭을 버리면 &amp;ldquo;론&amp;rdquo;으로 화료할 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또는 내가 3삭을 직접 뽑으면 &amp;ldquo;쯔모&amp;rdquo;로 화료할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;론으로 화료하게 되면, 해당 패를 버린 플레이어로부터 점수를 모두 가져오게 된다(이를 흔히 &amp;ldquo;쏘였다&amp;rdquo;고 표현한다).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;반대로 쯔모로 화료하게 되면, 나를 제외한 다른 플레이어들이 점수를 나누어 지불하게 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단, 내가 동 위치에 있는 상태에서 상대방이 쯔모하게 되면 남들보다 더 점수를 내야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정리하자면 울지 않고(멘젠), 텐파이 상태, 패산에 패가 4장 이상 남아있는 상황이라면 리치를 걸 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;▼ 멘젠 상태에서 할 수 있는 역&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 105px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 16.0465%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 이름 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 7.32558%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 판수 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.5116%; text-align: center; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 조건 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 16.0465%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;일발&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 7.32558%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.5116%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리치 선언 후 한 턴 이내(아무도 치,퐁,깡을 하지 않음)에 화료하는 경우. &amp;rarr; 론, 쯔모 둘다 가능하다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 16.0465%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우라도라(뒷도라)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 7.32558%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;n&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.5116%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리치 화료 시에만 확인 가능한 보너스 도라. 도라 표시패 개수만큼 뒤집는다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 16.0465%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멘젠쯔모&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 7.32558%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.5116%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리치를 걸지 않아도, 멘젠 상태에서 쯔모하는 경우 성립되는 역이다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 16.0465%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;더블리치&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 7.32558%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 76.5116%; height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;첫 턴에서(모든 플레이어가 멘젠) 타패 전에 리치하는 경우 성립된다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;▶ 무지성 리치는 안된다!&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;초보자일 때는 리치 버튼이 보이면 바로 누르고 싶어 지지만, 상황을 고려하지 않고 리치를 걸면 오히려 불리해질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같은 상황에서 내가 화료하기 위해 필요한 패는 오로지 3삭 하나뿐이다. 만약 3삭이 이미 여러 장 버려져 있다면, 남아 있는 3삭의 개수가 적어 화료 확률이 크게 낮아진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 리치를 선언하면 이후에는 패의 형태를 변경할 수 없기 때문에, 수비적인 선택이 매우 제한된다. 상황에 따라 안전패를 선택하기 어려워져, 오히려 상대에게 쏘일 위험이 커질 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 리치는 가능한 한 &lt;b&gt;대기패의 종류가 많고(좋은 대기)&lt;/b&gt;, 화료 확률이 높은 상황에서 거는 것이 좋다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 울어도 된다! 탕야오(断么九)와 역패(役牌)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1493&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ5kiF/dJMcaiimxVL/eIst0515ZKPLWFFjousynK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ5kiF/dJMcaiimxVL/eIst0515ZKPLWFFjousynK/img.png&quot; data-alt=&quot;[탕야오와 쿠이탕]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ5kiF/dJMcaiimxVL/eIst0515ZKPLWFFjousynK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ5kiF%2FdJMcaiimxVL%2FeIst0515ZKPLWFFjousynK%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;730&quot; height=&quot;170&quot; data-origin-width=&quot;1493&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[탕야오와 쿠이탕]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수패 중 1과 9(노두패/요구패)와 자패(동&amp;middot;남&amp;middot;서&amp;middot;북, 백&amp;middot;발&amp;middot;중)가 하나도 포함되지 않은 상태로 화료하면 탕야오(断么九)가 성립한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수패 중에서 1과 9를 노두패(老頭牌 혹은 요구패)라고 하며, 자패는 풍패와 삼원패를 포함한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 손패가 2~8의 만수, 통수, 삭수로만 이루어져야 성립하는 역이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;울어서 만든 탕야오를 쿠이탕(喰い断)이라고 하며, 룰에 따라 이를 인정하지 않는 경우도 있지만, 많이 플레이하는 작혼에서는 유효한 역으로 인정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;탕야오는 비교적 빠르게 만들 수 있는 역이기 때문에, 상대가 빠르게 리치를 걸었거나 판의 흐름이 불리하다고 판단될 때 빠르게 화료하여 판을 넘기는 전략으로 활용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;838&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cecf3h/dJMcabwLBT0/9AbYwVCmENFcgsPGMt49MK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cecf3h/dJMcabwLBT0/9AbYwVCmENFcgsPGMt49MK/img.png&quot; data-alt=&quot;[자풍과 장풍]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cecf3h/dJMcabwLBT0/9AbYwVCmENFcgsPGMt49MK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcecf3h%2FdJMcabwLBT0%2F9AbYwVCmENFcgsPGMt49MK%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;730&quot; height=&quot;387&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;838&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[자풍과 장풍]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마작에는 &lt;b&gt;자풍(自風)과 장풍(場風)&lt;/b&gt;이라는 개념이 있다. 내가 앉아 있는 자리의 바람을 자풍이라고 하며(위 예시에서는 서), 현재 판의 바람을 장풍이라고 한다(위 예시에서는 동1국이므로 동).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 개념이 중요한 이유는, 자풍이나 장풍에 해당하는 바람 패를 커쯔로 완성하면, 이 역시 1판의 역으로 인정되기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OK4io/dJMcaiimxWw/eYwAqMXJLLovLbrBUVBR1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OK4io/dJMcaiimxWw/eYwAqMXJLLovLbrBUVBR1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OK4io/dJMcaiimxWw/eYwAqMXJLLovLbrBUVBR1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOK4io%2FdJMcaiimxWw%2FeYwAqMXJLLovLbrBUVBR1K%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;730&quot; height=&quot;185&quot; data-origin-width=&quot;1458&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같이 동1국에서 서쪽에 앉아 있을 경우, 서를 커쯔로 만들면 자풍 역이 성립하고, 동을 커쯔로 만들면 장풍 역이 성립한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만약 동1국에서 동쪽에 앉아 있는 상태에서 동 커쯔를 만들었다면, &lt;b&gt;자풍과 장풍이 동시에 성립하여 2판&lt;/b&gt;을 얻을 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;또한 삼원패(백&amp;middot;발&amp;middot;중) 중 하나만 커쯔로 만들어도 이 역시 자풍,장풍과 관계 없이 역으로 성립한다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. 몸통이 모두 슌쯔! 핑후(&lt;b&gt;平和)&amp;lt;멘젠&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;초보자들은 멘탕핑(멘젠,탕야오,핑후)을 만들어 화료하라고 한다. 멘젠과 탕야오와 달리 핑후는 그 조건이 조금 복잡하다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qN0Nb/dJMcaiJoQqb/5Z9IL5dcHCyF7LnL5ITgH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qN0Nb/dJMcaiJoQqb/5Z9IL5dcHCyF7LnL5ITgH0/img.png&quot; data-alt=&quot;[핑후가 가능한 예시]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qN0Nb/dJMcaiJoQqb/5Z9IL5dcHCyF7LnL5ITgH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqN0Nb%2FdJMcaiJoQqb%2F5Z9IL5dcHCyF7LnL5ITgH0%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;730&quot; height=&quot;193&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[핑후가 가능한 예시]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;패산의 상태를 먼저 살펴보자. 234삭으로 슌쯔, 345삭으로 슌쯔, 456통으로 슌쯔, 8통 또이츠(머리), 4만 5만을 가지고 있는 상태에서 두 종류의 패(3만, 6만)가 들어오면 화료할 수 있는 상태다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때 론이나 쯔모로 화료하면 핑후라는 역을 달성할 수 있다. 핑후의 조건은 다음과 같다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멘쯔(몸통) 4개가 전부 슌쯔.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또이츠(머리)는 수패 혹은 객풍패 (자풍, 장풍, 삼원패는 불가)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;양면 대기 상태.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같이, &lt;b&gt;앞뒤로 패를 기다리는 양면 대기 상태에서만 핑후가 성립한다.&lt;/b&gt; 역패를 머리로 사용할 경우 핑후는 성립하지 않는다. 따라서 핑후는 이름 그대로 &amp;ldquo;평범한 화료&amp;rdquo;라는 의미를 지닌다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;▶ 마작의 점수 계산&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;핑후는 멘젠 상태에서 &lt;b&gt;추가 부수가 붙지 않는 손패&lt;/b&gt; 를 의미한다. 이때 &amp;lsquo;부수&amp;rsquo;란 손패의 구조에 따라 계산되는 점수 단위이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마작의 점수는 판(역의 개수) x 부(패의 구조 점수)로 계산된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기본적으로 20부에서 시작하고, 다음과 같은 경우에 부가 추가 계산된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;같은 패 3개(커쯔)가 있을 때.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;역패(자풍, 장풍, 삼원패)를 머리로 사용할 때&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;양면 대기가 아닌 대기 형태일 때.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;쯔모(+2부) 또는 멘젠 론(+10부)으로 화료할 때.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;쯔모 또는 멘젠 론으로 화료할 때 부수가 추가되므로, &amp;ldquo;핑후는 멘젠 쯔모일 때만 가능한가?&amp;rdquo;라고 생각할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 이는 아니다.   &lt;b&gt;핑후 쯔모는 특례로 쯔모 2부를 계산하지 않는다.&lt;/b&gt; 따라서 멘젠 쯔모 시에도 20부를 유지하며 핑후가 성립한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1679&quot; data-origin-height=&quot;972&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/13cMp/dJMcadOP7rD/bHUGyt24EWUy4yR5fVhgI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/13cMp/dJMcadOP7rD/bHUGyt24EWUy4yR5fVhgI0/img.png&quot; data-alt=&quot;[핑후가 성립하지 않을 때]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/13cMp/dJMcadOP7rD/bHUGyt24EWUy4yR5fVhgI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F13cMp%2FdJMcadOP7rD%2FbHUGyt24EWUy4yR5fVhgI0%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;730&quot; height=&quot;423&quot; data-origin-width=&quot;1679&quot; data-origin-height=&quot;972&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[핑후가 성립하지 않을 때]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;4. 같은 슌쯔가 2개?! 이페코(&lt;b&gt;一盃口)&amp;lt;멘젠&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HRlPb/dJMcaiQacmz/DJeChZQ9fYEWuNFwRK1krK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HRlPb/dJMcaiQacmz/DJeChZQ9fYEWuNFwRK1krK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HRlPb/dJMcaiQacmz/DJeChZQ9fYEWuNFwRK1krK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHRlPb%2FdJMcaiQacmz%2FDJeChZQ9fYEWuNFwRK1krK%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;730&quot; height=&quot;154&quot; data-origin-width=&quot;1370&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멘젠 상태에서 같은 슌쯔가 두 개 있는 경우 이페코 역이 성립한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만약 이페코가 2개(예시 123만/123만, 456통/456통)있으면, 3판 역인 &lt;b&gt;량페코(二盃口)&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt; 5. 화료를 모두 머리로! 치또이츠(七対子)&amp;lt;멘젠, 2판&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;295&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cP4x6U/dJMcadVCCbB/QWLx7KtMGHomt5SaDBKDk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cP4x6U/dJMcadVCCbB/QWLx7KtMGHomt5SaDBKDk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cP4x6U/dJMcadVCCbB/QWLx7KtMGHomt5SaDBKDk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcP4x6U%2FdJMcadVCCbB%2FQWLx7KtMGHomt5SaDBKDk0%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;730&quot; height=&quot;174&quot; data-origin-width=&quot;1241&quot; data-origin-height=&quot;295&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 처럼 14개의 패를 모두 또이츠(7개)로 모은 경우 성립한다. 당연하게도 치,퐁,깡을 하게 되면 멘쯔가 형성되어 패의 구조가 깨지므로, 멘젠 상태에서만 성립할 수 있는 역이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;6. 몸통이 모두 커쯔! 또이또이(&lt;b&gt;対々和)&amp;lt;2판&amp;gt;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1305&quot; data-origin-height=&quot;276&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XhQoJ/dJMcaivR3vT/3IHnZKOsM64XMR2s4asRE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XhQoJ/dJMcaivR3vT/3IHnZKOsM64XMR2s4asRE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XhQoJ/dJMcaivR3vT/3IHnZKOsM64XMR2s4asRE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXhQoJ%2FdJMcaivR3vT%2F3IHnZKOsM64XMR2s4asRE1%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;730&quot; height=&quot;154&quot; data-origin-width=&quot;1305&quot; data-origin-height=&quot;276&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;몸통을 모두 커쯔(후로 가능)로 만들면 성립하는 역이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;초보자들에게는 비추천되는 역인데, 퐁도 가능하고 외우기 쉬운 역이다 보니 무작정 또이또이로 가는 경우가 많다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;핑후와 정반대 역이기 때문에, 핑후에서 추가적으로 붙는 역(멘젠쯔모, 리치, 탕야오 등)을 노릴 수 없으며, 무엇보다 많이 울수록 방어하기도(내 패가 공개되므로) 어려워진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;7. 마작의 스트레이트, 일기통관(一気通貫)&amp;lt;멘젠2판, 후로1판&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;155&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ppErD/dJMcahqeVuh/YAEIu3fAihbTCKFqRPFQz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ppErD/dJMcahqeVuh/YAEIu3fAihbTCKFqRPFQz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ppErD/dJMcahqeVuh/YAEIu3fAihbTCKFqRPFQz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FppErD%2FdJMcahqeVuh%2FYAEIu3fAihbTCKFqRPFQz1%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;730&quot; height=&quot;75&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;155&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 종류의 수패를 1~9까지 모아 123-456-789로 슌쯔 3개를 만들면 성립한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멘젠 시에는 2판 역으로, 후로 시에는 1판 역으로 취급한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;8. 삼색동순(三色同順)&amp;lt;멘젠2판, 후로1판&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;145&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oxRcY/dJMcaduxdBc/nkAmHBcjq1W46YkYL2Y7dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oxRcY/dJMcaduxdBc/nkAmHBcjq1W46YkYL2Y7dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oxRcY/dJMcaduxdBc/nkAmHBcjq1W46YkYL2Y7dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoxRcY%2FdJMcaduxdBc%2FnkAmHBcjq1W46YkYL2Y7dk%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;730&quot; height=&quot;69&quot; data-origin-width=&quot;1529&quot; data-origin-height=&quot;145&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;서로 다른 종류의 같은 슌쯔(2만3만4만/2삭3삭4삭/2통3통4통)를 모으면 성립한다. 일기통관과 동일하게 후로시 한 판 감소한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;반대로 서로 다른 종류의 같은 커쯔(222만/222삭/222통)를 모으면 &lt;b&gt;삼색동각(三色同刻)이 된다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위에서는 자주 등장하며 실전에서 활용도가 높은 역들을 중심으로 정리하였다. 이제 나머지 역들을 간단히 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 518px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; text-align: center; width: 20.2326%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 이름&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; text-align: center; width: 79.6512%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 조건 &lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;해저로월(海底撈月)&lt;br /&gt;&amp;lt;1판&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;패산의 맨 마지막에 남아있는 패를 해저(海底)라 하며, 이것을 쯔모해서 화료하는 경우 성립한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하저로어(河底撈魚) &lt;br /&gt;&amp;lt;1판&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막에 버려지는 패(하저패)로 화료하는 경우 성립한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;영상개화(嶺上開花)&lt;br /&gt;&amp;lt;1판&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깡을 한 뒤, 영상에서 가져온 패(영상패)로 화료하는 경우 성립한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;창깡(搶槓, 창공)&lt;br /&gt;&amp;lt;1판&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상대가 이미 퐁 해둔 상태에서 같은 패를 하나 더 가져와 깡(가깡)을 선언했을 때, 그 패가 화료하기 위한 패라면 론을 통해 화료할 수 있다. 이 때, 창깡이라는 1판 역이 성립한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;찬타(全帯幺九) &lt;br /&gt;&amp;lt;2판(후로1)&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 몸통과 머리에 반드시 1개 이상의 요구패(1,9,자패)를 포함하면 성립한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 20.2326%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;혼노두(混老頭)&amp;lt;2판&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 79.6512%;&quot;&gt;&lt;span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 몸통과 머리를 요구패로 만들면 성립한다. 반드시 자패가 포함되어야 한다.&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;소삼원(小三元)&amp;lt;2판&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;삼원패중 두 종류를 몸통, 한 종류를 머리로 만들면 성립한다.(구조상 손패에 소삼원 하나만 성립해도 4판 역이다.)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 20.2326%;&quot;&gt;산안커(三暗刻)&amp;lt;2판&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 79.6512%;&quot;&gt;안커 3개를 만들면 성립한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; width: 20.2326%;&quot;&gt;산깡쯔(三槓子)&amp;lt;2판&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; width: 79.6512%;&quot;&gt;안깡, 가깡, 대명깡 상관 없이 총 3번 깡을 하면 성립한다. 2판 역 치곤 매우 어려운 난이도.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;&lt;b&gt;혼일색(混一色)&lt;br /&gt;&amp;lt;3판(후로2)&amp;gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;한 종류의 수패와 자패만으로 역을 만들면 성립한다. 조건이 간단해 초보자들도 자주 노릴 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;준찬타(純全帯幺九)&lt;br /&gt;&amp;lt;3판(후로2&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;요구패가 한개 이상만 있으면 되는 찬타와 달리, 모든 몸통과 머리에 자패 없이 노두패가 들어가는 형태로 만들어야 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 42px;&quot;&gt;
&lt;td style=&quot;height: 42px; width: 20.2326%;&quot;&gt;청일색(清一色)&lt;br /&gt;&amp;lt;6판(후로5)&amp;gt;&lt;/td&gt;
&lt;td style=&quot;height: 42px; width: 79.6512%;&quot;&gt;한 종류의 &amp;ldquo;수패&amp;rdquo;로만 화료했을 때 성립한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 56px;&quot;&gt;
&lt;td style=&quot;height: 56px; width: 20.2326%;&quot;&gt;유국만관 (流し満貫)&lt;/td&gt;
&lt;td style=&quot;height: 56px; width: 79.6512%;&quot;&gt;자신의 버림패가 모두 요구패인 상태에서 유국(아무도 화료하지 않고 게임이 끝남)하면 성립한다. &lt;s&gt;요구패만 나와서 불쌍하니 점수를 주겠다는 느낌.&lt;br /&gt;&lt;/s&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - &lt;span data-token-index=&quot;0&quot;&gt;누군가 한번이라도 내 버림패를 치,퐁,깡 하면 성립하지 않음.&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 매우 큰 한 탕을 노리는 역, 역만&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;역만은 난이도가 매우 높지만, 완성하는 순간 판을 뒤집을 수 있는 강력한 조합이다. 그만큼 보기 드물지만, 기본적인 형태를 알고 있지 않으면 눈앞의 역만을 놓치는 아쉬운 상황이 생길 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 이번에는 비교적 자주 등장하는 대표적인 역만들을 간단히 살펴보자.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;역만은 한 번에 최소 32,000점을 얻을 수 있는 역이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 국사무쌍(国士無双)&amp;lt;멘젠&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwiDRc/dJMcajhezpv/kq3alYhQ39IV3joWIvNnqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwiDRc/dJMcajhezpv/kq3alYhQ39IV3joWIvNnqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwiDRc/dJMcajhezpv/kq3alYhQ39IV3joWIvNnqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwiDRc%2FdJMcajhezpv%2Fkq3alYhQ39IV3joWIvNnqk%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;594&quot; height=&quot;147&quot; data-origin-width=&quot;594&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;13종의 요구패(1통, 9통, 1삭, 9삭, 1만, 9만, 동, 남, 서, 북, 백, 발, 중)를 모두 갖추고 있는 상태에서 화료하면 성립한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;머리를 만들어야 하기 때문에, 위의 13종 중 하나는 2개를 모아야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;처음 패를 받았을 때, 서로 다른 요구패가 9종류 있으면 &lt;b&gt;유국(판 리셋)&lt;/b&gt;을 선언할 수 있다. 하지만 유국을 선언하지 않고 국사무쌍을 노리는 경우도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;조합만 보면 어려운 역으로 생각되지만, 스안커, 대삼원과 더불어 가장 쉬운 축에 속하는 역만이다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #cf5148;&quot; data-token-index=&quot;0&quot;&gt;▶ &lt;/span&gt;&lt;span style=&quot;color: #cf5148;&quot; data-token-index=&quot;1&quot;&gt;국사무쌍 13면대기(国士無双 十三面聽)&amp;nbsp;&amp;lt;멘젠&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;147&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I4P3V/dJMcab4BXgZ/OY1oyaGhCn4PVsTgLPEVI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I4P3V/dJMcab4BXgZ/OY1oyaGhCn4PVsTgLPEVI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I4P3V/dJMcab4BXgZ/OY1oyaGhCn4PVsTgLPEVI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI4P3V%2FdJMcab4BXgZ%2FOY1oyaGhCn4PVsTgLPEVI1%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;687&quot; height=&quot;147&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 요구패 13종을 중복 없이 모으면, 어느 요구패를 하나 더 가져와도 화료할 수 있는 상태가 되므로13면 대기 형태를 만들 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;국사무쌍 13면 대기의 경우 더블 역만 처리된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;국사무쌍 같은 경우, 생각보다 쏘이기 쉽다. 요구패는 잘 쓰기 않기 때문에 버리는 경우가 많기 때문이다. 따라서 상대방이 요구패가 아닌 중장패(2~8)만 버린다면 국사무쌍을 의심해봐도 된다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 스안커(四暗刻)&amp;lt;멘젠&amp;gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;148&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ErAWy/dJMcafzcmiM/jnCS3c4kW8ZvJIdA9cZDHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ErAWy/dJMcafzcmiM/jnCS3c4kW8ZvJIdA9cZDHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ErAWy/dJMcafzcmiM/jnCS3c4kW8ZvJIdA9cZDHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FErAWy%2FdJMcafzcmiM%2FjnCS3c4kW8ZvJIdA9cZDHK%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;589&quot; height=&quot;148&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;148&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 멘쯔를 안커로 만들면 성립한다. &lt;b&gt;단, 마지막 멘쯔를 론으로 완성할 경우 해당 멘쯔는 안커로 인정되지 않으므로 스안커는 쯔모로 화료해야 성립한다.&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-origin-width=&quot;595&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d86DSA/dJMcaiWVX3h/BzouLtFRtqGwYKK4kkkZD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d86DSA/dJMcaiWVX3h/BzouLtFRtqGwYKK4kkkZD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d86DSA/dJMcaiWVX3h/BzouLtFRtqGwYKK4kkkZD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd86DSA%2FdJMcaiWVX3h%2FBzouLtFRtqGwYKK4kkkZD1%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;595&quot; height=&quot;153&quot; data-origin-width=&quot;595&quot; data-origin-height=&quot;153&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단, 위와 같이 멘쯔를 모두 안커로 만든 상태에서 또이츠를 대기중이라면 론 할 수 있고, 이는 스안커 단기로 더블 역만 처리된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. 대삼원(大三元)&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQGfCD/dJMb996N3ba/XcM8dnokUv2i45GH5UyuNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQGfCD/dJMb996N3ba/XcM8dnokUv2i45GH5UyuNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQGfCD/dJMb996N3ba/XcM8dnokUv2i45GH5UyuNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQGfCD%2FdJMb996N3ba%2FXcM8dnokUv2i45GH5UyuNK%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;421&quot; height=&quot;74&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 삼원패를 멘쯔로 만들거나, 마지막에 완성시켜 화료하는 경우 성립한다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;굳이 멘젠일 필요가 없으므로 가장 하기 쉬운 역이지만, 삼원패 2개가 퐁 되면 그때부터 남은 삼원패를 함부로 버리지 않기에 어찌 보면 가장 어려운 역이기도 하다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 론을 할수 없다고? 후리텐(振聴)&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu07L3/dJMcaiQaczF/HKTB4BJBBm4IFiz6NP8941/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu07L3/dJMcaiQaczF/HKTB4BJBBm4IFiz6NP8941/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu07L3/dJMcaiQaczF/HKTB4BJBBm4IFiz6NP8941/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu07L3%2FdJMcaiQaczF%2FHKTB4BJBBm4IFiz6NP8941%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;500&quot; height=&quot;281&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내가 버린 패에 화료에 필요한 패가 하나라도 포함되어 있다면, &lt;b&gt;상대의 버림패로 론을 할 수 없다. &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를 들어, 3만을 버린 상태에서 텐파이에 진입했고 화료패가 3만, 6만이라면,&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;3만뿐만 아니라 6만으로도 론할 수 없다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 리치를 건 상태에서 화료 가능한 패를 론하지 않으면, &lt;b&gt;그 판이 끝날 때까지 론할 수 없는 영구 후리텐 상태가 된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;후리텐은 사람이 아닌 현재 대기 패를 기준으로 적용되는 규칙이다. 따라서 후리텐 상태에서는 쯔모로만 화료할 수 있으며, 패를 바꿔 대기 형태에서 이미 버린 패가 제외되면 후리텐은 해제된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단, 리치를 건 이후에는 대기를 변경할 수 없으며, 화료 가능한 패를 론하지 않은 경우 해당 판이 끝날 때까지 론할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 마작 용어 정리&lt;/span&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수패&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;숫자가 적힌 패&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자패&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;동&amp;middot;남&amp;middot;서&amp;middot;북의 풍패와 백&amp;middot;발&amp;middot;중의 삼원패&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노두패&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수패 중 1과 9&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;중장패&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;수패 중 2~8&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멘츠&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;몸통&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또이츠&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;머리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;슌쯔&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;연속된 수패 3장&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;커쯔&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;같은 패 3장&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;텐파이&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;화료하기 까지 딱 1장의 패만 남은 상태&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;쯔모&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;텐파이 상태에서 패를 뽑아 화료&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;론&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;텐파이 상태에서 상대의 버림패로 화료&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;후로(울기)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상대방의 버림패를 가져오는 모든 행위&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;멘젠&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;후로하지 않은 상태&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;치&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내 왼쪽의 플레이어로부터 버림패를 가져와 슌쯔를 만든다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;퐁&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위치의 관계 없이 버림패를 가져와 커쯔를 만든다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깡&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;같은 패 4장을 모아 만드는 멘쯔이다&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;대명깡&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상대의 버림패로부터 깡&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가깡&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;퐁 후에 내가 뽑은 패로 깡&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;암깡&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;손패 4장으로 깡&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>게임/리치마작</category>
      <category>리치마작</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/57</guid>
      <comments>https://hate-errorlog.tistory.com/57#entry57comment</comments>
      <pubDate>Sun, 22 Mar 2026 21:57:47 +0900</pubDate>
    </item>
    <item>
      <title>[C++, 자료구조] 트리(Tree)</title>
      <link>https://hate-errorlog.tistory.com/56</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 비선형 자료구조 - 트리(Tree)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우리가 여태까지 다룬 자료구조(배열, 리스트, 스택, 큐, 덱)는 각 데이터가 앞 뒤로 하나의 데이터와만 연결되는 구조를 가지는 &amp;ldquo;선형 자료구조&amp;rdquo;였다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 장에서 설명할 트리(Tree)는 하나의 데이터가 여러 개의 하위 데이터를 가질 수 있는 구조로, &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;계층적인 관계(Hierarchical Relationshipe)&lt;/b&gt;&lt;/span&gt;를 &lt;b&gt;표현하는&lt;/b&gt; 비선형 자료구조인 트리에 대해 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자료구조를 단순히 &amp;ldquo;저장하고 꺼내는 도구&amp;rdquo;로 이해하기 쉽지만, 자료구조의 본질은 &lt;b&gt;어떤 대상을 구조적으로 표현하는 도구이다.&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서, 트리의 추상 자료형(ADT)을 정의할 때도 &amp;ldquo;저장과 삭제가 편리한가?&amp;rdquo;보다 &amp;ldquo;계층 구조를 표현하기에 적절하게 정의되었는가?&amp;rdquo;의 관점에서 바라보는 것이 옳다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 트리의 표현&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트리도 우리 주변에서 쉽게 예시를 찾을 수 있는 자료구조이다. 폴더 구조나 집안의 족보 혹은 기업 및 정보의 조직도로 트리의 예시를 들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1mwIu/dJMcadHR46l/dMxhHAKPpH8vwgEuJCScLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1mwIu/dJMcadHR46l/dMxhHAKPpH8vwgEuJCScLk/img.png&quot; data-alt=&quot;[트리의 예시 - 1]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1mwIu/dJMcadHR46l/dMxhHAKPpH8vwgEuJCScLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1mwIu%2FdJMcadHR46l%2FdMxhHAKPpH8vwgEuJCScLk%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;650&quot; height=&quot;339&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[트리의 예시 - 1]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;나무처럼 보이진 않지만, 이 자료구조에 트리라는 이름이 붙은 이유는 &amp;ldquo;가지를 늘려가며 뻗어나간다&amp;rdquo;라는 공통점이 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOvZjB/dJMcagEFhFL/vo5zwxCRdF4mXa03UK3NDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOvZjB/dJMcagEFhFL/vo5zwxCRdF4mXa03UK3NDK/img.png&quot; data-alt=&quot;[트리의 예시 - 2]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOvZjB/dJMcagEFhFL/vo5zwxCRdF4mXa03UK3NDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOvZjB%2FdJMcagEFhFL%2Fvo5zwxCRdF4mXa03UK3NDK%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;539&quot; height=&quot;383&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[트리의 예시 - 2]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같은 트리를 &amp;ldquo;의사 결정 트리(Decision tree)&amp;rdquo;라 한다. 의사 결정 트리는 다양한 데이터 분석 기법의 도구가 되며, 경영학 등 공학 이외의 영역에서도 유용하게 사용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 트리를 이용해 무엇을 저장하고 꺼내야 한다는 생각을 지우고, 무엇을 표현하는 도구라고 생각하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 트리 용어&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/F4lDV/dJMcahQ4QJn/4mseAbUsTG71EB1B43TJTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/F4lDV/dJMcahQ4QJn/4mseAbUsTG71EB1B43TJTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/F4lDV/dJMcahQ4QJn/4mseAbUsTG71EB1B43TJTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FF4lDV%2FdJMcahQ4QJn%2F4mseAbUsTG71EB1B43TJTk%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;506&quot; height=&quot;475&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노드(Node)&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트리의 구성 요소에 해당하는 A, B, C, D, E, F와 같은 요소.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;간선(Edge)&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노드와 노드를 연결하는 연결선&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;루트 노드(Root Node)&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트리의 최상단에 존재하는 A와 같은 노드.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단말 노드(Leaf Node/Terminal Node)&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아래로 또 다른 노드가 연결되어 있지 않은(자식이 없는) C, D, E, F와 같은 노드.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다른 표현으로 Terminal Node라고 불리지만, Leaf Node라는 표현이 더 많이 쓰인다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;내부 노드(Internal Node)&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단말 노드를 제외한 모든 노드.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvlORO/dJMcac3htJG/Ep43tOnaJN1KukeFnAjtd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvlORO/dJMcac3htJG/Ep43tOnaJN1KukeFnAjtd1/img.png&quot; data-alt=&quot;[트리 관계]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvlORO/dJMcac3htJG/Ep43tOnaJN1KukeFnAjtd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvlORO%2FdJMcac3htJG%2FEp43tOnaJN1KukeFnAjtd1%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;493&quot; height=&quot;448&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[트리 관계]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 위처럼 트리의 노드 간에는 부모(parent), 자식(child), 형제(sibling)의 관계가 성립된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX68N2/dJMb996ADwo/7p81L7ZJlJ8IbTl9EJT8l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX68N2/dJMb996ADwo/7p81L7ZJlJ8IbTl9EJT8l0/img.png&quot; data-alt=&quot;[트리의 높이와 레벨]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX68N2/dJMb996ADwo/7p81L7ZJlJ8IbTl9EJT8l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX68N2%2FdJMb996ADwo%2F7p81L7ZJlJ8IbTl9EJT8l0%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;728&quot; height=&quot;389&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[트리의 높이와 레벨]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트리에서는 루트 노드부터 각 층별로 숫자를 매겨 이를 트리의 &lt;b&gt;&amp;ldquo;레벨&amp;rdquo;&lt;/b&gt;이라고 하고, 트리의 최고 레벨을 가리켜 &lt;b&gt;&amp;ldquo;높이&amp;rdquo;&lt;/b&gt;라 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 이진트리(Binary Tree)와 서브 트리(Sub Tree)&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;446&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kB5z5/dJMcagxQS9A/OvLLOvihNVGfTx7WCiddqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kB5z5/dJMcagxQS9A/OvLLOvihNVGfTx7WCiddqK/img.png&quot; data-alt=&quot;[서브 트리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kB5z5/dJMcagxQS9A/OvLLOvihNVGfTx7WCiddqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkB5z5%2FdJMcagxQS9A%2FOvLLOvihNVGfTx7WCiddqK%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;730&quot; height=&quot;415&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;446&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[서브 트리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;큰 트리는 작은 트리로 구성이 되고, 큰 트리에 속하는 작은 트리를 가리켜 &lt;b&gt;&amp;ldquo;서브 트리(Sub Tree)&amp;rdquo;&lt;/b&gt; 라 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;서브 트리의 아래에는 더 작은 서브 트리가 존재한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHmSbZ/dJMcabDljcd/fh0QqsviCNTcuRNaYnpyGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHmSbZ/dJMcabDljcd/fh0QqsviCNTcuRNaYnpyGK/img.png&quot; data-alt=&quot;[이진 트리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHmSbZ/dJMcabDljcd/fh0QqsviCNTcuRNaYnpyGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHmSbZ%2FdJMcabDljcd%2Ffh0QqsviCNTcuRNaYnpyGK%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;658&quot; height=&quot;408&quot; data-origin-width=&quot;658&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[이진 트리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 그림에서 보이는 트리가 중점을 두어 살펴보고 구현할 &amp;ldquo;이진트리(Binary tree)&amp;rdquo;이다. 이진트리를 간단하게 설명하자면 자식 노드가 두 개씩 달린 트리를 의미한다. 자세한 조건은 다음과 같다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;루트 노드를 중심으로 두 개의 서브 트리로 나뉜다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;나뉜 두 서브 트리도 모두 이진트리이어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxRFwj/dJMcaiJbvMr/0nkk6ET2IqMULMFrSSN7oK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxRFwj/dJMcaiJbvMr/0nkk6ET2IqMULMFrSSN7oK/img.png&quot; data-alt=&quot;[다양한 이진 트리의 종류]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxRFwj/dJMcaiJbvMr/0nkk6ET2IqMULMFrSSN7oK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxRFwj%2FdJMcaiJbvMr%2F0nkk6ET2IqMULMFrSSN7oK%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;730&quot; height=&quot;293&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;294&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[다양한 이진 트리의 종류]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노드가 위치할 수 있는 곳에 노드가 존재하지 않는다면, 공집합(empty set) 노드가 존재하는 것으로 간주할 수 있다. 물론 공집합 노드도 이진트리의 판단에 있어서 노드로 인정하기에, 위와 같은 형태의 트리도 이진 트리라 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 이진 트리의 폭은 생각보다 넓고, 이에 따라 이진트리도 특성에 따라 세분화된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 포화 이진(Full Binary) 트리와 완전 이진(Complete Binary) 트리&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xgtEK/dJMcagdArl2/ihbWs7sZvOVbFlfOa6BC0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xgtEK/dJMcagdArl2/ihbWs7sZvOVbFlfOa6BC0K/img.png&quot; data-alt=&quot;[포화 이진 트리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xgtEK/dJMcagdArl2/ihbWs7sZvOVbFlfOa6BC0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxgtEK%2FdJMcagdArl2%2FihbWs7sZvOVbFlfOa6BC0K%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;700&quot; height=&quot;417&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[포화 이진 트리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위의 이진트리는 모든 레벨이 꽉 차있다. 노드를 더 추가하려면 레벨을 늘려야 하는데, 이렇게 레벨이 모두 꽉 찬 이진트리를 가리켜 &lt;b&gt;&amp;ldquo;포화 이진트리&amp;rdquo;라&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-origin-width=&quot;709&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXYRG/dJMcabDljga/G9TeJ3Qrm294qzDyK6zQrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXYRG/dJMcabDljga/G9TeJ3Qrm294qzDyK6zQrk/img.png&quot; data-alt=&quot;[완전 이진 트리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXYRG/dJMcabDljga/G9TeJ3Qrm294qzDyK6zQrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXYRG%2FdJMcabDljga%2FG9TeJ3Qrm294qzDyK6zQrk%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;709&quot; height=&quot;496&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[완전 이진 트리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;포화 이진트리처럼 레벨이 꽉 찬 상태는 아니지만, 빈 틈 없이 채워진 트리를 가리켜 &amp;ldquo;완전 이진트리&amp;rdquo;라 한다. 여기서 말하는 &amp;ldquo;빈 틈 없이 채워진 트리&amp;rdquo;가 가지는 의미는,&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;노드가 위에서 아래로, 왼쪽에서 오른쪽의 순서대로 채워졌다!&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;를 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHhSUa/dJMcaflqNKC/BS47u6LLKOlMKq6aSlgv41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHhSUa/dJMcaflqNKC/BS47u6LLKOlMKq6aSlgv41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHhSUa/dJMcaflqNKC/BS47u6LLKOlMKq6aSlgv41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHhSUa%2FdJMcaflqNKC%2FBS47u6LLKOlMKq6aSlgv41%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;592&quot; height=&quot;411&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 참고로 이런 트리는 이진트리의 조건에는 부합하지만, 포화도 완전도 아닌 그냥 이진트리이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 이진트리의 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이진트리는 재귀적인 특성을 지니고 있어서 어느정도 재귀 함수에 익숙해야 구현하는데 큰 어려움이 없을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 배열 기반? 리스트 기반?&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이진 트리 역시 배열 기반으로도, 연결 리스트 기반으로도 구현이 가능하다. 트리를 표현하기엔 연결 리스트가 더 유연하기 때문에 대부분은 연결 리스트를 기반으로 구현할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 완전 이진트리의 경우, 트리가 완성된 이후부터는 그 트리를 대상으로 매우 빈번한 탐색이 일어나기에 탐색이 용이한 배열 기반으로 먼저 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFabaL/dJMcaaqTDoS/YOwPiQlPs4wZrCnhf07Btk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFabaL/dJMcaaqTDoS/YOwPiQlPs4wZrCnhf07Btk/img.png&quot; data-alt=&quot;[노드 번호가 부여된 이진 트리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFabaL/dJMcaaqTDoS/YOwPiQlPs4wZrCnhf07Btk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFabaL%2FdJMcaaqTDoS%2FYOwPiQlPs4wZrCnhf07Btk%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;639&quot; height=&quot;436&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[노드 번호가 부여된 이진 트리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열을 기반으로 이진트리를 구현하려면, 각 노드에 번호를 구현해야 한다. 이 번호가 의미하는 바는, 각 노드의 데이터가 저장되어야 할 배열의 인덱스 값을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdboAX/dJMcacCfuU8/RWYWXrQI7gPIK900h8xsOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdboAX/dJMcacCfuU8/RWYWXrQI7gPIK900h8xsOK/img.png&quot; data-alt=&quot;[배열 기반 이진 트리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdboAX/dJMcacCfuU8/RWYWXrQI7gPIK900h8xsOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdboAX%2FdJMcacCfuU8%2FRWYWXrQI7gPIK900h8xsOK%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;730&quot; height=&quot;355&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[배열 기반 이진 트리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;길이가 8인 배열을 선언하여 노드 번호 1~5까지 노드에 저장된 데이터를 배열에 저장한 결과이다. 번호가 1인 루트 노드의 데이터 A는 인덱스가 1인 위치에 저장되었고, 번호가 3인 노드의 데이터 C는 3인 위치에 저장되었다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인덱스 0은 사용하지 않는 편이 구현의 편의와 실수할 확률을 낮추기 때문에 사용하지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY2IFs/dJMcajuzfzA/oUvO1xgpCGH9S6iGQA3WVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY2IFs/dJMcajuzfzA/oUvO1xgpCGH9S6iGQA3WVk/img.png&quot; data-alt=&quot;[리스트 기반 이진 트리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY2IFs/dJMcajuzfzA/oUvO1xgpCGH9S6iGQA3WVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY2IFs%2FdJMcajuzfzA%2FoUvO1xgpCGH9S6iGQA3WVk%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;552&quot; height=&quot;395&quot; data-origin-width=&quot;552&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[리스트 기반 이진 트리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그림으로 봐도 리스트 기반의 구현 방식이 바로 이해될 수 있다. 사실 연결 리스트의 구성 형태가 트리와 일치하기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 헤더 파일 정의&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;#pragma once
using BData = int;

struct BNode
{
	BData data;
	BNode* left = nullptr;
	BNode* right = nullptr;
};

BData GetNodeData(BNode* node);
void SetNodeData(BNode* node, BData data);

BNode* GetLeftSubTree(BNode* node);
BNode* GetRightSubTree(BNode* node);

void MakeLeftSubTree(BNode* main, BNode* sub);
void MakeRightSubTree(BNode* main, BNode* sub);
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot; data-token-index=&quot;0&quot;&gt; [BinaryTree.h] &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이진트리를 구현하기 위해서는 각 노드를 표현하는 구조체 하나만으로도 기본적인 트리 구조를 구성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇다면, 이진 트리를 표현한 구조체는 정의하지 않아도 될까?&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 리스트 기반 큐를 구현할 때는 노드를 표현한 구조체와 큐를 표현한 구조체를 각각 정의하였다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 이진트리에서는 노드가 위치할 수 있는 곳에 노드가 존재하지 않아도, 공집합 노드가 존재하는 것으로 간주하고 공집합 노드도 이진트리를 판단하는 데 있어서 노드로 인정받는다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 자식 노드가 하나도 없는 노드도 그 자체로 이진트리이기 때문에 별도로 이진트리를 표현하기 위한 구조체는 구현하지 않아도 된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그럼 헤더파일에 선언된 함수의 기능을 정리해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;BData GetNodeData(BNode node) / void SetNodeData(BNode node, BData data)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노드에 저장된 데이터를 반환하거나, 노드에 데이터를 저장한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;BNode GetLeftSubTree(BNode node) / BNode* GetRightSubTree(BNode* node)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;파라미터로 넘어온 노드의 왼쪽/오른쪽 서브 트리의 주소 값을 반환한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;void MakeLeftSubTree(BNode main, BNode sub)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;왼쪽 서브 트리를 연결한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;void MakeRightSubTree(BNode main, BNode sub)&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;오른쪽 서브 트리를 연결한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. 이진트리의 구현&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이진 트리의 구현은 의외로 쉽다. 코드도 그렇게 길지 않으니 천천히 살펴보는 것만으로도 충분히 이해가 가능하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;#include &quot;BinaryTree.h&quot;

BData GetNodeData(BNode* node)
{
	if(node != nullptr &amp;amp;&amp;amp; node-&amp;gt;data != 0)
		return node-&amp;gt;data;

	return BData();
}

void SetNodeData(BNode* node, BData data)
{
	if (node != nullptr)
		node-&amp;gt;data = data;
}

BNode* GetLeftSubTree(BNode* node)
{
	return (node != nullptr &amp;amp;&amp;amp; node-&amp;gt;left != nullptr) ? node-&amp;gt;left : nullptr;
}

BNode* GetRightSubTree(BNode* node)
{
	return (node != nullptr &amp;amp;&amp;amp; node-&amp;gt;right != nullptr) ? node-&amp;gt;right : nullptr;
}

void MakeLeftSubTree(BNode* main, BNode* sub)
{
	if (main == nullptr)
		return;

	if (main-&amp;gt;left != nullptr)
		delete main-&amp;gt;left;

	main-&amp;gt;left = sub;
}

void MakeRightSubTree(BNode* main, BNode* sub)
{
	if (main == nullptr)
		return;

	if (main-&amp;gt;right != nullptr)
		delete main-&amp;gt;right;

	main-&amp;gt;right = sub;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot; data-token-index=&quot;0&quot;&gt; [BinaryTree.cpp] &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;코드를 잘 보면 문제가 생길 만한 부분이 존재한다. 아래 글을 보기 전에 먼저 어떤 함수에서 어떤 부분이 문제가 생길지 생각해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;void MakeLeftSubTree(BNode* main, BNode* sub)
{
	if (main == nullptr)
		return;

	if (main-&amp;gt;left != nullptr)
		delete main-&amp;gt;left;

	main-&amp;gt;left = sub;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;248&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl2YDy/dJMcadgQobM/9NNVYbk5weJzPS0F4XW7FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl2YDy/dJMcadgQobM/9NNVYbk5weJzPS0F4XW7FK/img.png&quot; data-alt=&quot;[MakeLeftSubTree()]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl2YDy/dJMcadgQobM/9NNVYbk5weJzPS0F4XW7FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl2YDy%2FdJMcadgQobM%2F9NNVYbk5weJzPS0F4XW7FK%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;714&quot; height=&quot;248&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;248&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[MakeLeftSubTree()]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문제는 MakeLeftSubTree() 함수와 MakeRightSubTree() 함수에서 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 번의 delete가 전부이기 때문에, 삭제할 서브 트리가 하나의 노드로 이뤄져 있다면 문제없지만, 그렇지 않다면 메모리 누수로 이어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 둘 이상의 서브 트리를 구성하는 모든 노드를 대상으로 delete를 해야 한다. 이렇게 모든 노드를 방문하는 것을 가리켜 &amp;ldquo;순회&amp;rdquo;라고 하는데, 데이터가 나란히 있는 다른 자료구조와 달리 트리는 별도의 방법이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;순회를 살펴보기 전에 이진트리 사용 예제인 Main함수를 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &quot;BinaryTree.h&quot;
using namespace std;

int main()
{
	auto node1 = new BNode;
	auto node2 = new BNode;
	auto node3 = new BNode;
	auto node4 = new BNode;

	SetNodeData(node1, 1);
	SetNodeData(node2, 2);
	SetNodeData(node3, 3);
	SetNodeData(node4, 4);

	MakeLeftSubTree(node1, node2); // node1의 왼쪽 서브 트리는 node2
	MakeRightSubTree(node1, node3); // node1의 오른쪽 서브 트리는 node3
	MakeLeftSubTree(node2, node4); // node2의 왼쪽 서브 트리는 node4

	cout &amp;lt;&amp;lt; &quot;Node1의 왼쪽 자식 노드 데이터 : &quot; 
		&amp;lt;&amp;lt; GetNodeData(GetLeftSubTree(node1)) &amp;lt;&amp;lt; endl;

	cout &amp;lt;&amp;lt; &quot;Node1의 왼쪽 자식 노드의 왼쪽 자식 노드의 데이터 : &quot;
		&amp;lt;&amp;lt; GetNodeData(GetLeftSubTree(GetLeftSubTree(node1))) &amp;lt;&amp;lt; endl;

	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;299&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwh18r/dJMcaaqTDBv/W0tSSQR8uQovmIhyJvTyjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwh18r/dJMcaaqTDBv/W0tSSQR8uQovmIhyJvTyjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwh18r/dJMcaaqTDBv/W0tSSQR8uQovmIhyJvTyjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdwh18r%2FdJMcaaqTDBv%2FW0tSSQR8uQovmIhyJvTyjk%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;730&quot; height=&quot;198&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;299&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/08SOm/dJMcaaqTDCq/UTI1HQ708cXpkpl2cVx1t1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/08SOm/dJMcaaqTDCq/UTI1HQ708cXpkpl2cVx1t1/img.png&quot; data-alt=&quot;[트리 사용 예제]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/08SOm/dJMcaaqTDCq/UTI1HQ708cXpkpl2cVx1t1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F08SOm%2FdJMcaaqTDCq%2FUTI1HQ708cXpkpl2cVx1t1%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;489&quot; height=&quot;339&quot; data-origin-width=&quot;489&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[트리 사용 예제]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 Main 함수를 통해 형성되는 이진트리의 구조는 위와 같다. 1이 저장된 노드의 왼쪽 자식 노드와, 그 자식 노드의 왼쪽 자식 노드를 출력했을 때, 위 구조처럼 출력이 되는것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 이진 트리의 순회(Traversal)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아까 트리를 구현함에 있어서 &amp;ldquo;재귀&amp;rdquo;를 이해해야 한다고 했는데, 이진트리를 순회할 때 재귀 방식이 사용된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;s&gt;무조건 재귀 방식을 사용하는 것은 아니고, 스택이나 큐를 사용해 반복문 방식으로도 구현한다.&lt;/s&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;트리를 순회하는 방식에는 크게 3가지 방식이 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;전위 순회(Preorder Traversal) : 루트를 먼저 방문. (루트 &amp;rarr; 왼쪽 &amp;rarr; 오른쪽)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;중위 순회(Inorder Traversal) : 루트를 중간에 방문. (왼쪽 &amp;rarr; 루트 &amp;rarr; 오른쪽)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;후위 순회(Postorder Traversal) : 루트 노드를 마지막에 방문. (왼쪽 &amp;rarr; 오른쪽 &amp;rarr; 루트)&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 순회의 재귀적 표현&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 3가지의 방식 모두 재귀적으로 구현만 하면 문제는 해결된다. 재귀가 끼어있어서 감을 잡기 힘들 수도 있는데, 먼저 중위 순회를 진행하는 함수를 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/92vxn/dJMcaibphcB/U2zbVFVfrpkqrJmKff0xv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/92vxn/dJMcaibphcB/U2zbVFVfrpkqrJmKff0xv1/img.png&quot; data-alt=&quot;[중위 순회]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/92vxn/dJMcaibphcB/U2zbVFVfrpkqrJmKff0xv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F92vxn%2FdJMcaibphcB%2FU2zbVFVfrpkqrJmKff0xv1%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;694&quot; height=&quot;347&quot; data-origin-width=&quot;694&quot; data-origin-height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[중위 순회]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;서브 트리라고 해서 순회의 방법이 달라지는 것은 아니다. 간단하게 전체를 순회하는 함수는 아래와 같이 정의할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;void InorderTraverse(BNode* node)
{
	if (node == nullptr)
		return;

	// 왼쪽 서브트리 부터 탐색
	InorderTraverse(node-&amp;gt;left);
	
	cout &amp;lt;&amp;lt; &quot;Node Data : &quot; &amp;lt;&amp;lt; node-&amp;gt;data &amp;lt;&amp;lt; endl;
	
	// 오른쪽 서브트리 탐색
	InorderTraverse(node-&amp;gt;right);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVtbve/dJMcadVpcHP/R1MJrOADx3jomoBhnk3QuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVtbve/dJMcadVpcHP/R1MJrOADx3jomoBhnk3QuK/img.png&quot; data-alt=&quot;[InorderTraverse()]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVtbve/dJMcadVpcHP/R1MJrOADx3jomoBhnk3QuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVtbve%2FdJMcadVpcHP%2FR1MJrOADx3jomoBhnk3QuK%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;730&quot; height=&quot;175&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[InorderTraverse()]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;일단은 트리의 전체 노드를 순회하며 저장된 데이터를 출력하는 함수를 작성하였다. 재귀의 탈출 조건은 자식이 더 이상 존재하지 않는 경우(node == nullptr인 경우)가 바로 탈출 조건이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Main에서도 해당 함수를 호출하면, 왼쪽 서브트리의 단말 노드부터 거슬러 올라가 루트를 거쳐 오른쪽 서브트리로 넘어가는 것을 확인할 수 있다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;void PreorderTraverse(BNode* node)
{
	if (node == nullptr)
		return;

	cout &amp;lt;&amp;lt; &quot;node : &quot; &amp;lt;&amp;lt; node-&amp;gt;data &amp;lt;&amp;lt; endl;
	PreorderTraverse(node-&amp;gt;left);
	PreorderTraverse(node-&amp;gt;right);
}

void PostorderTraverse(BNode* node)
{
	if (node == nullptr)
		return;

	PostorderTraverse(node-&amp;gt;left);
	PostorderTraverse(node-&amp;gt;right);
	cout &amp;lt;&amp;lt; &quot;node : &quot; &amp;lt;&amp;lt; node-&amp;gt;data &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot; data-token-index=&quot;0&quot;&gt; [전위, 후위 순회 방식] &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;중위 순회 방식과 동일하게 전위와 후위 순회 방식도 구현할 수 있다. 차이는 루트 노드를 방문하는 문장이 삽입된 위치이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 노드의 방문 목적을 자유롭게 구성하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노드를 순회하는 데 있어서 목적은 데이터 출력이 전부가 아니다. 따라서 함수 포인터를 사용해서 노드를 방문했을 때 할 일을 결정해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;// **BinaryTree.h**
void InorderTraverse(BNode* node, void (*visit)(BNode*));

// **BinaryTree.cpp**
void InorderTraverse(BNode* node, void (*visit)(BNode*))
{
	if (node == nullptr)
		return;

	// 왼쪽 서브트리 부터 탐색
	InorderTraverse(node-&amp;gt;left, visit);

	visit(node);

	// 오른쪽 서브트리 탐색
	InorderTraverse(node-&amp;gt;right, visit);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[함수 포인터 추가]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수의 주소 값을 매개변수 visit를 통해 전달받도록 변경하였다. 그리고 이 함수를 기반으로 노드의 방문은 다음과 같이 처리되도록 변경하였다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;visit(node) &amp;larr; 노드의 방문&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 매개변수 visit에 전달되는 함수에 따라 노드의 방문결과가 결정되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &quot;BinaryTree.h&quot;
using namespace std;

void PrintNodeData(BNode* node);

int main()
{
	auto node1 = new BNode;
	auto node2 = new BNode;
	auto node3 = new BNode;
	auto node4 = new BNode;

	SetNodeData(node1, 1);
	SetNodeData(node2, 2);
	SetNodeData(node3, 3);
	SetNodeData(node4, 4);

	MakeLeftSubTree(node1, node2); // node1의 왼쪽 서브 트리는 node2
	MakeRightSubTree(node1, node3); // node1의 오른쪽 서브 트리는 node3
	MakeLeftSubTree(node2, node4); // node2의 왼쪽 서브 트리는 node4

	cout &amp;lt;&amp;lt; &quot;중위 순회 결과&quot; &amp;lt;&amp;lt; endl;
	InorderTraverse(node1, PrintNodeData);
	cout &amp;lt;&amp;lt; endl;

	return 0;
}

void PrintNodeData(BNode* node)
{
	cout &amp;lt;&amp;lt; &quot;Data : &quot; &amp;lt;&amp;lt; GetNodeData(node) &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[Main.cpp]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Main 함수에서도 위와 같이 코드를 수정해 주면, 함수 포인터를 통해 중위 순회에서 방문 목적을 지정해 줄 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. 순회를 통한 노드의 삭제&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 MakeLeftSubTree() 함수와 MakeRightSubTree() 함수의 문제에 대해 살펴봤으니, 구현한 순회 함수를 통해 문제를 해결해 보자.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;서브 트리의 자식 노드들까지 순회를 통해 모두 제거.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ekMu95/dJMcajafGDL/MlWUs0vmCr7XjqCOOhdKu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ekMu95/dJMcajafGDL/MlWUs0vmCr7XjqCOOhdKu1/img.png&quot; data-alt=&quot;[노드의 삭제]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ekMu95/dJMcajafGDL/MlWUs0vmCr7XjqCOOhdKu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FekMu95%2FdJMcajafGDL%2FMlWUs0vmCr7XjqCOOhdKu1%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;717&quot; height=&quot;286&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[노드의 삭제]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상황을 다시 한번 살펴보면, 1번 노드의 왼쪽 자식인 2번 노드를 삭제해야 한다고 해보자. 그럼 2번 노드의 모든 자식 노드들을 순회하며 삭제해야 할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;373&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb8kLB/dJMcabQQ1ZJ/UjWnoZkYnGMvIH9supX5f0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb8kLB/dJMcabQQ1ZJ/UjWnoZkYnGMvIH9supX5f0/img.png&quot; data-alt=&quot;[중위 순회의 문제점]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb8kLB/dJMcabQQ1ZJ/UjWnoZkYnGMvIH9supX5f0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb8kLB%2FdJMcabQQ1ZJ%2FUjWnoZkYnGMvIH9supX5f0%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;590&quot; height=&quot;373&quot; data-origin-width=&quot;590&quot; data-origin-height=&quot;373&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[중위 순회의 문제점]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 여기서 살펴봐야 할 점이 있는데, 중위 순회의 경우 왼쪽 &amp;rarr; 루트 &amp;rarr; 오른쪽의 순서로 순회한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;왼쪽 노드를 방문하고 자식 노드가 더 이상 없으면 왼쪽 노드를 삭제한 뒤, 루트 노드가 삭제된다. 이렇게 되면 루트 노드가 삭제되어 루트 노드의 오른쪽 노드는 더 이상 접근할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;void MakeLeftSubTree(BNode* main, BNode* sub)
{
	if (main == nullptr)
		return;

	if (main-&amp;gt;left != nullptr)
	{
		PostorderTraverse(main-&amp;gt;left, [](BNode* node) { delete node; });
		main-&amp;gt;left = nullptr;
	}

	main-&amp;gt;left = sub;
}

void MakeRightSubTree(BNode* main, BNode* sub)
{
	if (main == nullptr)
		return;

	if (main-&amp;gt;right != nullptr)
	{
		PostorderTraverse(main-&amp;gt;right, [](BNode* node) { delete node; });
		main-&amp;gt;right = nullptr;
	}
	
	main-&amp;gt;right = sub;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[하위 노드의 모든 노드 삭제]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같은 문제를 방지하기 위해 &lt;b&gt;후위 순회&lt;/b&gt;를 사용하여 자식 노드부터 차례대로 삭제한 뒤, 마지막에 부모 노드를 삭제하도록 구현하였다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;PostorderTraverse(main-&amp;gt;left, [](BNode* node) { delete node; });
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[](BNode* node) { delete node; }는 이름이 없는 익명 함수(람다)로, 방문한 노드를 즉시 delete 하도록 정의된 콜백 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &quot;BinaryTree.h&quot;
using namespace std;

void PrintNodeData(BNode* node);

int main()
{
	auto node1 = new BNode;
	auto node2 = new BNode;
	auto node3 = new BNode;
	auto node4 = new BNode;

	SetNodeData(node1, 1);
	SetNodeData(node2, 2);
	SetNodeData(node3, 3);
	SetNodeData(node4, 4);

	MakeLeftSubTree(node1, node2); // node1의 왼쪽 서브 트리는 node2
	MakeRightSubTree(node1, node3); // node1의 오른쪽 서브 트리는 node3
	MakeLeftSubTree(node2, node4); // node2의 왼쪽 서브 트리는 node4

	cout &amp;lt;&amp;lt; &quot;중위 순회 결과&quot; &amp;lt;&amp;lt; endl;
	InorderTraverse(node1, PrintNodeData);
	cout &amp;lt;&amp;lt; endl;

	// 노드 1의 왼쪽 자식 교체
	auto node5 = new BNode;
	SetNodeData(node5, 5);
	MakeLeftSubTree(node1, node5);

	cout &amp;lt;&amp;lt; &quot;중위 순회 결과&quot; &amp;lt;&amp;lt; endl;
	InorderTraverse(node1, PrintNodeData);
	cout &amp;lt;&amp;lt; endl;

	return 0;
}

void PrintNodeData(BNode* node)
{
	cout &amp;lt;&amp;lt; &quot;Data : &quot; &amp;lt;&amp;lt; GetNodeData(node) &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLFGxA/dJMcadVpcUZ/XXhCK6VFgHSqvq3b892MK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLFGxA/dJMcadVpcUZ/XXhCK6VFgHSqvq3b892MK0/img.png&quot; data-alt=&quot;[실행 결과]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLFGxA/dJMcadVpcUZ/XXhCK6VFgHSqvq3b892MK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLFGxA%2FdJMcadVpcUZ%2FXXhCK6VFgHSqvq3b892MK0%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;730&quot; height=&quot;175&quot; data-origin-width=&quot;1092&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[실행 결과]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 트리의 구현과, 트리를 순회하는 재귀 호출 함수를 작성해 보았다. 다음에는 수식 트리를 사용해 계산기 프로그램을 만들어보자.&lt;/span&gt;&lt;/p&gt;</description>
      <category>C++/자료구조</category>
      <category>c++</category>
      <category>순회</category>
      <category>자료구조</category>
      <category>트리</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/56</guid>
      <comments>https://hate-errorlog.tistory.com/56#entry56comment</comments>
      <pubDate>Wed, 4 Mar 2026 17:48:06 +0900</pubDate>
    </item>
    <item>
      <title>[게임수학] 10-2. 깊이 값</title>
      <link>https://hate-errorlog.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; text-align: left;&quot;&gt;&amp;rarr; 이 글은&amp;nbsp;&lt;/span&gt;&lt;b&gt;「이득우의 게임 수학」&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; text-align: left;&quot;&gt;을 바탕으로 작성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 깊이 값&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3D 공간의 물체들을 화면이라는 2차원 평면에 투영하면, 화면 좌표에는 $x$와 $y$정보만 남게 된다. 이 상태에는 어떤 물체가 더 앞에 있고 뒤에 있는지를 구분할 수 없어 결국 나중에 그려진 물체가 화면의 앞에 보일 수밖에 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 물체가 카메라로부터 얼마나 떨어져 있는지 파악할 수 있는 데이터가 필요한데, 이를 깊이$^{Depth}$값 이라 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AS4Gm/dJMcadOAD9b/lIdhnmE7o3e1puerCxWlUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AS4Gm/dJMcadOAD9b/lIdhnmE7o3e1puerCxWlUK/img.png&quot; data-alt=&quot;[3차원으로 확장된 NDC 영역]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AS4Gm/dJMcadOAD9b/lIdhnmE7o3e1puerCxWlUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAS4Gm%2FdJMcadOAD9b%2FlIdhnmE7o3e1puerCxWlUK%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;422&quot; height=&quot;405&quot; data-origin-width=&quot;422&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[3차원으로 확장된 NDC 영역]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2차원 평면의 NDC에서 깊이 값을 추가하면 3차원 영역으로 확장된다. 깊이 값의 범위는 동일하게 $[-1,1]$이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LjZ3a/dJMcacIZO8M/tVZRWvsIFlM59JXqU7GkqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LjZ3a/dJMcacIZO8M/tVZRWvsIFlM59JXqU7GkqK/img.png&quot; data-alt=&quot;[카메라에 설정한 절두체]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LjZ3a/dJMcacIZO8M/tVZRWvsIFlM59JXqU7GkqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLjZ3a%2FdJMcacIZO8M%2FtVZRWvsIFlM59JXqU7GkqK%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;548&quot; height=&quot;425&quot; data-origin-width=&quot;548&quot; data-origin-height=&quot;425&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[카메라에 설정한 절두체]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;NDC영역과 마찬가지로 카메라에 부여한 시야각은 깊이값과 무관하기 때문에 근평면$^{Near plan}$과 원평면 $^{Fraplane}$이라는 추가 속성을 부여해야 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사각뿔 영역을 근평면과 원평면으로 잘라주면 위 그림과 같은 형태가 나오게 되는데 이를 절두체$^{Frustum}$라 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 원근 투영 행렬에 깊이값 추가&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P \cdot \vec{v}=\begin{bmatrix}\frac{d}{a} &amp;amp; 0 &amp;amp; 0 \\0 &amp;amp; d &amp;amp; 0 \\0 &amp;amp; 0 &amp;amp; -1\end{bmatrix}\begin{bmatrix}v_x \\v_y \\v_z\end{bmatrix}=\begin{bmatrix}\frac{d}{a}\, v_x \\d\, v_y \\- v_z\end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[원근 투영 행렬]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 구한 3X3 행렬에서 깊이 값을 계산하는데 필요한 값을 추가하기 위해 3행을 4행으로 옮기고, 3행은 깊이 값을 구하는 용도로 변경하자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;135&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deqE58/dJMcabi1s8Y/25NBad56ZCkx8I75vVxaQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deqE58/dJMcabi1s8Y/25NBad56ZCkx8I75vVxaQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deqE58/dJMcabi1s8Y/25NBad56ZCkx8I75vVxaQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeqE58%2FdJMcabi1s8Y%2F25NBad56ZCkx8I75vVxaQ1%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;431&quot; height=&quot;135&quot; data-origin-width=&quot;431&quot; data-origin-height=&quot;135&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px;&quot;&gt;[깊이값을 계산하기 위한 미지수 추가 - 1]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깊이 값을 계산하는데 사용하는 3행은 4개의 미지수$i,j,k,l$로 지정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 깊이 값은 카메라 전방 향향($z$축)의 거리만을 의미한다. 따라서 동일한 $z$값을 가지는 점들은 $x, y$위치와 관계없이 같은 깊이를 가져야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ z&amp;prime;=ivx+jvy+kvz+l $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만약 깊이값을 계산하는데 $v_x$와 $v_y$가 포함된다면 깊이값이 $x,y$에 따라 달라진다는 의미이다. 그러므로 깊이 계산식에서는 $v_x,v_y$가 포함될 수 없으며 원근 투영의 3번째 행에서 $i,j$는 0이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9R7SI/dJMcadOAFfe/X7THUCEKCy8jMPAaKKQa7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9R7SI/dJMcadOAFfe/X7THUCEKCy8jMPAaKKQa7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9R7SI/dJMcadOAFfe/X7THUCEKCy8jMPAaKKQa7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9R7SI%2FdJMcadOAFfe%2FX7THUCEKCy8jMPAaKKQa7k%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;412&quot; height=&quot;137&quot; data-origin-width=&quot;412&quot; data-origin-height=&quot;137&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;[깊이값을 계산하기 위한 미지수 추가 - 2]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 두 미지수의 값을 얻기 위해 근평면과 원평면의 값을 사용하자.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라로부터 근평면까지의 거리 $= n$&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라로부터 원평면까지의 거리 $= f$&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라로부터 $n$만큼 시선 방향으로 이동한, 근평면 상에 위치한 점의 뷰 공간 좌표는 $(0,0,-n,1)$이 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이는 깊이 값의 시작지점이기 때문에 NDC좌표 $(0,0,-1)$에 대응.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;동일하게 뷰 공간의 원평면상의 좌표는 $(0,0,-f,1)$인데 이는 깊이 값의 끝 지점이기 때문에 NDC$(0,0,1)$에 대응된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원근 투영 행렬에 뷰 공간의 점을 곱한 결과는 클립 좌표가 된다. 근평면의 점을 $P_1$, 원평면의 점을 $P_2$라 한다면,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btNDer/dJMcaibndbK/isjVs26UXa4QJ9mpFolGE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btNDer/dJMcaibndbK/isjVs26UXa4QJ9mpFolGE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btNDer/dJMcaibndbK/isjVs26UXa4QJ9mpFolGE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtNDer%2FdJMcaibndbK%2FisjVs26UXa4QJ9mpFolGE1%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;415&quot; height=&quot;311&quot; data-origin-width=&quot;415&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;아직 $k,l$값을 모르기 때문에 클립 좌표의 세 번째 요소를 직접 계산할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_1\cdot P_{near} = (0,0,-kn + l , n) \\ \ \\ z_{ndc} = \frac{-kn + l}{n} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 근평면은 NDC에서 $z = -1$에 대응되어야 하므로,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ \frac{-kn + l}{n} = -1 $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;NDC는 클립 좌표를 마지막 요소인 $w$로 나눈 값이므로, $w = n$ 일 때 NDC가 $-1$이 되기 위해서는 클립 좌표의 $z$값이&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ z_c = -n $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이어야 한다. 따라서, 근평면의 클립 좌표는 $(0,0,-n,n)$이 되고, 원평면의 클립 좌표도 동일한 방식으로 $(0,0,f,f)$가 되어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;423&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUc8OA/dJMcac9Zm0z/Jc9nf3Uhh3kyIf9eD0RT7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUc8OA/dJMcac9Zm0z/Jc9nf3Uhh3kyIf9eD0RT7K/img.png&quot; data-alt=&quot;[뷰 공간의 좌표와 NDC값의 비교]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUc8OA/dJMcac9Zm0z/Jc9nf3Uhh3kyIf9eD0RT7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUc8OA%2FdJMcac9Zm0z%2FJc9nf3Uhh3kyIf9eD0RT7K%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;680&quot; height=&quot;423&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;423&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[뷰 공간의 좌표와 NDC값의 비교]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ed3kxw/dJMcac9Zm0M/TgO3al0Xb5qAtsR6B50CE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ed3kxw/dJMcac9Zm0M/TgO3al0Xb5qAtsR6B50CE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ed3kxw/dJMcac9Zm0M/TgO3al0Xb5qAtsR6B50CE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fed3kxw%2FdJMcac9Zm0M%2FTgO3al0Xb5qAtsR6B50CE0%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;396&quot; height=&quot;300&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;300&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 위와 같은 식을 얻을 수 있다. 여기서 두 행렬의 3행과 뷰 공간의 점을 내적하면 다음 두 식을 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ -kn + l = -n \\ -kf + l = f $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$n,f$는 카메라에 설정된 상수이므로, 두 식을 서로 빼, $l$을 소거한 후 $k$값을 구하면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ k = \frac{-(n + f)}{(-n + f)} = \frac{n + f}{n - f} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 구한 $k$를 대입해 $l$을 계산하면,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ l = \frac{2nf}{(n-f)} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 깊이 값을 산출해주는 최종 원근 투영 행렬 $P$는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NcO0i/dJMcabwweaw/WZBvSn119cUhQAnSKTmgX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NcO0i/dJMcabwweaw/WZBvSn119cUhQAnSKTmgX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NcO0i/dJMcabwweaw/WZBvSn119cUhQAnSKTmgX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNcO0i%2FdJMcabwweaw%2FWZBvSn119cUhQAnSKTmgX0%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;300&quot; height=&quot;179&quot; data-origin-width=&quot;268&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 원근 보정 매핑&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원근 투영 행렬에서 깊이 값을 추가해 문제를 해결했지만, 아직 한 가지 문제가 남아있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIgBbM/dJMcaaj54AS/cGfksuyw7Nl0M1yLQuKLK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIgBbM/dJMcaaj54AS/cGfksuyw7Nl0M1yLQuKLK1/img.png&quot; data-alt=&quot;[기존 텍스처 매핑의 문제점]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIgBbM/dJMcaaj54AS/cGfksuyw7Nl0M1yLQuKLK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIgBbM%2FdJMcaaj54AS%2FcGfksuyw7Nl0M1yLQuKLK1%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;730&quot; height=&quot;288&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[기존 텍스처 매핑의 문제점]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각 픽셀의 무게중심좌표를 구한 뒤, UV좌표를 계산하고 텍스처 매핑을 구현했지만, 평평하게 그려져야 할 눈과 눈썹이 처지거나 올라가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이는 원근 투영이 &amp;ldquo;선형 변환&amp;rdquo;이 아니어서 생기는 문제인데, 왜 이런 상황이 나오는지 자세히 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQAvc3/dJMcabi1tdf/ISNhNfKEML0A3wtwCRQCtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQAvc3/dJMcabi1tdf/ISNhNfKEML0A3wtwCRQCtK/img.png&quot; data-alt=&quot;[시야각 사이에 걸쳐 있는 두 점의 투영 결과]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQAvc3/dJMcabi1tdf/ISNhNfKEML0A3wtwCRQCtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQAvc3%2FdJMcabi1tdf%2FISNhNfKEML0A3wtwCRQCtK%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;500&quot; height=&quot;554&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[시야각 사이에 걸쳐 있는 두 점의 투영 결과]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;투영하기 전의 두 점$(P_1, P_2)$가 카메라 시야각에 걸쳐있다면, 투영한 점$(N_1, N_2)$의 $x$좌표값은 $-1, 1$이 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라 정면에 위치한 $P_3$는 투영 후 투영 평면의 정 중앙에 위치한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;투영 후 $N_1$과 $N_2$를 이어 선분을 생성하면, 선분의 중앙에 위치한 $N_3$의 무게중심좌표는 0.5가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 투영 전, 선분$P_1P_2$ 내에 위치한 점 $P_3$의 무게중심좌표는 0.5에 위치하지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mX2K5/dJMcaaj54Bt/HBwS8EfjO2w4UyIbSATl51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mX2K5/dJMcaaj54Bt/HBwS8EfjO2w4UyIbSATl51/img.png&quot; data-alt=&quot;[뷰 공간의 선을 수평으로 조정]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mX2K5/dJMcaaj54Bt/HBwS8EfjO2w4UyIbSATl51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmX2K5%2FdJMcaaj54Bt%2FHBwS8EfjO2w4UyIbSATl51%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;545&quot; height=&quot;234&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[뷰 공간의 선을 수평으로 조정]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원근 투영은 다음과 같이 계산된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ x_{ndc} = \frac{x}{-z} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 좌표를 나누는 연산이 포함되는데, 이 나눗셈 때문에 선형 관계가 유지되지 않는다. 깊이가 다르면 선형 관계가 깨진다는 의미이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3D 공간에서 선형 보간된 점은 $P = \alpha P_1 + \beta P_2$의 형태이지만, 투영 후에는 $N = \frac{P}{w}$가 되므로 결국,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ \frac{\alpha P_1 + \beta P_2}{w} \ne \alpha \frac{P_1}{w_1} + \beta\frac{P_2}{w_2} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가 된다. 즉, 투영 전 무게중심좌표와 투영 후 무게중심좌표가 서로 다르게 되므로 이 차이로 인해 텍스처 왜곡이 발생하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 투영 보정 보간&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이를 수정하기 위해선 &lt;b&gt;&amp;ldquo;투영 전 무게중심좌표&amp;rdquo;&lt;/b&gt;를 사용해야 하고, NDC의 무게중심으로부터 투영 전의 무게중심좌표를 계산하는 것을 &lt;b&gt;투영 보정 보간(Perspective corrention interpolation)&lt;/b&gt;이라 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;NDC의 무게중심좌표를 투영 전의 무게중심좌표로 돌려주는 계산식을 유도하기 위해 반비례 함수 $y = -\frac{1}{x}$가 가진 성질을 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;459&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yKInQ/dJMcaadkPcc/RgFStGlySALbpNNe0DaUk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yKInQ/dJMcaadkPcc/RgFStGlySALbpNNe0DaUk1/img.png&quot; data-alt=&quot;[반비례 함수 그래프]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yKInQ/dJMcaadkPcc/RgFStGlySALbpNNe0DaUk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyKInQ%2FdJMcaadkPcc%2FRgFStGlySALbpNNe0DaUk1%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;641&quot; height=&quot;459&quot; data-origin-width=&quot;641&quot; data-origin-height=&quot;459&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[반비례 함수 그래프]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;함수 $y = -\frac{1}{x}$에서 $x = 2,4,6$에 대응되는 값은 각각 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ -\frac{1}{2}, -\frac{1}{4}, -\frac{1}{6} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$x$축에서 가운데 위치한 4는 2와 6의 정확한 중간에 있으므로 선형 보간의 기본 형태에 의해 다음과 같이 표현할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ 4 = a\cdot 2 + (1-a)\cdot 6 $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이를 정리하면,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ 4 = 2a + 6 - 6a \\ 4 = 6 - 4a \\ a = \frac{1}{2} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 4의 무게중심좌표는 0.5이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;▼ 선형 보간식의 의미&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-size: 16px; letter-spacing: 0px; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;예를 들어 2와 6가이의 어떤 값을 표현하고 싶다면, 그 값은 두 점의 일정 비율로 섞어서 나타낼 수 있다. 이는 일반적으로,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ x = ax_1 +(1-a)x_2 $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의 형태로 두며, 여기서 $a$는 두 점 사이의 위치 비율을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;보간을 할 때 가중치의 합은 항상 1이어야 하므로 $a + (1-a) = 1$이 되도록 정의한다. 이 식이 바로 선형 보간의 기본 형태이다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번엔 4에 대응하는 y값 $-\frac{1}{4}$의 무게중심좌표를 구해보자. 동일하게 선형 보간식을 사용하면,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ -\frac{1}{4} = a\cdot -\frac{1}{2} + (1-a) \cdot -\frac{1}{6} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가 되고, 위 식을 정리하면 $a = 0.25$가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 살펴봐야 할 점은,&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$x$축에서 2와 6의 중간인 4의 보간계수는 0.5 이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$y$값에서 $-\frac{1}{2}$와 $-\frac{1}{6}$ 사이에서 $-\frac{1}{4}$이 되도록 만드는 보간계수는 0.25이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ a_x = 0.5 \ \ \ne \ \ a_y = 0.25 $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;선형 함수라면 두 보간계수가 동일해야 하지만, 반비례 함수에서는 그렇지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만약 $y$축의 무게중심좌표 0.25로부터 $x$축의 무게중심좌표 0.5를 계산해 주는 식을 찾을 수 있다면, 이를 응용해 투영 전 무게중심좌표를 계산할 수 있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 투영 보정 보간식 유도&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$y$축에서의 무게중심좌표를 $q_1, q_2$로 지정하고, 이를 사용해 $y$축의 두 점 $y_1, y_2$ 사이의 점 $y'$을 구하는 수식을 정리하자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ y ' = q_1 \cdot y_1 + q_2 \cdot y_2 $$&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$q_1 + q_2 = 1$&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번엔 반비례가 적용되기 전 $x$축의 수식을 정리해 보자. 무게중심좌표를 $t_1, t_2$로 지정하고, $x_1,x_2$사이의 점 $x'$을 구하는 수식은 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ x' = t_1\cdot x_1 + t_2 \cdot x_2 $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 값은 서로 반비례 관계이므로, $y$값을 $x$값으로 표현하면 아래와 같은 식이 성립한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ \frac{1}{x'} = q_1\cdot \frac{1}{x_1} + q_2 \cdot \frac{1}{x_2} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇다면 주어진 두 점 $y_1,y_2$의 값과 둘의 무게중심좌표를 사용해 $x'$값을 계산할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ x' = \frac{1}{(q_1\cdot \frac{1}{x_1}+q_2\cdot\frac{1}{x_2})} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;양변에 분모를 곱하면 다음의 식이 성립하고,&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwMG32/dJMcac3fu8e/ryAOtzw4xw1TBKqRH3wXk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwMG32/dJMcac3fu8e/ryAOtzw4xw1TBKqRH3wXk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwMG32/dJMcac3fu8e/ryAOtzw4xw1TBKqRH3wXk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwMG32%2FdJMcac3fu8e%2FryAOtzw4xw1TBKqRH3wXk1%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;272&quot; height=&quot;134&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;양변의 덧셈항을 분리하면 다음의 관계를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ t_1 = \frac{x'}{x_1}q_1, \ \ \ t_2 = \frac{x'}{x_2}q_2 $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞선 예의 $y$축에서 무게중심좌표는 각각 0.25와 0.75였다. 이를 통해 $x$축에서의 무게중심좌표 0.5가 잘 계산되는지 확인해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ q_1 = \frac{1}{4}, \ \ q_2 = \frac{3}{4} $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ t_1 = \frac{x'}{x_1} q_1 = \frac{4}{2} \cdot \frac{1}{4} = 0.5 \\ t_2 = \frac{x'}{x_2} q_2 = \frac{4}{6} \cdot \frac{3}{4} = 0.5 $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위처럼 $x$축에서의 무게중심좌표 0.5가 잘 계산됨을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 점에 조합에 대한 무게중심좌표를 확장해 삼각형을 구정하는 세 점의 조합에도 동일하게 적용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qfrv5/dJMcaih69Z9/i07QnpCn9Z1V9ZtHr7g4M1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qfrv5/dJMcaih69Z9/i07QnpCn9Z1V9ZtHr7g4M1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qfrv5/dJMcaih69Z9/i07QnpCn9Z1V9ZtHr7g4M1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqfrv5%2FdJMcaih69Z9%2Fi07QnpCn9Z1V9ZtHr7g4M1%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;366&quot; height=&quot;138&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 식을 사용하여 NDC에서 구한 무게중심좌표를 투영 전 무게중심좌표로 변환해 보자. NDC로 변환할 때 나누는 값은 뷰 공간의 $z$값이므로, 위 식에서 $x$를 $-z$로 치환하면 최종 투영 보정 보간식을 얻을 수 있다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;141&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o7ZyT/dJMcachUJ5r/foDRvMUMr6Za6iKPx5WVjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o7ZyT/dJMcachUJ5r/foDRvMUMr6Za6iKPx5WVjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o7ZyT/dJMcachUJ5r/foDRvMUMr6Za6iKPx5WVjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo7ZyT%2FdJMcachUJ5r%2FfoDRvMUMr6Za6iKPx5WVjK%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;379&quot; height=&quot;141&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;141&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; font-size: 16px; letter-spacing: 0px;&quot;&gt;[최종 투영 보정 보간식]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 깊이 버퍼&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;같은 거리에 있는 두 게임 오브젝트가 서로 겹쳐있다면, 물체 단위로 그리는 순서를 조절하는 것으로는 문제를 해결할 수 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;근본적인 해결 방식은 오브젝트 단위가 아닌, 삼각형의 픽셀 단위로 깊이를 비교하여 가까운 곳의 픽셀만 그리는 것이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이를 구현하기 위해 화면의 픽셀마다 현재의 깊이 값을 보관해 주는 별도의 저장 공간이 필요한데, 이를 &lt;b&gt;깊이 버퍼(Depth buffer)&lt;/b&gt;라고 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;깊이 버퍼에 저장된 값을 비교하여 현재 깊이 값이 작은 경우에만 픽셀을 찍도록 로직을 구성하면, 그리지 않아도 되는 픽셀을 파악해 그리기를 건너뛸 수 있는데, 이 작업을 깊이 테스팅이라 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>게임수학</category>
      <category>게임수학</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/55</guid>
      <comments>https://hate-errorlog.tistory.com/55#entry55comment</comments>
      <pubDate>Sat, 28 Feb 2026 19:40:16 +0900</pubDate>
    </item>
    <item>
      <title>[게임수학] 10-1. 원근 투영</title>
      <link>https://hate-errorlog.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; text-align: left;&quot;&gt;&amp;rarr; 이 글은&amp;nbsp;&lt;/span&gt;&lt;b&gt;「이득우의 게임 수학」&lt;/b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; text-align: left;&quot;&gt;을 바탕으로 작성했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 원근 투영 변환 원리&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QP5wk/dJMcahQ2VWC/kKQtzKIZ50wYkzMYSO2clk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QP5wk/dJMcahQ2VWC/kKQtzKIZ50wYkzMYSO2clk/img.png&quot; data-alt=&quot;[투시 원근법]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QP5wk/dJMcahQ2VWC/kKQtzKIZ50wYkzMYSO2clk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQP5wk%2FdJMcahQ2VWC%2FkKQtzKIZ50wYkzMYSO2clk%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;630&quot; height=&quot;480&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[투시 원근법]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우리 눈은 멀리 있는 물체일수록 작게 보이는 구조를 가지고 있다. 이는 눈이 외부의 빛을 한 점(망막)으로 모아 받아들이기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;르네상스 시대의 화가들은 이 원리를 그림에 적용해 입체감을 표현하는 기법을 만들었고, 이를 투시 원근법(Perspective projection drawing)이라 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d9Un1U/dJMcagq4bkM/rskv7lCWeoC5l5GrvGKcrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d9Un1U/dJMcagq4bkM/rskv7lCWeoC5l5GrvGKcrk/img.png&quot; data-alt=&quot;[카메라의 화각]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d9Un1U/dJMcagq4bkM/rskv7lCWeoC5l5GrvGKcrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd9Un1U%2FdJMcagq4bkM%2Frskv7lCWeoC5l5GrvGKcrk%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;578&quot; height=&quot;418&quot; data-origin-width=&quot;578&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[카메라의 화각]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;우리가 만드는 3D 공간은 결국 2D화면(모니터)에 표현되어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 3차원 공간의 모든 점들을 2차원 화면 위에 자연스럽게 투영하기 위해, 멀리 있는 것은 작게, 가까운 것은 크게 보이도록 하는 변환 과정이 필요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 변환을 바로 &lt;b&gt;&amp;ldquo;원근 투영 변환(Perspective projection transformation)&amp;rdquo;&lt;/b&gt;이라 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;가상공간에서 우리 눈에 대응되는 물체는 카메라이고, 이 카메라가 보는 범위를 화각(Field of view)라 한다.&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라에 화각을 설정하면 위와 같은 균등한 사각뿔 영역이 만들어진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-21 181406.png&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;428&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rl6HF/dJMcahDuKEO/N7MR7YRoJsa1Cp2tmYoT80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rl6HF/dJMcahDuKEO/N7MR7YRoJsa1Cp2tmYoT80/img.png&quot; data-alt=&quot;[투영 평면과 초점 거리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rl6HF/dJMcahDuKEO/N7MR7YRoJsa1Cp2tmYoT80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frl6HF%2FdJMcahDuKEO%2FN7MR7YRoJsa1Cp2tmYoT80%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;530&quot; height=&quot;375&quot; data-filename=&quot;스크린샷 2025-11-21 181406.png&quot; data-origin-width=&quot;605&quot; data-origin-height=&quot;428&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[투영 평면과 초점 거리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라에서부터 3차원 공간의 모든 물체의 상이 맺히는 &lt;b&gt;&amp;ldquo;가상의 평면&amp;rdquo;&lt;/b&gt;을 투영 평면(Projection plane)이라 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;투영 평면은 &amp;ldquo;3D 점의 상이 어디에 맺힐 것인가&amp;rdquo;를 정의하는 개념적 기준이고, 실제 계산은 원근 투영 변환 행렬을 통해 수행된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라로부터 투영 평면까지의 거리를 초점 거리(Focal Length)라 하며, 이는 원근감과 화면에 보이는 물체 크기에 영향을 준다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. NDC(Normalized device coordinate) 영역&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-21 182434.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAe2zu/dJMcagYTNMS/wQHPgUaZCE8QedwVTN5Ld0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAe2zu/dJMcagYTNMS/wQHPgUaZCE8QedwVTN5Ld0/img.png&quot; data-alt=&quot;[오른쪽에서 바라본 투영 평면]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAe2zu/dJMcagYTNMS/wQHPgUaZCE8QedwVTN5Ld0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAe2zu%2FdJMcagYTNMS%2FwQHPgUaZCE8QedwVTN5Ld0%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;650&quot; height=&quot;503&quot; data-filename=&quot;스크린샷 2025-11-21 182434.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[오른쪽에서 바라본 투영 평면]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원근 투영을 구현하기 위해 투영 평면의 위치를 지정해야 하는데, 일반적으로 계산의 편의를 위해 위아래 크기가 각각 1이 되는 지점으로 결정한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 좌우와 상하가 각각 $[-1,1]$의 범위를 가지는 정사각형 평면에 물체의 상이 맺힌다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;투영 평면에 대응하는 정사각형 영역의 2차원 좌표 시스템을 &lt;b&gt;&amp;ldquo;NDC(Normalized device coordinate)&amp;rdquo;&lt;/b&gt;라 부른다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-11-21 183429.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;683&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd3CbY/dJMcaih68MS/eGFpNIj9yGDMCeiK8jMX30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd3CbY/dJMcaih68MS/eGFpNIj9yGDMCeiK8jMX30/img.png&quot; data-alt=&quot;[투영 평면의 NDC 영역]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd3CbY/dJMcaih68MS/eGFpNIj9yGDMCeiK8jMX30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd3CbY%2FdJMcaih68MS%2FeGFpNIj9yGDMCeiK8jMX30%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;650&quot; height=&quot;277&quot; data-filename=&quot;스크린샷 2025-11-21 183429.png&quot; data-origin-width=&quot;1604&quot; data-origin-height=&quot;683&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[투영 평면의 NDC 영역]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;투영 평면에 대응되는 NDC가 언제나 일정한 값을 가져야 한다면, 카메라의 화각이 변활 때, 초점 거리는 달라질 수밖에 없다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;화각이 커질수록 초점 거리는 짧아진다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;화각이 작아질수록 초점 거리는 멀어진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇다면 화각과 초점 거리의 관계는 아래처럼 직각삼각형의 $\tan$ 함수로 결정된다.&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;스크린샷 2025-11-21 183832.png&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;657&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cashx5/dJMcaiI9yUm/cZuITPWM7eI6yQFK2YKmx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cashx5/dJMcaiI9yUm/cZuITPWM7eI6yQFK2YKmx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cashx5/dJMcaiI9yUm/cZuITPWM7eI6yQFK2YKmx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcashx5%2FdJMcaiI9yUm%2FcZuITPWM7eI6yQFK2YKmx0%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;650&quot; height=&quot;657&quot; data-filename=&quot;스크린샷 2025-11-21 183832.png&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;657&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ \tan(\frac{\theta}{2}) = \frac{1}{d} $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[화곽과 초점거리의 관계]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 주어진 화각 $\theta$에 따른 초점 거리 $d$는 양변에 $d$를 곱하고, 다시 양변에 $\tan(\frac{\theta}{2})$로 나누면 아래와 같이 정리된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ d = \frac{1}{\tan(\frac{\theta}{2})} $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 원근 투영 행렬&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;지금까지의 공간 변환은 $x,y,z$축이 서로 직교하고 크기가 동일한 형태였다. 이러한 공간을 유클리드 공간(Euclidean space)라 하고, 여태까지 다룬 로컬, 월드, 뷰 모두 유클리드 공간의 형태다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정육면체 형태의 공간.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;유클리드 공간의 점들은 원근 투영 변환을 통해 사각뿔 형태로 바뀌게 되며, 이를 사영 공간(동차좌표 공간)이라 한다. 그중 카메라 시야 영역은 절두체 형태로 정의된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사영 공간에서 $x,y$축은 여전히 직교하지만, $z$축은 깊이에 따라 $x,y$축에 영향을 준다. &amp;rarr; 이는 초점 거리에 따라 투영 평면의 크기가 달라지기 때문.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 사영 공간의 성질 때문에 &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존의 표준기저벡터의 변화를 관찰하는 방법으로는 행렬을 설계할 수 없다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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-01-08 005202.png&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgdvOD/dJMcahQ2V2p/xPlaotobUsEVqIcY1ipP81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgdvOD/dJMcahQ2V2p/xPlaotobUsEVqIcY1ipP81/img.png&quot; data-alt=&quot;[투영의 진행 과정]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgdvOD/dJMcahQ2V2p/xPlaotobUsEVqIcY1ipP81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgdvOD%2FdJMcahQ2V2p%2FxPlaotobUsEVqIcY1ipP81%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;730&quot; height=&quot;286&quot; data-filename=&quot;스크린샷 2026-01-08 005202.png&quot; data-origin-width=&quot;957&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[투영의 진행 과정]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서, $x$축을 배제(0으로 고정)한 $y, z$축으로 공간을 설정하고 투명 평면에 상이 맺히는 과정을 생각해 보자.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;뷰 공간의 점 $P_{view}$가 투영 평면에 투영된 점을 $P_{ndc}$라 할 때, 투영 과정은 위와 같이 진행된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$x$축을 0으로 고정했으니, 각 공간의 점 $P$의 좌표는 다음과 같이 표현할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{view} = (0, v_y, v_z) \\ P_{ndc} = (0, n_y) $$&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-01-08 023033.png&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bor1qb/dJMcad18b5q/ZxrhKXGbIV8RtMKwiT0020/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bor1qb/dJMcad18b5q/ZxrhKXGbIV8RtMKwiT0020/img.png&quot; data-alt=&quot;[두 개의 닮은꼴 삼각형]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bor1qb/dJMcad18b5q/ZxrhKXGbIV8RtMKwiT0020/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbor1qb%2FdJMcad18b5q%2FZxrhKXGbIV8RtMKwiT0020%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;730&quot; height=&quot;359&quot; data-filename=&quot;스크린샷 2026-01-08 023033.png&quot; data-origin-width=&quot;956&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[두 개의 닮은꼴 삼각형]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 점의 관계를 파악하기 위해 닮은 꼴 삼각형 두 개를 그리면, 닮은꼴 성질로부터 아래의 비가 완성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ n_y : d = v_y : -v_z $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 투영평면의 $y$값인 $n_y$는 아래와 같은 방식으로 얻을 수 있고,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ n_y = \frac{d \cdot v_y}{-v_z} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라의 좌우와 상하의 시야각은 동일하므로, NDC의 $y$값 또한 0으로 고정시키면 $x,z$축의 평면을 사용하는 방식으로 구할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ n_x = \frac{d \cdot v_x}{-v_z} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 초점거리와 뷰 좌표로부터 $P_{ndc}$는 아래와 같이 계산된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{ndc} = (n_x, n_y) = (\frac{d \cdot v_x}{-v_z},\frac{d \cdot v_y}{-v_z}) = -\frac{d}{v_z} (v_x, v_y) $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 종횡비&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;NDC영역은 $[-1,1]$의 범위를 가진, 정사각형 모양의 정규화된 좌표계를 가지므로 실제 화면에 출력하기 위해서는 변환을 통해 화면 해상도에 맞게 변환해주어야 한다.&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-01-08 210922.png&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckJ0xp/dJMcaaRW6fj/Ck2EVpkwanjGvsrAMW8keK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckJ0xp/dJMcaaRW6fj/Ck2EVpkwanjGvsrAMW8keK/img.png&quot; data-alt=&quot;[종횡비를 고려하지 않아서 발생하는 문제]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckJ0xp/dJMcaaRW6fj/Ck2EVpkwanjGvsrAMW8keK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckJ0xp%2FdJMcaaRW6fj%2FCk2EVpkwanjGvsrAMW8keK%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;482&quot; height=&quot;279&quot; data-filename=&quot;스크린샷 2026-01-08 210922.png&quot; data-origin-width=&quot;482&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[종횡비를 고려하지 않아서 발생하는 문제]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 모니터는 1:1 비율이 아닌 16:9, 4:3처럼 가로 세로 비율이 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이때, 가로와 세로를 균등하게 늘리면 물체가 찌그러지게 되는데, 이는 화면의 가로 세로 비, 즉(종횡비 - Aspect ratio)를 고려하지 않았기 때문이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 종횡비를 미리 파악해 NDC에 원을 투영할 때 종횡비를 거꾸로 뒤집은 비율을 적용해 먼저 찌그러트려보자.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2026-01-08 232029.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxXjZO/dJMcaibnb5S/K6AgCz1Aunai2E6Tx7n13k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxXjZO/dJMcaibnb5S/K6AgCz1Aunai2E6Tx7n13k/img.png&quot; data-alt=&quot;[종횡비를 고려해 처리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxXjZO/dJMcaibnb5S/K6AgCz1Aunai2E6Tx7n13k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxXjZO%2FdJMcaibnb5S%2FK6AgCz1Aunai2E6Tx7n13k%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;298&quot; data-filename=&quot;스크린샷 2026-01-08 232029.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[종횡비를 고려해 처리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;종횡비는 보통 하나의 축 크기를 1로 지정하고, 다른 축의 크기를 상대적으로 측정해 나타낸다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;많이 사용하는 1920x1080 해상도의 세로축을 기준으로 측정한 종횡비는 $a = \frac{1920}{1080} = \frac{16}{9} \approx 1.777...$ 이 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{ndc} = -\frac{d}{v_z}(\frac{v_x}{a}, v_y) $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[NDC 좌표 수정]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;NDC의 투영 결과를 좌우로 찌그러트리려면 $x$축 값을 변경해야 하는데, 종횡비의 역수 $\frac{1}{a}$을 곱하면 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 NDC 좌표를 계산하는 공식은 위와 같이 변경된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막으로 뷰 공간의 점$P_{view}$의 $x,y$ 값으로 만든 벡터를 $\vec v = (v_x, v_y)$로 지정하면, $P_{ndc}$의 두 점의 좌표는 다음과 같이 계산할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{ndc} = P \cdot \vec{v}= \begin{bmatrix}\frac{1}{a}\,\frac{d}{-v_z} &amp;amp; 0 \\0 &amp;amp; \frac{d}{-v_z}\end{bmatrix}\cdot\begin{bmatrix}v_x \\v_y\end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[원근 투영 행렬]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 이 행렬엔 문제가 있는데, 변환할 점의 $z$값이 행렬에 사용되다 보니 변환할 점마다 행렬을 새로 생성해야 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;미리 곱해둔 행렬을 사용하여 연산량을 줄이는 장점이 사라짐.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그렇다면 우리는 카메라 설정만으로 행렬을 만들어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;행렬의 구성 요소를 자세히 보면 $-v_z$를 모두 분모로 사용하는데, 이러면 행렬에서 이 값을 제거하고 결과에서 나눠줄 수 있을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 원근 투영 행렬에서 값 제거&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 설계한 원근 투영 행렬에서 $-v_z$값을 제거하고 행렬의 곱하는 벡터의 값을 $\vec v = (v_x, v_y, v_z)$로 지정하자.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$\vec v$는 뷰 좌표계의 점과 동일한 값.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P \cdot \vec{v}=\begin{bmatrix}\frac{d}{a} &amp;amp; 0 &amp;amp; 0 \\0 &amp;amp; d &amp;amp; 0 \\0 &amp;amp; 0 &amp;amp; -1\end{bmatrix}\begin{bmatrix}v_x \\v_y \\v_z\end{bmatrix}=\begin{bmatrix}\frac{d}{a}\, v_x \\d\, v_y \\- v_z\end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;최종적으로 얻어야 할 NDC 좌표와는 다르지만, 위 행렬$P$는 카메라 설정 값으로만 구성된다. 이렇게 되면 모든 점에 대해 원근 투영 행렬 $P$를 사용할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{clip} = (\frac{d \cdot v_x}{a}, d \cdot v_y, -v_z) $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[클립 좌표]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 원근 투영 행렬 $P$로 변환되는 좌표계 클립을 클립 좌표(Clip coordinate)라 부른다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 뷰 공간 좌표 적용&lt;/span&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{ndc} = (\frac{d \cdot v_x}{-v_z \cdot a}, \frac{d \cdot v_y}{-v_z}, 1) $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막으로 클립 좌표의 점의 각 요소를 세 번째 값인 $-v_z$로 나누면 최종적인 NDC의 좌표를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 동차 좌표계&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 점에서 원근 투영 행렬을 사용할 수 있도록 두 단계로 구분하고, 사용하는 벡터의 값도 한 차원 높였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 한 차원 높인 벡터를 사용하는 것을 게임 제작에서 보통 동차 좌표계(Homogenous coordinate system)를 사용한다고 표현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;좀 전에 &amp;ldquo;유클리드 공간의 점들은 원근 투영 변환을 통해 사각뿔 형태로 바뀌게 되며, 이를 사영 공간이라 한다.&amp;rdquo;라고 설명하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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-01-09 003042.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u1lcz/dJMcagYTNUB/WWyfX8BRRfApHUus8TSHbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u1lcz/dJMcagYTNUB/WWyfX8BRRfApHUus8TSHbk/img.png&quot; data-alt=&quot;[사영 공간에서 점의 이동과 투영된 점의 변화]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u1lcz/dJMcagYTNUB/WWyfX8BRRfApHUus8TSHbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu1lcz%2FdJMcagYTNUB%2FWWyfX8BRRfApHUus8TSHbk%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;333&quot; data-filename=&quot;스크린샷 2026-01-09 003042.png&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[사영 공간에서 점의 이동과 투영된 점의 변화]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사영 공간의 점은 카메라와 가까울수록 투영 평면의 원점과 멀어지고, 멀수록 투영 평면의 원점과 가까워지는 반비례 관계이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3차원 사영 공간의 점을 $(x', y', z')$으로 표기하고, 투영된 NDC 좌표를 $(x,y)$로 표기한다면, 사영 공간의 마지막 차원 값의 반비례로 영향을 받으므로 아래와 같은 관계가 성립한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ x = \frac{x'}{z'} \ \ \ \ y = \frac{y'}{z'} $$&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-01-09 003423.png&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;350&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUJBvN/dJMcabXBiW5/K9euBzkv60KIUDxnkAuU4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUJBvN/dJMcabXBiW5/K9euBzkv60KIUDxnkAuU4K/img.png&quot; data-alt=&quot;[직선의 방정식]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUJBvN/dJMcabXBiW5/K9euBzkv60KIUDxnkAuU4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUJBvN%2FdJMcabXBiW5%2FK9euBzkv60KIUDxnkAuU4K%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;350&quot; data-filename=&quot;스크린샷 2026-01-09 003423.png&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;350&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[직선의 방정식]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 직선의 방정식을 사영 공간의 좌표로 표현해 보자. 위 식을 사용하여 $x,y$를 치환하면,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ \frac{y'}{z'} = a\frac{x'}{z'}+b $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 식의 양변에 $z'$을 곱하면 상수가 사라지고, 세 미지수의 차수가 모두 1차식으로 동일한 수식이 구성된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ y' = ax' +bz' $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 미지수에 대한 차수가 모두 동일한 방정식을 동차 방정식이라 부르며, 사영 공간에서 사용하는 좌표계도 동차 좌표계라 부를 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정리하자면 원근 투영 행렬을 구하기 위한 첫 단계의 클립 좌표는, 투영되기 전 사영 공간의 좌표를 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 원근 투영에서 등장한 사영, 클립, 동차 공간은 모두 동일한 공간을 가리키고, 클립, 동차 좌표 역시 동일한 좌표계를 가리킨다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사영 공간의 점이 카메라로부터 멀어질수록 투영 평면의 원점$(0,0)$에 가까워지는데, NDC의 원점은 회화의 투시 원근 기법에서 이야기하는 &lt;b&gt;&amp;ldquo;소실점(Vanishing Point)&amp;rdquo;&lt;/b&gt; 에 해당된다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 정리&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;다음 내용인 깊이값으로 넘어가기 전에 내용을 다시 한번 정리해 보겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 월드 &amp;rarr; 뷰 공간&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;월드 공간의 물체 좌표를 카메라를 원점으로 다시 계산한 공간.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;카메라가 바라보는 방향이 기준축이 되고, View 행렬은 카메라 트랜스폼의 역행렬이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 투영 평면&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;뷰 공간에서 카메라 앞에는 투영 평면이 있고, 원근 투영으로 모든 물체의 상이 해당 평면에 맺힌다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 이 투영 평면을 $[-1,1]$로 정규화한 공간을 NDC라 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;투영 평면에 대응되는 NDC는 언제나 일정한 값을 가져야 하므로, 화각$(\frac{\theta}{2})$에 따라 초점 거리는 달라진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;화각이 클수록 초점 거리$(d)$는 짧아지고, 화각이 작을수록 초점 거리는 멀어지는 $d = \frac{1}{\tan(\frac{\theta}{2})}$ 관계를 가지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. 투영 행렬&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;사영 공간에서 x, y축은 직교하지만 두 축은 z축의 영향을 받는다(초점 거리에 따라 투영 평면의 크기가 달라지기 때문). x축을 0으로 고정하고, view와 ndc 좌표는 다음과 같이 표현한다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{view} = (0, v_y, v_z) \\ P_{ndc} = (0, n_y) $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 점의 관계는 두 개의 닮은 꼴 삼각형 성질로부터 구할 수 있고, x값과 y값을 0으로 고정시키는 방법으로 $P_{ndc}$ 좌표를 구할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{ndc} = (n_x, n_y) = (\frac{d \cdot v_x}{-v_z},\frac{d \cdot v_y}{-v_z}) = -\frac{d}{v_z} (v_x, v_y) $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;NDC 영역은 정사각형 영역이기 때문에, 화면 모니터 비율에 맞추어 늘리게 되면 물체가 찌그러지는 현상이 생긴다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{ndc} = P \cdot \vec{v}= \begin{bmatrix}\frac{1}{a}\,\frac{d}{-v_z} &amp;amp; 0 \\0 &amp;amp; \frac{d}{-v_z}\end{bmatrix}\cdot\begin{bmatrix}v_x \\v_y\end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 미리 좌우로 찌그러트리면 종횡비 문제를 해결할 수 있고, 종횡비의 역수인$\frac{1}{a}$를 $x$좌표에 곱하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 위 행렬은 변환할 점의 $z$값이 사용되다 보니, 이를 배제해야 한다. 앞서 설계한 원근 투영에서 $-v_z$값을 제거하고 곱하는 벡터의 값을 $\vec v = (v_x, v_y, v_z)$로 지정하면,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P \cdot \vec{v}=\begin{bmatrix}\frac{d}{a} &amp;amp; 0 &amp;amp; 0 \\0 &amp;amp; d &amp;amp; 0 \\0 &amp;amp; 0 &amp;amp; -1\end{bmatrix}\begin{bmatrix}v_x \\v_y \\v_z\end{bmatrix}=\begin{bmatrix}\frac{d}{a}\, v_x \\d\, v_y \\- v_z\end{bmatrix} $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;행렬 $P$는 카메라 설정 값으로만 구성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{clip} = (\frac{d \cdot v_x}{a}, d \cdot v_y, -v_z) $$&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[클립 좌표]&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;$$ P_{ndc} = (\frac{d \cdot v_x}{-v_z \cdot a}, \frac{d \cdot v_y}{-v_z}, 1) $$&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막으로 클립 좌표의 점의 각 요소를 세 번째 값인 $-v_z$로 나누면 최종적인 NDC의 좌표를 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>게임수학</category>
      <category>게임수학</category>
      <category>원근투영행렬</category>
      <category>종횡비</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/54</guid>
      <comments>https://hate-errorlog.tistory.com/54#entry54comment</comments>
      <pubDate>Sat, 28 Feb 2026 18:20:51 +0900</pubDate>
    </item>
    <item>
      <title>[C++, 자료구조] 덱(Deque)</title>
      <link>https://hate-errorlog.tistory.com/53</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 덱(Deque)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스택은 같은 한쪽(Top)에서 데이터를 넣고 빼는 구조였고, 큐는 뒤에서 데이터를 넣고 앞에서 데이터를 꺼내는 자료구조였다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQuwo9/dJMcaa4WQi2/LV5dpkhb1sgeJJRUc8lD40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQuwo9/dJMcaa4WQi2/LV5dpkhb1sgeJJRUc8lD40/img.png&quot; data-alt=&quot;[Deque]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQuwo9/dJMcaa4WQi2/LV5dpkhb1sgeJJRUc8lD40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQuwo9%2FdJMcaa4WQi2%2FLV5dpkhb1sgeJJRUc8lD40%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;650&quot; height=&quot;430&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Deque]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱은 앞으로도 뒤로도 넣을 수 있고, 앞으로도 뒤로도 뺄 수 있는, 스택과 큐의 특성을 모두 갖추고 있는 자료구조이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Deque는 &amp;lsquo;디큐&amp;rsquo;로 읽기 쉬운데, 이러면 큐의 Dequeue연산과 발음이 같아져 &amp;ldquo;덱&amp;rdquo;으로 발음한다&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Double-Ended Queue의 줄임말이다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ 덱의 추상 자료형&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 특성을 살펴봤으니, 이번엔 덱의 추상 자료형에 대해 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;정의&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;void InitDeque(Deque* dq)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 초기화를 진행한다. 덱 생성 후 가장 먼저 호출한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;bool IsEmpty(Deque* dq)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱이 빈 경우 ture, 그렇지 않은 경우는 false를 반환한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;void PushFront(Deque* dq, Data data)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 머리에 데이터를 저장한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;void PushBack(Deque* dq, Data data)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 꼬리에 데이터를 저장한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Data PopFront(Deque* dq)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 머리에 위치한 데이터를 반환 및 소멸한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Data PopBack(Deque* dq)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 꼬리에 위치한 데이터를 반환 및 소멸한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Data GetFirst(Deque* dq)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 머리에 위치한 데이터를 소멸하지 않고 반환한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Data GetLast(Deque* dq)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱의 꼬리에 위치한 데이터를 소멸하지 않고 반환한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱도 마찬가지로 배열, 혹은 리스트를 기반으로 작성할 수 있지만 데이터의 삽입 특징에 따라 가장 어울리는 &amp;ldquo;양방향 연결 리스트&amp;rdquo;를 사용하여 구현할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 덱의 구현&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. Deque.h&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#pragma once
typedef int Data;

struct DequeNode
{
	DequeNode* prev = nullptr;
	DequeNode* next = nullptr;
	Data data;
}; typedef DequeNode Node;

struct Deque
{
	Node* head = nullptr;
	Node* tail = nullptr;
};

void InitDeque(Deque* dq);

void push_front(Deque* dq, Data value);
void push_back(Deque* dq, Data value);
Data pop_front(Deque* dq);
Data pop_back(Deque* dq);

Data get_front(Deque* dq);
Data get_back(Deque* dq);

bool IsEmpty(Deque* dq);
&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 정리한 추상 자료형을 기반으로 헤더 파일을 작성하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1645&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cZ61Ei/dJMcacItv5k/mWTleBgEhS0KRKhMtIvjJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cZ61Ei/dJMcacItv5k/mWTleBgEhS0KRKhMtIvjJk/img.png&quot; data-alt=&quot;[꼬리가 있는 양방향 연결 리스트]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cZ61Ei/dJMcacItv5k/mWTleBgEhS0KRKhMtIvjJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcZ61Ei%2FdJMcacItv5k%2FmWTleBgEhS0KRKhMtIvjJk%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;700&quot; height=&quot;147&quot; data-origin-width=&quot;1645&quot; data-origin-height=&quot;345&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[꼬리가 있는 양방향 연결 리스트]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;머리를 가리키는 Head 포인터를 덱의 앞으로, 꼬리를 가리키는 Tail 포인터를 덱의 끝으로 설정하여 데이터를 추가하거나 반환한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. Deque.cpp&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;1. InitDeque() / IsEmpty()&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void InitDeque(Deque* dq)
{
	if(dq-&amp;gt;head != nullptr)
	{
		while (IsEmpty(dq) == false)
		{
			pop_front(dq);
		}
	}

	dq-&amp;gt;head = nullptr;
	dq-&amp;gt;tail = nullptr;
}

bool IsEmpty(Deque* dq)
{
	return dq-&amp;gt;head == nullptr;   // 헤드 노드가 nullptr이라면, 덱은 비어있다.
}
&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;덱을 초기화하고 나서 가장 먼저 호출하는 함수이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이전과 동일하게 덱에 데이터가 있는 상태에서 head와 tail을 nullptr로 만들어버리면, 기존 데이터에는 접근이 불가능하니 pop_front() 함수를 사용하여 할당된 메모리를 해제한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;2. push_front()&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;void push_front(Deque* dq, Data value)
{
	auto newNode = new Node;

	newNode-&amp;gt;data = value;
	newNode-&amp;gt;next = dq-&amp;gt;head;

	if(IsEmpty(dq))
	{
		dq-&amp;gt;tail = newNode;
	}
	else
	{
		dq-&amp;gt;head-&amp;gt;prev = newNode;
	}

	dq-&amp;gt;head = newNode;
}
&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;노드를 생성하고, 인자로 전달된 데이터를 초기화한다. 새롭게 생성된 노드의 next 포인터는 기존 head가 가리키는 노드를 가리키도록 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm0nC6/dJMcabCMQJC/vYJ8msa8jjYsCFKBWiCDk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm0nC6/dJMcabCMQJC/vYJ8msa8jjYsCFKBWiCDk0/img.png&quot; data-alt=&quot;[데이터가 추가되는 경우]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm0nC6/dJMcabCMQJC/vYJ8msa8jjYsCFKBWiCDk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm0nC6%2FdJMcabCMQJC%2FvYJ8msa8jjYsCFKBWiCDk0%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;730&quot; height=&quot;209&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[데이터가 추가되는 경우]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;처음으로 데이터가 추가되는 경우라면(dq&amp;rarr;head == null), tail도 newNode를 가리켜야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 데이터가 존재하는 경우라면, head가 가리키는 노드의 prev 포인터를 newNode를 가리키게 한 뒤, head노드를 옮긴다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;3. push_back()&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;void push_back(Deque* dq, Data value)
{
	auto newNode = new Node;

	newNode-&amp;gt;data = value;
	newNode-&amp;gt;prev = dq-&amp;gt;tail;

	if(IsEmpty(dq))
	{
		dq-&amp;gt;head = newNode;
	}
	else
	{
		dq-&amp;gt;tail-&amp;gt;next = newNode;
	}

	dq-&amp;gt;tail = newNode;
}
&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;push_front()와 동일한 원리로 노드를 추가한다. 데이터가 끝(tail)에 추가된다는 점만 조심해 주면 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;4. pop_front()&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Data pop_front(Deque* dq)
{
	if(IsEmpty(dq))
	{
		cout &amp;lt;&amp;lt; &quot;Deque is empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	auto delNode = dq-&amp;gt;head;
	auto retData = delNode-&amp;gt;data;

	dq-&amp;gt;head = delNode-&amp;gt;next;
	delete delNode;

	if(dq-&amp;gt;head == nullptr)
	{
		dq-&amp;gt;tail = nullptr;
	}
	else
	{
		dq-&amp;gt;head-&amp;gt;prev = nullptr;
	}

	return retData; 
}

&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;head가 가리키는 노드의 포인터 정보와 데이터 정보를 저장하고, Head의 위치를 삭제될 노드의 다음 위치로 옮긴다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1567&quot; data-origin-height=&quot;482&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cND27c/dJMcagKRMGC/PntaJU6Jbk3ERNyPRpSSi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cND27c/dJMcagKRMGC/PntaJU6Jbk3ERNyPRpSSi1/img.png&quot; data-alt=&quot;[데이터를 반환하는 경우]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cND27c/dJMcagKRMGC/PntaJU6Jbk3ERNyPRpSSi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcND27c%2FdJMcagKRMGC%2FPntaJU6Jbk3ERNyPRpSSi1%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;730&quot; height=&quot;225&quot; data-origin-width=&quot;1567&quot; data-origin-height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[데이터를 반환하는 경우]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;데이터를 추가할 때와 마찬가지로, 반환하고 소멸시킬 때도 확인해야 할 경우가 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dq-&amp;gt;head == nullptr : 더이상 남아있는 데이터가 없으므로, tail 역시 nullptr로 변경한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dq-&amp;gt;head != nullptr : 옮겨진 head가 가리키는 prev가 소멸되었으니, nullptr로 변경한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;5. pop_back()&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;Data pop_back(Deque* dq)
{
	if(IsEmpty(dq))
	{
		cout &amp;lt;&amp;lt; &quot;Deque is empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	auto delNode = dq-&amp;gt;tail;
	auto retData = delNode-&amp;gt;data;

	dq-&amp;gt;tail = delNode-&amp;gt;prev;
	delete delNode;

	if(dq-&amp;gt;tail == nullptr)
	{
		dq-&amp;gt;head = nullptr;
	}
	else
	{
		dq-&amp;gt;tail-&amp;gt;next = nullptr;
	}

	return retData;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;pop_back() 함수 역시 pop_front()와 같은 구조이므로 따로 설명은 하지 않고 넘어가겠다.&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;▼ 전체 코드&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &quot;Deque.h&quot;
#include &amp;lt;iostream&amp;gt;
using namespace std;

void InitDeque(Deque* dq)
{
	if(dq-&amp;gt;head != nullptr)
	{
		while (IsEmpty(dq) == false)
		{
			pop_front(dq);
		}
	}

	dq-&amp;gt;head = nullptr;
	dq-&amp;gt;tail = nullptr;
}

void push_front(Deque* dq, Data value)
{
	auto newNode = new Node;

	newNode-&amp;gt;data = value;
	newNode-&amp;gt;next = dq-&amp;gt;head;

	if(IsEmpty(dq))
	{
		dq-&amp;gt;tail = newNode;
	}
	else
	{
		dq-&amp;gt;head-&amp;gt;prev = newNode;
	}

	dq-&amp;gt;head = newNode;
}

void push_back(Deque* dq, Data value)
{
	auto newNode = new Node;

	newNode-&amp;gt;data = value;
	newNode-&amp;gt;prev = dq-&amp;gt;tail;

	if(IsEmpty(dq))
	{
		dq-&amp;gt;head = newNode;
	}
	else
	{
		dq-&amp;gt;tail-&amp;gt;next = newNode;
	}

	dq-&amp;gt;tail = newNode;
}

Data pop_front(Deque* dq)
{
	if(IsEmpty(dq))
	{
		cout &amp;lt;&amp;lt; &quot;Deque is empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	auto delNode = dq-&amp;gt;head;
	auto retData = delNode-&amp;gt;data;

	dq-&amp;gt;head = delNode-&amp;gt;next;
	delete delNode;

	if(dq-&amp;gt;head == nullptr)
	{
		dq-&amp;gt;tail = nullptr;
	}
	else
	{
		dq-&amp;gt;head-&amp;gt;prev = nullptr;
	}

	return retData; 
}

Data pop_back(Deque* dq)
{
	if(IsEmpty(dq))
	{
		cout &amp;lt;&amp;lt; &quot;Deque is empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	auto delNode = dq-&amp;gt;tail;
	auto retData = delNode-&amp;gt;data;

	dq-&amp;gt;tail = delNode-&amp;gt;prev;
	delete delNode;

	if(dq-&amp;gt;tail == nullptr)
	{
		dq-&amp;gt;head = nullptr;
	}
	else
	{
		dq-&amp;gt;tail-&amp;gt;next = nullptr;
	}

	return retData;
}

Data get_front(Deque* dq)
{
	if(IsEmpty(dq))
	{
		cout &amp;lt;&amp;lt; &quot;Deque is empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	return dq-&amp;gt;head-&amp;gt;data;
}

Data get_back(Deque* dq)
{
	if (IsEmpty(dq))
	{
		cout &amp;lt;&amp;lt; &quot;Deque is empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	return dq-&amp;gt;tail-&amp;gt;data;
}

bool IsEmpty(Deque* dq)
{
	return dq-&amp;gt;head == nullptr;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;#include&amp;lt;iostream&amp;gt;
#include &quot;Deque.h&quot;
using namespace std;

int main()
{
	Deque dq;
	InitDeque(&amp;amp;dq);

	for(int i = 0; i &amp;lt;= 50; i++)
	{
		if(i % 2 == 0)
		{
			push_front(&amp;amp;dq, i);
		}
		else
		{
			push_back(&amp;amp;dq, i);
		}
	}

	PrintAllDataFromFront(&amp;amp;dq);

	for(int i = 0; i &amp;lt;= 25; i++)
	{
		cout &amp;lt;&amp;lt; &quot;Pop Front: &quot; &amp;lt;&amp;lt; pop_front(&amp;amp;dq) &amp;lt;&amp;lt; endl;
		cout &amp;lt;&amp;lt; &quot;Pop Back: &quot; &amp;lt;&amp;lt; pop_back(&amp;amp;dq) &amp;lt;&amp;lt; endl &amp;lt;&amp;lt; endl;
	}

	return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mp6q9/dJMcahXkaF9/jgSWOitikKQvnQqyJjCIkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mp6q9/dJMcahXkaF9/jgSWOitikKQvnQqyJjCIkk/img.png&quot; data-alt=&quot;[Main.cpp]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mp6q9/dJMcahXkaF9/jgSWOitikKQvnQqyJjCIkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMp6q9%2FdJMcahXkaF9%2FjgSWOitikKQvnQqyJjCIkk%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;730&quot; height=&quot;571&quot; data-origin-width=&quot;1108&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Main.cpp]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 0~50까지의 수 중 짝수는 덱의 앞에, 홀수는 뒤의 덱에 추가한 뒤 앞 뒤에서 하나씩 pop 하면 성공적으로 덱을 구현한 것을 확인할 수 있다. &lt;/span&gt;&lt;/p&gt;</description>
      <category>C++/자료구조</category>
      <category>c++</category>
      <category>Deque</category>
      <category>덱</category>
      <category>자료구조</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/53</guid>
      <comments>https://hate-errorlog.tistory.com/53#entry53comment</comments>
      <pubDate>Mon, 22 Dec 2025 16:53:02 +0900</pubDate>
    </item>
    <item>
      <title>[C++, 자료구조] 큐(Queue)</title>
      <link>https://hate-errorlog.tistory.com/52</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt; ■ 큐(Queue)&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgVr1i/dJMcadURdPm/IjcN4plaBNxydCpIRvzaa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgVr1i/dJMcadURdPm/IjcN4plaBNxydCpIRvzaa0/img.png&quot; data-alt=&quot;[큐의 예시]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgVr1i/dJMcadURdPm/IjcN4plaBNxydCpIRvzaa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgVr1i%2FdJMcadURdPm%2FIjcN4plaBNxydCpIRvzaa0%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;570&quot; height=&quot;384&quot; data-origin-width=&quot;570&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[큐의 예시]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 스택(Stack)은 먼저 들어온 것이 나중에 나가는 &amp;ldquo;선입후출(FILO)&amp;rdquo; 구조를 지녔다. 그리고 이걸 &amp;ldquo;쌓여있는 접시&amp;rdquo;, &amp;ldquo;프링글스 통 안의 감자칩&amp;rdquo;에 비유했었다,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;반대로 큐(Queue)는 &amp;ldquo;줄을 서 있는 사람들&amp;rdquo;처럼 먼저 들어온 것이 먼저 나가는 &amp;ldquo;선입선출(FIFO)&amp;rdquo;의 구조를 지닌다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;큐의 개념은 일상생활에서도 쉽게 찾을 수 있다. 고무호스, 터널, 극장표 예매처 등은 모두 큐의 동작 방식과 유사한 구조이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ 큐의 추상 자료형&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스택과 마찬가지로 큐의 추상 자료형도 정형화된 편이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;enqueue : 큐에 데이터를 넣는 연산.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;dequeue : 큐에서 데이터를 빼는 연산.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 143px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;정의&lt;/span&gt; &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; &lt;span style=&quot;text-align: start;&quot;&gt;의미&lt;/span&gt; &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;void InitQueue(Queue* pq)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;큐의 초기화를 진행한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;bool IsEmpty(Queue* pq)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;큐가 빈 경우 true, 비어있지 않은 경우 false를 반환한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;void Enqueue(Queue* pq, Data data)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;큐에 데이터를 저장한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Data Dequeue(Queue* pq)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;저장순서가 가장 앞선 데이터를 삭제하고 반환한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Data Peek(Queue* pq)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;저장순서가 가장 앞선 데이터를 반환하되 삭제하지 않는다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스택에서 데이터를 넣고 빼는 연산을 가리켜 각각 push, pop이라 하는 것처럼, 큐에서 데이터를 넣고 빼는 연산을 각각 enqueue, dequeue라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 큐의 설계&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. 배열 기반 구현&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스택과 큐의 가장 큰 차이는 &lt;b&gt;데이터를 꺼내는 위치&lt;/b&gt;, 즉 앞에서 꺼내느냐 뒤에서 꺼내느냐의 차이이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;그래서 얼핏 보면, 앞서 만든 스택 자료구조에서 단순히 꺼내는 위치만 바꾸면 큐를 만들 수 있을 것처럼 보일 수 있지만, 실제로는 그렇지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;1025&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ss15F/dJMcahptXgX/HgbmHNAP9HFkucSkhaJetK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ss15F/dJMcahptXgX/HgbmHNAP9HFkucSkhaJetK/img.png&quot; data-alt=&quot;[Enqueue 과정]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ss15F/dJMcahptXgX/HgbmHNAP9HFkucSkhaJetK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSs15F%2FdJMcahptXgX%2FHgbmHNAP9HFkucSkhaJetK%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;700&quot; height=&quot;675&quot; data-origin-width=&quot;1063&quot; data-origin-height=&quot;1025&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Enqueue 과정]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;F(Fornt)가 가리키는 것이 큐의 머리이고, R(Rear)이 가리키는 것이 큐의 꼬리이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Enqueue를 하게 되면 R이 다음 칸을 가리키고 그 칸에 데이터가 저장된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;Dequeue : f가 가리키는 데이터 반환 후, 데이터를 빈자리로 이동&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;953&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/croTUP/dJMcacBHItM/wEIsYuMdYvK4SQFzATKIRk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/croTUP/dJMcacBHItM/wEIsYuMdYvK4SQFzATKIRk/img.png&quot; data-alt=&quot;[잘못된 Dequeue 방식]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/croTUP/dJMcacBHItM/wEIsYuMdYvK4SQFzATKIRk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcroTUP%2FdJMcacBHItM%2FwEIsYuMdYvK4SQFzATKIRk%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;700&quot; height=&quot;670&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;953&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[잘못된 Dequeue 방식]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dequeue 연산을 할 때, 반환할 데이터를 항상 배열의 맨 앞(Index : 0)에 위치시켜 보자. 이 방식의 경우 아래와 같은 문제점이 생긴다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;항상 배열의 첫 번째 데이터만 Dequeue되므로, 굳이 F(fornt 인덱스)가 필요 없다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dequeue가 일어날 때마다, 남아 있는 모든 데이터를 한 칸씩 앞으로 옮겨야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;Dequeue : f가 가리키는 데이터 반환 후, f를 이동&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ldk1P/dJMcahbVU9e/nBABlPYESD49ZFJGMQC5WK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ldk1P/dJMcahbVU9e/nBABlPYESD49ZFJGMQC5WK/img.png&quot; data-alt=&quot;[일반적인 Dequeue 방식]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ldk1P/dJMcahbVU9e/nBABlPYESD49ZFJGMQC5WK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fldk1P%2FdJMcahbVU9e%2FnBABlPYESD49ZFJGMQC5WK%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;700&quot; height=&quot;646&quot; data-origin-width=&quot;992&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[일반적인 Dequeue 방식]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 이번엔 Dequeue 연산 시 F를 이동시키고 있다. 이 방식을 취하게 되면 Dequeue 연산에서 데이터의 이동이 필요하지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D9Mah/dJMcafrEGZT/weLvhTAoaYJRBkXNy748cK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D9Mah/dJMcafrEGZT/weLvhTAoaYJRBkXNy748cK/img.png&quot; data-alt=&quot;[R의 위치가 배열의 끝인 경우]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D9Mah/dJMcafrEGZT/weLvhTAoaYJRBkXNy748cK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD9Mah%2FdJMcafrEGZT%2FweLvhTAoaYJRBkXNy748cK%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;700&quot; height=&quot;259&quot; data-origin-width=&quot;964&quot; data-origin-height=&quot;357&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[R의 위치가 배열의 끝인 경우]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;문자 E가 배열의 끝에 저장되어 더 이상 R을 옮길 수 없는 상황이다. 이 상황에서 어떻게 enqueue 연산을 진행할 수 있을까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;바로 R을 다시 배열의 맨 앞으로 이동시키면 된다!! (F 역시 마찬가지.)&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 배열의 끝에 도달하면 다시 처음으로 돌아가 회전하는 방식의 구조를 &lt;b&gt;&amp;ldquo;원형 큐(circular queue)라 한다.&amp;rdquo;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. 원형 기반 큐&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;552&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T7JZ6/dJMcabQj1qY/hM1Q41CkGIkuvAPlS02EG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T7JZ6/dJMcabQj1qY/hM1Q41CkGIkuvAPlS02EG1/img.png&quot; data-alt=&quot;[3번의 Enqueue 연산]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T7JZ6/dJMcabQj1qY/hM1Q41CkGIkuvAPlS02EG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT7JZ6%2FdJMcabQj1qY%2FhM1Q41CkGIkuvAPlS02EG1%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;700&quot; height=&quot;219&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;552&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[3번의 Enqueue 연산]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원형 큐도 앞서 보인 큐와 같이 첫 번째 데이터가 추가되는 순간 F, R이 동시에 그 데이터를 가리킨다. 이후 Dequeue연산을 두 번 진행하면 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1807&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LSes2/dJMcafyp9cw/UbaqDDx9DTmpmpZEgs049k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LSes2/dJMcafyp9cw/UbaqDDx9DTmpmpZEgs049k/img.png&quot; data-alt=&quot;[2번의 Dequeue 연산]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LSes2/dJMcafyp9cw/UbaqDDx9DTmpmpZEgs049k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLSes2%2FdJMcafyp9cw%2FUbaqDDx9DTmpmpZEgs049k%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;700&quot; height=&quot;218&quot; data-origin-width=&quot;1807&quot; data-origin-height=&quot;562&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[2번의 Dequeue 연산]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 이제 구현만 하면 될 것 같지만, 현재 원형 큐에서 생각해 볼 문제가 아직 남아있다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;646&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lFnsd/dJMcaiaQtXP/GH0AF2Ax2CyKUOeNEr4RvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lFnsd/dJMcaiaQtXP/GH0AF2Ax2CyKUOeNEr4RvK/img.png&quot; data-alt=&quot;[큐의 상태]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lFnsd/dJMcaiaQtXP/GH0AF2Ax2CyKUOeNEr4RvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlFnsd%2FdJMcaiaQtXP%2FGH0AF2Ax2CyKUOeNEr4RvK%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;700&quot; height=&quot;344&quot; data-origin-width=&quot;1316&quot; data-origin-height=&quot;646&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[큐의 상태]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원형 큐에서 데이터가 모두 차 있는 경우와 비어 있는 경우는 모두 F가 R보다 한 칸 앞선 위치를 가리키는 형태를 갖는다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 F와 R의 위치 정보만으로 큐가 비어 있는 상태인지, 가득 찬 상태인지 구분할 수 없다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 문제를 해결하기 위해 배열의 크기가 N일 때, 최대 N - 1개의 데이터만 저장하도록 제한한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 배열에 N-1개의 데이터가 채워졌을 경우를 &amp;ldquo;꽉 찬 상태&amp;rdquo;로 간주하고, 항상 하나의 빈 공간을 유지한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1797&quot; data-origin-height=&quot;1046&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zmqbP/dJMcac9uEPI/EO8CHVM4CTiAwwZlbdGxS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zmqbP/dJMcac9uEPI/EO8CHVM4CTiAwwZlbdGxS0/img.png&quot; data-alt=&quot;[개선된 원형 큐의 꽉 찬 상황]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zmqbP/dJMcac9uEPI/EO8CHVM4CTiAwwZlbdGxS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzmqbP%2FdJMcac9uEPI%2FEO8CHVM4CTiAwwZlbdGxS0%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;700&quot; height=&quot;407&quot; data-origin-width=&quot;1797&quot; data-origin-height=&quot;1046&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[개선된 원형 큐의 꽉 찬 상황]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;enqueue 연산 시 R이 가리키는 위치를 한 칸 이동시킨 다음에 데이터를 저장한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dequeue 연산 시 F가 가리키는 위치를 한 칸 이동시킨 다음에 데이터를 반환한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 R과 F가 같은 위치를 가리킨다면 큐가 &amp;ldquo;비어있는 상태&amp;rdquo; R이 가리키는 앞 위치가 F라면 큐가 &amp;ldquo;꽉 찬 상태&amp;rdquo;로 해석할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 원형 큐의 구현(배열 기반)&lt;/span&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. CircularQueue.h&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#pragma once
constexpr int QUE_LENGTH = 100;
typedef int Data;

struct CircularQueue
{
	Data data[QUE_LENGTH];
	int front;
	int rear;
}; typedef CircularQueue CQueue;

void InitQueue(CQueue* pq);

void Enqueue(CQueue* pq, Data item);
Data Dequeue(CQueue* pq);
Data Peek(CQueue* pq);

bool IsEmpty(const CQueue* pq);
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot; data-token-index=&quot;0&quot;&gt; [CircularQueue.h] &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 정의한 큐의 추상 자료형을 기반으로 위의 헤더파일을 정의하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. CircularQueue.cpp&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;헤더파일에 선언된 함수의 구현을 보일 차례인데, 그에 앞서 아래의 함수를 살펴보자. 함수는 간단하지만 원형 큐의 핵심이라 할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int NextIndex(int index)
{
	return (index + 1 &amp;gt;= QUE_LENGTH - 1)
		? 0 : index + 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이는 큐의 다음 위치에 해당하는 배열의 인덱스 값을 반환하는 함수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;pos 를 전달하면 pos + 1의 값을 반환하지만, 큐의 길이보다 하나 작은 값이 인자로 전달된다면 0을 반환한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이는 F와 R이 배열의 끝에 도달했으므로 앞으로 이동해야 함을 의미한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &quot;CircularQueue.h&quot;
#include &amp;lt;iostream&amp;gt;
using namespace std;

int NextIndex(int index);

void InitQueue(CQueue* pq)
{
	pq-&amp;gt;front = pq-&amp;gt;rear = 0;
}

void Enqueue(CQueue* pq, Data item)
{
	auto nextIndex = NextIndex(pq-&amp;gt;rear);

	// Queue가 가득 찼는지 확인
	if(nextIndex == pq-&amp;gt;front)
	{
		cout &amp;lt;&amp;lt; &quot;Queue is Full!&quot; &amp;lt;&amp;lt; endl;
		return;
	}

	pq-&amp;gt;rear = nextIndex;
	pq-&amp;gt;data[pq-&amp;gt;rear] = item;
}

Data Dequeue(CQueue* pq)
{
	if(IsEmpty(pq))
	{
		cout &amp;lt;&amp;lt; &quot;Queue is Empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	pq-&amp;gt;front = NextIndex(pq-&amp;gt;front);
	return pq-&amp;gt;data[pq-&amp;gt;front];
}

Data Peek(CQueue* pq)
{
	if(IsEmpty(pq))
	{
		cout &amp;lt;&amp;lt; &quot;Queue is Empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	return pq-&amp;gt;data[NextIndex(pq-&amp;gt;front)];
}

int NextIndex(int index)
{
	return (index + 1 &amp;gt;= QUE_LENGTH - 1)
		? 0 : index + 1;
}

bool IsEmpty(const CQueue* pq)
{
	return pq-&amp;gt;front == pq-&amp;gt;rear;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LCTc6/dJMcacItvUQ/unfMWvwIcytSd8QFB8sxm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LCTc6/dJMcacItvUQ/unfMWvwIcytSd8QFB8sxm0/img.png&quot; data-alt=&quot;[CircularQueue.cpp]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LCTc6/dJMcacItvUQ/unfMWvwIcytSd8QFB8sxm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLCTc6%2FdJMcacItvUQ%2FunfMWvwIcytSd8QFB8sxm0%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;700&quot; height=&quot;152&quot; data-origin-width=&quot;1111&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[CircularQueue.cpp]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Queue에 2~100의 숫자 중 짝수만 Enqueue() 하고, 그 뒤에 Queue가 빌 때까지 Dequeue()하였다. 스택과 다르게 먼저 들어간 숫자가 먼저 나오는 &amp;ldquo;선입선출(FIFO)&amp;rdquo;구조로 만들어진 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 연결 리스트 기반 큐의 구현&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;배열을 기반으로 구현된 Stack을 Queue로 변경하려고 하면, 앞에서 살펴본 것처럼 고려해야 할 문제가 한두가지가 아니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 &amp;rdquo;연결 리스트 기반의 큐&amp;rdquo;는 배열 기반 구조에 비해 이러한 제약이 적기 때문에, 구조적으로는 스택 구현 방식을 어느 정도 재활용할 수 있지만, 아래와 같은 본질적인 동작 방식의 차이는 존재한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스택은 Push와 Pop이 같은 위치에서 이루어지지만, 큐는 Queue와 Dequeue가 다른 위치에서 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. ListQueue.h&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#pragma once
typedef int Data;

struct ListQueueNode
{
	Data data;
	ListQueueNode* next = nullptr;
}; typedef ListQueueNode Node;

struct ListQueue
{
	Node* front = nullptr;
	Node* rear = nullptr;
}; typedef ListQueue LQueue;

void InitQueue(LQueue* pq);

void Enqueue(LQueue* pq, Data item);
Data Dequeue(LQueue* pq);
Data Peek(LQueue* pq);

bool IsEmpty(const LQueue* pq);
&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;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위의 헤더파일에서 보이듯이 연결 리스트 기반 큐에서도 원형 큐와 마찬가지로 front와 rear을 유지해야 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;enqueue연산과 dequeue 연산이 이뤄지는 위치가 다르기 때문.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. ListQueue.cpp&lt;/span&gt;&lt;/h3&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;void InitQueue(LQueue* pq)
{
	if(pq-&amp;gt;front != nullptr &amp;amp;&amp;amp; pq-&amp;gt;rear != nullptr)
	{
		// 기존 노드가 존재한다면 해제
		while(IsEmpty(pq) == false)
		{
			Dequeue(pq);
		}
	}

	pq-&amp;gt;front = nullptr;
	pq-&amp;gt;rear = nullptr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;처음 큐를 생성하면 front와 rear이 가리킬 대상이 없으니, 초기에는 nullptr로 설정한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단, 데이터가 남아있는 상태에서 nullptr로 초기화 하면 기존 생성된 데이터에 접근할 수 없으니, Dequeue()를 호출하여 메모리 공간을 해제한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;crmsh&quot;&gt;&lt;code&gt;void Enqueue(LQueue* pq, Data item)
{
	auto node = new Node();
	node-&amp;gt;data = item;
	node-&amp;gt;next = nullptr;

	if(IsEmpty(pq) == true)
	{
		pq-&amp;gt;front = node;
		pq-&amp;gt;rear = node;
	}
	else
	{
		pq-&amp;gt;rear-&amp;gt;next = node;
		pq-&amp;gt;rear = node;
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1871&quot; data-origin-height=&quot;434&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ef9Phk/dJMcaiWaAwe/x8cjgB9jNM3LkXKQkzsnP1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ef9Phk/dJMcaiWaAwe/x8cjgB9jNM3LkXKQkzsnP1/img.png&quot; data-alt=&quot;[Enqueue()]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ef9Phk/dJMcaiWaAwe/x8cjgB9jNM3LkXKQkzsnP1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fef9Phk%2FdJMcaiWaAwe%2Fx8cjgB9jNM3LkXKQkzsnP1%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;700&quot; height=&quot;162&quot; data-origin-width=&quot;1871&quot; data-origin-height=&quot;434&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Enqueue()]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;큐에 아무런 데이터가 저장되지 않은 상태에서 데이터를 추가하면, F와 R모두 첫 번째 데이터를 가리켜야 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이후에 추가되는 데이터는 R만 움직이면 되므로 가장 끝에 있는 노드(R이 가리키는 노드)가 새 노드를 가리키게 한 다음, R이 새 노드를 가리키게 만든다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;Data Dequeue(LQueue* pq)
{
	if(IsEmpty(pq))
	{
		cout &amp;lt;&amp;lt; &quot;Queue is Empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	auto delNode = pq-&amp;gt;front;
	auto retData = delNode-&amp;gt;data;
	pq-&amp;gt;front = delNode-&amp;gt;next;

	delete delNode;
	return retData;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;494&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Xsu5A/dJMcagKRMro/4H5qxsau3JskaMgBqbSaa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Xsu5A/dJMcagKRMro/4H5qxsau3JskaMgBqbSaa1/img.png&quot; data-alt=&quot;[Dequeue()]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Xsu5A/dJMcagKRMro/4H5qxsau3JskaMgBqbSaa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXsu5A%2FdJMcagKRMro%2F4H5qxsau3JskaMgBqbSaa1%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;700&quot; height=&quot;190&quot; data-origin-width=&quot;1824&quot; data-origin-height=&quot;494&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Dequeue()]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dequene 과정에서는 신경 써야 할 부분이 F 하나이므로, Enqueue처럼 다른 포인터(R)를 건드릴 필요는 없다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;F가 다음 노드를 가리키게 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;F가 이전에 가리키던 노드를 소멸시킨다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &quot;ListQueue.h&quot;
#include &amp;lt;iostream&amp;gt;
using namespace std;

void InitQueue(LQueue* pq)
{
	if(pq-&amp;gt;front != nullptr &amp;amp;&amp;amp; pq-&amp;gt;rear != nullptr)
	{
		// 기존 노드가 존재한다면 해제
		while(IsEmpty(pq) == false)
		{
			Dequeue(pq);
		}
	}

	pq-&amp;gt;front = nullptr;
	pq-&amp;gt;rear = nullptr;
}

void Enqueue(LQueue* pq, Data item)
{
	auto node = new Node();
	node-&amp;gt;data = item;
	node-&amp;gt;next = nullptr;

	if(IsEmpty(pq) == true)
	{
		pq-&amp;gt;front = node;
		pq-&amp;gt;rear = node;
	}
	else
	{
		pq-&amp;gt;rear-&amp;gt;next = node;
		pq-&amp;gt;rear = node;
	}
}

Data Dequeue(LQueue* pq)
{
	if(IsEmpty(pq))
	{
		cout &amp;lt;&amp;lt; &quot;Queue is Empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	auto delNode = pq-&amp;gt;front;
	auto retData = delNode-&amp;gt;data;
	pq-&amp;gt;front = delNode-&amp;gt;next;

	delete delNode;
	return retData;
}

Data Peek(LQueue* pq)
{
	if(IsEmpty(pq))
	{
		cout &amp;lt;&amp;lt; &quot;Queue is Empty!&quot; &amp;lt;&amp;lt; endl;
		return -1;
	}

	return pq-&amp;gt;front-&amp;gt;data;
}

bool IsEmpty(const LQueue* pq)
{
	return pq-&amp;gt;front == nullptr;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot; data-token-index=&quot;0&quot;&gt; [ListQueue.cpp 전체 코드] &lt;/span&gt;&lt;/p&gt;</description>
      <category>C++/자료구조</category>
      <category>c++</category>
      <category>Queue</category>
      <category>자료구조</category>
      <category>큐</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/52</guid>
      <comments>https://hate-errorlog.tistory.com/52#entry52comment</comments>
      <pubDate>Mon, 22 Dec 2025 16:44:07 +0900</pubDate>
    </item>
    <item>
      <title>[Unity, C#] SOLID 원칙</title>
      <link>https://hate-errorlog.tistory.com/51</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ SOLID 원칙&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKgfGX/dJMcagKF6O1/edknN1ZOBJBh9AOVJPk6G1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKgfGX/dJMcagKF6O1/edknN1ZOBJBh9AOVJPk6G1/img.png&quot; data-alt=&quot;[Gamg of Four]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKgfGX/dJMcagKF6O1/edknN1ZOBJBh9AOVJPk6G1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKgfGX%2FdJMcagKF6O1%2FedknN1ZOBJBh9AOVJPk6G1%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;500&quot; height=&quot;469&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;469&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Gamg of Four]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;프로그래밍에서 널리 사용되는 디자인 패턴(Design Patterns)은 Gang of Four(GOF) 라 불리는 4명의 저자가 정리한 책에서 시작되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;디자인 패턴을 공부해본 사람이라면 알겠지만, 대부분의 책은 SOLID 원칙부터 소개하고, 대부분의 디자인 패턴 역시 이 SOLID 원칙을 준수하도록 설계되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;▶ SOLID 원칙&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Single responsibility : 단일 책임 원칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Open-closed : 개방-폐쇄 원칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Liskov substitution : 리스코프 치환 원칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Interface segregation : 인터페이스 분리 원칙&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Dependency inversion : 의존 역전 원칙&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 5가지 원칙의 앞 글자를 따서 &amp;ldquo;SOLID&amp;rdquo; 원칙이라 부른다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;물론 실제 프로그래밍에서 이 모든 원칙을 완벽하게 지키며 설계하는 것은 쉽지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 가능한 한 SOLID 원칙을 기반으로 구조를 설계하면 코드의 유지보수성이 높아지고, 읽기 좋은 깔끔한 구조를 만들 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;1. Single responsibility principle : 단일 책임 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;688&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXL2E1/dJMcaaKsy5Z/A98ToCSU01ZHR1F6ZqBh3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXL2E1/dJMcaaKsy5Z/A98ToCSU01ZHR1F6ZqBh3k/img.png&quot; data-alt=&quot;[Unity Component]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXL2E1/dJMcaaKsy5Z/A98ToCSU01ZHR1F6ZqBh3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXL2E1%2FdJMcaaKsy5Z%2FA98ToCSU01ZHR1F6ZqBh3k%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;372&quot; height=&quot;688&quot; data-origin-width=&quot;372&quot; data-origin-height=&quot;688&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Unity Component]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 클래스는 오직 하나의 책임만 가진다. (대표적인 예 : Unity의 Component 구조)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클래스는 그 책임을 완전히 캡슐화한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Unity의 컴포넌트를 보면 Audio Source는 오디오 출력을 담당하고, Mesh Renderer는 메쉬를 그리는 역할만 담당한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 &lt;b&gt;하나의 클래스가 &amp;ldquo;하나의 역할(책임)&amp;rdquo;만&lt;/b&gt; 수행하도록 하는 것이 바로 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;&amp;ldquo;단일 책임 원칙&amp;rdquo;&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 클래스는 자신이 맡은 책임을 완전히 캡슐화해야 하는데, 이는 다음을 의미한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;책임을 수행하는 데 필요한 데이터(변수)를 외부에 노출하지 않는다. (private 선언)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;책임과 관련된 동작(메서드)을 클래스 내부에서 직접 수행한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;외부에는 필요한 기능만 공개하고, 내부 구현 방식을 감춘다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;  단일 책임 원칙의 장점&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하나의 클래스가 하나의 책임만 수행하므로, 클래스의 길이가 짧아지고 읽기 쉬워진다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클래스가 작고 역할이 명확하므로 상속이나 확장이 쉬워지고, 재사용성이 높아진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class Player : MonoBehaviour
{
    [SerializeField] private string 이동입력;
    [SerializeField] private float 이동관련변수_1;
    private float _이동관련변수_2;
    private AudioSource _오디오변수;

    private void Start()
    {
        _오디오변수 = GetComponent&amp;lt;AudioSource&amp;gt;();
    }

    private void Update()
    {
		    // 입력 로직...
        // 이동 관련 로직...
    }

    private void OnTriggerEnter(Collider other)
    {
        _오디오변수.Play();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[단일 책임 원칙을 지키지 않은 코드]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 코드를 보면 Player 클래스가 이동, 입력, 오디오 재생을 모두 한 번에 담당하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 하나의 클래스 안에서 &amp;ldquo;서로 다른 여러 책임&amp;rdquo;이 섞여있으므로, 단일 책임 원칙을 지키지 않은 예시라 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Player : MonoBehaviour
{
    [SerializeField] private PlayerAudio playerAudio;
    [SerializeField] private PlayerInput playerInput;
    [SerializeField] private PlayerMovement playerMovement;

    private void Start()
    {
        //...
    }
}

public class PlayerAudio : MonoBehaviour
{
    //...
}

public class PlayerInput : MonoBehaviour
{
    //...
}

public class PlayerMovement : MonoBehaviour
{
    //...
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #333333; text-align: center;&quot;&gt;[단일 책임 원칙을 지킨 코드]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;각 역할을 담당(이동, 입력, 오디오)하는 클래스를 만들고, Player 클래스가 이들을 의존(사용)하도록 만들었다. 이렇게 책임을 독립된 클래스로 나누면 구조가 명확해지고, 각 클래스는 자신만의 역할을 수행한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한 PlayerAudio 같은 클래스는 NPC나 다른 오브젝트에도 재사용할 수 있어서 확장성이 크게 높아진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;2. Open-closed principle : 개방-폐쇄 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;&amp;ldquo;확장에 대해 열려 있어야 하고&amp;rdquo;&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;요구 사항이 변경될 때, 새로운 기능을 추가하여 모듈을 확장할 수 있어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 기존 기능을 바꾸지 않고 동작을 덧붙여 확장할 수 있어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;&lt;b&gt;&amp;ldquo;수정에 대해서는 닫혀 있어야 한다.&amp;rdquo;&lt;/b&gt;&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;코드를 수정하지 않아도 모듈의 기능을 확장하거나 변경 가능해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;말이 좀 모호할 수 있는데, 간단히 설명하자면 &amp;ldquo;상속&amp;rdquo;을 통해 기능을 확장하고, 기존에 잘 굴러가는 코드는 수정하지 말라는 얘기이다.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Rectangle
{
    public float width;
    public float height;
}

public class Circle
{
    public float radius;
}

public class AreaCalculator
{
    public float GetRectangleArea(Rectangle rect)
    {
        //...
    }
    
    public float GetCircleArea(Rectangle rect)
    {
        //...
    }
} 
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[나쁜 예시]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;AreaCalculator에서 각 도형의 부피를 계산하지만, 위와 같이 코드를 작성하면 도형을 추가할 때마다 원본 코드(AreaCalculator)를 수정해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한, 도형이 늘어날수록 AreaCalculator의 함수도 계속 추가해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public abstract class Shape
{
    public abstract float CalculateArea();
}

public class Rectangle : Shape
{
    public float width;
    public float height;
    
    public override float CalculateArea()
    {
        //...
    }
}

public class Circ : Shape
{
    public float radius;
    
    public override float CalculateArea()
    {
        //...
    }
}

public class AreaCalculator
{
    public float GetArea(Shape shape)
    {
        return shape.CalculateArea();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[좋은 예시]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;CalculateArea()라는 추상 메서드를 가진 Shape 클래스를 만들고, 각 도형 클래스가 이를 상속받아 자신만의 방식으로 면적을 계산하도록 구현했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 구현하면 새로운 도형이 추가되더라도, AreaCalculator 클래스는 전혀 수정할 필요가 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;새로운 도형이 기존 코드를 건드리지 않고 자연스럽게 확장되기 때문에, 이러한 구조가 개방-폐쇄 원칙을 잘 지킨 사례이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;3. Liskov substitution principle : 리스코프 치환 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;2600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bryKT0/dJMcaiobShy/5DcCj7j1Ck5C97968FkLE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bryKT0/dJMcaiobShy/5DcCj7j1Ck5C97968FkLE1/img.png&quot; data-alt=&quot;[바바라 리스코프(Barbara Liskov)]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bryKT0/dJMcaiobShy/5DcCj7j1Ck5C97968FkLE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbryKT0%2FdJMcaiobShy%2F5DcCj7j1Ck5C97968FkLE1%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;350&quot; height=&quot;490&quot; data-origin-width=&quot;1857&quot; data-origin-height=&quot;2600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[바바라 리스코프(Barbara Liskov)]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;파생 클래스는 기본 클래스를 대체할 수 있어야 한다.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;리스코프(Liskov)에 별다른 뜻이 있는 것은 아니고, 이 원칙을 개발한 사람의 이름을 따서 만들어졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 원칙 역시 조금 애매한 표현인데, 쉽게 얘기하면 상속받은 클래스(하위 클래스)는 부모 클래스의 방향성을 지켜줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;869&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zoou0/dJMcaaRelCe/tXpuOWHYO35GbGU59FggH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zoou0/dJMcaaRelCe/tXpuOWHYO35GbGU59FggH0/img.png&quot; data-alt=&quot;[Vehicle 클래스를 상속받는 두 클래스]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zoou0/dJMcaaRelCe/tXpuOWHYO35GbGU59FggH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzoou0%2FdJMcaaRelCe%2FtXpuOWHYO35GbGU59FggH0%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;730&quot; height=&quot;590&quot; data-origin-width=&quot;1075&quot; data-origin-height=&quot;869&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Vehicle 클래스를 상속받는 두 클래스]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Vehicle 클래스는 속도와 방향을 나타내는 변수와 전, 후, 좌, 우로 움직이는 기능을 갖추고 있다. 이 클래스를 상속받아 Car와 Truck 클래스를 만들었고, 두 클래스 모두 동일한 방식으로 이동할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자동차와 트럭 모두 같은 방식으로 움직이며, 다른 탈것을 만든다고 해도 Vehicle 클래스를 상속받으면 기본적인 이동 기능을 그대로 사용할 수 있기에, 아직까지 문제가 있는 코드는 아니다&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;743&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwn9wd/dJMcahv3ckf/dhQ5ptqO065WOUwQKSdKs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwn9wd/dJMcahv3ckf/dhQ5ptqO065WOUwQKSdKs0/img.png&quot; data-alt=&quot;[새로운 탈것(Train)의 추가]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwn9wd/dJMcahv3ckf/dhQ5ptqO065WOUwQKSdKs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbwn9wd%2FdJMcahv3ckf%2FdhQ5ptqO065WOUwQKSdKs0%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;730&quot; height=&quot;445&quot; data-origin-width=&quot;1218&quot; data-origin-height=&quot;743&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[새로운 탈것(Train)의 추가]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기에 레일을 따라 움직이는 새로운 탈것인 Train 클래스를 추가해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;자연스럽게 Vehicle 클래스를 상속받았지만, 문제는 부모 클래스에 있는 TurnLeft(), TurnRight() 메서드가 Train에서는 전혀 사용되지 않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 &lt;b&gt;부모 클래스의 기능이 자식 클래스에서 무효화되거나 사용하지 않는 구조&lt;/b&gt;는 부모 클래스로서의 방향성을 지키지 못한다는 의미이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이러한 설계는 리스코프 치환 원칙에 위배된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;1059&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxZWw8/dJMcadtBA9j/WGMG1P964j6H1FQHnqQovk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxZWw8/dJMcadtBA9j/WGMG1P964j6H1FQHnqQovk/img.png&quot; data-alt=&quot;[인터페이스로 책임 분리]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxZWw8/dJMcadtBA9j/WGMG1P964j6H1FQHnqQovk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxZWw8%2FdJMcadtBA9j%2FWGMG1P964j6H1FQHnqQovk%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;730&quot; height=&quot;636&quot; data-origin-width=&quot;1215&quot; data-origin-height=&quot;1059&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[인터페이스로 책임 분리]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 탈것의 기능을 &amp;ldquo;회전이 가능한가?&amp;rdquo;, &amp;ldquo;앞뒤로 이동이 가능한가?&amp;rdquo;처럼 역할 단위로 분리하고, 그 역할에 맞는 인터페이스를 정의한 뒤, 탈것이 필요한 기능만 조립하도록 만드는 방식이 더 올바르다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 하면 불필요한 메서드가 자식 클래스에 강제로 들어오는 일을 막을 수 있어, 리스코프 치환 원칙을 위반하지 않은 구조로 설계할 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;역할에 따라 인터페이스를 분리하고 필요한 기능만 구현.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상속(Inheritance) 보다 구성(Composition)을 우선적으로 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;4. Interface segregation principle : 인터페이스 분리 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;앞서 말한 단일 책임 원칙과 리스코프 치환 법칙에서 연장되는 원칙인데, 필요한 인터페이스만 만들고, 큰 인터페이스를 구체적이고 작은 단위로 분리하라는 얘기이다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;시스템의 내부 의존성을 약화하고 유연성을 강화한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public interface IUnitState
{
    public float HP { get; set; }
    public float MP { get; set; }
    public int Defense { get; set; }

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

    public void GoForward();
    public void Reverse();
    public void TurnLeft();
    public void TurnRight();
    
    // ....
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[IUnitState]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;게임에서 유닛이 가지는 상태와 행동은 매우 다양하다. 하지만 이 모든 기능을 하나의 인터페이스에 몰아넣어버리면, 유닛마다 필요하지 않는 기능까지 억지로 구현해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이런 구조는 앞서 설명한 원칙들, 특히 &amp;ldquo;단일 책임 원칙&amp;rdquo;, &amp;ldquo;리스코프 치환 원칙&amp;rdquo;, &amp;ldquo;인터페이스 분리 원칙&amp;rdquo;을 위배할 가능성이 매우 높다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1689&quot; data-origin-height=&quot;689&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HW9uM/dJMcafdVW78/SzMvvlHOuWWEudhPV5P8pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HW9uM/dJMcafdVW78/SzMvvlHOuWWEudhPV5P8pk/img.png&quot; data-alt=&quot;[인터페이스 분리 원칙]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HW9uM/dJMcafdVW78/SzMvvlHOuWWEudhPV5P8pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHW9uM%2FdJMcafdVW78%2FSzMvvlHOuWWEudhPV5P8pk%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;730&quot; height=&quot;298&quot; data-origin-width=&quot;1689&quot; data-origin-height=&quot;689&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[인터페이스 분리 원칙]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이처럼 인터페이스를 역할 단위로 최대한 작게 분리하여 구성하면, 유닛은 필요한 기능만 선택적으로 구현할 수 있게 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 불필요한 기능을 억지로 구현할 필요가 없고,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기능과 확장 조합이 훨씬 쉬워지며,&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;5. Dependency Inversion Principle : 의존 역전 원칙&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;의존 역전 원칙은 간단하게 말하면 소프트웨어 모듈들을 분리하는 특정 형식을 말한다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상위(High - level) 모듈은 하위(Low-level) 모듈의 것을 직접 가져오면 안 된다.&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상위, 하위 모듈 둘 다 추상화(abstraction)에 의존해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;추상화는 세부 사항에 의존해서는 안 된다.&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;세부 사항이 추상화에 의존해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클래스가 다른 클래스와 관계가 있으면 안된다.&lt;/span&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;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클래스가 다른 클래스의 작동 방식을 많이 알고 있으면, 종속성(Dependency) 또는 결합(Coupling)이 발생한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;488&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMqcQ/dJMcajtOFVG/NnFkc4VyvKG538VBgIyJiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMqcQ/dJMcajtOFVG/NnFkc4VyvKG538VBgIyJiK/img.png&quot; data-alt=&quot;[의존 역전 원칙]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMqcQ/dJMcajtOFVG/NnFkc4VyvKG538VBgIyJiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMqcQ%2FdJMcajtOFVG%2FNnFkc4VyvKG538VBgIyJiK%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;730&quot; height=&quot;219&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;488&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[의존 역전 원칙]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;코드를 작성하다 보면 자연스럽게 상위 레벨(Player)이 하위 레벨(KeyboardInput)에 의존하게 되고, 이것이 일반적인 &amp;ldquo;정방향 의존&amp;rdquo;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 의존 역전 원칙은 말 그대로, 이 관계를 뒤집어 상위 레벨과 하위 레벨 모두 추상화(IInput)에 의존하도록 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0te0L/dJMcahv3coD/KNoCaSR80XELg1n6p2k4Gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0te0L/dJMcahv3coD/KNoCaSR80XELg1n6p2k4Gk/img.png&quot; data-alt=&quot;[낮은 결합도와 높은 결합도]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0te0L/dJMcahv3coD/KNoCaSR80XELg1n6p2k4Gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0te0L%2FdJMcahv3coD%2FKNoCaSR80XELg1n6p2k4Gk%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;550&quot; height=&quot;479&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[낮은 결합도와 높은 결합도]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;또한, 두 클래스의 결합도가 높을수록 A 클래스의 내용을 수정했을 때, B 클래스의 내용도 함께 수정해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;지금은 예시로 두 클래스만 놓고 봤지만, 실제 개발에선 여러 클래스가 서로 얽혀 있는 경우가 많기 때문에 한 클래스를 수정하면 관련 클래스까지 줄줄이 수정해야 하는 문제가 생기기 쉽다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 되면 전체 구조의 유지보수가 매우 어려워진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bByZPm/dJMcac2xfhG/uGsPyVXaSzO7G8Aq8sdOYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bByZPm/dJMcac2xfhG/uGsPyVXaSzO7G8Aq8sdOYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bByZPm/dJMcac2xfhG/uGsPyVXaSzO7G8Aq8sdOYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbByZPm%2FdJMcac2xfhG%2FuGsPyVXaSzO7G8Aq8sdOYk%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;730&quot; height=&quot;279&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Switch : Monobehaviour
{
	public Door door;
	public bool isActivated;
	
	public void Toggle()
	{
		if(isActivated)
		{
			isActivated = false;
			door.Close();
			
			return;
		}
		
		isActivated = ture;
		door.Open();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;[예시 - 1]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 구조에서는 Switch가 Door 클래스를 직접 참조하고 있으며, 스위치를 작동할 때 Door의 메서드를 그대로 호출하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Door만 있는 상황은 괜찮지만, 기능을 확장시켜 스위치를 켜면 폭탄이 폭발하는 기능을 추가하거나, 전등을 키는 기능을 추가해야 한다고 생각해 보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이 경우, Switch 클래스 안에 폭탄용 ToggleBomb(), 전등용 ToggleLight()와 같이 새로운 메서드를 계속 추가해야 하는 상황이 생긴다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;즉, 기능을 확장할 때마다 Switch 코드를 수정해야 하므로 유지보수가 점점 어려워진다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;728&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/efVRM2/dJMcabCBiba/mEzA4X6RO8VLYPTXIcRER0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/efVRM2/dJMcabCBiba/mEzA4X6RO8VLYPTXIcRER0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/efVRM2/dJMcabCBiba/mEzA4X6RO8VLYPTXIcRER0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FefVRM2%2FdJMcabCBiba%2FmEzA4X6RO8VLYPTXIcRER0%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;730&quot; height=&quot;408&quot; data-origin-width=&quot;1304&quot; data-origin-height=&quot;728&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public interface ISwitchable
{
	public bool IsActive {get;}
	public void Activate();
	public void Deavtivate()
}

// higher - level
public class Switch : MonoBehaviour
{
	public ISwitchable client;
	
	public Toggle()
	{
		//...
	}
}

// lower - level
public class Door : MonoBehaviour, ISwitchable
{
	private bool isActive;
	public bool IsActive =&amp;gt; isActive;
	
	public void Activate() { // ... }
	public void Deactivate() { // ... }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot; data-token-index=&quot;0&quot;&gt; [의존성 역전 원칙]&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스위치가 가능한 오브젝트에서의 역할을 인터페이스로 따로 빼두고, 상위-하위 레벨 모두 이 인터페이스를 의존하게 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이렇게 만들면 어떠한 ISwitchable 객체가 추가되더라도, Switch의 Toggle() 메서드는 수정할 필요가 없어지게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt; SOLID 원칙의 핵심&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;결국 SOLID 원칙의 목적은 코드를 더 읽기 쉽고, 유지보수하기 쉬운 구조로 만드는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;한 가지 원칙을 지키기 시작하면, 다른 원칙들도 자연스럽게 지켜지는 경우가 많다. 따라서 코드를 작성할 때 항상 SOLID 원칙을 염두하면 변경에 강하고, 이해하기 쉬우며, 확장성이 높은 깔끔한 구조를 만들 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하나의 클래스와 인터페이스는 오직 하나의 역할만 수행하도록 설계한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;구현해야 하는 기능은 잘게 분리하여 인터페이스 단위로 모듈화 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;상속과 구성을 상황에 맞게 적절히 활용한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ abstract class VS Interface&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciY4L5/dJMcaaKszrw/fH99ixCJ1yP25g0ohVnjBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciY4L5/dJMcaaKszrw/fH99ixCJ1yP25g0ohVnjBK/img.png&quot; data-alt=&quot;[abstract class]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciY4L5/dJMcaaKszrw/fH99ixCJ1yP25g0ohVnjBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciY4L5%2FdJMcaaKszrw%2FfH99ixCJ1yP25g0ohVnjBK%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;730&quot; height=&quot;392&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[abstract class]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;추상 클래스를 구체 클래스(concrete class)가 상속받아 직접 내용을 구현하게 된다. 이렇게 직접적으로 상속을 받으면 &amp;ldquo;is&amp;rdquo; 관계가 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;추상 &amp;ldquo;클래스&amp;rdquo; 이기 때문에 필드, 스태틱 멤버, 전체 혹은 일부 구현된 메서드들을 가질 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;C#은 다중 상속이 불가능하고 오로지 한 개의 &amp;ldquo;클래스&amp;rdquo;만 상속받을 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bO0ueF/dJMcac2xfjZ/hwCljtxzYi2qfQKHgQcRb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bO0ueF/dJMcac2xfjZ/hwCljtxzYi2qfQKHgQcRb1/img.png&quot; data-alt=&quot;[interface]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bO0ueF/dJMcac2xfjZ/hwCljtxzYi2qfQKHgQcRb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbO0ueF%2FdJMcac2xfjZ%2FhwCljtxzYi2qfQKHgQcRb1%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;730&quot; height=&quot;418&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[interface]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따라서 C#에서 다중 상속을 구현하려면 Interface를 활용해야 한다. 인터페이스를 구현하게 되면 클래스와 인터페이스는 &amp;ldquo;can - do&amp;rdquo; 관계가 된다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;is - a 관계(추상 클래스 상속) : 자식 클래스는 부모 클래스의 &amp;ldquo;종류&amp;rdquo;이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;can - do 관계(인터페이스 구현) : 클래스가 특정 능력이나 기능을 &amp;ldquo;할 수 있다&amp;rdquo;는 의미.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; Abstract class &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; Interface &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메서드를 완전히 또는 일부 구현.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메서드를 선언만 가능. 구현 불가능.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;변수 및 필드 선언 / 사용.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;메서드와 프로퍼티 선언만 가능.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스태틱 멤버 가능.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;스태틱 멤버 불가.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;생성자 사용 가능.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;생성자 사용 불가능.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 액세스 한정자 가능.&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;모든 멤버는 public으로 취급.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #f89009;&quot;&gt;최종 정리&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; Single responsibility (단일 책임 원칙)&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클래스가 한 가지 작업만 수행한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; Open-Closed (개방 폐쇄 원칙)&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이미 작동하는 방식을 변경하지 않고도 클래스의 기능을 확장할 수 있어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; Liskov substitution (리스코프 치환 원칙)&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하위 클래스는 기본 클래스를 대체할 수 있어야 한다. 기본 클래스의 방향성을 유지해야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; Interface segregation (인터페이스 분리 원칙)&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인터페이스를 작게 유지. 클라이언트는 필요한 것만 구현.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; Dependency inversion (의존 역전 원칙)&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;추상화에 의존. 하나의 구체 클래스에서 다른 클래스로 직접 의존 금지.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Unity,C#/Unity 정보</category>
      <category>SOLID</category>
      <category>솔리드 원칙</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/51</guid>
      <comments>https://hate-errorlog.tistory.com/51#entry51comment</comments>
      <pubDate>Fri, 21 Nov 2025 00:22:47 +0900</pubDate>
    </item>
    <item>
      <title>[Unity] UI Toolkit 기본 사용법</title>
      <link>https://hate-errorlog.tistory.com/50</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ UI Toolkit&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cFM2Md/dJMcacBvOmR/zG4mHKIuvZtkwaFLR1d821/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cFM2Md/dJMcacBvOmR/zG4mHKIuvZtkwaFLR1d821/img.png&quot; data-alt=&quot;[UI Toolkit]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cFM2Md/dJMcacBvOmR/zG4mHKIuvZtkwaFLR1d821/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcFM2Md%2FdJMcacBvOmR%2FzG4mHKIuvZtkwaFLR1d821%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;730&quot; height=&quot;404&quot; data-origin-width=&quot;3840&quot; data-origin-height=&quot;2123&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[UI Toolkit]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UI Toolkit은 Unity 2022.2 이후 유니티의 기존 UGUI(Canvas 기반 UI)를 대체할 수 있도록 개발된 새로운 공식 UI 시스템이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기존 UGUI는 GameObject 기반으로 동작하는 UI 시스템으로 드래그 앤 드롭을 통한 직관적인 UI 제작이 가능하다. 또한 하이어라키 구조와 Canvas의 SortOrder를 이용해 UI의 렌더링 순서를 제어할 수 있다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UGUI는 편리하고 직관적인 반면에 아래와 같은 뚜렷한 단점도 존재한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 59px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;구분&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px; text-align: center;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;단점&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; GameObject 기반 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;&lt;span style=&quot;text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; UGUI의 모든 UI요소는 GameObject + Component 로 구성된다. &lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;&amp;rarr; 따라서 많은 UI요소가 있으면 UpdateLoop, Transform 갱신 등 CPU 오버헤드와메모리 사용량이 커진다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; Canvas 전체 리빌드 &lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; &amp;nbsp;Canvas내 어느 한 UI요소라도 변경되면, Unity는 Canvas 전체를 다시 빌드(Rebuild) 해야 한다. &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt; 실시간 갱신 비효율 &lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #333333; text-align: start; font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위와 같은 이유로 인하여, 실시간으로 수치나 상태가 바뀌는 UI(경험치, 체력바 등)는 Canvas 리빌드와 Layout 계산이 지속적으로 발생한다 &lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u5MDE/dJMcajm2ZkS/a4wFp7jncnoK0qprgfmLak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u5MDE/dJMcajm2ZkS/a4wFp7jncnoK0qprgfmLak/img.png&quot; data-alt=&quot;[Web 개발 방식을 적용]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u5MDE/dJMcajm2ZkS/a4wFp7jncnoK0qprgfmLak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu5MDE%2FdJMcajm2ZkS%2Fa4wFp7jncnoK0qprgfmLak%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;730&quot; height=&quot;470&quot; data-origin-width=&quot;1116&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Web 개발 방식을 적용]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UI Toolkit은 기존 UGUI의 성능 및 유지보수 문제를 해결하기 위해, 웹 개발에 사용되는 스크립트(HTML, CSS, Java)를 Unity 환경에 도입하여 UI를 구성할 수 있도록 했다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;.UXML(HTML) : 기존 웹 개발의 프런트 레이아웃과 비슷한 구조를 가지고 있다. 유니티에서 UI의 레이아웃을 담당한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;.USS(CSS) : UI의 스타일을 결정.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;.cs : 인터페이스의 컨트롤 및 이벤트는 C# 스크립트에서 정의.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;웹 개발과 상당히 흡사한 방식을 사용하기 때문에, 웹 개발 경험이 있다면 쉽게 인터페이스를 제작할 수 있고, 게임 내 UI뿐만 아니라 에디터 윈도우도 직접 제작할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ UI Toolkit 사용하기&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;인게임 UI는 아직 UGUI를 사용하는 경우가 많으므로, UI Toolkit을 사용하여 커스텀 에디터를 만들어보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brhvGS/dJMcabWT7Q5/lHQVDRNl8HL2yriY5QIhe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brhvGS/dJMcabWT7Q5/lHQVDRNl8HL2yriY5QIhe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brhvGS/dJMcabWT7Q5/lHQVDRNl8HL2yriY5QIhe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrhvGS%2FdJMcabWT7Q5%2FlHQVDRNl8HL2yriY5QIhe1%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;650&quot; height=&quot;560&quot; data-origin-width=&quot;783&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LVDa2/dJMcagjBMZL/jk3wIEtKe0843bqRrbY7xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LVDa2/dJMcagjBMZL/jk3wIEtKe0843bqRrbY7xk/img.png&quot; data-alt=&quot;[Editor Window 생성]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LVDa2/dJMcagjBMZL/jk3wIEtKe0843bqRrbY7xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLVDa2%2FdJMcagjBMZL%2Fjk3wIEtKe0843bqRrbY7xk%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;265&quot; height=&quot;115&quot; data-origin-width=&quot;265&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[Editor Window 생성]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Project &amp;rarr; Create &amp;rarr; UI Toolkit &amp;rarr; Editor Window를 클릭하고 이름을 입력하면 자동으로 3개의 파일(.cs, .uss, .uxml)파일이 만들어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ UI Builder&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1737&quot; data-origin-height=&quot;1078&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dx1esM/dJMcabCBhMo/S08JLTKNfVIWuZnQBOqVzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dx1esM/dJMcabCBhMo/S08JLTKNfVIWuZnQBOqVzK/img.png&quot; data-alt=&quot;[UI Builder]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dx1esM/dJMcabCBhMo/S08JLTKNfVIWuZnQBOqVzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdx1esM%2FdJMcabCBhMo%2FS08JLTKNfVIWuZnQBOqVzK%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;730&quot; height=&quot;453&quot; data-origin-width=&quot;1737&quot; data-origin-height=&quot;1078&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[UI Builder]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;만들어진 .uxml 파일을 클릭하면 위와 같이 UI를 만들 수 있는 UI Builder 창이 열리게 된다. UI Builder의 레이아웃과 역할은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Style sheet : 현재 문서에 연결된 USS 스타일시트 목록을 표시한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Hierarchy : UXML의 Visual Tree(계층구조)를 표시한다. 드래그 앤 드롭으로 부모/자식 관계를 조정하고 이름 등을 설정할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Library : 배치 가능한 UI요소(VisualElement, Lable, Button 등)와 공통 템플릿 제공.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Viewport : 편집 중인 UI를 실시간으로 확인한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Preview : 현재 상태의 UXML, USS 코드 미리 보기(읽기 전용)를 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Inspector : 선택한 UI요소의 속성을 편집한다. UGUI 컴포넌트가 아닌 웹에서 사용되는 프로퍼티를 Unity에서 사용 가능하도록 재구성.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ VisualElement&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VisualElement는 UI Toolkit에서 가장 기본이 되는 구성 요소이다. 모든 UI 요소들은 이 클래스를 상속받아 만들어진다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1421&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNt0vP/dJMcafdVWL7/qEv1KydktSPjxoEv5UuITk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNt0vP/dJMcafdVWL7/qEv1KydktSPjxoEv5UuITk/img.png&quot; data-alt=&quot;[VisualElement 생성]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNt0vP/dJMcafdVWL7/qEv1KydktSPjxoEv5UuITk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNt0vP%2FdJMcafdVWL7%2FqEv1KydktSPjxoEv5UuITk%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;730&quot; height=&quot;253&quot; data-origin-width=&quot;1421&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[VisualElement 생성]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 개의 VisualElement를 생성하고 이름을 각각 Main, Sub로 지정해 준 뒤, 인스펙터의 BackGround 탭에서 색상을 조절하면 위와 같이 배경색이 적용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VisualElement는 하위 UI 요소를 배치할 영역(Container)을 정의하는 데 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;851&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZQIOn/dJMcaiaEOMW/BYDc7urnDLutfySZK12xB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZQIOn/dJMcaiaEOMW/BYDc7urnDLutfySZK12xB1/img.png&quot; data-alt=&quot;[하위 오브젝트 배치]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZQIOn/dJMcaiaEOMW/BYDc7urnDLutfySZK12xB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZQIOn%2FdJMcaiaEOMW%2FBYDc7urnDLutfySZK12xB1%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;650&quot; height=&quot;479&quot; data-origin-width=&quot;1154&quot; data-origin-height=&quot;851&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[하위 오브젝트 배치]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;VisualElement 안에 요소를 배치하면, 그 영역에 맞게 UI 요소들이 배치되는 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 사진의 빨간색 박스 안의 4개의 버튼은 현재 선택한 VisualElement의 레이아웃 정렬 및 크기 설정 방식을 변경한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;좌측의 버튼부터 아래와 같은 기능을 한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VSoI5/dJMcagKF6zp/0cs7q8r1WHDYz4gEkWTs4k/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VSoI5/dJMcagKF6zp/0cs7q8r1WHDYz4gEkWTs4k/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;482&quot; data-filename=&quot;Honeycam 2025-11-13 01-59-57.gif&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VSoI5/dJMcagKF6zp/0cs7q8r1WHDYz4gEkWTs4k/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVSoI5%2FdJMcagKF6zp%2F0cs7q8r1WHDYz4gEkWTs4k%2Fimg.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;374&quot; height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beQplA/dJMcai9vRpJ/7jferOP9hUqt2K4YLfLCfk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beQplA/dJMcai9vRpJ/7jferOP9hUqt2K4YLfLCfk/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;482&quot; data-filename=&quot;Honeycam 2025-11-13 02-00-09.gif&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beQplA/dJMcai9vRpJ/7jferOP9hUqt2K4YLfLCfk/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeQplA%2FdJMcai9vRpJ%2F7jferOP9hUqt2K4YLfLCfk%2Fimg.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;374&quot; height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;[Flex Direction / Align Items ]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x1eNP/dJMcacuKaaO/IJWEqyjzdTgJ6PpQmQdfO0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x1eNP/dJMcacuKaaO/IJWEqyjzdTgJ6PpQmQdfO0/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;482&quot; data-filename=&quot;Honeycam 2025-11-13 02-00-21.gif&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x1eNP/dJMcacuKaaO/IJWEqyjzdTgJ6PpQmQdfO0/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx1eNP%2FdJMcacuKaaO%2FIJWEqyjzdTgJ6PpQmQdfO0%2Fimg.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;374&quot; height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFTcOI/dJMcahphQVS/pW9khRlY3puDJ11KtysZ7K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFTcOI/dJMcahphQVS/pW9khRlY3puDJ11KtysZ7K/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;374&quot; data-origin-height=&quot;482&quot; data-filename=&quot;Honeycam 2025-11-13 02-00-32.gif&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFTcOI/dJMcahphQVS/pW9khRlY3puDJ11KtysZ7K/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFTcOI%2FdJMcahphQVS%2FpW9khRlY3puDJ11KtysZ7K%2Fimg.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;374&quot; height=&quot;482&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;[Justify Content / Align self]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;현재 Viewport에서 설정된 테마는 기본적으로 &lt;b&gt;Cinemachine Runtime Theme&lt;/b&gt;가 적용되어 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;하지만 지금은 &lt;b&gt;런타임 UI가 아닌 커스텀 에디터 창&lt;/b&gt;이므로, 이 테마를 &lt;b&gt;Editor용 테마&lt;/b&gt;로 변경해 주어야 한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Cinemachine Runtime Theme를 사용하면 Viewport에서 보는 모습과 실제 커스텀 에디터는 다르게 보임.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;115&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QyZOp/dJMcaap9UNm/PzKKag5ALwn1mjdf9329aK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QyZOp/dJMcaap9UNm/PzKKag5ALwn1mjdf9329aK/img.png&quot; data-alt=&quot;[라이브러리 설정]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QyZOp/dJMcaap9UNm/PzKKag5ALwn1mjdf9329aK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQyZOp%2FdJMcaap9UNm%2FPzKKag5ALwn1mjdf9329aK%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;525&quot; height=&quot;115&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;115&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[라이브러리 설정]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UI Builder 좌측 하단 Library 패널의 오른쪽 상단 점(⋯) 메뉴를 클릭하고 Editor Extension Authoring&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;옵션을 활성화하면, Viewport의 Theme 드롭다운에서 Dark 혹은 Light테마를 선택할 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #006dd7;&quot;&gt;■ 코드 작성&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UXML로 UI의 레이아웃을 지정해주었으니, C# 코드를 통해 UI에 기능을 부여해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class TestWindow : EditorWindow
{
    [SerializeField] private VisualTreeAsset visualTreeAsset = default;

    [MenuItem(&quot;Window/UI Toolkit/TestWindow&quot;)]
    public static void ShowWindow()
    {
        var wnd = GetWindow&amp;lt;TestWindow&amp;gt;();
        wnd.titleContent = new GUIContent(&quot;TestWindow&quot;);
    }

    public void CreateGUI()
    {
        
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;[TestWindow.cs]&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;기본적으로 생성된 파일에는 위와 같이 작성되어 있다. 만약 처음부터 작성하는 경우라면, 아래와 같은 변수와 함수를 선언해주어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;1. VisualTreeAsset visualTreeAsset : .uxml 파일을 참조하고, 다루기 위한 변수. UXML이 UI구조를 저장한 설계도라면, VisualTreeAsset은 그 설계도를 메모리에서 다루는 리소스 객체이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;338&quot; data-origin-height=&quot;98&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BgAFS/dJMcachcRsU/hhSqufw6YHZqjIte1jKPqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BgAFS/dJMcachcRsU/hhSqufw6YHZqjIte1jKPqK/img.png&quot; data-alt=&quot;[.uxml 연결]&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BgAFS/dJMcachcRsU/hhSqufw6YHZqjIte1jKPqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBgAFS%2FdJMcachcRsU%2FhhSqufw6YHZqjIte1jKPqK%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;338&quot; height=&quot;98&quot; data-origin-width=&quot;338&quot; data-origin-height=&quot;98&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;[.uxml 연결]&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;rarr; 해당 변수를 선언하고 난 뒤, .uxml 파일을 링크해주어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;2. [MenuItem(&quot;경로&quot;)] : 상단 메뉴에 &amp;ldquo;경로&amp;rdquo;로 입력한 곳에 커스텀 에디터를 띄울 수 있는 버튼을 추가한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;3. public static void &amp;ldquo;이름:&amp;rdquo;() : 버튼이 클릭되면, 해당 함수를 호출하고 EditorWindow를 생성 및 호출한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #8a3db6;&quot;&gt;▶ CreateGUI()&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;EditorWindow 클래스에서 UI Toolkit을 사용할 때 자동으로 호출되는 함수이다. 이 함수에서 UI 요소에 기능을 추가하거나 새로운 UI요소를 생성할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public void CreateGUI()
{
	var root = rootVisualElement;
	visualTreeAsset.CloneTree(root);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;rootVisualElement는 현재 창의 최상위 컨테이너이다. 이 안에 모든 UI 요소(버튼, 라벨 등)가 들어간다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;CloneTree()는 인스펙터에서 참조한 UXML 파일(visualTreeAsset)을 기반으로 실제 UI 인스턴스를 생성하여 최상위 컨테이너 안에 배치한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;public void CreateGUI()
{
	var root = rootVisualElement;
	visualTreeAsset.CloneTree(root);
	
	var label = root.Q&amp;lt;Label&amp;gt;(&quot;TextInfo&quot;);
	var button = root.Q&amp;lt;Button&amp;gt;(&quot;TestButton&quot;);

	button.clicked += () =&amp;gt;
	{
		Debug.Log(label.text);
	};
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;UI요소를 가져올 땐 Q&amp;lt;T&amp;gt;(&amp;rdquo;이름&amp;rdquo;) 혹은 Query&amp;lt;T&amp;gt;(&amp;rdquo;이름&amp;rdquo;)을 사용한다.&lt;/span&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;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Q&amp;lt;T&amp;gt;(&amp;rdquo;이름&amp;rdquo;) : 첫 번째로 일치하는 요소 1개를 가져온다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Query&amp;lt;T&amp;gt;(&amp;rdquo;이름&amp;rdquo;) : 트리 내 모든 일치하는 요소를 검색하며, 결과를 리스트로 사용하려면 .ToList()를 함께 사용한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;위 코드는 UXML에 정의된 Lable과 Button 요소를 가져온 뒤, 버튼을 클릭하면 해당 라벨의 텍스트를 콘솔에 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Unity,C#/Unity 정보</category>
      <category>CustomWindow</category>
      <category>UI Toolkit</category>
      <category>커스텀 윈도우</category>
      <author>브라더스톤</author>
      <guid isPermaLink="true">https://hate-errorlog.tistory.com/50</guid>
      <comments>https://hate-errorlog.tistory.com/50#entry50comment</comments>
      <pubDate>Fri, 21 Nov 2025 00:00:23 +0900</pubDate>
    </item>
  </channel>
</rss>