<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>lumos-vid.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Wed, 13 Aug 2025 14:54:31 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>lumos-vid.log</title>
            <url>https://velog.velcdn.com/images/lumos-vid/profile/8507dd02-c515-4e7e-8e59-75142b057a1a/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. lumos-vid.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/lumos-vid" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Unity] 쿼터니언(Quaternion) 정리]]></title>
            <link>https://velog.io/@lumos-vid/Unity-%EC%BF%BC%ED%84%B0%EB%8B%88%EC%96%B8Quaternion-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/Unity-%EC%BF%BC%ED%84%B0%EB%8B%88%EC%96%B8Quaternion-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 13 Aug 2025 14:54:31 GMT</pubDate>
            <description><![CDATA[<p>유니티에서 회전을 표현하는 방식에는 오일러 각(Euler Angles) 과 쿼터니언(Quaternion) 두 가지가 존재한다. 오일러 각은 사람이 이해하기 쉬운 x, y, z 축 기준의 회전값(도 단위) 을 제공하지만, 연산 과정에서 짐벌락(Gimbal Lock) 문제를 일으킬 수 있다. 반면, 쿼터니언은 네 개의 실수 (x, y, z, w) 로 구성된 4차원 복소수 형태로, 연속적인 회전 연산에 강하며 짐벌락을 방지한다.</p>
<p>유니티 내부에서는 모든 회전이 쿼터니언으로 처리되며, 오일러 각은 단순히 변환을 위한 인터페이스 역할을 수행한다.</p>
<br>
<br>
<br>

<hr>
<h2 id="구조와-원리">구조와 원리</h2>
<p>쿼터니언은 다음과 같이 표현된다.</p>
<pre><code class="language-cs">q = w + xi + yj + zk</code></pre>
<ul>
<li>w : 스칼라(Scalar) 부분</li>
<li>x, y, z : 벡터(Vector) 부분</li>
<li>네 개의 값은 단위 쿼터니언(Unit Quaternion) 형태로 정규화(Normalization) 되어야 한다.</li>
</ul>
<p>회전은 축-각(Axis-Angle) 방식으로 정의되며, 특정 회전축 벡터와 회전 각도를 기반으로 쿼터니언 값이 계산된다.</p>
<pre><code class="language-cs">w = cos(θ / 2)
x = ux * sin(θ / 2)
y = uy * sin(θ / 2)
z = uz * sin(θ / 2)</code></pre>
<p>여기서 (ux, uy, uz) 는 회전축을 나타내는 단위 벡터이며, θ 는 회전 각도(라디안)이다.</p>
<br>
<br>
<br>

<hr>
<h2 id="유니티에서의-사용">유니티에서의 사용</h2>
<p>유니티에서는 Quaternion 구조체를 통해 쿼터니언을 다룬다. 주요 메서드와 프로퍼티는 다음과 같다.</p>
<ul>
<li><p><strong>Quaternion.identity</strong>
회전이 없는 단위 쿼터니언 (0, 0, 0, 1)</p>
</li>
<li><p><strong>Quaternion.Euler(Vector3 eulerAngles)</strong>
오일러 각을 쿼터니언으로 변환</p>
</li>
<li><p><strong>Quaternion.AngleAxis(float angle, Vector3 axis)</strong>
특정 축과 각도를 기반으로 회전 생성</p>
</li>
<li><p><strong>Quaternion.LookRotation(Vector3 forward, Vector3 up)</strong>
주어진 방향 벡터를 바라보도록 회전 생성</p>
</li>
<li><p><strong>Quaternion.Slerp(Quaternion a, Quaternion b, float t)</strong>
두 회전 사이를 구면선형보간(Spherical Linear Interpolation)</p>
</li>
</ul>
<br>
<br>
<br>

<hr>
<h2 id="특징-및-장점">특징 및 장점</h2>
<ul>
<li><p><strong>짐벌락 방지</strong>
오일러 각 방식에서 발생하는 회전 자유도 손실 문제를 방지한다.</p>
</li>
<li><p><strong>부드러운 회전 보간</strong>
Slerp 연산을 통해 균일한 속도로 회전 보간이 가능하다.</p>
</li>
<li><p><strong>누적 회전 처리에 강함</strong>
여러 회전을 연속적으로 적용할 때도 수치 안정성이 높다.</p>
</li>
</ul>
<br>
<br>
<br>

<hr>
<h2 id="주의사항">주의사항</h2>
<ul>
<li><p><strong>직접 값을 수정하지 말 것</strong>
쿼터니언의 (x, y, z, w) 값을 임의로 수정하면 회전이 깨질 수 있다. 반드시 제공되는 메서드를 사용하여 생성하거나 수정해야 한다.</p>
</li>
<li><p><strong>Euler 변환 최소화</strong>
잦은 쿼터니언 ↔ 오일러 변환은 부동소수점 오차를 누적시킬 수 있다.</p>
</li>
<li><p><strong>정규화 유지</strong>
회전 연산을 여러 번 수행하면 길이가 1이 아닌 쿼터니언이 될 수 있으므로 필요 시 Normalize 처리해야 한다.</p>
</li>
</ul>
<br>
<br>
<br>

<hr>
<p>쿼터니언은 유니티에서 안정적이고 정확한 회전을 구현하기 위한 핵심 수학적 구조이다. 비록 직관적으로 이해하기 어렵지만, 오일러 각의 한계를 극복하고 부드러운 회전을 구현할 수 있다는 점에서 필수적으로 숙지해야 한다. 실무에서는 오일러 각을 입력값으로 사용하되, 내부 회전 연산과 보간에는 쿼터니언을 사용하는 것이 일반적이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 월드 스페이스 (World Space) 와 로컬 스페이스 (Local Space)]]></title>
            <link>https://velog.io/@lumos-vid/Unity-%EC%9B%94%EB%93%9C-%EC%8A%A4%ED%8E%98%EC%9D%B4%EC%8A%A4-World-Space-%EC%99%80-%EB%A1%9C%EC%BB%AC-%EC%8A%A4%ED%8E%98%EC%9D%B4%EC%8A%A4-Local-Space</link>
            <guid>https://velog.io/@lumos-vid/Unity-%EC%9B%94%EB%93%9C-%EC%8A%A4%ED%8E%98%EC%9D%B4%EC%8A%A4-World-Space-%EC%99%80-%EB%A1%9C%EC%BB%AC-%EC%8A%A4%ED%8E%98%EC%9D%B4%EC%8A%A4-Local-Space</guid>
            <pubDate>Tue, 12 Aug 2025 13:45:58 GMT</pubDate>
            <description><![CDATA[<p>게임 오브젝트를 배치하거나 이동, 회전 스케일링하는 과정에서 좌표계의 개념은 매우 중요한 역할을 한다. Unity에서는 대표적으로 World Space와 Local Space라는 두 가지 좌표계 사용된다. 이 두 좌표계의 개념과 차이를 명확히 이해하면, 오브젝트의 트랜스폼(Transform)을 보다 정확하게 제어할 수 있다.</p>
<br>
<br>


<hr>
<h2 id="월드-스페이스wolrd-space">월드 스페이스(Wolrd Space)</h2>
<p>World Space는 전역 좌표계를 의미한다. 이는 Unity 씬(Scene) 전체의 절대적인 기준 좌표계로, 모든 오브젝트의 위치는 궁극적으로 World Space를 기준으로 표현된다.
예를 들어, (0, 0, 0) 좌표는 씬의 원점을 나타내며, 이는 씬의 카메라나 다른 오브젝트의 위치와 무관하다. 오브젝트를 월드 좌표로 이동시키면, 부모 오브젝트의 영향과 관계없이 절대적인 위치에 배치된다.</p>
<p><strong>특징</strong></p>
<ul>
<li>전역 기준의 절대 좌표계</li>
<li>부모-자식 관계의 영향을 받지 않음</li>
<li>씬 전체의 원점 (0, 0, 0)을 기준</li>
</ul>
<p><strong>예시 코드</strong></p>
<pre><code class="language-cs">// 오브젝트를 월드 좌표 (0, 5, 0) 위치로 설정
transform.position = new Vector3(0f, 5f, 0f);</code></pre>
<br>
<br>
<br>

<hr>
<h2 id="local-space">Local Space</h2>
<p>Local Space는 로컬 좌표계 또는 자기 좌표계를 의미한다.
이는 해당 오브젝트의 <strong>부모 오브젝트(Parent)</strong>를 기준으로 정의되는 상대적인 좌표계이다.
오브젝트가 부모를 가지지 않는 경우, Local Space는 World Space와 동일하게 동작한다.</p>
<p>Local Space는 부모의 위치, 회전, 스케일에 영향을 받기 때문에, 부모 오브젝트가 회전하면 자식 오브젝트의 로컬 축 방향도 함께 변한다.
따라서 로컬 좌표는 오브젝트가 부모 구조 내에서 어떻게 배치되는지를 나타낸다.</p>
<p><strong>특징</strong></p>
<ul>
<li>부모 오브젝트 기준의 상대 좌표계</li>
<li>부모의 이동, 회전, 스케일 변화에 따라 좌표계가 함께 변함</li>
<li>오브젝트의 Transform Inspector에서 Local Position/Rotation/Scale로 표시</li>
</ul>
<pre><code class="language-cs">// 부모 기준으로 오브젝트를 (0, 2, 0) 위치에 배치
transform.localPosition = new Vector3(0f, 2f, 0f);</code></pre>
<br>
<br>
<br>

<hr>
<h2 id="변환transform-시-고려-사항">변환(Transform) 시 고려 사항</h2>
<ul>
<li><p>World Space → Local Space 변환</p>
<pre><code class="language-cs">Vector3 localPos = transform.InverseTransformPoint(worldPosition);</code></pre>
</li>
<li><p>Local Space → World Space 변환</p>
<pre><code class="language-cs">Vector3 worldPos = transform.TransformPoint(localPosition);</code></pre>
</li>
</ul>
<p>이러한 변환 메서드는 좌표계를 명시적으로 변환할 때 사용된다. 특히, 물리 연산이나 경로 계산 시 두 좌표계의 변환은 필수적이다.</p>
<br>
<br>
<br>

<hr>
<h2 id="주의사항">주의사항</h2>
<p><strong>1. 부모의 회전 및 스케일 변화를 인지할 것</strong></p>
<ul>
<li>Local Space는 부모 오브젝트의 회전 및 스케일에 따라 축 방향과 좌표값이 변한다.</li>
<li>예를 들어, 부모가 90도 회전하면, 자식의 로컬 X축이 월드 Y축과 일치할 수 있다.</li>
<li>따라서, 회전한 부모 안에서 로컬 이동을 할 경우 예상과 다른 방향으로 움직일 수 있다.</li>
</ul>
<br>


<p><strong>2.좌표계 혼동으로 인한 비정상 동작</strong></p>
<ul>
<li>World Space 좌표를 Local Space로 직접 대입하거나, 그 반대를 할 경우 위치가 크게 틀어질 수 있다.</li>
<li>좌표 변환이 필요한 경우 반드시 TransformPoint, InverseTransformPoint, TransformDirection 등의 메서드를 활용해야 한다.</li>
</ul>
<br>


<p><strong>3.부모 변경 시 로컬 좌표가 재계산됨</strong></p>
<ul>
<li>transform.SetParent() 또는 parent 변경 시, 기본적으로 월드 좌표를 유지한 채 로컬 좌표가 재계산된다.</li>
<li>만약 로컬 좌표를 유지하고 싶다면 worldPositionStays 매개변수를 false로 지정해야 한다.</li>
</ul>
<pre><code class="language-cs">child.SetParent(newParent, false); // 로컬 </code></pre>
<br>

<p><strong>4.UI(Canvas) 환경에서의 World Space</strong></p>
<ul>
<li><p>UI 오브젝트의 RectTransform은 일반 Transform과 달리, World Space Canvas 환경에서는 모든 UI 요소가 절대 좌표를 기준으로 동작한다.</p>
</li>
<li><p>UI의 Local Position은 부모 RectTransform의 피벗과 앵커(anchor) 설정에도 영향을 받으므로, 정확한 배치를 위해서는 피벗/앵커 설정을 함께 확인해야 한다.</p>
</li>
</ul>
<br>

<p><strong>5.물리 연산 시 축 방향 오해 주의</strong></p>
<ul>
<li><p>Rigidbody.AddForce 등에 Local Space 축을 직접 전달하면 의도와 다른 방향으로 힘이 가해질 수 있다.</p>
</li>
<li><p>이 경우, transform.forward, transform.right 등 현재 방향 벡터를 활용하여 World Space 방향으로 변환하는 것이 안전하다.</p>
</li>
</ul>
<br>
<br>
<br>

<hr>
<h2 id="정리">정리</h2>
<table>
<thead>
<tr>
<th>구분</th>
<th>World Space</th>
<th>Local Space</th>
</tr>
</thead>
<tbody><tr>
<td>기준</td>
<td>씬 전체의 전역 좌표계</td>
<td>부모 오브젝트를 기준으로 한 상대 좌표계</td>
</tr>
<tr>
<td>영향 요소</td>
<td>부모 오브젝트의 이동, 회전, 스케일 무시</td>
<td>부모 오브젝트의 이동, 회전, 스케일 반영</td>
</tr>
<tr>
<td>좌표값 변경 시</td>
<td>절대 위치 변경</td>
<td>부모를 기준으로 상대 위치 변경</td>
</tr>
</tbody></table>
<p>World Space와 Local Space의 차이를 명확히 이해하는 것은, 오브젝트 배치나 움직임 제어, 카메라 연출 등 다양한 게임 개발 과정에서 필수적이다. 특히, 부모-자식 관계가 복잡하게 구성된 씬에서는 두 좌표계를 혼동하면 예상치 못한 동작이 발생할 수 있으므로, 상황에 맞는 좌표계를 선택하여 사용하는 것이 중요하다.</p>
<br>
<br>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 벡터의 내적과 외적]]></title>
            <link>https://velog.io/@lumos-vid/Unity-%EB%B2%A1%ED%84%B0%EC%9D%98-%EB%82%B4%EC%A0%81%EA%B3%BC-%EC%99%B8%EC%A0%81</link>
            <guid>https://velog.io/@lumos-vid/Unity-%EB%B2%A1%ED%84%B0%EC%9D%98-%EB%82%B4%EC%A0%81%EA%B3%BC-%EC%99%B8%EC%A0%81</guid>
            <pubDate>Mon, 11 Aug 2025 14:12:29 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>게임 개발에서 벡터 연산은 물리 계산, 방향 판정, 카메라 제어 등 다양한 영역에서 활용된다. 그중에서도 <strong>내적(Dot Product)</strong>과 <strong>외적(Cross Product)</strong>은 방향과 각도, 평면상의 수직 벡터 계산 등에 필수적인 연산이다. Unity는 Vector3.Dot과 Vector3.Cross 메서드를 통해 이를 간단히 계산할 수 있도록 지원한다.</p>
<p><br><br><br></p>
<hr>
<h2 id="내적dot-product">내적(Dot Product)</h2>
<p><strong>정의</strong>
두 벡터 A와 B의 내적은 다음과 같이 정의된다.</p>
<blockquote>
<p>A⋅B=∣A∣∣B∣cosθ</p>
</blockquote>
<ul>
<li>∣A∣, ∣B∣ : 각 벡터의 크기(Magnitude)</li>
<li>θ : 두 벡터가 이루는 각도</li>
</ul>
<br>

<p><strong>특징</strong></p>
<ul>
<li>결과값은 <strong>스칼라(Scalar)</strong>이다.</li>
<li>두 벡터의 방향이 완전히 같으면 cos𝜃=1 이므로 내적은 벡터 크기의 곱이 된다.</li>
<li>두 벡터가 서로 수직이면 cosθ=0 이므로 내적은 0이 된다.</li>
<li>두 벡터가 반대 방향이면 cosθ=−1 이므로 음수가 된다.</li>
</ul>
<br>

<p><strong>Unity에서의 사용</strong></p>
<pre><code class="language-cs">Vector3 a = new Vector3(1, 0, 0);
Vector3 b = new Vector3(0.5f, 0.5f, 0);
float dot = Vector3.Dot(a, b); // 0.5
</code></pre>
<br>

<p><strong>활용 예시</strong>
시야 판정
캐릭터 전방 벡터와 목표물 방향 벡터의 내적을 통해, 목표물이 시야각 내에 있는지 판정할 수 있다.</p>
<pre><code class="language-cs">Vector3 forward = transform.forward.normalized;
Vector3 toTarget = (target.position - transform.position).normalized;
float dot = Vector3.Dot(forward, toTarget);

if (dot &gt; Mathf.Cos(45f * Mathf.Deg2Rad))
{
    // 시야 45도 안에 목표물이 존재
}
</code></pre>
<p>정면/후면 판별
내적의 부호(양수/음수)를 사용하여 상대가 앞쪽에 있는지 뒤쪽에 있는지 구분 가능하다.</p>
<br>
<br>
<br>

<hr>
<h2 id="외적cross-product">외적(Cross Product)</h2>
<p><strong>정의</strong>
두 벡터 A와 B의 외적은 다음과 같이 정의된다.</p>
<blockquote>
<p>A×B=∣A∣∣B∣sinθ⋅n</p>
</blockquote>
<ul>
<li>∣A∣, ∣B∣ : 각 벡터의 크기</li>
<li>θ : 두 벡터가 이루는 각도</li>
<li>n : 두 벡터가 이루는 평면에 수직인 단위 벡터(Normal Vector)</li>
</ul>
<br>

<p><strong>특징</strong></p>
<ul>
<li>결과값은 <strong>벡터(Vector)</strong>이다.</li>
<li>방향은 오른손 법칙을 따른다.</li>
<li>오른손의 엄지 방향이 외적 결과 벡터 방향.</li>
<li>크기는 두 벡터로 이루어진 평행사변형의 넓이와 같다.</li>
<li>두 벡터가 평행하면 외적의 크기는 0이 된다.</li>
</ul>
<br>

<p>** Unity에서의 사용**</p>
<pre><code class="language-cs">Vector3 a = new Vector3(1, 0, 0);
Vector3 b = new Vector3(0, 1, 0);
Vector3 cross = Vector3.Cross(a, b); // (0, 0, 1)</code></pre>
<br>

<p><strong>활용 예시</strong>
법선 벡터 계산
3D 모델의 표면 법선을 구해 조명 계산이나 충돌 판정에 활용한다.</p>
<pre><code class="language-cs">Vector3 v1 = p2 - p1;
Vector3 v2 = p3 - p1;
Vector3 normal = Vector3.Cross(v1, v2).normalized;
</code></pre>
<br>

<p><strong>좌우 방향 판정</strong>
캐릭터 전방 벡터와 목표물 방향 벡터의 외적을 이용하여 목표물이 왼쪽에 있는지 오른쪽에 있는지 판별할 수 있다.</p>
<pre><code class="language-cs">Vector3 forward = transform.forward;
Vector3 toTarget = target.position - transform.position;
float dir = Vector3.Cross(forward, toTarget).y;

if (dir &gt; 0) { /* 왼쪽 */ }
else { /* 오른쪽 */ }
</code></pre>
<br>

<p><strong>내적과 외적 비교</strong></p>
<table>
<thead>
<tr>
<th>구분</th>
<th>내적 (Dot Product)</th>
<th>외적 (Cross Product)</th>
</tr>
</thead>
<tbody><tr>
<td>결과 타입</td>
<td>스칼라</td>
<td>벡터</td>
</tr>
<tr>
<td>주요 용도</td>
<td>각도 계산, 방향 비교</td>
<td>법선 계산, 수직 벡터 구하기</td>
</tr>
<tr>
<td>평행 여부 판정</td>
<td>1(같은 방향), -1(반대), 0(수직)</td>
<td>크기 0이면 평행</td>
</tr>
<tr>
<td>기하학적 의미</td>
<td>크기 곱 × 방향 유사도</td>
<td>평행사변형의 넓이와 방향</td>
</tr>
</tbody></table>
<br>
<br>
<br>

<hr>
<h2 id="결론">결론</h2>
<p>내적과 외적은 단순한 수학 연산을 넘어, 게임 로직의 핵심적인 도구로 활용된다. 시야 판정, 조명 계산, 이동 방향 판정 등 다양한 분야에서 필수적으로 사용되며, Unity에서 제공하는 Vector3.Dot과 Vector3.Cross를 통해 간결하고 효율적으로 구현할 수 있다. 이를 정확히 이해하고 적재적소에 활용하는 것이 효율적인 게임 개발의 기반이 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 타일맵 빌드시 주의사항]]></title>
            <link>https://velog.io/@lumos-vid/Unity-%ED%83%80%EC%9D%BC%EB%A7%B5-%EB%B9%8C%EB%93%9C%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD</link>
            <guid>https://velog.io/@lumos-vid/Unity-%ED%83%80%EC%9D%BC%EB%A7%B5-%EB%B9%8C%EB%93%9C%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD</guid>
            <pubDate>Fri, 08 Aug 2025 14:47:55 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>Unity 2D 프로젝트에서 Tilemap과 Tilemap Collider 2D를 이용해 맵 충돌 영역을 구성하는 경우,
에디터에서는 충돌이 정상적으로 동작하지만,
빌드 후 실행 파일에서 플레이해 보면 일부 타일의 충돌이 사라지는 현상이 발생할 수 있다.</p>
<p>에디터 모드에서는 타일의 Sprite가 정상적으로 보이고, 충돌 판정 또한 잘 작동한다.
그러나 빌드된 환경에서는 해당 타일들이 시각적으로는 존재하더라도 Tilemap Collider 2D가 생성되지 않거나 일부 영역이 비어 있는 문제가 발생한다.</p>
<br>
<br>
<br>

<hr>
<h2 id="원인">원인</h2>
<p>이 문제는 타일에 연결된 Sprite Asset이 빌드에 포함되지 않은 경우 발생한다.</p>
<p>Unity의 타일맵 시스템은 타일 데이터를 기반으로 콜라이더를 생성하는데,
이때 타일의 Sprite가 참조되지 않으면 빌드 시 해당 Sprite가 Unused Asset으로 인식되어 제외될 수 있다.</p>
<p>특히 다음과 같은 경우에 자주 발생한다.</p>
<br>


<ul>
<li><p><strong>Sprite가 프로젝트 내에 존재하지만, 직접적으로 Scene이나 Prefab에서 참조하지 않는 경우</strong>
(Tile Palette에서만 사용되고, 코드나 프리팹에서 Sprite를 직접 참조하지 않음.)</p>
</li>
<li><p><strong>Tile Asset이 ScriptableObject 형태로 존재하지만, 해당 Sprite 필드가 에디터 상에서만 유효</strong>
( 일부 커스텀 Tile 클래스에서 #if UNITY_EDITOR로 Sprite를 에디터 전용으로만 관리하는 경우) </p>
</li>
<li><p>** Addressables 또는 AssetBundle을 사용하지 않고, 기본 빌드 파이프라인만 사용하는 경우**
( Unity의 빌드 프로세스는 사용하지 않는 Sprite를 자동으로 스트리핑(제거)한다. )</p>
</li>
</ul>
<br>

<p>빌드 후에는 Tilemap Collider가 타일의 Sprite 정보를 참조하여 콜라이더를 만들지만, Sprite가 빠져있으므로 해당 타일을 무시하게 된다.
그 결과 충돌이 사라지는 것처럼 보인다.</p>
<br>
<br>

<hr>
<h2 id="재현-방법">재현 방법</h2>
<p>이 문제를 간단히 재현하려면 다음과 같은 단계를 거친다.</p>
<ol>
<li><p>Tilemap 생성</p>
</li>
<li><p>Tile Palette를 통해 임의의 Sprite로 Tile을 만들어 배치</p>
</li>
<li><p>Tilemap에 Tilemap Collider 2D를 부착</p>
</li>
<li><p>Sprite가 프로젝트에서 다른 곳에서는 전혀 참조되지 않도록 둔다</p>
</li>
<li><p>빌드 후 실행</p>
</li>
</ol>
<p>결과적으로, 해당 Sprite 기반의 타일은 빌드 시 Asset이 포함되지 않아 Collider가 생성되지 않는다.</p>
<br>
<br>

<hr>
<h2 id="해결방법">해결방법</h2>
<h3 id="resources-폴더-사용">Resources 폴더 사용</h3>
<p>해당 Sprite를 Assets/Resources 폴더 아래에 넣으면, Unity는 이를 빌드 시 무조건 포함한다.</p>
<p><strong>장점</strong>: 구현이 간단하며, 자동으로 빌드에 포함
<strong>단점</strong>: Resources 폴더에 있는 모든 Asset이 빌드에 포함되므로, 불필요한 용량 증가 가능성 있음</p>
<br>
<br>


<h3 id="addressables-사용">Addressables 사용</h3>
<p>타일 Sprite를 Addressable Asset으로 등록하고, 빌드 시 포함하도록 한다.</p>
<p>장점: 필요한 리소스만 효율적으로 관리 가능
단점: Addressables 셋업 과정 필요</p>
<br>
<br>


<h3 id="sprite-직접-참조">Sprite 직접 참조</h3>
<p>빌드에서 스트리핑되지 않도록, 임의의 Script나 ScriptableObject에 Sprite를 필드로 참조시킨다.</p>
<p>해당 Script를 씬의 빈 GameObject에 붙여서 Sprite를 드래그해 두면, Unity 빌드 파이프라인에서 사용 중인 리소스로 인식한다.</p>
<br>
<br>

<h3 id="custom-build-script">Custom Build Script</h3>
<p>빌드 전에 타일맵에 사용된 Sprite 목록을 추출하여 빌드 인클루드 리스트에 추가하는 방법이다.
IPreprocessBuildWithReport를 구현하여 빌드 직전에 Sprite 참조를 강제로 등록할 수 있다.</p>
<br>
<br>

<hr>
<p>이 문제는 Unity 빌드 파이프라인의 Unused Asset 제거 로직과 타일맵의 Sprite 기반 콜라이더 생성 로직이 맞물리면서 발생한다.
따라서 빌드 시 Sprite가 제거되지 않도록 참조를 유지하는 것이 핵심이다.</p>
<p><strong>추천 접근 순서</strong></p>
<p>간단한 프로젝트 → 방법 1(Resources) 또는 방법 3(직접 참조)
대규모 프로젝트 → 방법 2(Addressables) 또는 방법 4(빌드 스크립트)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 유니티 버츄얼 카메라 Follow 강제 이동]]></title>
            <link>https://velog.io/@lumos-vid/Unity-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EB%B2%84%EC%B8%84%EC%96%BC-%EC%B9%B4%EB%A9%94%EB%9D%BC-Follow-%EA%B0%95%EC%A0%9C-%EC%9D%B4%EB%8F%99</link>
            <guid>https://velog.io/@lumos-vid/Unity-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EB%B2%84%EC%B8%84%EC%96%BC-%EC%B9%B4%EB%A9%94%EB%9D%BC-Follow-%EA%B0%95%EC%A0%9C-%EC%9D%B4%EB%8F%99</guid>
            <pubDate>Thu, 07 Aug 2025 13:11:09 GMT</pubDate>
            <description><![CDATA[<h2 id="문제점">문제점</h2>
<p>버츄얼 카메라는 이전에 흔히 써오던 Lerp 를 통한 이동이라던지, 여러 복잡한 코드없이 훌륭한 연출을 도와주는 유니티의 기능이다.
기본적으로 우선순위를 이용해 보여지는 카메라를 지정하는데 이런 전환 과정이 자동적으로 부드럽게 보간 할 수 있는 특징이 있다.</p>
<p>문제는 강제로 카메라를 순간이동 할 때 제대로 적용시키기가 꽤나 어려웠다.
<img src="https://velog.velcdn.com/images/lumos-vid/post/06e86244-3771-4743-b040-f249003c3a3a/image.png" alt="">
Follow 에 타겟을 지정만 해도 카메라는 플레이어를 따라다니게 되는데,
카메라를 순간이동 시키기 위해 Follow 를 비우던, 댐핑을 코루틴을 통해 잠시 꺼보던지
어떻게 해도 잘 적용이 되지 않았다.</p>
<p>GPT나 Gemini 등 ai 를 통해 물어보아도 쉽게 해결되지 않았는데
실제로 해결방법은 생각보다 정말 간단했다.</p>
<br>
<br>
<br>

<hr>
<h2 id="해결-방법">해결 방법</h2>
<pre><code class="language-cs">CinemachineVirtualCamera vCam = UnityEngine.Object.FindAnyObjectByType&lt;CinemachineVirtualCamera&gt;();
vCam.OnTargetObjectWarped(playerMovement.transform, delta);</code></pre>
<p>사실상 코드 한줄이면 해결 할 수  있는 일이었다.
delta 에는 비교대상인 위치의 방향을 알려주면 되는데 
이는 대상(Target)이 공간이동했음을 가상 카메라(vcam)에 알리고, vcam이 내부 상태를 업데이트해서 카메라가 끊김없이 이동하도록 한다.</p>
<p>반드시 필요한 기능이고 자주 사용될 것이 뻔한 기능임에도 정보를 찾아보기가 어려워
직접 정리해본다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] A* 알고리즘]]></title>
            <link>https://velog.io/@lumos-vid/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-A-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</link>
            <guid>https://velog.io/@lumos-vid/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-A-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</guid>
            <pubDate>Wed, 06 Aug 2025 14:03:46 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>게임 개발에서 경로 탐색(Pathfinding)은 AI 캐릭터나 오브젝트가 특정 목적지까지 장애물을 피해 최적의 경로를 찾는 데 필수적인 기능이다. 특히 Grid 기반 맵을 사용하는 2D 혹은 3D 게임에서는 대표적인 휴리스틱 기반 탐색 알고리즘인 A* 알고리즘이 자주 사용된다. </p>
<p><br><br><br></p>
<hr>
<h2 id="a">A*</h2>
<p>A* 알고리즘은 BFS의 완전 탐색성과, DFS의 목표 지향적 성격을 적절히 결합한 휴리스틱 기반 최단 경로 탐색 알고리즘이다. 이 알고리즘은 현재 노드에서 시작하여 목표 노드까지의 예상 비용을 기준으로 다음 탐색 노드를 선택한다.</p>
<br>

<h3 id="평가-함수-fn">평가 함수 (f(n))</h3>
<p>A* 알고리즘은 각 노드에 대해 아래의 평가 함수를 사용하여 우선순위를 계산한다.</p>
<pre><code class="language-cs">f(n) = g(n) + h(n)</code></pre>
<ul>
<li><p>g(n): 시작 노드에서 현재 노드까지의 실제 이동 비용 (이동 거리)</p>
</li>
<li><p>h(n): 현재 노드에서 목표 노드까지의 휴리스틱(Heuristic) 비용 (예상 이동 거리)</p>
</li>
</ul>
<p>이 두 값을 더하여 가장 비용이 적은 노드를 우선적으로 탐색한다.</p>
<br>

<h3 id="휴리스틱-함수-h">휴리스틱 함수 (h)</h3>
<p>휴리스틱 함수는 실제 경로가 아닌 예상 경로 비용을 추정한다. 일반적으로 사용되는 휴리스틱 함수는 다음과 같다.</p>
<ul>
<li><p>** 맨해튼 거리 (Manhattan Distance)**
4방향(상하좌우) 이동만 가능한 경우 사용.
h(n) = |x1 - x2| + |y1 - y2|</p>
</li>
<li><p><strong>유클리디안 거리 (Euclidean Distance)</strong>
대각선 이동이 가능한 경우 사용.
h(n) = sqrt((x1 - x2)^2 + (y1 - y2)^2)</p>
</li>
<li><p><strong>체비셰프 거리 (Chebyshev Distance)</strong>
8방향 이동이 가능한 경우 사용.
h(n) = max(|x1 - x2|, |y1 - y2|)</p>
</li>
</ul>
<br>
<br>
<br>

<hr>
<h2 id="알고리즘-작동-방식">알고리즘 작동 방식</h2>
<ol>
<li><p>시작 노드를 Open 리스트에 추가한다.</p>
</li>
<li><p>Open 리스트에서 f(n) 값이 가장 작은 노드를 선택한다.</p>
</li>
<li><p>해당 노드를 Closed 리스트로 이동시키고, 인접 노드들을 탐색한다.</p>
</li>
<li><p>인접 노드가 이동 가능하고(장애물이 아니고), Closed 리스트에 없다면:</p>
</li>
<li><p>Open 리스트에 없으면 추가하고 g, h, f 값을 계산.</p>
</li>
<li><p>이미 Open 리스트에 있다면, 현재 경로가 더 효율적인 경우 g 값을 갱신하고 부모 노드를 변경한다.</p>
</li>
<li><p>목표 노드에 도달할 때까지 2~4 과정을 반복한다.</p>
</li>
<li><p>목표 노드에 도달하면 부모 노드를 따라 경로를 재구성한다.</p>
</li>
</ol>
<br>

<h3 id="유니티에서의-구현">유니티에서의 구현</h3>
<p><strong>노드 클래스 (Node)</strong></p>
<pre><code class="language-cs">public class Node
{
    public Vector2Int GridPosition;
    public bool IsWalkable;
    public int GCost;
    public int HCost;
    public int FCost =&gt; GCost + HCost;
    public Node Parent;
}</code></pre>
<br>

<p>** 그리드 매니저 (GridManager)**</p>
<ul>
<li>게임 맵을 일정한 크기의 그리드로 나눈다.</li>
<li>각 그리드 셀은 Node로 구성되며, 이동 가능 여부(IsWalkable)를 가진다.</li>
<li>맵의 정보를 기반으로 장애물을 설정한다.</li>
</ul>
<br>

<p><em>* A</em> 알고리즘 구현**</p>
<pre><code class="language-cs">public List&lt;Node&gt; FindPath(Vector2Int start, Vector2Int target)
{
    Node startNode = grid[start.x, start.y];
    Node targetNode = grid[target.x, target.y];

    List&lt;Node&gt; openList = new List&lt;Node&gt; { startNode };
    HashSet&lt;Node&gt; closedList = new HashSet&lt;Node&gt;();

    while (openList.Count &gt; 0)
    {
        Node currentNode = openList.OrderBy(n =&gt; n.FCost).ThenBy(n =&gt; n.HCost).First();

        if (currentNode == targetNode)
            return RetracePath(startNode, targetNode);

        openList.Remove(currentNode);
        closedList.Add(currentNode);

        foreach (Node neighbor in GetNeighbors(currentNode))
        {
            if (!neighbor.IsWalkable || closedList.Contains(neighbor))
                continue;

            int newMovementCostToNeighbor = currentNode.GCost + GetDistance(currentNode, neighbor);
            if (newMovementCostToNeighbor &lt; neighbor.GCost || !openList.Contains(neighbor))
            {
                neighbor.GCost = newMovementCostToNeighbor;
                neighbor.HCost = GetDistance(neighbor, targetNode);
                neighbor.Parent = currentNode;

                if (!openList.Contains(neighbor))
                    openList.Add(neighbor);
            }
        }
    }

    return null;
}</code></pre>
<br>

<p><strong>경로 재구성</strong></p>
<pre><code class="language-cs">List&lt;Node&gt; RetracePath(Node startNode, Node endNode)
{
    List&lt;Node&gt; path = new List&lt;Node&gt;();
    Node currentNode = endNode;

    while (currentNode != startNode)
    {
        path.Add(currentNode);
        currentNode = currentNode.Parent;
    }

    path.Reverse();
    return path;
}
</code></pre>
<hr>
<h2 id="최적화">최적화</h2>
<ul>
<li><p>Open 리스트를 List<Node> 대신 PriorityQueue나 Binary Heap으로 대체하여 성능 개선 가능</p>
</li>
<li><p>탐색 대상이 많아질 경우, 전체 그리드 생성 대신 필요 영역만 동적으로 생성</p>
</li>
<li><p>휴리스틱 계산이 너무 과도하거나 부정확하면 비효율적인 경로가 도출될 수 있음</p>
</li>
</ul>
<br>
<br>


]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 행동트리(Behaviour Tree) 정리]]></title>
            <link>https://velog.io/@lumos-vid/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%96%89%EB%8F%99%ED%8A%B8%EB%A6%ACBehaviour-Tree-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%ED%96%89%EB%8F%99%ED%8A%B8%EB%A6%ACBehaviour-Tree-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 05 Aug 2025 14:15:30 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>행동 트리(Behavior Tree)는 계층적 구조를 갖는 트리 형태의 자료구조이다. 이 자료구조는 주로 게임 내 인공지능 캐릭터의 의사결정을 정의하기 위해 사용된다. 행동 트리는 상태 기계(Finite State Machine, FSM)에 비해 모듈화와 확장성이 우수한 자료구조이다. 각 행동 단위는 노드(Node)로 표현되며, 이들은 부모-자식 관계를 통해 트리 형태로 구성된다.</p>
<br>
<br>
<br>

<hr>
<h2 id="구성-요소">구성 요소</h2>
<p>행동 트리는 다음과 같은 구성 요소로 이루어진 자료구조이다:</p>
<ul>
<li><p><strong>루트 노드(Root Node)</strong>: 트리 구조의 시작점이며, 전체 행동의 평가가 이 지점부터 시작된다.</p>
</li>
<li><p><strong>컴포지트 노드(Composite Node)</strong>: 두 개 이상의 자식 노드를 포함하며, 자식 노드들의 실행 순서와 결과를 제어하는 역할을 한다.</p>
</li>
<li><p><strong>데코레이터 노드(Decorator Node)</strong>: 하나의 자식 노드를 포함하며, 해당 자식 노드의 실행 결과를 수정하거나 조건을 추가하는 역할을 한다.</p>
</li>
<li><p><strong>리프 노드(Leaf Node)</strong>: 자식 노드가 없는 단말 노드이며, 실제 조건 판단 또는 행동 실행을 담당한다.</p>
</li>
</ul>
<p>이와 같은 구조는 트리 자료구조의 일반적인 계층적 특징을 따른다.</p>
<br>
<br>
<br>

<hr>
<h2 id="노드의-실행-결과">노드의 실행 결과</h2>
<p>행동 트리의 각 노드는 실행 결과를 반환하는 구조이다. 이는 상위 노드의 흐름 제어에 영향을 미친다. 반환값은 다음 세 가지 상태 중 하나이다:</p>
<ul>
<li><p><strong>Success</strong>: 노드의 작업이 정상적으로 완료된 상태이다.</p>
</li>
<li><p><strong>Failure</strong>: 노드의 작업이 실패한 상태이다.</p>
</li>
<li><p><strong>Running</strong>: 노드의 작업이 아직 완료되지 않았음을 나타내는 상태이다.</p>
</li>
</ul>
<p>이러한 상태는 트리 내에서의 흐름 제어를 위한 핵심 제어 신호로 작용한다.</p>
<br>
<br>
<br>

<hr>
<h2 id="노드의-종류">노드의 종류</h2>
<h3 id="컴포지트-노드">컴포지트 노드</h3>
<p>컴포지트 노드는 여러 자식 노드를 포함하는 노드이다.</p>
<br>

<ul>
<li><strong>Selector 노드</strong>
자식 노드를 순서대로 실행하며, 하나의 자식 노드라도 Success를 반환하면 자신도 Success를 반환하는 구조이다. 모든 자식 노드가 Failure를 반환할 경우에만 Failure를 반환한다.
논리 연산 OR의 역할을 하는 구조이다.</li>
</ul>
<br>

<ul>
<li><strong>Sequence 노드</strong>
자식 노드를 순서대로 실행하며, 하나의 자식 노드라도 Failure를 반환하면 자신도 Failure를 반환한다. 모든 자식 노드가 Success를 반환할 경우에만 Success를 반환한다. 논리 연산 AND의 역할을 하는 구조이다.</li>
</ul>
<br>
<br>


<h3 id="데코레이터-노드">데코레이터 노드</h3>
<p>데코레이터 노드는 하나의 자식 노드를 감싸고, 해당 노드의 반환값이나 조건을 수정하는 구조이다.</p>
<br>

<ul>
<li><strong>Inverter 노드</strong>
자식 노드의 결과를 반전시키는 역할을 하는 구조이다.
(Success ↔ Failure, Running은 그대로 유지)</li>
</ul>
<br>

<ul>
<li><strong>Repeater 노드</strong>
자식 노드를 지정된 횟수 또는 조건이 만족될 때까지 반복 실행하는 구조이다.</li>
</ul>
<br>

<ul>
<li><strong>리프 노드</strong>
리프 노드는 트리의 말단에 위치하며, 실제 게임 로직(조건 검사 또는 행동 수행)을 포함하는 구조이다.</li>
</ul>
<br>

<ul>
<li><strong>Condition 노드</strong>
특정 조건을 검사하여 Success 또는 Failure를 반환하는 구조이다.</li>
</ul>
<br>

<ul>
<li><strong>Action 노드</strong>
캐릭터가 실제로 수행하는 행동(예: 이동, 공격)을 실행하는 구조이다. 실행 중에는 Running 상태를 반환할 수 있으며, 완료 후 Success 또는 Failure를 반환한다.</li>
</ul>
<br>
<br>
<br>

<hr>
<h2 id="실행-흐름-구조">실행 흐름 구조</h2>
<p>행동 트리는 매 프레임 혹은 일정 주기마다 루트 노드로부터 하향식으로 평가되는 구조이다. 노드 평가 과정은 일반적으로 깊이 우선 탐색(DFS: Depth-First Search)을 따른다. 각 노드는 자식 노드의 실행 결과에 따라 자신의 상태를 결정하며, 이 상태는 상위 노드로 전파되는 방식이다.</p>
<br>
<br>
<br>

<hr>
<h2 id="장점">장점</h2>
<p>행동 트리는 다음과 같은 자료구조적 장점을 지닌다:</p>
<ul>
<li><p><strong>모듈성(Modularity)</strong>: 각 행동 단위가 독립적인 노드로 구성되므로 재사용과 테스트가 용이하다.</p>
</li>
<li><p><strong>유지보수성(Maintainability)</strong>: 트리 구조를 통해 전체 로직을 시각화할 수 있으므로 디버깅 및 변경이 수월하다.</p>
</li>
<li><p><strong>확장성(Extensibility)</strong>: 새로운 조건 또는 행동 노드를 추가하는 것이 구조적으로 간단하다.</p>
</li>
</ul>
<br>
<br>
<br>

<hr>
<h2 id="예제">예제</h2>
<pre><code class="language-cs">abstract class Node {
    public enum Status { Success, Failure, Running }
    public abstract Status Evaluate();
}

class Sequence : Node {
    private List&lt;Node&gt; children;

    public override Status Evaluate() {
        foreach (var child in children) {
            var result = child.Evaluate();
            if (result != Status.Success)
                return result;
        }
        return Status.Success;
    }
}

class IsEnemyInSight : Node {
    public override Status Evaluate() {
        return (EnemyDetected()) ? Status.Success : Status.Failure;
    }
}

class AttackEnemy : Node {
    public override Status Evaluate() {
        PerformAttack();
        return Status.Success;
    }
}
</code></pre>
<p>이 예제는 행동 트리의 Sequence 구조를 C#으로 표현한 것이다.</p>
<br>
<br>
<br>

<hr>
<h2 id="결론">결론</h2>
<p>행동 트리는 인공지능의 판단 구조를 정의하기 위한 트리 기반 자료구조이다. 각 노드는 독립적인 판단 및 행동 단위를 의미하며, 트리 전체는 복잡한 논리를 모듈화하여 구성할 수 있도록 돕는다. 유니티에서는 직접 구현 또는 시각적 툴을 통해 이 구조를 손쉽게 적용할 수 있으며, 특히 게임 캐릭터의 행동 로직 구현에 매우 적합한 자료구조이다.</p>
<br>
<br>
<br>

<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] DFS 와 BFS 정리]]></title>
            <link>https://velog.io/@lumos-vid/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-DFS-%EC%99%80-BFS-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-DFS-%EC%99%80-BFS-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 04 Aug 2025 14:11:20 GMT</pubDate>
            <description><![CDATA[<p>그래프(Graph)는 노드(Node)와 간선(Edge)으로 이루어진 자료구조로, 다양한 형태의 데이터 간 관계를 표현할 수 있다. 게임에서는 맵의 연결 구조, 캐릭터의 상태 전이(State Machine), 퀘스트 의존성 등에서 그래프 형태의 데이터를 자주 다룬다. 이러한 그래프를 탐색하기 위한 기본적인 알고리즘이 DFS와 BFS이다.</p>
<p><br><br><br></p>
<hr>
<h2 id="dfs-depth-first-search-깊이-우선-탐색">DFS (Depth-First Search, 깊이 우선 탐색)</h2>
<br>

<h3 id="개념">개념</h3>
<p>DFS는 그래프나 트리 구조에서 한 방향으로 가능한 깊은 지점까지 먼저 탐색한 후, 더 이상 진행할 수 없을 때 이전 단계로 되돌아가며 다른 방향을 탐색하는 방식이다. 재귀 호출을 이용하거나, 명시적으로 스택(Stack) 자료구조를 이용하여 구현할 수 있다.</p>
<br>
<br>


<h3 id="동작-원리">동작 원리</h3>
<ol>
<li><p>현재 노드를 방문 처리한다.</p>
</li>
<li><p>인접한 노드 중 방문하지 않은 노드가 있다면, 그 노드를 스택에 넣고 다시 1번으로 돌아간다.</p>
</li>
<li><p>더 이상 방문할 노드가 없으면 스택에서 이전 노드로 되돌아간다.</p>
</li>
<li><p>모든 노드를 방문할 때까지 위 과정을 반복한다.</p>
</li>
</ol>
<br>
<br>

<h3 id="활용-예시">활용 예시</h3>
<ul>
<li><p>미로 게임에서 목표 지점까지의 경로를 무작위로 탐색</p>
</li>
<li><p>AI가 먼저 도달 가능한 깊은 지역을 순차적으로 탐색할 때</p>
</li>
<li><p>재귀 기반의 탐색 구현이 가능한 상황 (단, 스택 오버플로우 주의)</p>
</li>
<li><p>랜덤 던전 생성 시, DFS를 통해 복잡한 미로 구조를 생성 가능.</p>
</li>
<li><p>플레이어의 선택 경로를 깊이 우선으로 처리하여 특정 조건 충족 시 다음 스킬을 개방하는 구조 구현.</p>
</li>
<li><p>추적 행동에서 일시적으로 가장 깊은 경로를 탐색하도록 설정 가능.</p>
</li>
</ul>
<pre><code class="language-cs">void DFS(Dictionary&lt;int, List&lt;int&gt;&gt; graph, int node, HashSet&lt;int&gt; visited)
{
    if (visited.Contains(node)) return;

    visited.Add(node);
    Debug.Log(&quot;Visited: &quot; + node);

    foreach (int neighbor in graph[node])
    {
        DFS(graph, neighbor, visited);
    }
}
</code></pre>
<br>
<br>
<br>

<hr>
<h2 id="bfs-breadth-first-search-너비-우선-탐색">BFS (Breadth-First Search, 너비 우선 탐색)</h2>
<br>

<h3 id="개념-1">개념</h3>
<p>BFS는 DFS와는 반대로, 가까운 노드부터 차례로 탐색해 나가는 방식이다. 탐색 대상은 큐(Queue) 자료구조를 이용하여 관리되며, 가장 가까운 거리에 있는 노드부터 순차적으로 탐색하게 된다.</p>
<br>

<h3 id="동작-원리-1">동작 원리</h3>
<ol>
<li><p>시작 노드를 큐에 넣고 방문 처리한다.</p>
</li>
<li><p>큐에서 노드를 꺼낸다.</p>
</li>
<li><p>해당 노드의 인접한 노드 중 방문하지 않은 노드를 큐에 추가하고 방문 처리한다.</p>
</li>
<li><p>큐가 빌 때까지 위 과정을 반복한다.</p>
</li>
</ol>
<br>

<h3 id="활용-예시-1">활용 예시</h3>
<ul>
<li><p>NPC 경로 탐색: 최단 거리 계산 시 BFS는 가장 기본적이고 정확한 선택이다.</p>
</li>
<li><p>퍼즐 게임 구현: 상태 공간을 너비 우선으로 탐색하여 정답 도출 시 활용.</p>
</li>
<li><p>네비게이션 메시 없이 간단한 길찾기: 타일 기반 이동 구조에서 장애물 회피 등 간단한 로직 처리에 적합.</p>
</li>
</ul>
<pre><code class="language-cs">void BFS(Dictionary&lt;int, List&lt;int&gt;&gt; graph, int start)
{
    Queue&lt;int&gt; queue = new Queue&lt;int&gt;();
    HashSet&lt;int&gt; visited = new HashSet&lt;int&gt;();

    queue.Enqueue(start);
    visited.Add(start);

    while (queue.Count &gt; 0)
    {
        int node = queue.Dequeue();
        Debug.Log(&quot;Visited: &quot; + node);

        foreach (int neighbor in graph[node])
        {
            if (!visited.Contains(neighbor))
            {
                visited.Add(neighbor);
                queue.Enqueue(neighbor);
            }
        }
    }
}</code></pre>
<br>
<br>
<br>

<hr>
<h2 id="dfs-vs-bfs-비교">DFS vs BFS 비교</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>DFS</th>
<th>BFS</th>
</tr>
</thead>
<tbody><tr>
<td>자료 구조</td>
<td>스택 (Stack) 또는 재귀</td>
<td>큐 (Queue)</td>
</tr>
<tr>
<td>사용 목적</td>
<td>완전 탐색, 깊은 조건 탐색</td>
<td>최단 경로 탐색</td>
</tr>
<tr>
<td>공간 복잡도</td>
<td>상대적으로 낮을 수 있음 (경우에 따라)</td>
<td>노드가 많을 경우 큐 크기가 커짐</td>
</tr>
<tr>
<td>구현 난이도</td>
<td>재귀적 구조로 간결</td>
<td>반복문과 큐로 구현</td>
</tr>
<tr>
<td>예시 활용</td>
<td>미로 생성, 스킬 트리, 백트래킹 문제</td>
<td>최단거리 탐색, 전염병 시뮬레이션 등</td>
</tr>
</tbody></table>
<br>
<br>
<br>

<hr>
<h2 id="마무리">마무리</h2>
<p>DFS와 BFS는 단순한 알고리즘 같지만, 게임 시스템 설계와 구현에서 전략적으로 매우 유용하게 활용될 수 있다. Unity 프로젝트를 설계할 때, 단순한 길찾기부터 스테이트 기반 AI, 스킬 전개, 맵 탐색 등에서 이들 탐색 알고리즘을 적절히 조합하면 더욱 정교한 시스템을 구축할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] 큐(Queue) 와 스택(Stack) 정리]]></title>
            <link>https://velog.io/@lumos-vid/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%81%90Queue-%EC%99%80-%EC%8A%A4%ED%83%9DStack-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%ED%81%90Queue-%EC%99%80-%EC%8A%A4%ED%83%9DStack-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 01 Aug 2025 14:13:18 GMT</pubDate>
            <description><![CDATA[<p><strong>스택(Stack)</strong>과 <strong>큐(Queue)</strong>는 선형 자료구조(Linear Data Structure)의 대표적인 형태로, 알고리즘 문제 해결, 시스템 아키텍처 설계, 소프트웨어 개발 등 다양한 영역에서 활용된다.</p>
<br>
<br>
<br>


<hr>
<h2 id="스택stack">스택(Stack)</h2>
<p>스택은 <strong>후입선출(Last-In, First-Out, LIFO)</strong>의 특성을 가진 자료구조이다. 즉, 가장 마지막에 삽입된 데이터가 가장 먼저 제거된다. 스택은 마치 책을 위로 쌓아올리고 위에서부터 꺼내는 것과 유사하다.</p>
<br>


<h3 id="주요-연산">주요 연산</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>push(item)</code></td>
<td>스택의 **맨 위(top)**에 요소를 삽입한다.</td>
</tr>
<tr>
<td><code>pop()</code></td>
<td>스택의 맨 위 요소를 제거하고 반환한다.</td>
</tr>
<tr>
<td><code>peek()</code></td>
<td>제거하지 않고 맨 위 요소를 반환한다. (읽기 전용)</td>
</tr>
<tr>
<td><code>isEmpty()</code></td>
<td>스택이 비어 있는지 여부를 반환한다.</td>
</tr>
<tr>
<td><code>size()</code></td>
<td>스택 내 요소의 개수를 반환한다.</td>
</tr>
</tbody></table>
<br>
<br>

<h3 id="내부-구현">내부 구현</h3>
<p>스택은 배열(Array) 또는 연결 리스트(Linked List)로 구현할 수 있다.</p>
<br>


<p><strong>배열 기반 스택</strong></p>
<ul>
<li><p>고정 크기 할당</p>
</li>
<li><p>O(1) 시간의 push, pop 가능</p>
</li>
<li><p>크기 초과 시 오버플로우 발생 가능</p>
</li>
</ul>
<br>


<p><strong>연결 리스트 기반 스택</strong></p>
<ul>
<li><p>크기 제한 없음</p>
</li>
<li><p>노드 기반 동적 메모리 할당</p>
</li>
<li><p>push, pop 시 O(1) 성능 유지</p>
</li>
</ul>
<br>
<br>

<h3 id="시간-복잡도">시간 복잡도</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
</tr>
</thead>
<tbody><tr>
<td><code>push</code></td>
<td>O(1)</td>
</tr>
<tr>
<td><code>pop</code></td>
<td>O(1)</td>
</tr>
<tr>
<td><code>peek</code></td>
<td>O(1)</td>
</tr>
</tbody></table>
<br>
<br>

<h3 id="활용-사례">활용 사례</h3>
<ul>
<li><p>재귀 함수 호출 시 콜 스택 관리</p>
</li>
<li><p>웹 브라우저 뒤로가기 기능</p>
</li>
<li><p>수식 계산기 (후위 표기법, 전위 표기법 등)</p>
</li>
<li><p>괄호 검사 (유효한 괄호 쌍 판별)</p>
</li>
</ul>
<br>
<br>
<br>


<hr>
<h2 id="큐queue">큐(Queue)</h2>
<p>큐는 선입선출(First In, First Out; FIFO) 방식으로 동작하는 선형 자료구조이다. 먼저 삽입된 요소가 먼저 제거되는 구조로, 현실의 대기열(줄 서기)을 모방한 구조이다.</p>
<br>
<br>

<h3 id="주요-연산-1">주요 연산</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>enqueue(item)</code></td>
<td>큐의 **뒤쪽(rear)**에 요소를 삽입한다.</td>
</tr>
<tr>
<td><code>dequeue()</code></td>
<td>큐의 <strong>앞쪽(front)</strong> 요소를 제거하고 반환한다.</td>
</tr>
<tr>
<td><code>peek()</code></td>
<td>제거하지 않고 맨 앞 요소를 반환한다.</td>
</tr>
<tr>
<td><code>isEmpty()</code></td>
<td>큐가 비어 있는지 여부를 반환한다.</td>
</tr>
<tr>
<td><code>size()</code></td>
<td>큐 내 요소의 개수를 반환한다.</td>
</tr>
</tbody></table>
<br>
<br>

<h3 id="종류">종류</h3>
<table>
<thead>
<tr>
<th>큐 종류</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>일반 큐</td>
<td>선입선출 구조의 가장 기본형</td>
</tr>
<tr>
<td>원형 큐 (Circular Queue)</td>
<td>배열을 원형 구조로 활용하여 공간 낭비를 최소화</td>
</tr>
<tr>
<td>이중 큐 (Deque)</td>
<td>양쪽에서 삽입과 삭제가 가능한 큐</td>
</tr>
<tr>
<td>우선순위 큐</td>
<td>요소마다 우선순위를 부여하여 높은 우선순위의 요소를 먼저 처리</td>
</tr>
</tbody></table>
<br>
<br>


<h3 id="내부-구현-1">내부 구현</h3>
<p>큐 또한 배열 또는 연결 리스트로 구현할 수 있다. Circular Queue는 배열로 구현 시 인덱스를 순환시켜 공간을 절약하는 구조이다.</p>
<br>

<p><strong>배열 기반 큐</strong></p>
<ul>
<li><p>큐의 맨 앞 요소를 제거할 때 모든 요소를 앞으로 한 칸 이동 → 비효율적</p>
</li>
<li><p>Circular Queue로 이를 개선 가능</p>
</li>
</ul>
<br>

<p><strong>연결 리스트 기반 큐</strong></p>
<ul>
<li>front와 rear 포인터를 통해 O(1) 시간에 삽입/삭제 가능</li>
</ul>
<br>
<br>

<h3 id="시간-복잡도-1">시간 복잡도</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th>시간 복잡도</th>
</tr>
</thead>
<tbody><tr>
<td><code>enqueue</code></td>
<td>O(1)</td>
</tr>
<tr>
<td><code>dequeue</code></td>
<td>O(1)</td>
</tr>
<tr>
<td><code>peek</code></td>
<td>O(1)</td>
</tr>
</tbody></table>
<br>
<br>

<h3 id="활용-사례-1">활용 사례</h3>
<ul>
<li><p>운영체제의 프로세스 스케줄링</p>
</li>
<li><p>프린터 작업 큐 관리</p>
</li>
<li><p>너비 우선 탐색 (BFS) 알고리즘</p>
</li>
<li><p>이벤트 핸들링 시스템, 메시지 큐</p>
</li>
</ul>
<br>
<br>
<br>

<hr>
<h2 id="stack-vs-queue-비교">Stack vs Queue 비교</h2>
<table>
<thead>
<tr>
<th>항목</th>
<th>Stack</th>
<th>Queue</th>
</tr>
</thead>
<tbody><tr>
<td>기본 원리</td>
<td>LIFO (후입선출)</td>
<td>FIFO (선입선출)</td>
</tr>
<tr>
<td>삽입 위치</td>
<td>top</td>
<td>rear</td>
</tr>
<tr>
<td>삭제 위치</td>
<td>top</td>
<td>front</td>
</tr>
<tr>
<td>주요 연산</td>
<td>push, pop, peek</td>
<td>enqueue, dequeue, peek</td>
</tr>
<tr>
<td>시간 복잡도</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>대표 활용 예</td>
<td>콜 스택, 괄호 검사, 수식 계산 등</td>
<td>BFS, 작업 처리, 스케줄링 등</td>
</tr>
</tbody></table>
<br>
<br>
<br>

<hr>
<h2 id="마무리">마무리</h2>
<p>실제 프로그램에서는 단순한 배열이나 리스트보다 이 구조들을 활용한 문제 해결이 훨씬 유연하고 효율적이다. 특히 알고리즘 문제 해결 시 이 구조의 원리적 이해와 구현 경험이 매우 중요하다. 직접 구현하고 다양한 시나리오에서 이들을 적용해 보는 연습이 필요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 멀티 쓰레딩 정리]]></title>
            <link>https://velog.io/@lumos-vid/Unity-%EB%A9%80%ED%8B%B0-%EC%93%B0%EB%A0%88%EB%94%A9-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/Unity-%EB%A9%80%ED%8B%B0-%EC%93%B0%EB%A0%88%EB%94%A9-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 31 Jul 2025 14:28:43 GMT</pubDate>
            <description><![CDATA[<h2 id="멀티-쓰레딩">멀티 쓰레딩</h2>
<p>Unity는 기본적으로 싱글 스레드 기반의 게임 엔진이다. 대부분의 Unity API는 메인 스레드에서만 호출이 가능하며, 렌더링, 물리 처리, UI 갱신 등 핵심적인 작업 역시 메인 스레드에서 수행된다. 그러나 게임의 복잡성이 증가함에 따라, 병렬 처리를 통해 성능을 향상시킬 필요성이 커지고 있다. 본 글에서는 Unity에서 멀티 쓰레딩을 구현하는 다양한 방법과 주의할 점을 상세히 다루고자 한다.</p>
<br>
<br>

<hr>
<h2 id="기본-구조">기본 구조</h2>
<p>Unity는 다음과 같은 구조로 스레드를 운용한다:</p>
<ul>
<li><p><strong>Main Thread</strong>: 대부분의 Unity API가 동작하는 기본 실행 스레드이다. 게임 로직, UI 처리 등이 여기에 포함된다.</p>
</li>
<li><p><strong>Render Thread</strong>: 일부 플랫폼에서 렌더링을 담당하는 전용 스레드이다.</p>
</li>
<li><p><strong>Worker Threads</strong>: 개발자가 직접 생성하거나 Unity의 Job System을 통해 활용 가능한 병렬 처리용 스레드이다.</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="구현-방법">구현 방법</h2>
<p><strong>C#의 Thread 클래스 사용</strong>
.NET의 표준 System.Threading.Thread 클래스를 사용하면 직접 스레드를 생성하여 작업을 분리할 수 있다.</p>
<pre><code class="language-cs">void Start()
{
    Thread thread = new Thread(HeavyTask);
    thread.Start();
}

void HeavyTask()
{
    // 백그라운드 작업 처리
    Debug.Log(&quot;작업 실행 중&quot;);
}</code></pre>
<p>단, Debug.Log()와 같은 UnityEngine API는 메인 스레드에서만 안전하게 호출될 수 있다. 위 코드는 예시일 뿐, 실제로는 주의가 필요하다.</p>
<br>
<br>

<hr>
<h2 id="task와-asyncawait">Task와 async/await</h2>
<p>비동기 처리를 위해 Task와 async/await를 사용할 수 있다. 이 방식은 코드 가독성이 뛰어나며 예외 처리 또한 간결하게 이뤄질 수 있다.</p>
<pre><code class="language-cs">async void Start()
{
    await Task.Run(() =&gt; HeavyComputation());
    Debug.Log(&quot;비동기 작업 완료&quot;);
}

void HeavyComputation()
{
    // CPU 연산 수행
    Thread.Sleep(1000);
}</code></pre>
<p>이 방식은 Unity API를 직접 호출하지 않는 범위 내에서 병렬 처리를 수행한 뒤, 작업 종료 후 메인 스레드로 안전하게 복귀할 수 있다는 장점을 가진다.</p>
<br>
<br>

<hr>
<h2 id="unity-job-system">Unity Job System</h2>
<p>Unity가 제공하는 멀티 쓰레딩 프레임워크로, Burst Compiler와 함께 사용될 때 매우 높은 성능을 기대할 수 있다.</p>
<pre><code class="language-cs">[BurstCompile]
public struct MyJob : IJob
{
    public void Execute()
    {
        // 병렬 처리할 작업
    }
}

void Start()
{
    var job = new MyJob();
    var handle = job.Schedule();
    handle.Complete();
}</code></pre>
<p>Job System은 메모리 안전성과 성능을 동시에 확보할 수 있도록 설계되었으며, NativeArray, NativeList 등의 구조체와 함께 사용된다. 단, UnityEngine 객체에 직접 접근하는 것은 불가능하다.</p>
<br>
<br>

<hr>
<h2 id="unity-dots-data-oriented-tech-stack">Unity DOTS (Data-Oriented Tech Stack)</h2>
<p>DOTS는 Unity의 데이터 중심 설계 방식을 따르며, ECS(Entity Component System)를 기반으로 멀티 스레딩을 극대화할 수 있다.</p>
<pre><code class="language-cs">Entities
    .WithName(&quot;MoveJob&quot;)
    .ForEach((ref Translation trans) =&gt;
    {
        trans.Value.y += 1f;
    }).ScheduleParallel();</code></pre>
<p> DOTS는 대규모 데이터를 다루는 데 적합하며, 수천 개 이상의 엔티티를 병렬로 처리할 수 있는 구조를 가진다.</p>
<br>
<br>

<hr>
<h2 id="unity-api와-스레드-안전성">Unity API와 스레드 안전성</h2>
<p>UnityEngine의 대부분의 API는 스레드에 안전하지 않다. 즉, 메인 스레드가 아닌 다른 스레드에서 호출될 경우 예기치 않은 동작이나 크래시가 발생할 수 있다. 대표적인 예시는 다음과 같다.</p>
<ul>
<li><p>Transform.position</p>
</li>
<li><p>GameObject.SetActive()</p>
</li>
<li><p>Debug.Log()</p>
</li>
<li><p>Instantiate(), Destroy()</p>
</li>
</ul>
<p>따라서 별도의 스레드에서 Unity API를 호출하고자 할 경우, 반드시 메인 스레드에서 해당 작업을 실행되도록 큐잉해야 한다.</p>
<pre><code class="language-cs">private static readonly ConcurrentQueue&lt;Action&gt; mainThreadQueue = new();

void Update()
{
    while (mainThreadQueue.TryDequeue(out var action))
    {
        action?.Invoke();
    }
}

// 백그라운드 스레드 내부
mainThreadQueue.Enqueue(() =&gt;
{
    someGameObject.SetActive(true); // 메인 스레드에서 실행됨
});</code></pre>
<br>
<br>

<hr>
<h2 id="스레딩-사용-시-고려사항">스레딩 사용 시 고려사항</h2>
<ul>
<li><p>** 스레드 안전성(Thread Safety)**: 공유 데이터에 접근할 경우 lock, Mutex, ConcurrentDictionary 등을 이용하여 동기화해야 한다.</p>
</li>
<li><p><strong>GC 부담</strong>: 너무 많은 스레드 또는 Task 생성은 가비지 컬렉션 비용을 증가시킬 수 있다.</p>
</li>
<li><p><strong>스레드 수 조절</strong>: 코어 수 이상으로 스레드를 생성할 경우 오히려 병목이 발생할 수 있다.</p>
</li>
<li><p><strong>디버깅 어려움</strong>: 병렬 처리는 디버깅이 까다로우며, 재현이 어려운 버그가 발생할 수 있다.</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="활용-사례">활용 사례</h2>
<table>
<thead>
<tr>
<th>분야</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>경로 탐색</td>
<td>A* 알고리즘 등 경로 계산은 연산량이 많아 별도 스레드에서 처리하는 것이 효율적이다.</td>
</tr>
<tr>
<td>월드 생성</td>
<td>노이즈 기반 월드 생성, 텍스처 샘플링 등의 작업은 Job System으로 분산 처리할 수 있다.</td>
</tr>
<tr>
<td>AI 의사결정</td>
<td>다수의 NPC가 동시에 의사결정을 수행할 경우 병렬 처리를 통해 성능을 확보할 수 있다.</td>
</tr>
<tr>
<td>비동기 네트워크</td>
<td>서버와의 통신은 비동기 방식으로 처리되어야 한다.</td>
</tr>
<tr>
<td>대용량 파일 처리</td>
<td>JSON, XML, CSV 등의 파싱 작업은 Task로 처리할 수 있다.</td>
</tr>
</tbody></table>
<br>
<br>

<hr>
<h2 id="결론">결론</h2>
<p>Unity는 기본적으로 싱글 스레드에서 동작하지만, 다양한 멀티 쓰레딩 기법을 통해 연산 효율을 높일 수 있다. C#의 Thread, Task, Unity의 Job System, DOTS 등 각각의 기법은 장단점을 가지며, 프로젝트의 성격에 맞게 선택되어야 한다.</p>
<p>멀티 쓰레딩은 성능 최적화의 강력한 도구가 될 수 있으나, 동시에 스레드 안전성, API 호출 제한, 디버깅 복잡도 등의 리스크도 내포하고 있다. 이러한 요소들을 충분히 고려한 후에 적절한 방법을 선택하는 것이 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자료구조] 연결리스트(LinkedList) 정리]]></title>
            <link>https://velog.io/@lumos-vid/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%97%B0%EA%B2%B0%EB%A6%AC%EC%8A%A4%ED%8A%B8LinkedList-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EC%97%B0%EA%B2%B0%EB%A6%AC%EC%8A%A4%ED%8A%B8LinkedList-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 30 Jul 2025 14:34:03 GMT</pubDate>
            <description><![CDATA[<h2 id="연결리스트linkedlist">연결리스트(LinkedList)</h2>
<p>연결 리스트는 데이터를 <strong>노드(Node)</strong>라는 단위 객체에 저장하고, 각 노드가 다음 노드를 가리키는 참조(Reference)를 통해 선형적인 순서를 구성하는 자료구조다.</p>
<p>배열(Array)이 메모리상에 연속된 공간에 데이터를 저장하는 <strong>밀집 리스트(Dense List)</strong>인 것과 달리, 연결 리스트는 메모리상에 흩어져 있는 데이터를 포인터를 통해 논리적으로 연결하는 <strong>연결 리스트(Linked List)</strong>다.</p>
<p>각 노드는 다음과 같은 두 가지 핵심 요소로 구성된다.</p>
<ul>
<li><p><strong>데이터(Data)</strong>: 노드가 실질적으로 저장하는 값.</p>
</li>
<li><p><strong>참조(Reference) / 포인터(Pointer)</strong>: 다음 노드의 메모리 주소를 가리키는 값. C#에서는 이를 &#39;참조&#39;라 칭한다. 마지막 노드의 참조는 null을 가리킨다.</p>
</li>
</ul>
<p>이러한 구조적 특성으로 인해 배열과 비교하여 명확한 장단점을 지닌다.</p>
<ul>
<li><p><strong>장점</strong>: 데이터의 삽입 및 삭제가 효율적이다. 특정 위치에 데이터를 추가하거나 제거할 때, 해당 위치의 이전 노드와 다음 노드의 참조만 변경하면 되므로 O(1)의 시간 복잡도를 가진다. 배열처럼 데이터를 한 칸씩 밀거나 당기는 작업이 불필요하다.</p>
</li>
<li><p><strong>단점</strong>: 특정 데이터에 대한 접근(Access) 및 탐색(Search) 속도가 느리다. 배열은 인덱스를 통해 O(1) 시간 복잡도로 데이터에 즉시 접근할 수 있으나, 연결 리스트는 원하는 데이터를 찾기 위해 반드시 첫 번째 노드(Head)부터 순차적으로 탐색해야 하므로 최악의 경우 O(n)의 시간 복잡도를 가진다.</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="구현방법">구현방법</h2>
<p><strong>Node<T> 클래스 정의</strong>
먼저, 연결 리스트의 기본 단위인 노드를 클래스로 정의한다. 제네릭 <T>를 사용하여 모든 데이터 타입을 수용할 수 있도록 설계한다.</p>
<pre><code class="language-cs">  /// &lt;summary&gt;
/// 연결 리스트의 개별 노드를 표현하는 클래스.
/// &lt;/summary&gt;
/// &lt;typeparam name=&quot;T&quot;&gt;노드가 저장할 데이터의 타입&lt;/typeparam&gt;
public class Node&lt;T&gt;
{
    // 실제 데이터
    public T Data { get; set; }
    // 다음 노드를 가리키는 참조
    public Node&lt;T&gt; Next { get; set; }

    public Node(T data)
    {
        this.Data = data;
        this.Next = null; // 생성 시점에는 다음 노드가 없으므로 null로 초기화
    }
}</code></pre>
<p><strong>MyLinkedList<T> 클래스 정의</strong>
정의한 Node<T>를 관리하고, 연결 리스트의 핵심 기능을 제공하는 MyLinkedList<T> 클래스를 구현한다.</p>
<pre><code class="language-cs">/// &lt;summary&gt;
/// 단일 연결 리스트를 구현한 클래스.
/// &lt;/summary&gt;
public class MyLinkedList&lt;T&gt;
{
    private Node&lt;T&gt; head; // 리스트의 시작을 가리키는 노드
    private int count;    // 리스트에 포함된 노드의 개수

    public int Count =&gt; count;
    public bool IsEmpty =&gt; count == 0;

    public MyLinkedList()
    {
        this.head = null;
        this.count = 0;
    }

    // ... (이하 핵심 기능 메서드 구현)
}</code></pre>
<br>

<p>** 핵심 기능 메서드 구현**</p>
<ul>
<li><strong>데이터 추가 (Add)</strong>
AddFirst(T data): 리스트 맨 앞에 데이터 추가
가장 간단하고 빠른 추가 방식이다. 새로운 노드를 생성하고, 새 노드의 Next가 기존의 head를 가리키게 한 후, head를 새 노드로 교체하면 된다. 시간 복잡도는 O(1)이다.</li>
</ul>
<pre><code class="language-cs">public void AddFirst(T data)
{
    Node&lt;T&gt; newNode = new Node&lt;T&gt;(data);
    newNode.Next = this.head; // 새 노드가 기존 head를 가리킴
    this.head = newNode;      // head를 새 노드로 업데이트
    count++;
}</code></pre>
<ul>
<li><strong>AddLast(T data): 리스트 맨 뒤에 데이터 추가</strong>
head부터 시작하여 Next가 null인 마지막 노드를 찾은 후, 그 노드의 Next를 새 노드로 연결해야 한다. 시간 복잡도는 O(n)이다.</li>
</ul>
<pre><code class="language-cs">  public void AddLast(T data)
{
    Node&lt;T&gt; newNode = new Node&lt;T&gt;(data);
    if (this.head == null)
    {
        this.head = newNode;
    }
    else
    {
        Node&lt;T&gt; current = this.head;
        while (current.Next != null)
        {
            current = current.Next;
        }
        current.Next = newNode;
    }
    count++;
}</code></pre>
<ul>
<li><strong>데이터 삭제 (Remove)</strong>
RemoveFirst(): 맨 앞 노드 삭제
head를 현재 head의 Next 노드로 변경하기만 하면 된다. 시간 복잡도는 O(1)이다.</li>
</ul>
<pre><code class="language-cs">  public void RemoveFirst()
{
    if (IsEmpty)
        throw new InvalidOperationException(&quot;List is empty.&quot;);

    this.head = this.head.Next;
    count--;
}</code></pre>
<ul>
<li><strong>데이터 탐색 (Find)</strong>
Find(T data): 특정 데이터 값을 가진 노드 탐색
head부터 시작하여 순차적으로 각 노드의 Data를 비교한다. 시간 복잡도는 O(n)이다.</li>
</ul>
<pre><code class="language-cs">  public Node&lt;T&gt; Find(T data)
{
    Node&lt;T&gt; current = this.head;
    while (current != null)
    {
        if (EqualityComparer&lt;T&gt;.Default.Equals(current.Data, data))
        {
            return current;
        }
        current = current.Next;
    }
    return null;
}</code></pre>
<br>

<p>** C# 내장 LinkedList<T> 클래스 활용 **
닷넷 프레임워크는 System.Collections.Generic 네임스페이스에 LinkedList<T>라는 강력한 이중 연결 리스트(Doubly Linked List) 클래스를 기본으로 제공한다.</p>
<p>이중 연결 리스트는 각 노드가 다음 노드(Next)뿐만 아니라 <strong>이전 노드(Previous)</strong>에 대한 참조도 함께 가지는 구조다. 이로 인해 양방향 탐색이 가능하며, 특정 노드 앞뒤에 데이터를 추가하거나 삭제하는 작업이 더 효율적이다.</p>
<p><strong>주요 속성 및 메서드</strong></p>
<ul>
<li><strong>First</strong>: 리스트의 첫 번째 노드(LinkedListNode<T>)를 반환한다.</li>
<li><strong>Last</strong>: 리스트의 마지막 노드를 반환한다.</li>
<li><strong>Count</strong>: 리스트의 노드 개수를 반환한다.</li>
<li><strong>AddFirst(T value)</strong>: 리스트 맨 앞에 새 노드를 추가한다. (O(1))</li>
<li><strong>AddLast(T value)</strong>: 리스트 맨 뒤에 새 노드를 추가한다. (O(1)) - Last 프로퍼티 덕분에 O(1)이 가능하다.</li>
<li><strong>AddBefore(LinkedListNode<T> node, T value)</strong>: 지정된 노드 node 앞에 새 노드를 추가한다. (O(1))</li>
<li><strong>AddAfter(LinkedListNode<T> node, T value)</strong>: 지정된 노드 node 뒤에 새 노드를 추가한다. (O(1))</li>
<li><strong>Remove(T value)</strong>: 특정 값을 가진 첫 번째 노드를 제거한다. (O(n))</li>
<li><strong>Remove(LinkedListNode<T> node)</strong>: 지정된 노드를 제거한다. (O(1))</li>
<li><strong>RemoveFirst()</strong>: 첫 번째 노드를 제거한다. (O(1))</li>
<li><strong>RemoveLast()</strong>: 마지막 노드를 제거한다. (O(1))</li>
<li><strong>Find(T value)</strong>: 특정 값을 가진 첫 번째 노드를 찾아 반환한다. (O(n))</li>
</ul>
<br>


<p><strong>예시</strong></p>
<pre><code class="language-cs">  // LinkedList&lt;string&gt; 인스턴스 생성
var names = new LinkedList&lt;string&gt;();

// 데이터 추가
names.AddLast(&quot;Alpha&quot;);
names.AddLast(&quot;Bravo&quot;);
names.AddLast(&quot;Charlie&quot;);
names.AddFirst(&quot;Start&quot;); // [&quot;Start&quot;, &quot;Alpha&quot;, &quot;Bravo&quot;, &quot;Charlie&quot;]

// 특정 노드 찾기 및 그 뒤에 데이터 추가
LinkedListNode&lt;string&gt; bravoNode = names.Find(&quot;Bravo&quot;);
if (bravoNode != null)
{
    names.AddAfter(bravoNode, &quot;Beta&quot;); // [&quot;Start&quot;, &quot;Alpha&quot;, &quot;Bravo&quot;, &quot;Beta&quot;, &quot;Charlie&quot;]
}

// 데이터 삭제
names.Remove(&quot;Start&quot;); // [&quot;Alpha&quot;, &quot;Bravo&quot;, &quot;Beta&quot;, &quot;Charlie&quot;]
names.RemoveLast();    // [&quot;Alpha&quot;, &quot;Bravo&quot;, &quot;Beta&quot;]

// 리스트 순회
Console.WriteLine(&quot;현재 리스트 요소:&quot;);
foreach (string name in names)
{
    Console.WriteLine(name);
}
// 출력:
// Alpha
// Bravo
// Beta</code></pre>
<br>
<br>

<hr>
<h2 id="마무리">마무리</h2>
<p>연결 리스트는 데이터의 삽입과 삭제가 빈번하게 발생하는 시나리오에서 성능을 발휘하는 자료구조다. 반면, 데이터에 대한 임의 접근(Random Access)이 중요하다면 배열 기반의 List<T>가 훨씬 유리하다.</p>
<p>이 두 자료구조의 내부 동작 원리와 성능 특성을 명확히 이해하고, 당면한 문제의 요구사항에 가장 적합한 것을 선택하여 사용하는 것이 중요하다. 대부분의 일반적인 시나리오에서는 List<T>가 더 나은 선택일 수 있지만, 큐(Queue), 스택(Stack)과 같은 다른 자료구조를 구현하거나, 대규모 데이터셋에서 삽입/삭제가 매우 잦은 특정 알고리즘 문제에서는 LinkedList<T>가 좋은 선택이 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 리플렉션(Reflection) 정리]]></title>
            <link>https://velog.io/@lumos-vid/C-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98Reflection-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/C-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98Reflection-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 29 Jul 2025 14:35:42 GMT</pubDate>
            <description><![CDATA[<h2 id="리플렉션reflection">리플렉션(Reflection)</h2>
<p>리플렉션이란 프로그램 실행 중에 코드의 구조(클래스, 메서드, 프로퍼티 등)에 접근하고, 조작할 수 있게 해주는 .NET의 강력한 기능이다. 즉, 컴파일된 코드(어셈블리)를 런타임에 분석할 수 있다.
이를 통해 객체의 타입, 메서드, 필드, 프로퍼티 등 다양한 정보를 동적으로 가져올 수 있고, 심지어 인스턴스 생성, 메서드 호출 등까지 할 수 있다.</p>
<br>
<br>

<hr>
<h2 id="주요-클래스">주요 클래스</h2>
<p>리플렉션을 사용할 때 주로 사용하는 네임스페이스와 클래스는 아래와 같다.</p>
<br>

<ul>
<li><strong>System.Reflection 네임스페이스</strong></li>
</ul>
<br>

<ul>
<li><strong>Type 클래스</strong>
가장 핵심이 되는 클래스. 타입 정보를 가져오거나, 메타데이터를 읽을 때 사용한다.</li>
</ul>
<br>

<ul>
<li><strong>MethodInfo, PropertyInfo, FieldInfo, ConstructorInfo 등</strong>
각각 메서드, 프로퍼티, 필드, 생성자에 대한 정보를 담는다.</li>
</ul>
<br>

<ul>
<li><strong>Assembly 클래스</strong>
어셈블리 전체에 대한 정보를 가져올 때 사용한다.</li>
</ul>
<br>
<br>

<hr>
<h2 id="사용법">사용법</h2>
<p><strong>Type 객체 얻기</strong>
Type 객체를 얻는 방법은 대표적으로 3가지가 있다.</p>
<pre><code class="language-cs">// 방법 1: 인스턴스에서 얻기
object obj = new MyClass();
Type type1 = obj.GetType();

// 방법 2: 타입에서 바로 얻기
Type type2 = typeof(MyClass);

// 방법 3: 문자열로부터 얻기 (네임스페이스 포함)
Type type3 = Type.GetType(&quot;MyNamespace.MyClass&quot;);</code></pre>
<br>

<p><strong>어셈블리 정보 가져오기</strong>
어셈블리 전체에 대한 정보를 얻고 싶으면 아래와 같이 한다.</p>
<pre><code class="language-cs">Assembly asm = Assembly.GetExecutingAssembly(); // 현재 실행중인 어셈블리
Type[] types = asm.GetTypes(); // 어셈블리에 정의된 모든 타입</code></pre>
<br>

<p><strong>필드, 프로퍼티, 메서드, 생성자 정보 얻기</strong></p>
<pre><code class="language-cs">Type type = typeof(MyClass);

// 필드
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);

// 프로퍼티
PropertyInfo[] properties = type.GetProperties();

// 메서드
MethodInfo[] methods = type.GetMethods();

// 생성자
ConstructorInfo[] ctors = type.GetConstructors();</code></pre>
<br>

<p><strong>인스턴스 생성</strong></p>
<pre><code class="language-cs">Type type = typeof(MyClass);

// 매개변수 없는 생성자
object instance = Activator.CreateInstance(type);

// 매개변수가 있는 생성자
object instance2 = Activator.CreateInstance(type, new object[] { &quot;파라미터값&quot; });</code></pre>
<br>

<p><strong>메서드 호출</strong></p>
<pre><code class="language-cs">Type type = typeof(MyClass);
object obj = Activator.CreateInstance(type);

MethodInfo method = type.GetMethod(&quot;MethodName&quot;);
method.Invoke(obj, new object[] { /* 파라미터 */ });</code></pre>
<br>


<p><strong>필드, 프로퍼티 값 읽기/쓰기</strong></p>
<pre><code class="language-cs">// 필드 읽기/쓰기
FieldInfo field = type.GetField(&quot;fieldName&quot;, BindingFlags.NonPublic | BindingFlags.Instance);
var value = field.GetValue(obj);
field.SetValue(obj, &quot;새 값&quot;);

// 프로퍼티 읽기/쓰기
PropertyInfo prop = type.GetProperty(&quot;PropertyName&quot;);
var propValue = prop.GetValue(obj);
prop.SetValue(obj, &quot;새 값&quot;);</code></pre>
<br>
<br>

<hr>
<h2 id="bindingflags">BindingFlags</h2>
<p>리플렉션에서 필드, 메서드 등 멤버를 찾을 때 접근 범위를 지정해야 한다. 그때 사용하는 게 BindingFlags다.</p>
<ul>
<li><p>Public, NonPublic: 공개/비공개 멤버</p>
</li>
<li><p>Instance, Static: 인스턴스/정적 멤버</p>
</li>
<li><p>DeclaredOnly: 선언된 멤버만(상속 제외)</p>
</li>
<li><p>여러 플래그를 | 로 조합해서 사용</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="예시">예시</h2>
<pre><code class="language-cs">public class TestClass
{
    private int _num = 10;
    public string Name { get; set; }

    public void Print(string msg)
    {
        Console.WriteLine($&quot;{Name}: {msg}&quot;);
    }
}

Type t = typeof(TestClass);
object inst = Activator.CreateInstance(t);

PropertyInfo pInfo = t.GetProperty(&quot;Name&quot;);
pInfo.SetValue(inst, &quot;홍길동&quot;);

FieldInfo fInfo = t.GetField(&quot;_num&quot;, BindingFlags.NonPublic | BindingFlags.Instance);
fInfo.SetValue(inst, 999);

MethodInfo mInfo = t.GetMethod(&quot;Print&quot;);
mInfo.Invoke(inst, new object[] { &quot;안녕!&quot; });
</code></pre>
<br>
<br>

<hr>
<h2 id="단점">단점</h2>
<ul>
<li><p>일반 코드보다 속도가 느리다. (런타임에 정보를 분석하기 때문)</p>
</li>
<li><p>코드가 복잡해질 수 있다.</p>
</li>
<li><p>컴파일 타임 타입 체크가 불가, 런타임 에러 위험이 있다.</p>
</li>
<li><p>필요할 때만, 신중하게 사용해야 한다.</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="마무리">마무리</h2>
<p>실제로 참조에 대한 구조 고민 때문에 리플렉션까지 찾아본 기억이 있다. 강력한 기능으로 보이지만 현실적으로 내가 만들 코드의 수준에서 정말 써야만 하는 순간은 생각보다 찾기가 힘들다. 
만약 리플렉션을 사용해야 하는 순간이 찾아온다면 그것보다 먼저 다른 구조나 대안이 없는지 고민을 해보는게 먼저라고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 클래스 상속과 다이아몬드 문제 (diamond problem)]]></title>
            <link>https://velog.io/@lumos-vid/C-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%81%EC%86%8D%EA%B3%BC-%EB%8B%A4%EC%9D%B4%EC%95%84%EB%AA%AC%EB%93%9C-%EB%AC%B8%EC%A0%9C-diamond-problem</link>
            <guid>https://velog.io/@lumos-vid/C-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%81%EC%86%8D%EA%B3%BC-%EB%8B%A4%EC%9D%B4%EC%95%84%EB%AA%AC%EB%93%9C-%EB%AC%B8%EC%A0%9C-diamond-problem</guid>
            <pubDate>Mon, 28 Jul 2025 14:02:45 GMT</pubDate>
            <description><![CDATA[<h2 id="다이아몬드-문제diamond-problem">다이아몬드 문제(Diamond Problem)</h2>
<p>다이아몬드 문제(Diamond Problem)는 객체 지향 프로그래밍에서 클래스가 다중 상속(Multiple Inheritance)을 사용할 때 발생할 수 있는 구조적 충돌 문제를 의미한다.</p>
<p>일반적으로 다음과 같은 형태를 가진다.</p>
<ul>
<li><p>클래스 B와 C는 A를 상속받는다.</p>
</li>
<li><p>클래스 D는 B와 C를 모두 상속받는다.</p>
</li>
</ul>
<p>이러한 형태는 꼭짓점 형태가 마름모(Diamond)를 형성하기 때문에 다이아몬드 문제라고 불린다.</p>
<br>
<br>

<hr>
<h2 id="다이아몬드-문제가-발생하는-이유">다이아몬드 문제가 발생하는 이유</h2>
<p>다이아몬드 문제의 핵심 원인은 다음과 같다.</p>
<ul>
<li><p>클래스 D가 B와 C를 통해 클래스 A를 두 번 이상 상속받게 되는 문제이다.</p>
</li>
<li><p>클래스 A가 가진 메소드나 필드를 D가 접근할 때, B와 C의 경로 중 어느 경로를 통해 접근해야 하는지 알 수 없게 된다.</p>
</li>
<li><p>결과적으로 상속 구조상 모호성(ambiguity)이 발생한다.</p>
</li>
</ul>
<p>다중 상속이 가능한 일부 프로그래밍 언어(C++, Python 등)는 이러한 문제를 겪고 있다.</p>
<br>
<br>

<hr>
<h2 id="유니티c에서-다이아몬드-문제의-발생-가능성">유니티(C#)에서 다이아몬드 문제의 발생 가능성</h2>
<p>유니티(Unity)는 기본적으로 C#을 사용하고 있으며, C#은 클래스 다중 상속을 지원하지 않는다.</p>
<p>따라서, 전통적인 의미에서의 다이아몬드 문제가 직접적으로 나타나지 않는다.</p>
<p>하지만 다음과 같은 경우에는 비슷한 형태의 다이아몬드 구조가 발생할 수 있다.</p>
<ul>
<li><p>인터페이스(interface)를 활용한 다중 상속 구현</p>
</li>
<li><p>인터페이스와 추상 클래스(Abstract class)의 조합을 통한 구현</p>
</li>
</ul>
<p>다만 C#에서 인터페이스는 구현이 없으므로, 동일한 이름의 메소드가 여러 인터페이스에 존재하더라도 구현은 하나만 존재하므로, 모호성이 비교적 제한적이다.</p>
<br>
<br>

<hr>
<h2 id="c에서-다이아몬드-문제의-예시와-해결-방법">C#에서 다이아몬드 문제의 예시와 해결 방법</h2>
<pre><code class="language-cs">// 최상위 부모 인터페이스
interface IA
{
    void Method();
}

// IA를 상속받는 두 개의 인터페이스
interface IB : IA {}
interface IC : IA {}

// 두 인터페이스를 다중 상속받는 클래스
class D : IB, IC
{
    // IA의 Method 구현
    public void Method()
    {
        Console.WriteLine(&quot;클래스 D에서 구현한 Method()&quot;);
    }
}</code></pre>
<p>위 코드에서 클래스 D는 인터페이스 IB와 IC를 상속받고 있으며, 두 인터페이스 모두 IA를 상속받았다.</p>
<p>클래스 D는 Method()를 반드시 구현해야 하며, 이 과정에서 별도의 모호성은 발생하지 않는다.</p>
<p>따라서 C#의 인터페이스를 활용하면 다이아몬드 문제가 간접적으로 발생하지 않고 해결된다.</p>
<br>
<br>

<hr>
<h2 id="추상-클래스와-인터페이스를-동시에-사용할-때-주의점">추상 클래스와 인터페이스를 동시에 사용할 때 주의점</h2>
<p>추상 클래스(Abstract class)를 인터페이스와 혼용할 경우, 인터페이스 메소드와 추상 클래스 메소드 이름이 충돌하면 모호성이 발생할 수 있다.</p>
<pre><code class="language-cs">interface IA
{
    void Method();
}

abstract class BaseClass
{
    public abstract void Method();
}

class DerivedClass : BaseClass, IA
{
    // 추상 클래스와 인터페이스가 요구하는 메소드 하나로 해결
    public override void Method()
    {
        Console.WriteLine(&quot;DerivedClass에서 구현한 Method()&quot;);
    }
}</code></pre>
<ul>
<li><p>이 경우에도 충돌하는 메소드를 하나의 구현으로 해결 가능하다.</p>
</li>
<li><p>메소드 구현 시 인터페이스의 명시적 구현(Explicit Implementation)을 이용하여 명확성을 높일 수도 있다.</p>
</li>
</ul>
<br>

<pre><code class="language-cs">class DerivedClass : BaseClass, IA
{
    // BaseClass 구현
    public override void Method()
    {
        Console.WriteLine(&quot;BaseClass에서 상속받은 Method()&quot;);
    }

    // IA 인터페이스의 명시적 구현
    void IA.Method()
    {
        Console.WriteLine(&quot;IA 인터페이스의 Method()&quot;);
    }
}</code></pre>
<ul>
<li>명시적 구현을 하면 호출하는 메소드의 참조 타입(interface 또는 base class)에 따라 정확하게 원하는 메소드가 호출된다.</li>
</ul>
<br>
<br>

<hr>
<h2 id="유니티에서-권장하는-설계-방법">유니티에서 권장하는 설계 방법</h2>
<p>유니티는 일반적으로 다음과 같은 설계를 권장한다.</p>
<ul>
<li><p>인터페이스를 사용하여 공통적인 기능을 명세하고 클래스 간의 유연성을 높인다.</p>
</li>
<li><p>상속보다는 컴포넌트 기반 설계(Component-based) 를 적극 활용하여 게임 오브젝트(GameObject)에 여러 컴포넌트를 추가하는 방식을 사용한다.</p>
</li>
</ul>
<pre><code class="language-cs">public interface IDamageable
{
    void TakeDamage(int amount);
}

public class Enemy : MonoBehaviour, IDamageable
{
    public void TakeDamage(int amount)
    {
        // 피해 처리 로직
    }
}

public class Player : MonoBehaviour, IDamageable
{
    public void TakeDamage(int amount)
    {
        // 플레이어 피해 처리 로직
    }
}</code></pre>
<ul>
<li>유니티는 상속보다 <strong>조합(composition)</strong>과 컴포넌트 방식을 통해 다이아몬드 문제를 원천적으로 회피하도록 설계되었다.</li>
</ul>
<br>
<br>

<hr>
<h2 id="정리-및-결론">정리 및 결론</h2>
<p>C#과 유니티 환경에서 클래스 다중 상속은 원칙적으로 허용되지 않으므로, 전통적인 다이아몬드 문제가 발생하지 않는다. 다만, 인터페이스와 추상 클래스를 사용할 때 메소드명이 겹치는 경우가 있을 수 있으며, 이는 명시적 구현 등으로 해결 가능하다.</p>
<p>유니티에서는 인터페이스와 컴포넌트 기반 설계를 활용하면, 다이아몬드 문제를 사전에 예방할 수 있으므로, 이러한 방법을 적극 권장한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 드로우콜(Draw Call)과 최적화 ]]></title>
            <link>https://velog.io/@lumos-vid/Unity-%EB%93%9C%EB%A1%9C%EC%9A%B0%EC%BD%9C%EA%B3%BC-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@lumos-vid/Unity-%EB%93%9C%EB%A1%9C%EC%9A%B0%EC%BD%9C%EA%B3%BC-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Fri, 25 Jul 2025 14:42:28 GMT</pubDate>
            <description><![CDATA[<h2 id="드로우콜">드로우콜</h2>
<p>유니티에서 화면에 오브젝트를 그리기 위해 GPU에게 내리는 명령 하나하나를 <strong>Draw Call(드로우콜)</strong>이라고 부른다. GPU는 이 드로우콜을 받을 때마다 렌더링 작업을 수행하기 위해 준비 단계를 거치는데, 이 준비 단계가 바로 오버헤드를 만들어낸다.</p>
<p>즉, 드로우콜 수가 많을수록 렌더링 성능이 떨어진다.</p>
<br>
<br>

<hr>
<h2 id="draw-call이-성능에-미치는-영향">Draw Call이 성능에 미치는 영향</h2>
<p>유니티 엔진은 씬에 있는 모든 오브젝트를 각각 렌더링할 때마다 GPU에게 개별적인 드로우콜을 날리게 되는데, 이 과정은 크게 다음과 같은 과정을 거친다.</p>
<ul>
<li><p>CPU가 GPU에게 렌더링 명령 전달</p>
</li>
<li><p>GPU가 렌더링 준비 및 상태 전환 (셰이더 교체, 텍스처 바인딩 등)</p>
</li>
<li><p>실제 렌더링 작업 수행</p>
</li>
</ul>
<p>여기서 <strong>성능 병목(bottleneck)</strong>은 대부분 2단계에서 발생한다. GPU가 텍스처나 머티리얼, 셰이더를 바꿔가며 상태를 변경하는 작업이 생각보다 비용이 크기 때문이다.</p>
<p>정리하면 Draw Call 수가 늘어나면 CPU와 GPU가 상태 변경으로 시간을 많이 보내게 되어 FPS가 떨어진다.</p>
<br>
<br>

<hr>
<h2 id="draw-call이-증가하는-대표적인-원인들">Draw Call이 증가하는 대표적인 원인들</h2>
<p><strong>1. 서로 다른 머티리얼 사용</strong>
오브젝트마다 개별적인 머티리얼을 사용하면 유니티는 각각 별도의 드로우콜을 발생시킨다.</p>
<p>같은 셰이더, 같은 텍스처라도 머티리얼 인스턴스가 다르면 드로우콜은 별개로 계산됨.</p>
<p>특히 실수로 같은 텍스처를 쓰는 머티리얼을 여러 개 만들면 쓸데없이 드로우콜이 폭발적으로 증가함.</p>
<br>


<p><strong>2. 투명(Transparent) 오브젝트</strong>
투명한 오브젝트(알파 블렌딩 사용)는 유니티가 뒷면과 앞면 렌더링을 여러 번 진행하면서 드로우콜 수가 늘어난다.</p>
<br>


<p><strong>3. 광원(Light) 및 그림자(Shadow)</strong>
라이트가 많아지면 라이트당 추가적인 드로우콜이 생긴다. 특히 실시간 그림자를 쓰면 그림자 패스가 별도의 렌더링 단계를 거치므로 드로우콜 수가 급격히 증가한다.</p>
<br>


<p><strong>4. UI Canvas의 분할</strong>
Canvas를 여러 개로 나누거나, UI가 복잡할수록 드로우콜이 늘어난다.</p>
<br>
<br>

<hr>
<h2 id="draw-call-최적화-방법">Draw Call 최적화 방법</h2>
<p>드로우콜 최적화의 핵심은 상태 변화를 최소화하는 것이다.</p>
<p><strong>1. 머티리얼 합치기 (Material Atlasing)</strong>
여러 오브젝트가 같은 머티리얼을 쓰면 한 번의 드로우콜로 묶어서 처리할 수 있다.</p>
<ul>
<li><p>텍스처 아틀라싱(Texture Atlasing): 여러 텍스처를 하나의 큰 텍스처로 묶어서 사용한다.</p>
</li>
<li><p>머티리얼 공유(Material Sharing): 중복되는 머티리얼을 최대한 공유한다.</p>
</li>
</ul>
<br>


<p>** 2. Static Batching**
유니티가 정적인 오브젝트를 미리 한 덩어리로 합쳐서 렌더링.</p>
<p>씬에 배치된 뒤 이동하지 않는 정적인 오브젝트는 Inspector에서 Static 체크박스를 체크하면 유니티가 자동으로 Static Batching을 적용한다.</p>
<p>드로우콜 수를 획기적으로 낮추지만, 메모리 사용량은 증가할 수 있다.</p>
<br>


<p><strong>3. Dynamic Batching</strong>
동적인 작은 오브젝트를 실시간으로 합쳐 렌더링.</p>
<p>유니티가 자동으로 동작하지만 제약 조건이 있음:</p>
<p>Vertex 수가 제한적임 (일반적으로 300 버텍스 이하)</p>
<p>같은 셰이더와 같은 머티리얼을 사용해야 함</p>
<p>성능 향상이 확실하지만, 동적으로 합치는데 CPU 오버헤드가 있으므로 잘 활용해야 함</p>
<br>

<p><strong>4. GPU Instancing</strong>
동일한 메쉬와 머티리얼을 사용하는 많은 오브젝트를 한번의 명령으로 GPU에게 전달해 대량 렌더링 처리.</p>
<p>특히 잔디, 나무, 파티클 등 같은 오브젝트가 반복되는 환경에서 효과적이다.</p>
<p>유니티에서 머티리얼 설정에서 Enable GPU Instancing 옵션을 활성화하면 적용된다.</p>
<br>

<p><strong>5. 라이트맵 사용 (Baked Lighting)</strong>
라이트맵을 구워서(베이크) 미리 계산된 빛 정보를 텍스처로 만들어 사용하면 실시간 조명이 줄어들어 드로우콜이 감소한다.</p>
<p>그림자, 광원의 동적 렌더링이 사라져 성능 향상이 크다.</p>
<br>


<p><strong>6. UI 최적화</strong>
UI 캔버스를 합쳐서 렌더링하면 드로우콜 감소.</p>
<p>Canvas를 잘 그룹화하고 불필요한 Canvas 분리를 피해야 함.</p>
<p>텍스처 아틀라싱을 UI에서도 적극적으로 사용한다.</p>
<br>
<br>

<hr>
<h2 id="드로우콜-최적화-시-고려-사항">드로우콜 최적화 시 고려 사항</h2>
<p>무조건 드로우콜을 낮추는 것이 정답은 아니다.</p>
<p>드로우콜을 너무 극단적으로 줄이면 메모리 사용량이 커지거나 GPU에 과부하가 걸릴 수도 있다.</p>
<p>프로젝트 특성에 맞게 적절한 균형을 잡아 최적화하는 것이 가장 중요하다.</p>
<p>프로파일러(Profiler)를 활용해 최적화 전후 성능을 분석하며 접근하는 것이 좋다.</p>
<br>
<br>

<hr>
<h2 id="마무리">마무리</h2>
<p>드로우콜 최적화의 핵심은 결국 GPU에게 내리는 명령의 횟수를 최소화하는 것이다. 이를 위해 머티리얼 공유, 아틀라싱, Static/Dynamic Batching, GPU Instancing 등을 적극 활용하면 최적화를 효율적으로 수행할 수 있다.</p>
<p>프로젝트 규모가 커질수록 최적화는 필수이며, 드로우콜 관리는 그 첫 번째 단계라고 할 수 있다. 드로우콜을 잘 관리하면 성능과 품질을 동시에 챙길 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 박싱, 언박싱 정리]]></title>
            <link>https://velog.io/@lumos-vid/C-%EB%B0%95%EC%8B%B1-%EC%96%B8%EB%B0%95%EC%8B%B1-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/C-%EB%B0%95%EC%8B%B1-%EC%96%B8%EB%B0%95%EC%8B%B1-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 24 Jul 2025 14:27:58 GMT</pubDate>
            <description><![CDATA[<h2 id="박싱boxing">박싱(Boxing)</h2>
<p>박싱이란 값 타입(Value Type) 데이터를 <strong>참조 타입(Reference Type)</strong>으로 변환하는 과정이다.
쉽게 말해, 스택(Stack)에 존재하는 값을 힙(Heap)에 복사해서, 객체(Object)처럼 사용하는 것.</p>
<br>

<p><strong>예시</strong></p>
<pre><code class="language-cs">int num = 123;           // 값 타입 (스택에 저장)
object obj = num;        // 박싱 발생 (힙에 int 값을 복사, obj는 참조 타입)</code></pre>
<br>

<p>이 코드를 실행하면, 다음과 같은 일이 발생한다.</p>
<ul>
<li><p>num이라는 <strong>int 값(값 타입)</strong>이 있다.</p>
</li>
<li><p>object는 참조 타입이기 때문에, 값을 직접 가질 수 없다.</p>
</li>
<li><p>이 때, CLR(Common Language Runtime)이 num의 값을 힙 영역에 새로 복사해서, 그 참조를 obj에 저장한다.</p>
</li>
<li><p>결국 obj는 int 값을 담은 객체를 가리키게 된다.</p>
</li>
</ul>
<br>


<p><strong>동작 구조</strong></p>
<ul>
<li><p>값 타입(예: int, double, struct 등)을 object, 인터페이스 타입 등에 할당하거나, 메서드에 파라미터로 넘길 때 박싱 발생.</p>
</li>
<li><p>박싱이 일어나면 값이 힙에 새로운 객체로 복사됨.</p>
</li>
<li><p>이 과정에서 런타임 오버헤드(추가적인 메모리 사용 및 성능 저하)가 발생할 수 있음.</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="언박싱unboxing">언박싱(Unboxing)</h2>
<p>언박싱은 <strong>참조 타입(object 등)</strong>에 저장된 값을 원래의 값 타입으로 다시 꺼내오는 과정이다.
즉, 힙에 저장된 값을 다시 스택에 복사하는 것.</p>
<p><strong>예시</strong></p>
<pre><code class="language-cs">object obj = 123;           // 박싱
int num = (int)obj;         // 언박싱</code></pre>
<ul>
<li><p>obj는 힙에 저장된 int 값을 가리킨다.</p>
</li>
<li><p>(int)obj를 통해 언박싱을 수행하면, 힙에 있는 int 값이 다시 스택에 복사된다.</p>
</li>
<li><p>반드시 <strong>명시적 형변환(캐스팅)</strong>이 필요하다. (암시적으로 불가능)</p>
</li>
</ul>
<br>

<p><strong>동작 구조</strong></p>
<ul>
<li><p>언박싱 시, object(혹은 인터페이스)의 참조가 원래의 값 타입인지 검사한다.</p>
</li>
<li><p>만약 타입이 다르면 InvalidCastException이 발생한다.</p>
</li>
<li><p>타입이 맞으면, 힙에 있던 값을 스택에 복사.</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="주의사항">주의사항</h2>
<ul>
<li><strong>성능이슈</strong>
박싱/언박싱은 암묵적으로 힙 메모리 할당과 복사, 그리고 타입 체크가 이루어져 성능 저하의 원인이 된다.
박싱이 빈번히 발생하는 코드(예: 박싱된 값의 반복적 사용, 컬렉션에서 값 타입을 object로 다루는 경우 등)는 최적화 필요.</li>
</ul>
<br>

<ul>
<li><strong>자주 발생하는 상황</strong>
Object 타입 컬렉션 사용 - C# 2.0 이전에는 List 등 컬렉션이 object 기반이었기 때문에 값 타입을 넣으면 항상 박싱/언박싱 발생.
예시: ArrayList list = new ArrayList(); list.Add(1);
또 값 타입이 인터페이스로 변환될 때 박싱이 발생할 수 있고 메서드 파라미터가 object일 때, 값 타입 인자를 object 파라미터로 전달 시 박싱한다.</li>
</ul>
<br>

<ul>
<li><strong>줄이는 방법</strong>
제네릭은 컴파일 타임에 타입이 결정되므로, 박싱/언박싱이 발생하지 않는다.
또 object, 인터페이스 대신 구체적 값 타입 사용하면 줄일 수 있다.</li>
</ul>
<br>

<ul>
<li><strong>예외</strong>
박싱된 값을 잘못된 타입으로 언박싱 시
런타임 예외(InvalidCastException)가 발생한다.</li>
</ul>
<pre><code class="language-cs">object obj = 123;
double d = (double)obj;    // 예외 발생</code></pre>
<br>
<br>

<p>가급적 박싱/언박싱을 피하기 위해 제네릭, 구체적 타입 사용을 권장한다.
내부 동작, 성능 이슈, 예외 발생 케이스까지 잘 이해해야 효율적이고 안정적인 C# 코드를 작성할 수 있다.</p>
<br>
<br>

]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] Coroutine(코루틴) 정리]]></title>
            <link>https://velog.io/@lumos-vid/Unity-Coroutine%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/Unity-Coroutine%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 23 Jul 2025 14:08:43 GMT</pubDate>
            <description><![CDATA[<h2 id="coroutine코루틴">Coroutine(코루틴)</h2>
<p>코루틴은 유니티에서 시간의 흐름에 따라 작업을 분할하여 처리할 수 있는 기능이다.
한 번에 끝나지 않고, 여러 프레임에 걸쳐서 코드를 조금씩 실행할 수 있게 해준다.
이걸 활용하면, 예를 들어 몇 초 대기 후에 동작을 실행하거나, 특정 조건이 충족될 때까지 반복 작업을 쉽게 구현할 수 있다.</p>
<p>유니티에서 시간 지연이나 반복 작업을 구현할 때,
만약 코루틴이 없다면 Update()에서 타이머 변수를 직접 관리하면서 코드를 작성해야 한다.</p>
<p>하지만 코루틴을 사용하면 복잡한 타이머 코드 없이,
마치 스크립트처럼 &quot;A 하고, 3초 기다렸다가 B 하고, 다시 C 실행&quot; 이런 식으로 직관적으로 작성할 수 있다.</p>
<br>
<br>

<hr>
<h2 id="사용방법">사용방법</h2>
<ul>
<li><strong>코루틴 함수 만들기</strong>
코루틴 함수는 반드시 <strong>IEnumerator</strong>를 반환해야 한다.</li>
</ul>
<pre><code class="language-cs">IEnumerator ExampleCoroutine()
{
    Debug.Log(&quot;코루틴 시작&quot;);

    yield return new WaitForSeconds(2f); // 2초 대기

    Debug.Log(&quot;2초 후 실행&quot;);

    yield return null; // 한 프레임 대기

    Debug.Log(&quot;한 프레임 후 실행&quot;);

    yield break; // 명시적으로 종료 (생략 가능)
}</code></pre>
<br>


<ul>
<li><strong>코루틴 시작과 종료</strong></li>
<li><em>시작*</em>: StartCoroutine(코루틴함수명());</li>
<li><em>종료*</em>: StopCoroutine(코루틴함수명()); 또는 StopAllCoroutines();</li>
</ul>
<pre><code class="language-cs">void Start()
{
    StartCoroutine(ExampleCoroutine());
}</code></pre>
<br>

<ul>
<li><strong>사용 예시</strong></li>
</ul>
<pre><code class="language-cs">IEnumerator RepeatAction()
{
    while(true)
    {
        Debug.Log(&quot;3초마다 반복&quot;);
        yield return new WaitForSeconds(3f);
    }
}</code></pre>
<pre><code class="language-cs">IEnumerator WaitForPlayerDead()
{
    yield return new WaitUntil(() =&gt; player.hp &lt;= 0);
    Debug.Log(&quot;플레이어 사망 감지&quot;);
}</code></pre>
<br>
<br>

<hr>
<h2 id="yield-return">yield return</h2>
<p>yield return은 코루틴 함수 내부에서 코드 실행을 일시정지하고, 나중에 다시 이어서 실행하게 만드는 키워드다.
쉽게 말해, &quot;여기서 잠깐 멈췄다가, 조건이 충족되면 그 다음 줄부터 다시 실행하겠다&quot; 라고 할 수 있다.</p>
<p>C#의 일반 메서드는 return을 만나면 함수가 완전히 끝나지만,
코루틴에서 yield return은 일종의 &quot;임시 저장 지점(checkpoint)&quot;을 만든다.
다음 프레임, 혹은 어떤 조건이 만족될 때, 거기서부터 이어서 실행하는 식이다.</p>
<p>코루틴 함수(IEnumerator)는 상태를 저장하는 이터레이터다.
yield return이 호출될 때마다, 유니티는 코루틴의 현재 상태를 저장하고 실행을 잠시 멈춘다. 그리고 조건이 충족(예: 시간이 지남, 특정 조건 만족 등)되면 저장된 상태에서부터 다시 함수가 재개된다.</p>
<p>이 덕분에, 한 번에 실행되지 않고, 여러 프레임에 걸쳐서 코드를 실행할 수 있다.</p>
<br>

<ul>
<li><p><strong>yield return null</strong>
→ 한 프레임 쉬고 다음 코드 실행</p>
</li>
<li><p><strong>yield return new WaitForSeconds(float time)</strong>
→ 지정한 시간(초)만큼 대기</p>
</li>
<li><p><strong>yield return new WaitForEndOfFrame()</strong>
→ 해당 프레임이 모두 끝난 후 실행</p>
</li>
<li><p><strong>yield return new WaitUntil(() =&gt; 조건식)</strong>
→ 특정 조건이 true가 될 때까지 대기</p>
</li>
<li><p><strong>yield return new WaitWhile(() =&gt; 조건식)</strong>
→ 특정 조건이 false가 될 때까지 대기</p>
</li>
<li><p><strong>yield break</strong>
→ 코루틴 즉시 종료</p>
</li>
</ul>
<br>

<p><strong>예시</strong></p>
<pre><code class="language-cs">IEnumerator Example()
{
    Debug.Log(&quot;A&quot;);             // 즉시 실행
    yield return new WaitForSeconds(2f); // 2초 대기
    Debug.Log(&quot;B&quot;);             // 2초 후 실행
    yield return null;          // 한 프레임 쉬기
    Debug.Log(&quot;C&quot;);             // 또 그 다음 프레임에 실행
}</code></pre>
<br>
<br>

<hr>
<h2 id="주의점">주의점</h2>
<p>코루틴은 메인 스레드(메인 루프)에서 돌아간다.
단순히 실행 순서를 쪼개서(일시정지-재개 반복) 여러 프레임에 걸쳐 나눠 실행할 뿐,
백그라운드에서 따로 실행되는 게 아니다.
따라서, 동시성 문제나 스레드 세이프티(thread safety) 문제는 발생하지 않는다.
하지만, <strong>무거운 작업(복잡한 계산, 대용량 파일 I/O, 네트워크 통신 등)</strong>을 코루틴에 넣는다고 해서 렉 없이 자동으로 빨라지거나 비동기 처리가 되는 게 아니다.
이런 경우에는 실제로 비동기 처리를 해야한다.</p>
<p>또 <strong>코루틴은 오브젝트의 라이프사이클에 의존한다.</strong> 코루틴을 실행한 MonoBehaviour가 비활성화(Disable)되거나, 파괴(Destroy)되면 코루틴도 강제로 중단된다.
특정 상황에서 코루틴이 예상치 못하게 중단되는 경우가 있으니
중요한 작업은 중단되었을 때 대처 로직을 넣어주는 것이 안전하다.</p>
<p><strong>yield return의 시간 계열 대기는 Time.timeScale 영향을 받는다</strong>
yield return new WaitForSeconds(3f)처럼 대기하는 경우
게임의 Time.timeScale 값이 바뀌면(슬로우, 일시정지 등) 실제 대기 시간도 달라진다. 실시간(Real Time) 기준 대기가 필요하다면
<strong>WaitForSecondsRealtime</strong>을 써야 한다.</p>
<p>그리고 가장 흔하게 하는 실수는 코루틴의 중복 호출이다.
같은 코루틴을 반복적으로 StartCoroutine하면 여러 개의 코루틴이 동시에 실행될 수 있다. (예: 버튼 클릭 시마다 StartCoroutine 호출 → 여러 번 동작)
보통은 Coroutine 타입 변수에 코루틴을 저장해서 필요할 때 중복 실행을 막거나, 기존 코루틴을 중단(StopCoroutine)하는 방식으로 관리한다.</p>
<p>즉, 코루틴은 강력하지만, 내부 동작 원리와 라이프사이클, 한계와 장단점을 명확히 이해해야 버그 없는 안정적인 게임 로직을 구현할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 접근제한자와 프로퍼티, 캡슐화]]></title>
            <link>https://velog.io/@lumos-vid/C-%EC%A0%91%EA%B7%BC%EC%A0%9C%ED%95%9C%EC%9E%90%EC%99%80-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%BA%A1%EC%8A%90%ED%99%94</link>
            <guid>https://velog.io/@lumos-vid/C-%EC%A0%91%EA%B7%BC%EC%A0%9C%ED%95%9C%EC%9E%90%EC%99%80-%ED%94%84%EB%A1%9C%ED%8D%BC%ED%8B%B0-%EC%BA%A1%EC%8A%90%ED%99%94</guid>
            <pubDate>Tue, 22 Jul 2025 14:43:42 GMT</pubDate>
            <description><![CDATA[<h2 id="접근-제한자access-modifier">접근 제한자(Access Modifier)</h2>
<p>접근 제한자는 클래스, 변수, 함수(메서드) 등 객체 지향 프로그래밍 요소의 접근 가능 범위를 지정하는 키워드이다.
코드의 특정 멤버가 클래스 내부, 외부, 자식 클래스 등에서 어떤 범위로 접근될 수 있는지 명확하게 제한 한다.</p>
<br>

<ul>
<li><strong>public</strong>
어디서든 접근이 가능함을 의미<pre><code class="language-cs">public int health;</code></pre>
</li>
</ul>
<br>

<ul>
<li><strong>private</strong>
선언된 클래스 내부에서만 접근이 가능<pre><code class="language-cs">private int score;</code></pre>
</li>
</ul>
<br>

<ul>
<li><strong>protected</strong>
선언된 클래스 및 이를 상속한 자식 클래스에서만 접근 가능<pre><code class="language-cs">protected float speed;</code></pre>
</li>
</ul>
<br>

<ul>
<li><strong>internal</strong>
동일한 어셈블리(프로젝트) 내에서만 접근 가능, 유니티에서는 잘 사용되지 않는다.</li>
</ul>
<br>
<br>

<hr>
<h2 id="장점">장점</h2>
<p>캡슐화를 위한 기본 도구이며, 외부 접근을 제한하기 무분별한 외부 참조나 실수를 방지 할 수 있고, 또 다른 사람이 코드를 읽을때 ‘외부에서 사용되는구나’ 하는 가독성 측면에서 장점이 있다. 실제로 타인의 코드를 볼때 전부다 public 으로 되어있으면 시작부터 굉장한 스트레스를 받고 시작하는 경우가 더러 있었다.</p>
<br>
<br>

<hr>
<h2 id="캡슐화encapsulation">캡슐화(Encapsulation)</h2>
<p>캡슐화는 객체 지향 프로그래밍의 4대 특징 중 하나로, 데이터와 그 데이터를 처리하는 메서드를 하나로 묶고,
외부로부터 데이터를 보호(은닉)하는 개념이다.
즉, 내부 구현 내용을 숨기고, 외부에서는 지정된 인터페이스(메서드, 프로퍼티)만을 통해 접근하도록 제한한다.</p>
<p>캡슐화의 목적으로는 주로 다음과 같다.</p>
<ul>
<li><strong>데이터 보호</strong>: 객체 내부 데이터의 직접적인 접근을 막음으로써 잘못된 사용이나 변경으로부터 보호</li>
<li><strong>유지보수성 향상</strong>: 내부 구현을 변경하더라도, 외부에는 영향을 주지 않음</li>
<li><strong>코드의 신뢰성 증대</strong>: 데이터 무결성을 유지</li>
</ul>
<br>
<br>

<hr>
<h2 id="구현방법">구현방법</h2>
<p>주로 private 접근 제한자를 사용하여 변수(필드)를 숨기고,
외부에서 값을 읽거나 쓸 필요가 있을 때는 public 메서드(getter/setter) 또는 <strong>프로퍼티(property)</strong> 를 사용한다.</p>
<pre><code class="language-cs">public class Player
{
    private int health; // 직접 접근 불가

    // Getter, Setter 메서드
    public int GetHealth()
    {
        return health;
    }

    public void SetHealth(int value)
    {
        // 유효성 검사 등의 로직 추가 가능
        health = value;
    }

    // 또는 C# 프로퍼티
    public int Health
    {
        get { return health; }
        set { health = value; }
    }
}</code></pre>
<br>

<ul>
<li><p><strong>유니티에서의 활용</strong>
유니티에서는 public 필드로 선언하면 Inspector 창에서 값을 직접 수정할 수 있지만
무분별한 public 사용은 코드의 안전성을 해칠 수 있으므로,
[SerializeField] private 형태로 필드를 선언하고,
필요하다면 public 프로퍼티를 통해 값을 노출하는 것이 권장된다.</p>
<pre><code class="language-cs">public class Enemy : MonoBehaviour
{
  [SerializeField]
  private int hp; // Inspector 노출, 외부 직접 접근 불가

  public int Hp // 외부에서 읽기 전용
  {
      get { return hp; }
  }
}</code></pre>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="프로퍼티property">프로퍼티(Property)</h2>
<p><strong>프로퍼티(Property)</strong>는 객체 지향 프로그래밍(C#)에서 필드의 값을 안전하게 읽거나 쓸 수 있도록 제공하는 특수한 메서드 집합이다.
즉, 필드(변수)에 직접 접근하지 않고, get/set 접근자를 통해 간접적으로 값에 접근할 수 있는 기능이다</p>
<br>

<p><strong>목적</strong></p>
<ul>
<li><p><strong>캡슐화 강화</strong>: 필드에 대한 직접적인 접근을 막고, 값의 읽기/쓰기를 통제할 수 있습다.</p>
</li>
<li><p><strong>데이터 무결성 유지</strong>: 값을 변경할 때 유효성 검사, 로깅, 트리거 등의 부가적인 로직을 삽입할 수 있다.</p>
</li>
<li><p><strong>외부와의 인터페이스 제공</strong>: 외부에서 필드를 사용하는 방식은 단순하지만, 내부적으로는 세밀한 제어가 가능하다.</p>
</li>
</ul>
<br>
<br>

<hr>
<h2 id="구현방법-1">구현방법</h2>
<ul>
<li><strong>get 접근자</strong>: 프로퍼티의 값을 가져올 때 실행되는 코드 블록</li>
<li><strong>set 접근자</strong>: 프로퍼티에 값을 대입할 때 실행되는 코드 블록</li>
</ul>
<p>value는 set 접근자 내부에서 대입되는 값을 나타낸다.</p>
<pre><code class="language-cs">private int age; // private 필드

public int Age // 프로퍼티
{
    get { return age; }        // 값을 읽을 때 실행
    set { age = value; }       // 값을 쓸 때 실행
}</code></pre>
<br>
<br>

<p><strong>읽기 전용/쓰기 전용</strong></p>
<ul>
<li><strong>읽기 전용 프로퍼티</strong>: set 접근자를 생략</li>
<li><strong>쓰기 전용 프로퍼티</strong>: get 접근자를 생략</li>
</ul>
<pre><code class="language-cs">public int Age
{
    get { return age; }
}

// 쓰기 전용
public int Age
{
    set { age = value; }
}</code></pre>
<br>
<br>

<p><strong>자동구현 프로퍼티</strong>
간단히 값을 저장하고 반환하는 경우, C#에서는 자동 구현 프로퍼티 문법을 제공한다.</p>
<pre><code class="language-cs">public int Age { get; set; }</code></pre>
<br>
<br>

<p><strong>활용</strong>
프로퍼티를 사용하면, 값의 변경에 제약을 두거나 안전하게 값을 변경 할 수 있다.</p>
<pre><code class="language-cs">private int hp;

public int Hp
{
    get { return hp; }
    set
    {
        if (value &lt; 0)
            hp = 0;       // 0 이하로 내려가지 않도록 제약
        else
            hp = value;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 클래스와 객체 정리]]></title>
            <link>https://velog.io/@lumos-vid/C-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EA%B0%9D%EC%B2%B4-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/C-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%99%80-%EA%B0%9D%EC%B2%B4-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Mon, 21 Jul 2025 11:38:14 GMT</pubDate>
            <description><![CDATA[<h2 id="클래스">클래스</h2>
<p>클래스는 데이터와 그 데이터를 조작하는 기능(메서드)을 <strong>하나로 묶어 놓은 사용자 정의 참조 타입</strong>이다.
클래스는 객체를 만들기 위한 설계도이며, 객체가 가져야 할 <strong>속성(필드, 프로퍼티)</strong>과 <strong>행동(메서드)</strong>을 정의한다.</p>
<p>클래스 자체는 메모리에 할당되지 않는다.
클래스 정의만으로는 아무런 실체가 존재하지 않는다.
실제로 동작하거나 값을 가지기 위해서는 <strong>객체(인스턴스)</strong>로 생성되어야 한다.</p>
<pre><code class="language-cs">public class Book
{
    // 필드(속성)
    public string title;        // 참조 타입 필드
    public int pageCount;       // 값 타입 필드

    // 메서드(행동)
    public void Read()
    {
        Console.WriteLine(&quot;Reading the book...&quot;);
    }
}</code></pre>
<br>
<br>

<hr>
<h2 id="객체">객체</h2>
<p>객체란 <strong>클래스를 기반으로 실제 메모리 상에 생성된 실체(Instance)</strong>를 의미한다.
객체는 클래스에 정의된 모든 속성과 행동을 실제로 가지며, 각 객체는 서로 독립적으로 동작한다.</p>
<pre><code class="language-cs">Book b1 = new Book();
b1.title = &quot;C# Programming&quot;;
b1.pageCount = 300;

Book b2 = new Book();
b2.title = &quot;OOP Principles&quot;;
b2.pageCount = 250;

b1.Read();   // 출력: Reading the book...</code></pre>
<p>위의 예시에서 b1과 b2는 Book 클래스의 서로 다른 두 객체이다.
각각의 객체는 독립적인 값을 가지며, 자신만의 상태를 유지한다. C# 에서는 new () 해주게 되면 객체를 생성하는것과 같고 이때 메모리에 할당된다.</p>
<br>
<br>

<hr>
<h2 id="필드field와-데이터-타입">필드(Field)와 데이터 타입</h2>
<p>클래스의 필드는 객체가 가지는 <strong>데이터(속성)</strong>를 나타낸다.
필드는 <strong>값 타입(Value Type)</strong>과 <strong>참조 타입(Reference Type)</strong>으로 구분된다.</p>
<ul>
<li><strong>값 타입 필드</strong>
값 타입은 실제 데이터를 그대로 저장한다.
대표적으로 int, float, double, bool, char, 구조체(struct) 등이 있다.
값 타입 필드는 객체가 생성될 때, 그 객체의 메모리 공간 안에 실제 데이터가 저장된다. 값타입은 복사 시 값 자체가 복사된다. 서로 다른 객체의 값 타입 필드는 완전히 독립적이다.</li>
</ul>
<pre><code class="language-cs">public class Point
{
    public int x;  // 값 타입 필드
    public int y;  // 값 타입 필드
}

Point p1 = new Point();
p1.x = 10;
p1.y = 20;</code></pre>
<br>

<ul>
<li><strong>참조 타입 필드</strong>
참조 타입은 실제 데이터가 아닌, 데이터가 저장된 위치(주소)를 저장한다.
대표적으로 클래스, 인터페이스, 델리게이트 등이 있다. 복사 시 주소값(참조)이 복사된다. 여러 객체가 같은 참조 타입 데이터(예: 배열, 객체 등)를 참조할 수 있다.</li>
</ul>
<pre><code>public class Student
{
    public string name;    // 참조 타입 필드
    public int age;        // 값 타입 필드
}

Student s1 = new Student();
s1.name = &quot;Alice&quot;;  // &quot;Alice&quot;라는 문자열 데이터의 참조(주소)가 저장됨
s1.age = 21;        // 값 21이 저장됨</code></pre><p>name 필드는 string(참조 타입)이므로, 문자열 데이터 자체가 아니라 해당 데이터의 위치 정보(참조)가 저장된다.</p>
<p>age 필드는 값 타입이므로, 숫자 21이 객체 내에 직접 저장된다.</p>
<br>
<br>

<hr>
<h2 id="값타입은-객체인가">값타입은 객체인가?</h2>
<ul>
<li><strong>NET에서 &quot;객체&quot;의 의미</strong>
C#에서 &quot;객체(object)&quot;란, <strong>참조 타입(reference type)</strong>을 기반으로 new 키워드 등으로 동적으로 생성된 인스턴스를 지칭하는 것이 일반적이다.
예를 들어, 클래스(class), 배열(array), 문자열(string), 델리게이트(delegate), 인터페이스(interface) 등은 모두 참조 타입이다.
참조 타입의 객체는 힙(Heap) 메모리에 저장되며, 변수에는 객체의 &quot;주소(참조)&quot;가 할당된다.
값 타입은 실제 데이터 값이 직접 저장되는 타입이므로 변수의 메모리 공간에 직접 값이 저장된다.
일반적으로 값 타입은 별도의 &quot;객체&quot;를 생성하지 않고 변수 자체가 곧 데이터이다.</li>
</ul>
<br>

<ul>
<li><p><strong>&quot;객체&quot;라는 용어의 혼란</strong>
.NET에서 모든 타입은 System.Object로부터 파생된다.
따라서, 값 타입 역시 간접적으로는 Object를 상속받는다.
하지만, &quot;객체&quot;라는 용어를 쓸 때,
참조 타입은 new로 생성된 명확한 객체(인스턴스)를 의미한다.
값 타입은 new 없이 변수에 직접 값이 할당되며, 이것을 &quot;객체&quot;라 부르지 않는 것이 일반적이다.</p>
</li>
<li><p><strong>박싱(Boxing)과 언박싱(Unboxing)</strong>
값 타입도 <strong>박싱(Boxing)</strong>이 일어날 때에는 임시적으로 객체로 취급된다.
박싱이란, 값 타입의 값을 힙에 저장하여 참조 타입의 객체처럼 사용하는 과정이다.</p>
</li>
</ul>
<pre><code class="language-cs">int x = 100;
object obj = x; // x가 박싱되어 object 객체로 변환됨</code></pre>
<p>즉 값 타입 변수는 박싱될때 객체로 사용되고 흔히 코딩하는과정에서 값타입 변수들은 마치 &#39;객체&#39; 처럼 쓰인다고 보는게 맞다고 생각하면 될것이다.</p>
<br>
<br>



<hr>
<h2 id="클래스와-구조체의-차이">클래스와 구조체의 차이</h2>
<p>클래스와 유사한 개념으로 <strong>구조체(struct)</strong>가 있다.
구조체는 값 타입이며, 클래스는 참조 타입이다.
따라서, 구조체의 필드들은 복사될 때 값이 통째로 복사된다.
쉽게 볼수 있는 예시는 Vector 등이 있다.</p>
<br>
<br>

<hr>
<h2 id="마무리">마무리</h2>
<p>클래스는 객체를 만들기 위한 설계도이며, 객체는 메모리상에 존재하는 실체이다. 클래스의 필드는 값 타입(실제 데이터 저장)과 참조 타입(데이터 위치 저장)으로 나뉘고 값 타입 필드는 객체의 메모리에 직접 저장되며, 참조 타입 필드는 해당 데이터의 참조(주소)를 저장한다. 값 타입은 복사 시 값 자체가, 참조 타입은 주소(참조)가 복사된다.</p>
<ul>
<li>클래스: 참조 타입, 설계도</li>
<li>객체: 클래스의 인스턴스, 실체</li>
<li>필드: 객체가 가지는 속성, 값 타입/참조 타입</li>
<li>값 타입 필드: 실제 데이터가 객체에 저장</li>
<li>참조 타입 필드: 데이터의 위치(참조)가 저장</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 콜백(Callback)과 대리자(delegate) 정리]]></title>
            <link>https://velog.io/@lumos-vid/C-%EC%BD%9C%EB%B0%B1Callback%EA%B3%BC-%EB%8C%80%EB%A6%AC%EC%9E%90delegate-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/C-%EC%BD%9C%EB%B0%B1Callback%EA%B3%BC-%EB%8C%80%EB%A6%AC%EC%9E%90delegate-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Fri, 18 Jul 2025 14:25:09 GMT</pubDate>
            <description><![CDATA[<h2 id="대리자delegate">대리자(delegate)</h2>
<p>대리자(delegate)는 함수를 참조할 수 있는 타입이다. 즉, 대리자는 특정 시그니처(매개변수와 반환형)를 가진 함수를 변수처럼 저장하고, 나중에 실행할 수 있도록 한다.
기본적인 대리자는 아래와 같이 선언한다.</p>
<pre><code class="language-cs">public delegate int CalcDelegate(int x, int y);</code></pre>
<p>이 선언은 두 개의 int를 입력받아 int를 반환하는 함수를 저장할 수 있는 타입인 CalcDelegate를 정의한다.</p>
<br>
<br>

<hr>
<h2 id="사용이유">사용이유</h2>
<p>일반적으로 함수는 직접 호출해야 하지만, 대리자를 사용하면 함수를 변수에 저장하거나, 다른 함수의 인자로 전달하여 실행 시점을 동적으로 조정할 수 있다.
이로 인해 코드의 유연성과 재사용성이 높아진다.
특히 이벤트 기반 프로그래밍이나 라이브러리 개발에서 자주 사용된다.</p>
<br>
<br>

<hr>
<h2 id="콜백callback">콜백(callback)</h2>
<p>콜백(callback)은 특정 시점에 실행되도록 사전에 등록해두는 함수를 의미한다.
일반적으로 콜백은 작업이 완료되거나, 이벤트가 발생했을 때 호출된다.
C#에서 콜백은 주로 대리자를 통해 구현된다.</p>
<br>
<br>

<hr>
<h2 id="대리자와-콜백의-관계">대리자와 콜백의 관계</h2>
<ul>
<li><p><strong>대리자(delegate)</strong>: 함수를 저장할 수 있는 변수(타입)이다.</p>
</li>
<li><p><strong>콜백(callback)</strong>: 특정 시점에 호출되도록 전달된 함수이다.</p>
</li>
</ul>
<p>콜백을 구현하려면, 함수를 전달해야 하므로 대리자가 필요하다.</p>
<br>
<br>

<hr>
<h2 id="예시-코드">예시 코드</h2>
<ul>
<li><strong>대리자 선언과 사용</strong></li>
</ul>
<pre><code class="language-cs">public delegate void MessageDelegate(string message);

void PrintMessage(string message) {
    Debug.Log(message);
}

MessageDelegate del = PrintMessage;
del(&quot;Hello, Delegate!&quot;);</code></pre>
<ul>
<li><strong>콜백 함수로 전달</strong></li>
</ul>
<pre><code class="language-cs">void DoSomething(MessageDelegate callback)
{
    // 작업 수행
    callback(&quot;작업 완료&quot;);
}

void PrintMessage(string message) {
    Debug.Log(message);
}

DoSomething(PrintMessage);</code></pre>
<br>
<br>

<hr>
<h2 id="내장-대리자-action-func">내장 대리자: Action, Func</h2>
<p>C#에서는 직접 대리자를 선언하지 않고, 일반적으로 내장된 Action 및 Func 대리자를 사용한다.</p>
<ul>
<li><p><strong>Action</strong>: 반환값이 없는 대리자</p>
</li>
<li><p><strong>Func</strong>: 반환값이 있는 대리자</p>
</li>
</ul>
<pre><code class="language-cs">Action&lt;string&gt; action = (msg) =&gt; Debug.Log(msg);
Func&lt;int, int, int&gt; func = (a, b) =&gt; a + b;</code></pre>
<br>
<br>

<hr>
<h2 id="활용-예시">활용 예시</h2>
<p>Unity 엔진에서 버튼 클릭, 애니메이션 완료, 네트워크 응답 등 다양한 상황에서 콜백이 사용된다.
예를 들어, Button.onClick은 내부적으로 대리자를 사용하여 클릭 시 실행할 함수를 등록한다.</p>
<pre><code class="language-cs">myButton.onClick.AddListener(() =&gt; Debug.Log(&quot;버튼 클릭됨&quot;));</code></pre>
<br>
<br>

<hr>
<h2 id="정리">정리</h2>
<ul>
<li><p><strong>대리자(delegate)</strong>: 함수를 저장하고 전달할 수 있는 타입</p>
</li>
<li><p><strong>콜백(callback)</strong>: 특정 시점에 실행되도록 전달된 함수(주로 대리자를 통해 구현)</p>
</li>
<li><p><strong>Action/Func</strong>: C#에서 제공하는 대표적인 대리자 타입</p>
</li>
</ul>
<br>
<br>

]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] Monobehaviour 정리]]></title>
            <link>https://velog.io/@lumos-vid/Unity-Monobehaviour-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@lumos-vid/Unity-Monobehaviour-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 17 Jul 2025 14:22:18 GMT</pubDate>
            <description><![CDATA[<h2 id="monobehaviour">Monobehaviour</h2>
<p>Unity에서 Monobehaviour는 모든 스크립트 컴포넌트의 기본이 되는 클래스다.
사용자는 Monobehaviour를 상속하여 자신의 C# 스크립트를 작성하며, 이를 통해 Unity의 오브젝트에 다양한 동작을 부여할 수 있다.</p>
<br>
<br>

<hr>
<h2 id="주요-특징">주요 특징</h2>
<p><strong>컴포넌트 기반 구조</strong>
Unity의 모든 컴포넌트는 GameObject에 부착되어 동작한다. Monobehaviour를 상속한 스크립트 역시 하나의 컴포넌트로 취급되어 GameObject에 AddComponent 형태로 추가할 수 있다.</p>
<br>

<p><strong>전용 함수</strong>
충돌이나 트리거 이벤트(예: OnCollisionEnter, OnTriggerEnter) 등의 메시지 함수나 전용 인터페이스등 유니티에 맞춰져 있는 전용 함수들을 사용 할 수 있다.</p>
<br>

<p><strong>라이프사이클</strong>
Monobehaviour는 게임 오브젝트의 생명주기 동안 자동으로 호출되는 여러 메서드를 제공한다.</p>
<ul>
<li><strong>Awake</strong>
오브젝트가 씬에 활성화되는 순간 한 번 호출된다. 오브젝트가 카메라에 보여지기 전에, 가장 빠른 초기화 시점에 실행된다. 다른 스크립트와의 Awake 와 실행 순서를 보장하지 않기 때문에 오브젝트 본인에게 달려있는 내부 컴포넌트 참조 혹은 다른 초기 초기화 들을 진행하기에 적합하다. </li>
</ul>
<br>

<ul>
<li><strong>OnEnable</strong>
오브젝트가 활성화될 때마다 실행되며, 비활성 → 활성화 과정이 반복될 때마다 호출한다. Awake, Start 가장 큰 차이는 활성화 될때마다 실행된다는 점으로 어떤 이벤트의 구독이나 코루틴, 타이머 등 자주 활성화 비활성화 되는 상황에 주로 사용된다. 활성화 되는 즉시 반응하기 위해 Start 보다 먼저 호출된다.</li>
</ul>
<br>

<ul>
<li><strong>Start</strong>
오브젝트가 씬에 활성화된 뒤, 첫 프레임이 시작되기 직전에 한 번 호출된다. Awake가 모두 끝난 후, 다음 프레임 Update 직전에 실행된다. 외부 스크립트간의 Awake 들이 서로 순서를 보장하지 않기 때문에 외부 참조를 진행하기에 적합하다. 외부 참조가 필요한 작업들을 처리를 주로 하게 된다.</li>
</ul>
<br>

<ul>
<li><strong>FixedUpdate</strong> 
물리 연산을 일정한 시간 간격으로 처리한다. 일반적인 Update 와 달리 고정 프레임으로 반복되는데 물리 엔진(Physics) 의 연산 타이밍에 맞추게 된다. 기기간 다른 성능에 상관없이 일관된 물리 연산을 하기 위해 이렇게 되었는데 그런 만큼 Update 와 로직을 공유하지는 않는지 주의해야 한다.</li>
</ul>
<br>

<ul>
<li><strong>Update</strong>
매 프레임마다 호출되어, 주로 입력 처리나 오브젝트 상태 업데이트에 사용된다. 이 또한 다른 스크립트와의 순서를 보장하지 않기 때문에 참조에 대한 확인이 필요하고 매 프레임 호출되기 때문에 로직 상에서 성능적인 이슈가 발생한다면 업데이트에서 비싼 비용을 처리하는 경우가 대부분이므로 주의가 필요하다.</li>
</ul>
<br>

<ul>
<li><strong>LateUpdate</strong>
Update 이후에 호출되고 주요 사용점은 Awake 와 Start 의 사용용도와 마찬가지로 다른 순서에 상관없는 처리들을 Update 에서 하고 그 정보를 바탕으로 처리해야 할 때 사용된다. 주로 카메라 관련해서 많이 사용하게 되는데 상황에 따른 카메라 이벤트를 발생시키고 최종 결정을 하는 용도로 많이 쓰인다.
그렇지만 여러 스크립트간의 주기에 대한 동기화가 힘드므로 정말 필수적인 용도가 아니라면 지양하는게 좋다.</li>
</ul>
<br>

<ul>
<li><strong>OnDisable, OnDestroy</strong>
각각 비활성화, 파괴될 때 호출됩니다. 이벤트의 구독해제나 기타 참조 해제등에 주로 사용됩니다.</li>
</ul>
<br>

<hr>
<h2 id="사용-이유">사용 이유</h2>
<p><strong>Unity Editor와의 통합</strong>
Monobehaviour를 상속한 스크립트는 Inspector에서 쉽게 속성을 수정하거나, 에디터 상에서 컴포넌트처럼 관리할 수 있다.</p>
<br>

<p><strong>직관적인 오브젝트 제어</strong>
GameObject와 연동되어 오브젝트의 상태나 동작을 쉽고 직관적으로 제어할 수 있다.</p>
<br>

<p><strong>게임 로직 구현의 표준 구조</strong>
Unity에서 동적인 게임 로직을 작성할 때 기본적으로 Monobehaviour를 상속하는 것이 표준적인 방법이다.</p>
<br>
<br>

<hr>
<h2 id="주의-사항">주의 사항</h2>
<p>new로 인스턴스화하지 않고 AddComponent혹은 Instantiate 해야하며 전용 매서드를 사용할때 매개변수에 대해 오타가 있는지 확인해야한다.
특히 라이프사이클에 대한 실수는 초보 개발자가 주로 하는 실수이기에 확실한 인지가 필요하고 프로젝트 세팅에서 직접 순서를 지정해주기 보단 코드상으로 순서가 크게 중요치 않은 구조를 짜는게 유지보유리하다.</p>
]]></description>
        </item>
    </channel>
</rss>