<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Hepari&apos;s Blog</title><description>rosmontisu</description><link>https://heparidayo.com/</link><language>en</language><item><title>[PCG] 4. HISM 기반 3D 렌더링 및 은면 제거</title><link>https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg04/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg04/</guid><description>n만 개의 타일 데이터를 HISM으로 압축하여 드로우콜을 최적화하고, 불필요한 벽 메쉬 생성을 막는 은면 제거(Culling) 로직을 구현하자</description><pubDate>Tue, 23 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;지난 5월, 2D Grid 배열을 활용하여 방+복도 데이터를 메모리에 올렸다. 하지만 지금까지 뷰포트에 띄운 &lt;code&gt;DrawDebugSolidBox&lt;/code&gt;는 실제 게임에서 사용할 수 없는 에디터 전용 시각화 도구이다.&lt;/p&gt;
&lt;p&gt;이번에는 이 n만 개의 데이터 배열을 바탕으로 실제 3D Static Mesh를 월드에 배치하되, Draw Call 문제를 방지하기 위해 &lt;code&gt;HISM&lt;/code&gt; 컴포넌트와 은면 제거 알고리즘을 도입하여 렌더링을 최적화해 보았다.&lt;/p&gt;
&lt;h2&gt;1. Actor 스폰의 Draw Call 병목&lt;/h2&gt;
&lt;p&gt;100m x 100m 크기의 맵은 1m 단위 타일(여기서는 기본 제공되는 Cube) 10,000개로 이루어진다. 만약 이 타일마다 일반적인 &lt;code&gt;AStaticMeshActor&lt;/code&gt;를 1만 개 스폰한다면 어떻게 될까?&lt;/p&gt;
&lt;p&gt;CPU는 GPU에게 &quot;1번 큐브 그려라&quot;, &quot;2번 큐브 그려라&quot; 하며 1만 번의 렌더링 명령(Draw Call)을 내리게 되고, 이는 CPU 병목 현상으로 이어지며 컴퓨터의 성능과 맵 크기에 따라 엔진이 튕길 수 있다.&lt;/p&gt;
&lt;h2&gt;2. HISM을 활용한 하드웨어 인스턴싱&lt;/h2&gt;
&lt;p&gt;이를 해결하기 위해 &lt;strong&gt;HISM(Hierarchical Instanced Static Mesh)&lt;/strong&gt; 컴포넌트를 사용했다. HISM은 동일한 메쉬 1만 개를 그릴 때, 메쉬 원본 데이터 1개와 1만 개의 좌표(Transform) 배열만 GPU로 넘겨 &lt;strong&gt;단 1회의 드로우콜&lt;/strong&gt;로 렌더링을 끝내는 최적화 기법이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ISM&lt;/code&gt;과 달리 계층적(Hierarchical) 구조를 가져, 시야 밖의 인스턴스를 통째로 Frustum Culling하거나 거리별 LOD 적용이 가능하다는 장점이 있어 거대 맵 생성에 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 에디터 타임 메모리 관리 (&lt;code&gt;ClearInstances&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;PCG 툴은 에디터의 디테일 패널에서 Seed 값을 변경(&lt;code&gt;OnConstruction&lt;/code&gt;)할 때마다 맵을 실시간으로 다시 생성한다. 이때 기존 메쉬 데이터를 지우지 않고 인스턴스를 계속 추가하면 에디터의 메모리가 쌓인다.&lt;/p&gt;
&lt;p&gt;이를 방지하기 위해 렌더링 루프 시작 전, 이전 맵의 찌꺼기 데이터를 확실하게 비워주는 생명주기(Lifecycle) 관리를 적용했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 에디터 블루프린트에서 설정한 메쉬 할당 및 기존 인스턴스 초기화
RoomFloorHISM-&amp;gt;SetStaticMesh(RoomFloorMesh);
RoomFloorHISM-&amp;gt;ClearInstances();

// 복도 및 벽 HISM도 동일하게 ClearInstances() 호출...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 인접 타일 검사를 통한 은면 제거 (Culling)&lt;/h2&gt;
&lt;p&gt;1만 개의 타일 중, 플레이어가 걸어 다니는 방과 복도를 제외한 7~8천 개의 잉여 타일은 그냥 두툼한 벽으로 남는다. 이 벽 타일마다 모두 메쉬를 생성하면 보이지 않는 맵 외곽과 땅속 허공까지 수천 개의 큐브가 렌더링되는 메모리 낭비가 발생한다.&lt;/p&gt;
&lt;p&gt;따라서 **&quot;상하좌우 4방향 중 바닥이 단 하나라도 있는 벽(노출된 벽)만 렌더링한다&quot;**는 은면 제거(Hidden Surface Removal) 로직을 추가했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bool bShouldRenderWall = false;

// 4방향 탐색
for (int i = 0; i &amp;lt; 4; ++i)
{
    int32 nx = X + Dx[i];
    int32 ny = Y + Dy[i];
    
    if (IsValidGridPosition(nx, ny))
    {
        int32 nIndex = Get1DIndex(nx, ny);
        // 내 주변에 방이나 복도(바닥)가 있는가?
        if (GridMap[nIndex] != ETileType::Wall)
        {
            bShouldRenderWall = true; // 노출된 벽이므로 렌더링 승인
            break; 
        }
    }
}

// 렌더링 승인이 떨어진 벽만 Z축으로 올려서 HISM에 추가
if (bShouldRenderWall)
{
    FTransform WallTransform(FRotator::ZeroRotator, Position + FVector(0, 0, TileSize), FVector(1.0f));
    WallHISM-&amp;gt;AddInstance(WallTransform);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;5. 결과 시각화 및 다음 목표&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-6.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;위 이미지는 1M Cube 메쉬가 HISM으로 생성된 결과이다. 언리얼 콘솔로 &lt;code&gt;stat rhi&lt;/code&gt; 확인 결과, 1만 개의 공간을 렌더링할때 DrawPrimitive Calls가 3회(방, 복도, 벽)만 실행되고 60프레임 이상을 안정적으로 유지한다. 지하에서 맵을 올려다보면 내부 벽면만 생성되고 맵 외곽의 불필요한 메쉬는 Culling 된 것을 확인할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이제 최적화된 3D 렌더링 뼈대가 완성되었다. 다음 달에는 벽변과 바닥에 비트마스킹, 푸아송 디스크 샘플링 등으로 프롭을 자연스럽게 배치해볼 것이다.&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>[PCG] 3. 2D Grid 메모리 최적화 및 A* 기반 타일 맵 생성</title><link>https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg03/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg03/</guid><description>논리적 그래프인 Vector를 물리적인 Grid 타일로 변환하고, A* 알고리즘에 가중치를 적용하여 자연스러운 복도를 생성하자</description><pubDate>Wed, 03 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;지난 4월, 언리얼 엔진으로 데이터 구조체를 이식하고 MST를 활용하여 방과 방을 잇는 논리적 그래프(선)를 시각화했다. 하지만 선은 캐릭터가 걸어 다닐 수 있는 물리적 공간이 아니다.&lt;/p&gt;
&lt;p&gt;이번에는 100m x 100m의 공간을 1m 크기의 타일들로 쪼개어 메모리에 올리고, A* 알고리즘을 통해 논리적 선을 실제 타일 복도로 변환하는 과정을 구현해보았다.&lt;/p&gt;
&lt;h2&gt;1. 논리적 선(Vector)에서 물리적 면(Grid)로&lt;/h2&gt;
&lt;p&gt;기존에 구한 데이터는 두 방의 중심점을 잇는 좌표로, 이를 실제 던전으로 렌더링하려면 타일 맵 데이터로 변환해야 한다.&lt;/p&gt;
&lt;p&gt;가장 먼저 타일의 상태를 정의할 열거형을 만들었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Wall&lt;/code&gt;: 파내지 않은 꽉 찬 벽&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RoomFloor&lt;/code&gt;: 방의 바닥&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CorridorFloor&lt;/code&gt;: 복도의 바닥&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 1차원 배열을 활용한 2D Grid 메모리 최적화&lt;/h2&gt;
&lt;p&gt;2D 타일 맵을 구현할 때 흔히 &lt;code&gt;TArray&amp;lt;TArray&amp;lt;uint8&amp;gt;&amp;gt;&lt;/code&gt; 형태의 2차원 배열을 떠올리기 쉽다. 하지만 이는 메모리가 연속적으로 할당되지 않아 CPU 캐시 지역성(Cache Locality)이 떨어져 병목의 원인이 된다.&lt;/p&gt;
&lt;p&gt;대신 1차원 배열을 2차원처럼 다루는 방식으로 메모리 파편화를 방지했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 2D 맵 데이터를 담을 1차원 배열 (캐시 최적화)
TArray&amp;lt;ETileType&amp;gt; GridMap;
int32 GridSizeX;
int32 GridSizeY;

// 2D(X,Y) -&amp;gt; 1D 인덱스로 변환하는 인라인 함수
// inline대신 FORCEINLINE 사용 (MSVC의 __forceinline 등으로 강제로 인라인화할 것을 명령)
FORCEINLINE int32 Get1DIndex(int32 X, int32 Y) const
{
    return Y * GridSizeX + X;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;생성된 모든 Room 데이터의 영역을 1m 단위 타일 좌표로 변환하고, 이 1차원 &lt;code&gt;GridMap&lt;/code&gt; 배열의 해당 인덱스들을 &lt;code&gt;ETileType::RoomFloor&lt;/code&gt;로 덮어씌워 방을 파냈다.&lt;/p&gt;
&lt;h2&gt;3. A* 길찾기를 이용한 복도 생성&lt;/h2&gt;
&lt;p&gt;방을 모두 파낸 후, 4월에 구했던 MST &lt;code&gt;Corridors&lt;/code&gt; 배열을 순회하며 A*로 탐색했다.&lt;/p&gt;
&lt;p&gt;이때 매번 노드를 &lt;code&gt;new&lt;/code&gt;로 할당하면 GC 부하와 메모리 릭이 발생할 수 있으므로, 언리얼의 &lt;code&gt;TUniquePtr&lt;/code&gt;와 &lt;code&gt;TMap&lt;/code&gt;을 컨테이너로 메모리 소유권을 관리했다. 또한, 성능이 중요한 닫힌 목록(&lt;code&gt;ClosedList&lt;/code&gt;)은 2차원 맵 크기와 동일한 1차원 &lt;code&gt;TArray&amp;lt;bool&amp;gt;&lt;/code&gt;로 할당하여 탐색시 O(1) 시간복잡도를 유도한다.&lt;/p&gt;
&lt;h2&gt;4. A* 가중치를 이용한 우회 경로 (Soft Constraint)&lt;/h2&gt;
&lt;p&gt;단순하게 A* 알고리즘 적용 시 최단 거리를 찾기 위해 복도가 방을 항상 뚫고 직진해버린다. 방의 모양이 훼손되지 않도록 탐색 중 바로 옆 타일이 &lt;code&gt;RoomFloor&lt;/code&gt;일 경우 가중치(현재, +500점)를 부여하는 부드러운 제약(Soft Constraint) 방식을 적용했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int32 MoveCost = 10; // 평지 이동 기본 비용

// [가중치] 방을 피해서 돌아가도록 가중치 부여
if (GridMap[NeighborIndex] == ETileType::RoomFloor)
{
    // 도착지가 남의 방 내부에 있는 경우가 아니라면 가중치
    if (!(NeighborX == TargetX &amp;amp;&amp;amp; NeighborY == TargetY))
    {
        MoveCost += 500;
    }
}

int32 NewG = CurrentNode-&amp;gt;G + MoveCost;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 결과로 A* 알고리즘은 가중치를 받으면서 방을 뚫는 경우는 거의 없어지고, 대부분 방의 외각 벽을 따라 우회하는 경로를 찾게 된다.&lt;/p&gt;
&lt;h2&gt;5. 결과 시각화 및 다음 목표&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;위 이미지는 타일 렌더링 결과로, 초록색 타일은 방(Room)을, 노란색 타일은 방을 우회하며 이어진 복도(A* Corridor)를 나타낸다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;기존에 호출하던 &lt;code&gt;DrawDebugLine&lt;/code&gt; 기반의 렌더링 함수를 지우고, &lt;code&gt;GridMap&lt;/code&gt; 전체를 순회하며 바닥 타일에 &lt;code&gt;DrawDebugSolidBox&lt;/code&gt;를 그리는 함수로 교체했다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-6.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;다음은 &lt;code&gt;HISM(Hierarchical Instanced Static Mesh)&lt;/code&gt; 컴포넌트를 활용해 위 사진과 같이 3D 스태틱 메쉬로 교체하고, Draw Call 최적화에 대해 알아보자.&lt;/p&gt;
</content:encoded></item><item><title>[PCG] 2. 언리얼 엔진 이식 및 MST 기반 그래프 생성</title><link>https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg02/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg02/</guid><description>C++ 구조체를 언리얼 엔진에 이식하고, 크루스칼 알고리즘(MST)을 이용해 던전의 순환 그래프를 설계하자</description><pubDate>Mon, 04 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;지난 3월, C++ 콘솔 환경에서 BSP 공간 분할과 데이터 구조를 테스트했다. 이번에는 해당 구조체를 언리얼 환경으로 이식하고, 단순 트리 구조였던 복도 연결을 MST를 이용해 그래프로 연결해보았다.&lt;/p&gt;
&lt;h2&gt;1. 언리얼 C++ 메모리 및 난수 엔진 이식&lt;/h2&gt;
&lt;p&gt;이전 C++ 콘솔 프로젝트에서 중요했던 RAII패턴(스마트 포인터), 결정론 보장, 구조체를 언리얼 환경에 맞게 수정해서 이식해야한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RAII 패턴과 언리얼 GC&lt;/strong&gt;&lt;br /&gt;
&lt;code&gt;UObject&lt;/code&gt; GC 시스템이 아닌, 언리얼 Core 라이브러리(&lt;code&gt;TArray&lt;/code&gt; &lt;code&gt;TUniquePtr&lt;/code&gt;)를 활용해 데이터 계층의 메모리를 직접 제어 가능하게 설계해야한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;std::unique_ptr&lt;/code&gt; -&amp;gt; &lt;code&gt;TUniquePtr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;std::vector&lt;/code&gt; -&amp;gt; &lt;code&gt;TArray&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;결정론 보장&lt;/strong&gt;&lt;br /&gt;
상대적으로 무거운 C++의 메르센 트위스터 방식에서 언리얼이 제공하는 난수 구조체로 교체했다. 이제 재귀 함수가 호출될때마다 난수 엔진을 재생성하지 않고, 진입점(&lt;code&gt;OnConstruction&lt;/code&gt;)에서 &lt;code&gt;FRandomStream&lt;/code&gt;을 Seed로 초기화하고 참조로 넘긴다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;std::mt19937&lt;/code&gt; -&amp;gt; &lt;code&gt;FRandomStream&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;네임스페이스 충돌 해결&lt;/strong&gt;&lt;br /&gt;
UE 라이브러리에 &lt;code&gt;FEdge&lt;/code&gt;와 충돌이 발생하여 간선 구조체의 이름을 수정했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FEdge&lt;/code&gt; -&amp;gt; &lt;code&gt;FRoomEdge&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. OnConstruction과 DrawDebug를 활용한 데이터 시각화&lt;/h2&gt;
&lt;p&gt;정상적으로 로직이 이식되었는지 확인하기 위해, &lt;code&gt;DrawDebug&lt;/code&gt; 함수를 만들었다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;APCGGenerator&lt;/code&gt; 액터의 &lt;code&gt;OnConstruction&lt;/code&gt; 함수를 오버라이드하여, 에디터 디테일 패널의 변수를 바꾸면 뷰포트의 맵 형태가 실시간으로 수정되도록 하였다.&lt;/p&gt;
&lt;h2&gt;3. BSP 통로의 한계 &amp;amp; 그래프 도입&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;기존 BSP 트리 구조만으로 방을 연결하면, 몇가지 거슬리는 점이 생긴다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;물리적으로 바로 옆에 있는 방이라도, 트리상 멀리 떨어져 있다면 빙 돌아서 가야한다.&lt;/li&gt;
&lt;li&gt;사이클이 없어, 무조건 한번 왔던 길로만 되돌아 갈 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이를 해결하기 위해, 방과 방 사이를 트리 계층과 무관하게 최단 거리로 잇는 MST를 도입했다.&lt;/p&gt;
&lt;h2&gt;4. Union-Find와 Kruskal 기반 MST 구현&lt;/h2&gt;
&lt;p&gt;가장 먼저 두 방이 이미 연결되었는지 판별하여 사이클을 막아주는 분리 집합 클래스 &lt;code&gt;FUnionFind&lt;/code&gt;를 구현했다. 이때 탐색 최적화를 위해 경로 압축(Path Compression)을 적용하여 &lt;code&gt;Find&lt;/code&gt; 연산 시간을 $O(\alpha(N))$으로 줄였다.&lt;/p&gt;
&lt;p&gt;백준에서 문제를 풀때와는 다르게 Union by Rank 최적화는 생략하였다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct FUnionFind
{
    TMap&amp;lt;FBSPNode*, FBSPNode*&amp;gt; Parent;

    // 경로 압축(Path Compression)이 적용된 Find
    FBSPNode* Find(FBSPNode* Node)
    {
        if (Parent[Node] == Node) return Node;
        return Parent[Node] = Find(Parent[Node]); 
    }
    // ... Union 함수 생략
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이후 모든 말단 노드(Room) 간의 거리를 계산하여 정렬한 뒤, 크루스칼로 MST를 추출했다.&lt;/p&gt;
&lt;h2&gt;5. 순환로 생성&lt;/h2&gt;
&lt;p&gt;MST로 생성한 그래프는 사이클이 없어, BSP와 마찬가지로 왔던길로만 되돌아 갈 수 있다. 따라서 크루스칼 알고리즘 진행 중에 사이클을 형성하여 버려지는 간선 중 15%를 무작위(&lt;code&gt;FRandomStream&lt;/code&gt;)로 부활시켜 맵에 Loop를 추가하였다.&lt;/p&gt;
&lt;p&gt;단, 맵의 끝과 끝에 노드가 다이렉트로 연결되는 교차 간선을 막기위해, &lt;code&gt;MaxLoopDistance&lt;/code&gt; (맵 전체 폭의 15%) 거리 제한을 두었다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (DSU.Union(Edge.NodeA, Edge.NodeB))
{
    // 사이클이 없으면 메인 뼈대 (빨간선)
    Corridors.Emplace(X1, Y1, X2, Y2, false);
}
else
{
    // 사이클이 발생하더라도, 거리가 짧은 간선은 15% 확률로 Loop로 부활 (파란선)
    if (Edge.Distance &amp;lt; MaxLoopDistance &amp;amp;&amp;amp; RNG.FRand() &amp;lt; 0.15f)
    {
        Corridors.Emplace(X1, Y1, X2, Y2, true); 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;6. 결과 시각화 및 다음 목표&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;MST15Loop.gif&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;위 이미지는 107번 시드로, 빨간선은 메인 뼈대 파란선은 Loop이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;상세한 코드는 &lt;a href=&quot;https://github.com/heparidayo/UnrealPCG&quot;&gt;깃허브 링크&lt;/a&gt;에서 확인 가능하다.&lt;/p&gt;
&lt;p&gt;다음으로는 그래프를 기반으로 실제 물리적 배치를 위한 배열 최적화와 A* 길찾기 등을 구현해볼것이다.&lt;/p&gt;
</content:encoded></item><item><title>[UE5] 0-1. 개발 환경 구축부터 네트워크 세션 기초 설계까지</title><link>https://heparidayo.com/posts/unrealengine/projects/tps/tps01/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/projects/tps/tps01/</guid><description>(이전 게시글 0번 1번 통합) UE5.7 기준 데디케이티드 서버, GAS, Enhanced Input 환경 구축과 세션 기반 네트워크 연결 테스트 통합 정리</description><pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;언리얼 프로젝트를 본격적으로 개발하기 전, 데디케이티드 서버 구조를 상정하고 GAS를 적용한 초기 환경 구축부터 다중 접속을 위한 네트워크 세션 설계까지의 과정을 정리했다.&lt;/p&gt;
&lt;h2&gt;1. 데디케이티드 서버 빌드 타겟과 로그 설정&lt;/h2&gt;
&lt;p&gt;언리얼 엔진의 기본 템플릿은 에디터와 클라이언트 빌드만 제공한다. 서버 전용 로직을 검증하기 위해 별도의 &lt;code&gt;.Target.cs&lt;/code&gt; 설정이 필요하다.&lt;/p&gt;
&lt;p&gt;일반적으로 &lt;code&gt;Shipping&lt;/code&gt; 빌드에서는 성능 최적화를 위해 모든 로그가 제거된다. 하지만 데디케이티드 서버 운영 중 발생하는 예외 상황을 추적하기 위해서는 로그가 필요하므로, 서버 타겟 설정에서 명시적으로 로그를 활성화해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// CoopGameServer.Target.cs
public class CoopGameServerTarget : TargetRules
{
    public CoopGameServerTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Server; // 타입 Server로 변경
        DefaultBuildSettings = BuildSettingsVersion.V6;
        IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;

        ExtraModuleNames.Add(&quot;CoopGame&quot;);

        // 1. 서버는 당연히 Development빌드가 아니라 Shipping빌드를 한다.
        // 2. Shipping빌드는 로그가 남지 않아 디버깅이 어렵다.
        // 3. 그래서 Shipping빌드에서도 로그가 남도록 설정한다.
        bUseLoggingInShipping = true;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;p&gt;타겟 파일을 추가하거나 수정했을 때는 반드시 &lt;code&gt;.uproject&lt;/code&gt; 파일 우클릭 후 &lt;code&gt;Generate Visual Studio project files&lt;/code&gt;를 실행해야 프로젝트 탐색기에 정상적으로 반영된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 버전 관리 시스템 (Git) 환경 설정&lt;/h2&gt;
&lt;p&gt;협업 및 버전 관리를 위해 &lt;code&gt;.gitignore&lt;/code&gt;와 &lt;code&gt;.gitattributes&lt;/code&gt;를 설정한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;.gitignore 설정&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# Unreal Engine
Binaries/
Build/
DerivedDataCache/
Intermediate/
Saved/
*.VC.db
*.opensdf
*.opendb
*.sdf
*.sln
*.suo
*.xcodeproj
*.xcworkspace

# 단, 이것들은 추적
!Config/
!Content/
!Source/
!Plugins/
!*.uproject

# Visual Studio
# .sln은 Generate Visual Studio project files로 언제든 재생성 가능하므로 추적할 필요 없다.
.vs/
*.sln
*.suo
*.user
*.userosscache
*.sln.docstates
*.VC.db
*.VC.VC.opendb

# Visual Studio Code
.vscode/

# Rider (혹시 나중에 쓸 경우 대비)
.idea/

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LFS (.gitattributes)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;*.uasset filter=lfs diff=lfs merge=lfs -text
*.umap filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.wav filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. GAS(Gameplay Ability System) 디버깅 환경&lt;/h2&gt;
&lt;p&gt;콘솔 창(&lt;code&gt;~&lt;/code&gt;)에서 &lt;code&gt;ShowDebug AbilitySystem&lt;/code&gt; 명령어를 입력했을 때, 현재 캐릭터가 가진 태그, 이펙트, Attribute(체력, 마나 등)을 오버레이로 확인하기 위해 &lt;code&gt;DefaultGame.ini&lt;/code&gt;에 다음 설정을 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;; 콘솔 창(~ 키 입력)열고, &quot;ShowDebug abilitysystem&quot; 입력
; 화면에 GAS 관련 정보 (현재 가진 태그, 이펙트, 체력, 마나 등 Attribute) 오버레이 표시
[/Script/GameplayAbilities.AbilitySystemGlobals]
bUseDebugTargetFromHud=true

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Enhanced Input: WASD 이동 로직 재구성&lt;/h2&gt;
&lt;p&gt;UE5의 Enhanced Input은 유연하지만, 2D Axis 기반의 입력 처리 시 축 변환(Swizzle)과 반전(Negate)의 개념을 정확히 적용해야 한다.&lt;/p&gt;
&lt;p&gt;엔진은 기본적으로 모든 입력을 &apos;오른쪽(X+)&apos; 기준으로 받는다. 따라서 &lt;code&gt;IA_Move&lt;/code&gt; (Axis2D) 액션 하나로 방향을 제어하려면 다음과 같은 Modifiers 세팅이 필요하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;W (위):&lt;/strong&gt; &lt;code&gt;Swizzle Input Axis Values&lt;/code&gt; (입력을 Y축으로 변환, 이때 XYZ -&amp;gt; YXZ 설정 필요)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;S (아래):&lt;/strong&gt; &lt;code&gt;Swizzle Input Axis Values&lt;/code&gt; + &lt;code&gt;Negate&lt;/code&gt; (Y축으로 변환, 이때 XYZ -&amp;gt; YXZ 설정 필요. 그 후 반전)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;D (오른쪽):&lt;/strong&gt; 모디파이어 없음 (기본 X축 양수)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A (왼쪽):&lt;/strong&gt; &lt;code&gt;Negate&lt;/code&gt; (X축 반전)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. C++ 플레이어 캐릭터 클래스 설계&lt;/h2&gt;
&lt;p&gt;기본 템플릿의 &lt;code&gt;BP_ThirdPersonCharacter&lt;/code&gt; 블루프린트를 재사용하거나 에셋 경로를 하드코딩하는 대신, BP가 상속받는 기반이 되는 C++ 기반 클래스(&lt;code&gt;ACoopGameCharacter&lt;/code&gt;)를 직접 구현하여 로직과 시각적 에셋을 분리한다.&lt;/p&gt;
&lt;p&gt;카메라 세팅, 이동 컴포넌트 처리, Enhanced Input의 IMC(Input Mapping Context) 바인딩과 같은 로직은 C++에서 담당하고,&lt;/p&gt;
&lt;p&gt;이를 상속받은 BP에서는 메쉬나 애니메이션 블루프린트(AnimBP) 등 시각적 에셋만 할당하여 로직과 에셋을 분리한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;새로운 입력(단축키, 액션 등) 추가 프로세스&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;IA 에셋 생성:&lt;/strong&gt; 에디터에서 &lt;code&gt;IA_Jump&lt;/code&gt; 등의 Input Action 에셋 생성.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IMC 매핑:&lt;/strong&gt; &lt;code&gt;IMC_Default&lt;/code&gt;에 IA를 추가하고 입력 키 매핑 (필요시 Modifiers/Triggers 세팅).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.h 변수 노출:&lt;/strong&gt; 헤더 파일에서 &lt;code&gt;UPROPERTY&lt;/code&gt;를 사용해 BP에 에셋을 연결할 슬롯 생성.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;.cpp 바인딩 및 구현:&lt;/strong&gt; 소스 파일에 동작 함수를 구현하고 &lt;code&gt;BindAction&lt;/code&gt;으로 IA와 매핑.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BP 할당:&lt;/strong&gt; C++을 상속받은 BP를 열고 노출된 슬롯에 IMC와 IA 에셋 할당.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이때 클래스 헤더에 에셋 참조를 선언할 때 사용하는 &lt;code&gt;UPROPERTY&lt;/code&gt; 매크로는 에디터에 변수를 노출하고 직렬화하기 위해 필수적이다. (유니티의 &lt;code&gt;[SerializeField]&lt;/code&gt; 특성(Attribute)와 유사하다. 인스펙터(디테일 패널)에 변수를 노출하고 값을 직렬화하여 저장할 수 있게 해준다.)&lt;/p&gt;
&lt;p&gt;참고로 괄호 안에 &lt;code&gt;EditAnywhere&lt;/code&gt; 같은 지정자(Specifier)를 넣지 않고 빈 &lt;code&gt;UPROPERTY()&lt;/code&gt;만 선언하면 BP나 에디터에 노출되지 않는다. (GC에 의해 임의로 제거될 수도 있다는걸 조심하자)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPROPERTY(EditAnywhere, Category = &quot;Input&quot;)
class UInputMappingContext* DefaultMappingContext;

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 언리얼 엔진 네트워크 모델 이해&lt;/h2&gt;
&lt;p&gt;기본적인 개발 환경과 로컬 캐릭터 제어 구조가 잡혔다면, 다음은 다중 접속을 위한 네트워크 설계다. 이를 위해 언리얼의 리플리케이션(Replication) 개념을 짚고 넘어가자&lt;/p&gt;
&lt;h3&gt;서버-클라이언트 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[Dedicated Server]:&lt;/strong&gt; 게임 로직의 모든 권위(Authority)를 가진다. 화면을 렌더링하지 않으며, 게임 상태만 관리하고 이를 클라이언트에 복제(Replicate)한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[Client]:&lt;/strong&gt; 플레이어의 입력을 서버로 전송하고, 서버로부터 받은 상태를 화면에 렌더링하는 역할만 수행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Actor의 3가지 네트워크 역할 (Role)&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;위치&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ROLE_Authority&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;이 Actor의 실제 주인. 핵심 로직의 실행 권한을 가짐.&lt;/td&gt;
&lt;td&gt;서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ROLE_AutonomousProxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;로컬 플레이어가 직접 조종하는 Actor.&lt;/td&gt;
&lt;td&gt;클라이언트 (본인)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ROLE_SimulatedProxy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;다른 플레이어의 Actor를 복제해서 보여주는 형태.&lt;/td&gt;
&lt;td&gt;클라이언트 (타인)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;권한 분리 예시&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 서버에서만 실행해야 하는 로직 (데미지 처리, 아이템 드롭 등)
if (HasAuthority()) { ... }

// 로컬 플레이어에서만 실행해야 하는 로직 (카메라 효과, HUD 등 UI 처리)
if (IsLocallyControlled()) { ... }

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 주요 프레임워크 클래스 설계&lt;/h2&gt;
&lt;p&gt;네트워크 상에서 각 클래스가 어디에 존재하고 무슨 역할을 하는지 정의한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;클래스&lt;/th&gt;
&lt;th&gt;존재 위치&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AGameMode&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;서버 전용&lt;/td&gt;
&lt;td&gt;게임 룰, 플레이어 입장/퇴장, 캐릭터 스폰 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AGameState&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;서버 + 전체 클라이언트&lt;/td&gt;
&lt;td&gt;게임 전체의 상태 (타이머, 팀 점수, 접속자 수 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;APlayerState&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;서버 + 전체 클라이언트&lt;/td&gt;
&lt;td&gt;개별 플레이어의 상태 (HP, 닉네임, 킬/데스 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;APlayerController&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;서버 + 해당 클라이언트&lt;/td&gt;
&lt;td&gt;입력 처리 및 클라이언트-서버 간의 통신(RPC) 창구&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;C++ 클래스 구현&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. CoopGameMode (부모: AGameModeBase)&lt;/strong&gt;
플레이어의 접속 및 퇴장 로그를 서버에 기록한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// CoopGameMode.cpp
#include &quot;Core/CoopGameMode.h&quot;
#include &quot;Core/CoopGameState.h&quot;
#include &quot;Core/CoopPlayerState.h&quot;

ACoopGameMode::ACoopGameMode()
{
    GameStateClass = ACoopGameState::StaticClass();
    PlayerStateClass = ACoopPlayerState::StaticClass();
}

void ACoopGameMode::PostLogin(APlayerController* NewPlayer)
{
    Super::PostLogin(NewPlayer);
    UE_LOG(LogTemp, Log, TEXT(&quot;Player Joined: %s&quot;), *NewPlayer-&amp;gt;GetName());
}

void ACoopGameMode::Logout(AController* Exiting)
{
    Super::Logout(Exiting);
    UE_LOG(LogTemp, Log, TEXT(&quot;Player Left: %s&quot;), *Exiting-&amp;gt;GetName());
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. CoopGameState (부모: AGameStateBase)&lt;/strong&gt;
접속 중인 플레이어 수를 서버 권위로 관리하고 클라이언트에 복제한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// CoopGameState.h
UPROPERTY(Replicated, BlueprintReadOnly, Category = &quot;Game&quot;)
int32 ConnectedPlayerCount;

// CoopGameState.cpp
void ACoopGameState::GetLifetimeReplicatedProps(TArray&amp;lt;FLifetimeProperty&amp;gt;&amp;amp; OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(ACoopGameState, ConnectedPlayerCount);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. CoopPlayerState (부모: APlayerState)&lt;/strong&gt;
추후 GAS와 연동될 플레이어의 생존 여부 등을 관리한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// CoopPlayerState.h
UPROPERTY(Replicated, BlueprintReadOnly, Category = &quot;Player&quot;)
bool bIsAlive;

// CoopPlayerState.cpp
void ACoopPlayerState::GetLifetimeReplicatedProps(TArray&amp;lt;FLifetimeProperty&amp;gt;&amp;amp; OutLifetimeProps) const
{
    Super::GetLifetimeReplicatedProps(OutLifetimeProps);
    DOREPLIFETIME(ACoopPlayerState, bIsAlive);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;DefaultGame.ini 등록&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[/Script/EngineSettings.GameMapsSettings]
GlobalDefaultGameMode=/Script/CoopGame.CoopGameMode

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 온라인 서브시스템 (OSS) 및 세션 구현&lt;/h2&gt;
&lt;p&gt;현재는 로컬 LAN 테스트를 위해 &lt;code&gt;NULL&lt;/code&gt; 서브시스템을 사용하며, 추후 Steam OSS로 교체할 수 있는 구조로 설계한다. &lt;em&gt;(에디터 Plugins에서 &lt;code&gt;Online Subsystem&lt;/code&gt; 및 &lt;code&gt;Online Subsystem Utils&lt;/code&gt; 활성화 필수)&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;DefaultEngine.ini 설정&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[OnlineSubsystem]
DefaultPlatformService=NULL

[OnlineSubsystemNULL]
bEnabled=true

[/Script/Engine.GameEngine]
+NetDriverDefinitions=(DefName=&quot;GameNetDriver&quot;,DriverClassName=&quot;OnlineSubsystemUtils.IpNetDriver&quot;,DriverClassNameFallback=&quot;OnlineSubsystemUtils.IpNetDriver&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GameInstance를 활용한 세션 관리&lt;/h3&gt;
&lt;p&gt;세션 로직은 씬 전환(Level Travel) 시에도 파괴되지 않고 유지되는 &lt;code&gt;UGameInstance&lt;/code&gt;에 구현하는 것이 안정적이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CoopGameInstance.cpp (주요 로직 요약)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 세션 생성
void UCoopGameInstance::CreateCoopSession(int32 MaxPlayers)
{
    // ... OSS 인터페이스 획득 생략 ...
    FOnlineSessionSettings SessionSettings;
    SessionSettings.bIsLANMatch = true; // NULL OSS 기준
    SessionSettings.NumPublicConnections = MaxPlayers;
    SessionSettings.bShouldAdvertise = true;
    
    SessionInterface-&amp;gt;CreateSession(0, COOP_SESSION_NAME, SessionSettings);
}

// 세션 생성 성공 시 데디서버 레벨로 이동
void UCoopGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
    if (bWasSuccessful) {
        GetWorld()-&amp;gt;ServerTravel(&quot;/Game/Levels/L_GameMap?listen&quot;);
    }
}

// 세션 참가 성공 시 클라이언트 이동
void UCoopGameInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{
    if (Result == EOnJoinSessionCompleteResult::Success) {
        APlayerController* PC = GetFirstLocalPlayerController();
        FString TravelURL;
        if (SessionInterface-&amp;gt;GetResolvedConnectString(SessionName, TravelURL)) {
            PC-&amp;gt;ClientTravel(TravelURL, ETravelType::TRAVEL_Absolute);
        }
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 접속 테스트 및 검증&lt;/h2&gt;
&lt;p&gt;작성된 코드가 데디서버 환경에서 정상적으로 동작하는지 확인한다.&lt;/p&gt;
&lt;h3&gt;방법 1: 에디터 내 테스트 (PIE)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;설정:&lt;/strong&gt; &lt;code&gt;Play As Listen Server&lt;/code&gt;, &lt;code&gt;Number of Players: 2+&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과:&lt;/strong&gt; 클라이언트 창이 2개 이상 생성되며 캐릭터가 동일 맵에서 동기화되는지 확인.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;방법 2: 커맨드라인 데디서버 테스트&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;서버 실행&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;UnrealEditor.exe&quot; &quot;[프로젝트경로]/CoopGame.uproject&quot; /Game/Levels/L_GameMap -server -log&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;클라이언트 실행&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;UnrealEditor.exe&quot; &quot;[프로젝트경로]/CoopGame.uproject&quot; 127.0.0.1 -game -log&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;결과:&lt;/strong&gt; 서버 터미널에 &lt;code&gt;Player Joined&lt;/code&gt; 로그가 정상적으로 출력되는지 확인.
&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[PCG] 1. C++ 콘솔 기반 BSP 알고리즘 테스트</title><link>https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg01/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/plugins/pcg/pcg01/</guid><description>언리얼 엔진 도입 전, C++ 콘솔 환경에서 BSP 알고리즘을 활용한 공간 분할 및 생성 로직을 테스트해보자</description><pubDate>Tue, 24 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;절차적 월드 생성(PCG) 시스템을 언리얼 엔진에 구현하기에 앞서, C++ 콘솔 환경에서 표준 라이브러리와 알고리즘만으로 격자형 맵을 시각화하는 테스트를 진행했다.&lt;/p&gt;
&lt;h2&gt;1. BSP 트리 구조체 설계&lt;/h2&gt;
&lt;p&gt;맵을 이진 공간 분할(BSP; Binary Space Partitioning)로 쪼개는 과정에서 생성되는 노드들은 메모리 관리를 용이하게 하기 위해 모던 C++의 &lt;code&gt;std::unique_ptr&lt;/code&gt;를 활용하여 설계했다. (추후 언리얼 엔진으로 이식할 때 &lt;code&gt;TUniquePtr&lt;/code&gt;로 대체할 예정이다.)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct FBSPNode
{
    FRect Area; // 할당된 전체 구역
    FRect Room; // 구역 내에 파내어질 실제 방 공간
    
    // 부모 노드 소멸 시 하위 트리의 모든 메모리가 자동 해제됨
    std::unique_ptr&amp;lt;FBSPNode&amp;gt; LeftChild;
    std::unique_ptr&amp;lt;FBSPNode&amp;gt; RightChild;
    // ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 RAII 패턴을 통해, 월드를 리셋할 때 최상단 Root 노드만 날려주면되는 안전한 구조가된다.&lt;/p&gt;
&lt;h2&gt;2. 결정론(Determinism) 보장과 난수 엔진 초기화 병목 해결&lt;/h2&gt;
&lt;p&gt;특정 Seed 값을 입력하면 언제나 100% 동일한 월드가 생성되는걸 본 적이 있을것이다. 이런걸 &lt;code&gt;결정론 보장&lt;/code&gt;이라고 한다.&lt;/p&gt;
&lt;p&gt;초기 구현에서는 방의 크기나 패딩(Padding)을 연산하는 재귀 함수 내부에서 매번 난수 엔진을 생성하게 구현하였는데...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// [개선 전] 호출마다 난수 엔진 생성 -&amp;gt; 결정론 보장 불가 및 CPU 병목 발생
std::random_device RandomDevice;
std::mt19937 Generator(RandomDevice()); 
int PaddingX = padX(Generator);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;std::mt19937&lt;/code&gt;은 내부 상태가 2KB로 상대적으로 무거운편이다. 이를 재귀적으로 생성하면 오버헤드가 발생한다. 더 큰 문제로는 &lt;code&gt;random_device&lt;/code&gt;를 매번 참조하므로 Seed 고정 자체가 불가능해진다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해 프로그램 진입점 &lt;code&gt;main&lt;/code&gt;에서 1개의 전역 RNG 객체를 Seed 값과 함께 초기화하고, 모든 함수에 &lt;code&gt;std::mt19937&amp;amp; RNG&lt;/code&gt;참조로 전달하도록 코드를 수정하였다.&lt;/p&gt;
&lt;h2&gt;3. 생성 함수 분리 (결합도 줄이기)&lt;/h2&gt;
&lt;p&gt;하나의 재귀 함수 내부에서 공간 분할, 방 생성, 경로 연결을 모두 처리하면 코드의 결합도가 지나치게 높아지고 유지보수가 어려워진다. 월드 생성을 3단계로 함수를 분리하였다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GenerateBSPTree&lt;/code&gt; (공간 분할):&lt;/strong&gt; &lt;code&gt;Area&lt;/code&gt;를 기준으로 맵을 끝까지 쪼갠다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GenerateRooms&lt;/code&gt; (방 파내기):&lt;/strong&gt; 더 이상 쪼개지지 않은 말단 노드(Leaf)를 찾아, &lt;code&gt;Area&lt;/code&gt; 내부에 &lt;code&gt;Padding&lt;/code&gt;을 적용한 &lt;code&gt;Room&lt;/code&gt;을 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;GenerateCorridors&lt;/code&gt; (경로 연결):&lt;/strong&gt; 방과 방 사이를 잇는 &lt;code&gt;FCorridor&lt;/code&gt; 복도 데이터를 추가한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;경로 연결을 위해 방 데이터가 기록된 무작위 &lt;code&gt;Leaf Node&lt;/code&gt;를 재귀적으로 탐색해 반환하는 헬퍼 함수 &lt;code&gt;GetRandomLeaf&lt;/code&gt;도 구현하였다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FBSPNode* LeftLeaf = GetRandomLeaf(Node-&amp;gt;LeftChild.get(), RNG);
FBSPNode* RightLeaf = GetRandomLeaf(Node-&amp;gt;RightChild.get(), RNG);

if (LeftLeaf &amp;amp;&amp;amp; RightLeaf) 
{
    // 임시 객체 생성 비용 최소화를 위해 push_back 대신 emplace_back 사용
    OutCorridors.emplace_back(LeftCenterX, LeftCenterY, RightCenterX, LeftCenterY);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 결과 시각화 및 다음 목표&lt;/h2&gt;
&lt;h3&gt;생성 과정 gif&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;PCG-Console.gif&quot; alt=&quot;alt text&quot; /&gt;
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;점선(.)은 쪼개진 Area 구역을, 실선(#, +)은 실제 파내어진 Room 공간을, 별표(*)는 방과 방을 잇는 통로를 의미한다. 모든 방이 끊김 없이 연결된 것을 확인할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;상세한 코드는 &lt;a href=&quot;https://github.com/heparidayo/BSPConsole/tree/master/BSPConsole&quot;&gt;깃허브 링크&lt;/a&gt;에서 확인 가능하다.&lt;br /&gt;
&lt;a href=&quot;https://github.com/heparidayo/BSPConsole/blob/master/BSPConsole/BSPNode.h&quot;&gt;BSPNode.h&lt;/a&gt;&lt;br /&gt;
&lt;a href=&quot;https://github.com/heparidayo/BSPConsole/blob/master/BSPConsole/BSPConsole.cpp&quot;&gt;main.cpp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;다음으로는 콘솔에서 테스트한 BSP 구조체를 UE5 환경에 이식해볼 것이다. &lt;code&gt;TUniquePtr&lt;/code&gt;를 적용하고, 액터 스폰 대신 디버깅 라인을 활용해 3D 시각화 테스트를 진행할 예정이다.&lt;/p&gt;
</content:encoded></item><item><title>다수의 StaticMesh 컴포넌트 배치시의 부하 원인 (Draw Call)</title><link>https://heparidayo.com/posts/unrealengine/study/uestudy1/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/study/uestudy1/</guid><description>동일한 UStaticMesh 컴포넌트를 월드에 다수 배치할 때 발생하는 시스템 부하의 원인을 메모리와 연산 관점에서 알아보고, 순수하게 폴리곤이 높은 USkeletalMesh에 경우도 알아보자.</description><pubDate>Fri, 27 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;언리얼 엔진 C++ 개발 중 동일한 &lt;code&gt;UStaticMesh&lt;/code&gt;를 사용하는 컴포넌트(예: 분수대, 총알 등)를 월드에 수십, 수백 개 배치할 때 발생하는 시스템 부하의 원인에 대해 찾아보고 정리해보았다.&lt;/p&gt;
&lt;p&gt;흔히 직관적으로 &quot;화면에 그려지는 개수가 많아지니 데이터가 복사되어 메모리 부하가 클 것&quot;이라고 오해하지만, 실제 게임 엔진의 병목 지점은 완전히 다른 곳에 있다.&lt;/p&gt;
&lt;h2&gt;1. 메모리 관점: 복사가 아닌 &apos;참조&apos; (Flyweight Pattern)&lt;/h2&gt;
&lt;p&gt;월드에 동일한 액터를 100개 배치한다고 해서 형태를 구성하는 메시 데이터(버텍스, 인덱스, UV 등)가 메모리에 100번 복사되지 않는다. 현대 게임 엔진은 플라이웨이트 패턴(Flyweight Pattern)을 기반으로 리소스를 관리한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;시스템 RAM (CPU 영역):&lt;/strong&gt; 엔진 초기화 시 원본 &lt;code&gt;UStaticMesh&lt;/code&gt; 데이터는 단 한 번만 로드된다. 각 컴포넌트(&lt;code&gt;UStaticMeshComponent&lt;/code&gt;)는 이 원본 데이터의 메모리 주소를 가리키는 포인터만을 소유한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;VRAM (GPU 영역):&lt;/strong&gt; 렌더링을 위해 그래픽 카드로 데이터가 넘어갈 때도 마찬가지다. 메시의 버텍스 정보는 GPU 메모리의 버텍스 버퍼(Vertex Buffer)에 딱 한 번만 올라간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결과적으로 인스턴스가 아무리 늘어나도 동일한 데이터를 재참조할 뿐이므로, 데이터 &apos;복사&apos;로 인한 메모리 오버헤드는 0에 가깝다.&lt;/p&gt;
&lt;h2&gt;2. 렌더링 관점: 진짜 부하의 원인은 &apos;드로우 콜(Draw Call)&apos;&lt;/h2&gt;
&lt;p&gt;메모리 복사 부하가 없다면 왜 객체를 많이 그릴수록 프레임이 떨어질까? 부하의 핵심은 데이터의 크기가 아니라 &lt;strong&gt;명령의 횟수&lt;/strong&gt;에 있다.&lt;/p&gt;
&lt;p&gt;CPU가 GPU에게 &quot;이 메시를 그려라&quot;라고 명령을 내리는 과정을 &lt;strong&gt;드로우 콜(Draw Call)&lt;/strong&gt; 이라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;CPU -&amp;gt; GPU&lt;/code&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;CPU가 렌더링할 객체의 월드 트랜스폼(위치, 회전, 스케일)을 계산한다.&lt;/li&gt;
&lt;li&gt;GPU에게 렌더링 상태 변경(머티리얼, 쉐이더 설정 등)을 지시한다.&lt;/li&gt;
&lt;li&gt;GPU에게 그리기(Draw) 명령을 보낸다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 과정에서 CPU와 GPU 사이의 통신 비용(Context Switching)이 발생한다. 화면에 그릴 객체가 수백 개로 늘어나면 이 통신 횟수 자체가 기하급수적으로 증가한다. 결국 CPU는 명령을 보내느라 과부하가 걸리고, GPU는 이전 명령을 처리하고 다음 명령을 기다리며 노는(Idle) &lt;strong&gt;병목 현상&lt;/strong&gt;이 발생한다. 부하는 그리는 행위(연산) 그 자체와 명령 하달 과정에서 발생한다.&lt;/p&gt;
&lt;h2&gt;3. 최적화 기법: 하드웨어 인스턴싱 (Hardware Instancing)&lt;/h2&gt;
&lt;p&gt;엔진은 드로우 콜 병목을 해결하기 위해 하드웨어 인스턴싱이라는 최적화 기법을 사용한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;기존 방식:&lt;/strong&gt; 100개의 분수대를 그리기 위해 CPU가 GPU에게 100번의 드로우 콜을 보냄.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인스턴싱 방식:&lt;/strong&gt; 버텍스 정보는 1번만 참조하되, 100개의 분수대 &lt;code&gt;위치 정보(Transform Matrix)&lt;/code&gt;를 배열로 묶어 GPU에 전달. CPU는 &quot;이 1개의 버텍스 데이터를 사용해서, 여기 전달한 100개의 위치에 한 번에 그려라&quot;라는 &lt;code&gt;단일 명령(1 Draw Call)&lt;/code&gt;을 내림.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 통해 CPU의 오버헤드를 극적으로 줄이고, GPU의 연산 유닛(ALU)을 효율적으로 점유하여 렌더링을 수행할 수 있다.&lt;/p&gt;
&lt;h2&gt;4. C++ 생성자 코드와의 연관성&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// 컴포넌트 생성 및 에셋 참조
Body = CreateDefaultSubobject&amp;lt;UStaticMeshComponent&amp;gt;(TEXT(&quot;Body&quot;));
static ConstructorHelpers::FObjectFinder&amp;lt;UStaticMesh&amp;gt; BodyMeshRef(TEXT(&quot;경로&quot;));
if (BodyMeshRef.Succeeded())
{
	Body-&amp;gt;SetStaticMesh(BodyMeshRef.Object);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위의 C++ 코드는 단순히 컴포넌트를 만들고 에셋을 끼워 넣는 과정처럼 보이지만, 엔진 구조적으로는 &lt;strong&gt;CDO(Class Default Object)&lt;/strong&gt; 를 구성하는 단계다.&lt;/p&gt;
&lt;p&gt;런타임에 동적으로 에셋을 복사해오는 것이 아니라, 엔진 초기화 단계에서 &lt;code&gt;ConstructorHelpers&lt;/code&gt;를 통해 에셋을 단 한 번 메모리에 적재해 CDO를 완성한다. 이후 게임 내에서 스폰되는 모든 해당 클래스의 인스턴스들은 이 CDO에 적재된 에셋의 주소값만을 안전하게 참조(Reference)하도록 설계되어 있다.&lt;/p&gt;
&lt;h2&gt;5. 심화: 하이폴리곤 메시 자체의 크기는 부하를 주지 않는가?&lt;/h2&gt;
&lt;p&gt;앞서 설명한 내용이 CPU와 GPU 간의 &lt;code&gt;통신 병목(Draw Call)&lt;/code&gt;에 대한 것이라면, 메시 자체가 가진 버텍스 수(5만~10만 개 이상의 하이폴리곤)는 GPU 내부의 연산 병목을 유발한다. 메모리(VRAM)에 한 번만 올라가서 공유되더라도, 매 프레임 이를 처리하는 연산량은 정직하게 버텍스 수에 비례하기 때문이다.&lt;/p&gt;
&lt;h3&gt;5-1. 버텍스 쉐이더(Vertex Shader) 연산&lt;/h3&gt;
&lt;p&gt;GPU는 렌더링할 때 메시에 포함된 모든 버텍스의 화면상 &lt;code&gt;위치를 계산(Transform)&lt;/code&gt;해야 한다. 버텍스가 10만 개라면, 화면에 1번 그릴 때마다 GPU 연산 유닛(ALU)이 10만 번의 버텍스 쉐이더 프로그램을 실행해야 한다. 여러 개를 배치하면 이 연산량은 배로 늘어난다.&lt;/p&gt;
&lt;h3&gt;5-2. USkeletalMesh의 스키닝(Skinning) 비용&lt;/h3&gt;
&lt;p&gt;정적인 &lt;code&gt;UStaticMesh&lt;/code&gt;와 달리, 애니메이션이 들어간 하이폴리곤 캐릭터의 &lt;code&gt;USkeletalMesh&lt;/code&gt;는 높은 부하를 유발한다. 매 프레임 캐릭터가 움직일 때마다 10만 개의 버텍스는 자신과 연결된 뼈대(Bone)의 가중치(Weight)를 바탕으로 위치를 재계산해야 한다. 이 행렬 곱셈 연산이 GPU에 큰 부하를 가한다.&lt;/p&gt;
&lt;h3&gt;5-3. 쿼드 오버드로우(Quad Overdraw)와 마이크로 트라이앵글(Micro-triangle)&lt;/h3&gt;
&lt;p&gt;하이폴리곤 메시는 화면에 작게 표시될 때 1픽셀보다 &lt;code&gt;Micro-triangle&lt;/code&gt;들을 무수히 만들어낸다. GPU는 픽셀을 칠할 때 2x2(Quad) 단위로 병렬 처리하는데, 삼각형이 너무 조밀하면 실제 그릴 필요가 없는 픽셀까지 연산에 포함되어 렌더링 효율이 급락한다.&lt;/p&gt;
&lt;h3&gt;5-4. 언리얼 엔진의 해결책&lt;/h3&gt;
&lt;p&gt;이러한 GPU 연산 부하를 막기 위해 엔진은 다음과 같은 기술을 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;StaticMesh&lt;/strong&gt;: 언리얼 엔진 5의 나나이트(Nanite) 기술을 통해 화면에 보이는 픽셀 수준으로 폴리곤을 실시간 병합/분할하여 버텍스 연산 부하를 최적화한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SkeletalMesh (캐릭터):&lt;/strong&gt; 뼈대 애니메이션 연산 때문에 나나이트 적용이 까다롭다. 따라서 거리에 따라 폴리곤 수를 강제로 줄여 연산량을 통제하는 LOD(Level of Detail) 적용이 필수적이다.&lt;/p&gt;
&lt;h2&gt;6. 정리&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;메시는 복사되지 않고 한 번만 로드되어 &lt;strong&gt;참조&lt;/strong&gt;된다. (메모리 최적화)&lt;/li&gt;
&lt;li&gt;다수의 &lt;code&gt;StaticMesh&lt;/code&gt;배치시에 발생하는 부하 --&amp;gt; ~X 데이터 복사로 인한 메모리 부하? X~ , &lt;strong&gt;반복 연산&lt;/strong&gt;과 &lt;strong&gt;드로우 콜 오버헤드&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;하이폴리곤 &lt;code&gt;USkeletalMesh&lt;/code&gt;에 발생하는 부하 --&amp;gt; GPU 내부의 &lt;strong&gt;버텍스 쉐이더&lt;/strong&gt; 및 &lt;strong&gt;스키닝 연산 병목&lt;/strong&gt;과 &lt;strong&gt;나나이트 적용의 어려움&lt;/strong&gt;(Quad Overdraw)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;6. Reference&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://fragmentbuffer.com/gpu-performance-for-game-artists/&quot;&gt;GPU Performance for Game Artists (FragmentBuffer)&lt;/a&gt;&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;렌더링 파이프라인(Draw Call, Vertex/Pixel Shader)과 메모리 대역폭이 실제 게임 에셋(Mesh, Texture)에 어떤 방식으로 병목을 일으키는지 상세히 풀어낸 아티클입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/System] 언리얼 엔진 데이터 관리 및 시스템 구조 </title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/4_assets/uepart1-13-1415/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/4_assets/uepart1-13-1415/</guid><description>UE5 기준 C++ Serialization, UPackage Asset 관리, Module&amp;Plugin 빌드 시스템 구조를 알아보자</description><pubDate>Sat, 14 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;언리얼 엔진의 데이터 저장 방식부터 모듈 시스템까지 묶어서 정리하였다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 직렬화 (Serialization)&lt;/h2&gt;
&lt;p&gt;언리얼에서 UObject의 데이터를 저장하고 불러들이는 기본 원리이다.&lt;/p&gt;
&lt;h3&gt;1.1 직렬화와 언리얼의 방식&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;직렬화(Serialization):&lt;/strong&gt; 메모리 상의 오브젝트를 디스크 저장이나 네트워크 전송이 가능한 바이트 스트림(Byte Stream)으로 변환하는 과정이다. 역직렬화(Deserialization)는 그 반대다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;언리얼의 구현:&lt;/strong&gt; 언리얼은 &lt;code&gt;FArchive&lt;/code&gt; 클래스와 시프트 연산자(&lt;code&gt;&amp;lt;&amp;lt;&lt;/code&gt;)를 재정의하여 사용한다.&lt;/li&gt;
&lt;li&gt;저장과 로딩 모두 &lt;code&gt;Ar &amp;lt;&amp;lt; Data&lt;/code&gt; 하나의 코드로 통일하여 작성한다. (아카이브 내부 상태인 &lt;code&gt;IsLoading()&lt;/code&gt;, &lt;code&gt;IsSaving()&lt;/code&gt;이 방향을 결정함)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 실습: 일반 C++ 구조체 직렬화&lt;/h3&gt;
&lt;p&gt;가장 기초적인 단계로 일반 구조체를 파일로 입출력하는 방법이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct FStudentData
{
    int32 Order = -1;
    FString Name = TEXT(&quot;기본이름&quot;);

    // 직렬화를 위한 연산자 오버로딩 (FArchive 통로 약속)
    friend FArchive&amp;amp; operator&amp;lt;&amp;lt;(FArchive&amp;amp; Ar, FStudentData&amp;amp; InData)
    {
        Ar &amp;lt;&amp;lt; InData.Order;
        Ar &amp;lt;&amp;lt; InData.Name;
        return Ar;
    }
};

// 파일 저장 예시
const FString RawDataAbsolutePath = FPaths::Combine(*SavedDir, TEXT(&quot;RawData.bin&quot;));
FStudentData RawDataSrc(16, TEXT(&quot;홍길동&quot;));

FArchive* FileWriterAr = IFileManager::Get().CreateFileWriter(*RawDataAbsolutePath);
if (FileWriterAr)
{
    *FileWriterAr &amp;lt;&amp;lt; RawDataSrc; // 오버로딩된 operator&amp;lt;&amp;lt; 호출
    FileWriterAr-&amp;gt;Close();
    delete FileWriterAr;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;1.3 실습: UObject 직렬화 (2단계 방식)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;UObject&lt;/code&gt;는 자체적으로 &lt;code&gt;Serialize&lt;/code&gt; 함수를 가진다. 보통 객체를 &lt;strong&gt;메모리 버퍼&lt;/strong&gt;로 먼저 변환한 뒤, 그 버퍼를 &lt;strong&gt;파일&lt;/strong&gt;로 저장하는 2단계 방식을 주로 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 1. 객체를 메모리(Buffer)에 쓰기
TArray&amp;lt;uint8&amp;gt; BufferArray;
FMemoryWriter MemoryWriterAr(BufferArray); 
StudentSrc-&amp;gt;Serialize(MemoryWriterAr); 

// 2. 메모리를 파일에 쓰기 (TUniquePtr 활용으로 자동 메모리 해제)
if (TUniquePtr&amp;lt;FArchive&amp;gt; FileWriterAr = TUniquePtr&amp;lt;FArchive&amp;gt;(IFileManager::Get().CreateFileWriter(*ObjectPath)))
{
    *FileWriterAr &amp;lt;&amp;lt; BufferArray; 
    FileWriterAr-&amp;gt;Close();
}

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;JSON 직렬화 팁&lt;/strong&gt;
JSON 포맷을 사용하려면 &lt;code&gt;Build.cs&lt;/code&gt;에 반드시 &lt;code&gt;&quot;Json&quot;&lt;/code&gt;, &lt;code&gt;&quot;JsonUtilities&quot;&lt;/code&gt; 모듈을 추가해야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;흐름: &lt;code&gt;[UObject] &amp;lt;-&amp;gt; (Converter) &amp;lt;-&amp;gt; [FJsonObject] &amp;lt;-&amp;gt; (Serializer) &amp;lt;-&amp;gt; [String]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 패키지(Package)와 에셋(Asset)&lt;/h2&gt;
&lt;p&gt;직렬화된 데이터를 에디터가 인식할 수 있는 &lt;code&gt;.uasset&lt;/code&gt; 형태로 관리하는 구조이다.&lt;/p&gt;
&lt;h3&gt;2.1 패키지와 에셋의 관계&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;패키지 (&lt;code&gt;UPackage&lt;/code&gt;): 여러 언리얼 오브젝트를 포장하는 최상위 오브젝트이다.&lt;/li&gt;
&lt;li&gt;에셋 (Asset): 패키 내부에 포함된 오브젝트 중 에디터 콘텐츠 브라우저에 노출되는 메인 오브젝트이다.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;일반적으로 1개의 패키지는 1개의 에셋을 가진다.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 오브젝트 패스와 에셋 로딩 전략&lt;/h3&gt;
&lt;p&gt;에셋은 메모리 관리를 위해 고유한 경로인 오브젝트 패스 (Object Path)로 관리된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;형식: &lt;code&gt;패키지명.에셋명&lt;/code&gt; (예: &lt;code&gt;/Game/Student.TopStudent&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;메모리 부하를 줄이기 위해 필요한 시점에 로딩하는 전략을 사용한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;생성자 로딩 (&lt;code&gt;ConstructorHelpers&lt;/code&gt;): 클래스 생성자에서 미리 로드. (에러 시 크래시 주의)&lt;/li&gt;
&lt;li&gt;동기 로딩 (&lt;code&gt;LoadObject&lt;/code&gt;): 런타임에 즉시 로드. (프레임 드랍 유발 가능)&lt;/li&gt;
&lt;li&gt;비동기 로딩 (&lt;code&gt;StreamableManager&lt;/code&gt;): 백그라운드에서 로딩 후 콜백 실행. (대규모 게임 필수)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 비동기 로딩 예시
FStreamableManager StreamableManager;
const FString Path = TEXT(&quot;/Game/Student.TopStudent&quot;);

StreamableManager.RequestAsyncLoad(Path, [&amp;amp;]() 
{
    // 로딩 완료 후 실행될 람다
});

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 모듈(Module)과 플러그인(Plugin)&lt;/h2&gt;
&lt;p&gt;저장된 데이터와 작성된 코드들이 실제로 어떻게 엔진에서 뭉쳐서 돌아가는지에 대한 구조이다.&lt;/p&gt;
&lt;h3&gt;3.1 C++ 프로젝트와 모듈&lt;/h3&gt;
&lt;p&gt;언리얼 엔진의 모든 소스 코드는 &lt;code&gt;모듈(Module)&lt;/code&gt; 단위로 구성된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;블루프린트 프로젝트는 엔진 기본 모듈만 사용하지만, C++ 프로젝트는 개발자가 직접 작성한 커스텀 C++ 모듈을 엔진에 추가하여 작동하는 원리이다.&lt;/li&gt;
&lt;li&gt;컴파일 결과물은 에디터용 빌드 시 &lt;code&gt;DLL 동적 라이브러리&lt;/code&gt; 형태로 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 UBT (Unreal Build Tool)&lt;/h3&gt;
&lt;p&gt;언리얼은 멀티 플랫폼 빌드를 위해 Visual Studio에 종속되지 않고 자체적인 UBT (C# 프로그램)를 사용한다.&lt;/p&gt;
&lt;p&gt;이를 위해 두 가지 중요한 C# 설정 파일이 필요하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.Target.cs&lt;/code&gt; (타겟 설정): 게임/에디터 등 전체 솔루션의 빌드 환경 설정.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.Build.cs&lt;/code&gt; (모듈 설정): 특정 모듈의 의존성(위의 JSON 모듈 추가 등) 설정.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;[UE 5.7 기준 Editor 타겟 파일 예시]&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;using UnrealBuildTool;
using System.Collections.Generic;

public class UnrealBuildSystemEditorTarget : TargetRules
{
    public UnrealBuildSystemEditorTarget(TargetInfo Target) : base(Target)
    {
        Type = TargetType.Editor;
        DefaultBuildSettings = BuildSettingsVersion.V6;
        IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_7;
        CppStandard = CppStandardVersion.Cpp20; // C++20 지원
        
        bOverrideBuildEnvironment = true; 
        ExtraModuleNames.AddRange(new string[] { &quot;UnrealBuildSystem&quot; });
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;요약 (System Architecture Flow)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;코드 (Module): &lt;code&gt;.Build.cs&lt;/code&gt;와 &lt;code&gt;.Target.cs&lt;/code&gt;를 통해 C++ 코드를 UBT가 DLL 모듈로 컴파일한다.&lt;/li&gt;
&lt;li&gt;데이터 생성 (Serialization): 컴파일된 코드가 실행되며 &lt;code&gt;FArchive&lt;/code&gt;를 통해 데이터를 직렬화한다.&lt;/li&gt;
&lt;li&gt;포장 및 관리 (Package &amp;amp; Asset): 직렬화된 데이터는 &lt;code&gt;UPackage&lt;/code&gt;로 묶여 콘텐츠 브라우저에 &lt;code&gt;.uasset&lt;/code&gt;으로 노출되며, 오브젝트 패스를 통해 효율적으로 로드된다.&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>[Algorithm] Permutation Cycle Decomposition</title><link>https://heparidayo.com/posts/algorithm/permutationcycle/</link><guid isPermaLink="true">https://heparidayo.com/posts/algorithm/permutationcycle/</guid><description>순열 사이클 분할 개념, DFS를 이용해 배열 정렬 최소 교환 횟수를 구하는 방법을 알아보자.</description><pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;개념&lt;/h1&gt;
&lt;p&gt;알고리즘 문제에서 배열을 정렬하기 위해 필요한 &lt;code&gt;최소 교환 횟수&lt;/code&gt;를 묻는다면, 높은 확률로 순열 사이클 분할 문제이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$N$개의 원소로 이루어진 순열&lt;/li&gt;
&lt;li&gt;현재 인덱스($i$)와 그 위치에 있는 값($A[i]$)을 간선으로 연결하면($i \rightarrow A[i]$)&lt;/li&gt;
&lt;li&gt;배열은 서로 겹치지 않는 여러 개의 사이클(Cycle)로 분해된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이것을 순열 사이클 분할(Permutation Cycle Decomposition)이라고 한다.&lt;/p&gt;
&lt;h2&gt;예시&lt;/h2&gt;
&lt;p&gt;아래와 같은 배열이 있다고 가졍해보자.&lt;/p&gt;
&lt;p&gt;$$N = 5, \quad Arr = { 2, 4, 5, 1, 3 }$$&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;1-based index ($i$)&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;값 ($A[i]$)&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이 관계를 그래프로 따라가 보면 2개의 독립적인 사이클이 만들어진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cycle 1&lt;/strong&gt;: $1 \rightarrow 2 \rightarrow 4 \rightarrow 1$ (길이 3)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cycle 2&lt;/strong&gt;: $3 \rightarrow 5 \rightarrow 3$ (길이 2)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;최소 교환 횟수 공식&lt;/h2&gt;
&lt;p&gt;순열 사이클 분할을 이용하면 배열을 정렬하는 데 필요한 &lt;strong&gt;최소 교환 횟수&lt;/strong&gt;를 구할 수 있다.&lt;/p&gt;
&lt;p&gt;$$\text{최소 교환 횟수} = N - (\text{사이클의 개수})$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;왜 이런 공식이 나올까?&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;길이가 1인 사이클&lt;/strong&gt;: 이미 제자리에 있는 원소이므로, 교환이 필요 없다. (0회)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;길이가 $K$인 사이클&lt;/strong&gt;: $K$개의 원소가 서로 자리를 바꾸며 얽혀 있다. 이를 모두 제자리로 보내려면 &lt;strong&gt;$K-1$번의 교환&lt;/strong&gt;이 필요하다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;전체 $N$개의 노드가 $C$개의 사이클로 분할되어 있고, 각 사이클의 길이를 $L_1, L_2, \dots, L_C$라고 할때, 전체 교환 횟수는 각 사이클을 정렬하는 횟수의 합이다.&lt;/p&gt;
&lt;p&gt;$$\begin{aligned} \text{Total Swaps} &amp;amp;= \sum_{i=1}^{C} (L_i - 1) \ &amp;amp;= \sum_{i=1}^{C} L_i - \sum_{i=1}^{C} 1 \ &amp;amp;= N - C \end{aligned}$$&lt;/p&gt;
&lt;p&gt;위의 예시 ${ 2, 4, 5, 1, 3 }$에 적용해 보면:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전체 노드 개수 $N$ = 5&lt;/li&gt;
&lt;li&gt;사이클의 개수 $C$ = 2&lt;/li&gt;
&lt;li&gt;최소 교환 횟수 = $5 - 2 = 3$번&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;예제&lt;/h1&gt;
&lt;h2&gt;메인 로직&lt;/h2&gt;
&lt;p&gt;DFS나 BFS로 미방문 노드 사이클을 탐색하고, 그 횟수를 카운팅한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;
const int INF = 0x3f3f3f3f;

int n;
int arr[INF];
bool vis[INF];

int main()
{
    int cnt = 0;

    for (int i = 1; i &amp;lt;= n; i++)
    {
        // // 미 방문 노드 발견 (새로운 사이클 발견)
        if (!vis[i])
        {
            cnt++;
            int cur = i;

            // DFS
            while (!vis[cur])
            {
                vis[cur] = true;
                cur = arr[cur]; // 다음 노드로 이동
            }
        }
    }

    // n - 사이클 개수
    cout &amp;lt;&amp;lt; n - cnt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;연습 문제 - BOJ 10451 순열 사이클&lt;/h2&gt;
&lt;p&gt;순열 사이클의 수를 출력하는 문제다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

int main()
{
    int t; cin &amp;gt;&amp;gt; t;
    while (t--)
    {
        int n; cin &amp;gt;&amp;gt; n;
        vector&amp;lt;int&amp;gt; vec(n + 1);
        for (int i = 1; i &amp;lt;= n; i++)
            cin &amp;gt;&amp;gt; vec[i];

        int cnt = 0;
        vector&amp;lt;bool&amp;gt; vis(n + 1, false);
        for (int i = 1; i &amp;lt;= n; i++)
        {
            // 사이클 찾았음
            if (vis[i] == false)
            {
                cnt += 1;
                int cur = i;

                // DFS
                while (vis[cur] == false)
                {
                    vis[cur] = true;
                    cur = vec[cur];
                }
            }
        }
        cout &amp;lt;&amp;lt; cnt &amp;lt;&amp;lt; &apos;\n&apos;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;연습 문제 - BOJ 34078 벨과 와이즈의 가게 홍보&lt;/h2&gt;
&lt;p&gt;내림차순 케이스를 조심하자..&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;vector&amp;gt;
using namespace std;

int main()
{
    int n; cin &amp;gt;&amp;gt; n;
    vector&amp;lt;int&amp;gt; vec(n + 1, 0);
    for (int i = 1; i &amp;lt;= n; i++)
        cin &amp;gt;&amp;gt; vec[i];

    // 웅나 수 = n탐색 돌리기
    // 웅나 시간 = n - 사이클의 수

    int cnt1 = 0;
    vector&amp;lt;bool&amp;gt; vis(n + 1, false);
    for (int i = 1; i &amp;lt;= n; i++)
    {
        if (vis[i] == false)
        {
            int cur = i;
            cnt1 += 1; // 사이클 시작점 발 견
            while (vis[cur] == false)
            {
                vis[cur] = true;
                cur = vec[cur];
            }
        }
    }

    // 내림차순 검사 { 4 3 2 1 } 케이스
    int cnt2 = 0;
    vis.assign(n + 1, false);
    for (int i = 1; i &amp;lt;= n; i++)
    {
        if (vis[i] == false)
        {
            int cur = i;
            cnt2 += 1; // 사이클 시작점 발 견
            while (vis[cur] == false)
            {
                vis[cur] = true;
                cur = vec[n - cur + 1];
            }
        }
    }
	
	// 모든 웅나의 키는 다르므로, n-2
    cout &amp;lt;&amp;lt; n - 2 &amp;lt;&amp;lt; &apos; &apos; &amp;lt;&amp;lt; n - max(cnt1, cnt2);

}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[UE5/Data] 12. UE GC &amp; FGCObject</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/3_datastructmemory/uepart1-12/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/3_datastructmemory/uepart1-12/</guid><description>언리얼 GC의 동작 원리와 주의점 및 C++ 클래스를 보호하는 FGCObject를 알아보자</description><pubDate>Wed, 11 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;C++ 언어 메모리 관리의 문제점과 GC&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;잘못된 포인터 사용 예시
&lt;ul&gt;
&lt;li&gt;메모리 누수 (Leak) : new &amp;lt;-&amp;gt; delete 짝을 못 맞춤. 힙에 메모리가 그대로 남음&lt;/li&gt;
&lt;li&gt;허상 포인터 (Dangling Pointer) : 이미 해제하여 무효화된 오브젝트의 주소를 가리키는 포인터&lt;/li&gt;
&lt;li&gt;와일드 포인터 (Wild Pointer) : 값이 초기화되지 않아 엉뚱한 주소를 가리키는 포인터&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GC 시스템&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;동적으로 생성된 모든 오브젝트 정보를 모아둔 저장소를 사용해 사용되지 않는 메모리를 추적&lt;/li&gt;
&lt;li&gt;마크-스윕(Mark-Sweep) 방식
&lt;ul&gt;
&lt;li&gt;GCCycle 기본값 60초&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;병렬처리, 클러스터링 - 성능 향상을 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GC를 위한 객체 저장소&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;GUObjectArray: 관리되는 모든 UObject 정보를 저장하는 전역 변수&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;lt;Object, Flag&amp;gt; 구조로 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;각 요소에는 플래그(Flag)가 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Garbage Flag : 참조가 없어 회수 예정&lt;/li&gt;
&lt;li&gt;RootSet Flag : 참조가 없어도 회수X (특별한 오브젝트)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GC의 메모리 회수&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;지정된 시간에 따라 주기적으로 메모리 회수 (기본 값 60초)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Garbage Flag로 설정된 오브젝트를 파악하고 메모리를 안전하게 회수&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Garbage Flag는 수동으로 설정 X, 시스템이 알아서 설정함&lt;/p&gt;
&lt;p&gt;한 번 생성된 언리얼 오브젝트는 바로 삭제가 불가능함. &lt;code&gt;Delete&lt;/code&gt; 키워드로 삭제하는 게 아님, 레퍼런스 정보를 없애는 것으로 GC가 자동으로 메모리를 회수하도록 설정&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;RootSet Flag 설정&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AddToRoot&lt;/code&gt; 함수를 호출해 루트셋 플래그를 설정하면 최초 탐색 목록으로 설정됨
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RootSet&lt;/code&gt;으로 설정된 언리얼 오브젝트는 메모리 회수로부터 보호받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RemoveFromRoot&lt;/code&gt; 함수를 호출해 루트셋 플래그를 제거할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;콘텐츠를 만들 때 권장하는 방식은 아님&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;언리얼 오브젝트를 통한 포인터 문제의 해결&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;메모리 누수 문제&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GC를 통해 자동 해결&lt;/li&gt;
&lt;li&gt;&lt;em&gt;C++ 오브젝트는 직접 신경 써야 함 (or 스마트 포인터 사용)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;댕글링 포인터 문제&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;탐지를 위한 함수 제공 &lt;code&gt;::IsValid()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;C++ 오브젝트는 직접 신경 써야 함 (or 스마트 포인터 사용)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;와일드 포인터 문제&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UPROPERTY 속성을 지정하면 자동으로 &lt;code&gt;nullptr&lt;/code&gt;로 초기화해 줌&lt;/li&gt;
&lt;li&gt;&lt;em&gt;C++ 오브젝트의 포인터는 직접 &lt;code&gt;nullptr&lt;/code&gt;로 초기화 필요 (or 스마트 포인터 사용)&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;회수되지 않는 언리얼 오브젝트&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;언리얼 엔진 방식으로 참조를 설정한 언리얼 오브젝트&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UPROPERTY로 참조된 언리얼 오브젝트 (대부분 이걸 사용)&lt;/li&gt;
&lt;li&gt;AddReferencedObjects 함수를 통해 참조를 설정한 오브젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RootSet으로 지정된 언리얼 오브젝트&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;오브젝트 포인터는 가급적 &lt;code&gt;UPROPERTY&lt;/code&gt;로 선언하고, 메모리는 가비지 컬렉터가 자동으로 관리하도록 위임한다.&lt;/p&gt;
&lt;h2&gt;일반 클래스에서 언리얼 오브젝트를 관리하는 경우?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;UPROPERTY를 사용하지 못하는 일반 C++ 클래스가 언리얼 오브젝트를 관리해야 하는 경우&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;FGCObject&lt;/code&gt; 클래스를 상속받은 후 &lt;code&gt;AddReferencedObjects&lt;/code&gt; 함수를 구현한다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;함수 구현부에서 관리할 언리얼 오브젝트를 추가해 줌&lt;/p&gt;
&lt;p&gt;아래 실습에서 직접 사용해보자.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;언리얼 오브젝트의 관리 원칙&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;생성된 언리얼 오브젝트를 유지하기 위해 레퍼런스 참조 방법을 설계할 것
&lt;ul&gt;
&lt;li&gt;언리얼 오브젝트 내의 언리얼 오브젝트 : UPROPERTY 사용&lt;/li&gt;
&lt;li&gt;일반 C++ 내의 언리얼 오브젝트 : FGCObject의 상속 후 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생성된 언리얼 오브젝트는 강제로 지우려 하지 말 것
&lt;ul&gt;
&lt;li&gt;참조를 끊는다는 생각으로 설계&lt;/li&gt;
&lt;li&gt;GC에게 회수를 재촉 가능 (ForceGarbageCollection 함수)&lt;/li&gt;
&lt;li&gt;콘텐츠 제작에서 Destroy 함수를 사용할 수 있으나, 결국 내부 동작은 똑같음 (GC 위임)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;h2&gt;GC 테스트 환경 제작&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;프로젝트 설정에서 GCCycle 시간을 3초로 단축 설정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;새로운 GameInstance의 두 함수를 오버라이드&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Init: 어플리케이션이 초기화될 때 호출&lt;/li&gt;
&lt;li&gt;Shutdown: 어플리케이션이 종료될 때 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;테스트 시나리오&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;플레이 버튼을 누를 때 Init 함수에서 오브젝트를 생성&lt;/li&gt;
&lt;li&gt;3초 이상 대기해 GC 발동&lt;/li&gt;
&lt;li&gt;플레이 중지를 눌러 Shutdown 함수에서 생성한 오브젝트의 유효성을 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;실습 1. UObject의 UPROPERTY() 유무 차이&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private:
	TObjectPtr&amp;lt;class UStudent&amp;gt; NonPropStudent;
	
	UPROPERTY()
	TObjectPtr&amp;lt;class UStudent&amp;gt; PropStudent;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NonPropStudent = NewObject&amp;lt;UStudent&amp;gt;(); // UPROPERTY() X

PropStudent = NewObject&amp;lt;UStudent&amp;gt;(); // UPROPERTY() O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;댕글링 포인터 문제 발생. (UPROPERTY가 없어 GC가 정리)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: NonPropStudent - 널 포인터가 아닌 언리얼 오브젝트
LogTemp: NonPropStudent - 유효하지 않은 언리얼 오브젝트

LogTemp: PropStudent - 널 포인터가 아닌 언리얼 오브젝트
LogTemp: PropStudent - 유효한 언리얼 오브젝트
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;실습 2. TArray&amp;lt;TPtr*&amp;gt; UPROPERTY() 유무 차이&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TArray&amp;lt;TObjectPtr&amp;lt;class UStudent&amp;gt;&amp;gt; NonPropStudents;

UPROPERTY()
TArray&amp;lt;TObjectPtr&amp;lt;class UStudent&amp;gt;&amp;gt; PropStudents;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 실습 2
NonPropStudents.Add(NewObject&amp;lt;UStudent&amp;gt;());

PropStudents.Add(NewObject&amp;lt;UStudent&amp;gt;());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UPROPERTY()가 없을 경우 배열 내부에 Ptr에 댕글링 포인터 발생 (GC 정리)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: NonPropStudents[0] - 널 포인터가 아닌 언리얼 오브젝트
LogTemp: NonPropStudents[0] - 유효하지 않은 언리얼 오브젝트

LogTemp: PropStudents[0] - 널 포인터가 아닌 언리얼 오브젝트
LogTemp: PropStudents[0] - 유효한 언리얼 오브젝트
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;실습 3. 일반 C++ 객체 안에 있는 UObject&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FStudentManager* StudentManager = nullptr;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 실습 3
// Manager는 일반 C++ 객체
// 안에 있는 UObject를 관리할 수 있는 능력이 전혀 없음
// 이 클래스는 UProperty 같은 걸 쓸 수 없기 때문에
// GC가 발동되면 안에 있는 UObject가 가비지 컬렉션 대상이 되어버림
StudentManager = new FStudentManager(NewObject&amp;lt;UStudent&amp;gt;());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;댕글링 포인터 발생 (C++ 내부 객체를 GC가 정리)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: StudentInManager - 널 포인터가 아닌 언리얼 오브젝트
LogTemp: StudentInManager - 유효하지 않은 언리얼 오브젝트
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;실습 4. FGCObject로 C++ 객체 내부 보호&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;FGCObject&lt;/code&gt;를 상속받고 다음 2개의 함수를 구현해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GCObject.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;virtual void AddReferencedObjects( FReferenceCollector&amp;amp; Collector ) = 0;

/** Overload this method to report a name for your referencer */
virtual FString GetReferencerName() const = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아래와 같이 먼저 상속을 받은 후&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;StudentManager.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UNREALMEMORY_API FStudentManager : public FGCObject
{
public:
	FStudentManager(class UStudent* InStudent) : SafeStudent(InStudent) {}
	
	// 함수 오버라이드 1 
	virtual void AddReferencedObjects(FReferenceCollector&amp;amp; Collector) override;
	
	// 함수 오버라이드 2
	virtual FString GetReferencerName() const override
	{
		return TEXT(&quot;FStudentManager&quot;);
	}
	
private:
	class UStudent* SafeStudent = nullptr;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;AddReferencedObjects&lt;/code&gt;를 마저 구현한다: &lt;code&gt;AddReferencedObject(UStudent*)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;StudentManager.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;StudentManager.h&quot;
#include &quot;Student.h&quot;

void FStudentManager::AddReferencedObjects(FReferenceCollector&amp;amp; Collector)
{
	if (SafeStudent-&amp;gt;IsValidLowLevel())
	{
		Collector.AddReferencedObject(SafeStudent);
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;h2&gt;1. C++ 메모리 관리의 한계와 GC의 필요성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;문제점:&lt;/strong&gt; C++의 수동 메모리 관리는 누수(Leak), 댕글링 포인터(Dangling), 와일드 포인터(Wild) 등의 치명적인 오류를 유발하기 쉽다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결책:&lt;/strong&gt; 언리얼 엔진은 가비지 컬렉션(GC) 시스템을 도입하여 사용되지 않는 메모리를 자동으로 회수하고, 포인터의 유효성을 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. GC(Garbage Collection)의 동작 원리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mark-Sweep 방식:&lt;/strong&gt; 지정된 주기(기본 60초)마다 어플리케이션을 일시 정지하고 메모리를 검사한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RootSet:&lt;/strong&gt; GC의 시작점. 여기에 등록된 객체와, 그 객체가 참조하는 모든 객체는 &apos;사용 중&apos;으로 간주하여 회수하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;회수 대상:&lt;/strong&gt; RootSet에서 도달할 수 없는(Unreachable) 객체는 가비지(Garbage)로 간주하여 메모리를 해제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 언리얼 오브젝트(UObject) 보호 방법&lt;/h2&gt;
&lt;p&gt;GC가 멀쩡한 오브젝트를 회수해가지 않도록 &apos;참조&apos;를 명확히 해야 한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;언리얼 오브젝트 내부:&lt;/strong&gt; 멤버 변수 포인터에 반드시 &lt;code&gt;UPROPERTY()&lt;/code&gt; 매크로를 붙인다. (가장 권장됨)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;일반 C++ 클래스 내부:&lt;/strong&gt; &lt;code&gt;FGCObject&lt;/code&gt;를 상속받고 &lt;code&gt;AddReferencedObjects&lt;/code&gt; 함수를 오버라이드하여 참조를 알린다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특수 상황:&lt;/strong&gt; &lt;code&gt;AddToRoot&lt;/code&gt; 함수로 강제 보호할 수 있으나, 관리가 어려워 권장하지 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;4. 안전한 언리얼 코딩 표준&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;선언:&lt;/strong&gt; &lt;code&gt;UObject&lt;/code&gt;를 가리키는 모든 포인터는 &lt;code&gt;TObjectPtr&amp;lt;T&amp;gt;&lt;/code&gt;와 &lt;code&gt;UPROPERTY()&lt;/code&gt;를 조합하여 선언한다.
&lt;ul&gt;
&lt;li&gt;자동 초기화(nullptr), 댕글링 포인터 감지, 참조 카운팅이 자동으로 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;생성:&lt;/strong&gt; &lt;code&gt;new&lt;/code&gt; 대신 &lt;code&gt;NewObject&amp;lt;T&amp;gt;()&lt;/code&gt;를 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;소멸:&lt;/strong&gt; &lt;code&gt;delete&lt;/code&gt;를 절대 직접 호출하지 않는다.
&lt;ul&gt;
&lt;li&gt;필요 없다면 &lt;code&gt;nullptr&lt;/code&gt;를 대입해 참조를 끊거나, &lt;code&gt;ConditionalBeginDestroy()&lt;/code&gt; 등을 사용해 GC에게 처리를 위임한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검증:&lt;/strong&gt; 포인터 사용 전 &lt;code&gt;IsValid()&lt;/code&gt; 또는 &lt;code&gt;::IsValid()&lt;/code&gt;로 유효성을 검증하는 습관을 들인다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;실제로 코딩할때..&lt;/h2&gt;
&lt;h3&gt;1. UPROPERTY() 필수&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UObject&lt;/code&gt; 상속 클래스의 멤버 변수 포인터를 선언할 때 &lt;code&gt;UPROPERTY()&lt;/code&gt; 매크로를 잊지 말자.
&lt;ul&gt;
&lt;li&gt;이걸 빼먹으면 GC가 마음대로 메모리를 회수해간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. NewObject&amp;lt;T&amp;gt;() 사용&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UObject&lt;/code&gt;를 생성할 때 습관적으로 C++의 &lt;code&gt;new&lt;/code&gt;를 쓰지 말고, 반드시 &lt;code&gt;NewObject&amp;lt;T&amp;gt;()&lt;/code&gt;를 사용하자.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;new&lt;/code&gt;로 만든 &lt;code&gt;UObject&lt;/code&gt;는 GC의 보호를 전혀 받지 못한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. IsValid() 습관화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;포인터 변수에 접근(&lt;code&gt;-&amp;gt;&lt;/code&gt;)하기 전, &lt;code&gt;IsValid()&lt;/code&gt; 혹은 &lt;code&gt;::IsValid()&lt;/code&gt;로 유효성을 체크하는 습관을 가지자.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 일반 클래스는 조심 (FGCObject)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;일반 C++ 클래스(구조체, 매니저 등)에 &lt;code&gt;UObject&lt;/code&gt; 포인터를 멤버로 넣어야 한다면 &lt;code&gt;FGCObject&lt;/code&gt; 상속을 고려하자.
&lt;ul&gt;
&lt;li&gt;일반 클래스는 &lt;code&gt;UPROPERTY&lt;/code&gt;를 못 쓴다. 그냥 두면 댕글링 포인터가 되어버린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. Destroy()의 GC 시차&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Actor-&amp;gt;Destroy()&lt;/code&gt;를 호출했다고 해서 그 즉시 메모리가 &lt;code&gt;null&lt;/code&gt;이 되는 게 아님을 기억하자.
&lt;ul&gt;
&lt;li&gt;GC가 돌기 전까지는 삭제 대기(Pending Kill) 상태로 메모리에 남아있다. 이 시차를 조심하자.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/Data] 11. UStruct &amp; Map</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/3_datastructmemory/uepart1-11/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/3_datastructmemory/uepart1-11/</guid><description>언리얼 구조체 UStruct의 사용법, TMap/TMultiMap의 특징 및 </description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;UStruct (언리얼 구조체)&lt;/h1&gt;
&lt;p&gt;여러 변수를 하나의 묶음으로 관리할 수 있게 해주는 사용자 정의 데이터 타입.&lt;/p&gt;
&lt;h2&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;경량 객체:&lt;/strong&gt; 데이터 저장 및 전송에 특화되어 있으며, &lt;code&gt;UObject&lt;/code&gt;보다 가볍다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;명명 규칙:&lt;/strong&gt; 언리얼 구조체의 이름은 접두사 &lt;code&gt;F&lt;/code&gt;로 시작 (예: &lt;code&gt;FStudentData&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;리플렉션 지원 (&lt;code&gt;GENERATED_BODY&lt;/code&gt;)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;엔진 내부적으로는 &lt;code&gt;UScriptStruct&lt;/code&gt; 클래스로 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;제약 사항&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UPROPERTY()&lt;/code&gt;를 통해 변수(속성)는 리플렉션에 노출 가능&lt;/li&gt;
&lt;li&gt;단, &lt;code&gt;UFUNCTION()&lt;/code&gt; 매크로는 사용X (블루프린트에서 호출 가능한 함수 생성 불가). C++ 멤버 함수 자체는 생성 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메모리 관리&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UObject&lt;/code&gt;와 달리 &lt;strong&gt;가비지 컬렉터(GC)의 자동 관리를 받지 않는다.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;주로 스택(Stack)에 할당하거나, 다른 클래스의 멤버 변수로 포함되어 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NewObject&amp;lt;T&amp;gt;()&lt;/code&gt; API를 사용할 수 없으며, &lt;code&gt;new&lt;/code&gt; 연산자나 스택 할당을 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;내부 구조 (리플렉션 계층)&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UStruct&lt;/code&gt;는 &lt;code&gt;UField&lt;/code&gt;를 상속&lt;/li&gt;
&lt;li&gt;내부에 &lt;code&gt;UField* Next&lt;/code&gt; 포인터 등을 통해 구조체 안에 정의된 속성(Property)들을 리스트 형태로 연결하여 관리 (위 그림에 포함(composition)관계)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;비교:&lt;/strong&gt; &lt;code&gt;UClass&lt;/code&gt;는 함수(&lt;code&gt;UFunction&lt;/code&gt;) 정보까지 포함하지만, &lt;code&gt;UScriptStruct&lt;/code&gt;는 데이터만&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;실습 코드&lt;/h1&gt;
&lt;h2&gt;구조체 &amp;amp; TArray 활용&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.h&lt;/strong&gt; : 배열로 구조체 관리 예제&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pragma once
#include &quot;CoreMinimal.h&quot;
#include &quot;Engine/GameInstance.h&quot;
#include &quot;MyGameInstance.generated.h&quot;

// 1. 구조체 선언
USTRUCT()
struct FStudentData
{
	GENERATED_BODY()
	
	// 기본 생성자 (리플렉션시 필요)
	FStudentData()
	{
		Name = TEXT(&quot;기본이름&quot;);
		Order = -1;
	}
	
	// 임의 생성자
	FStudentData(FString InName, int32 InOrder) : Name(InName), Order(InOrder) {}

	UPROPERTY()
	FString Name;

	UPROPERTY()
	int32 Order;
};

// 2. 클래스 선언
UCLASS()
class UNREALCONTAINER_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()

public:
	virtual void Init() override;

private:
	// 일반 구조체 배열 (GC관리 대상 아님)
	TArray&amp;lt;FStudentData&amp;gt; StudentsData;
	
	// 언리얼 오브젝트 포인터 배열
	// TArray 내부에서 포인터를 관리할때는 UPROPERTY() 매크로 필수
	// 없으면, GC가 참조 인식을 못해 객체를 삭제함 (Dangling Pointer - 크래시!)
	UPROPERTY() 
	TArray&amp;lt;TObjectPtr&amp;lt;class UStudent&amp;gt;&amp;gt; Students;
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;TArray 사용 시 주의사항&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;일반 C++ 타입 / 구조체 (&lt;code&gt;FStudentData&lt;/code&gt;)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TArray&lt;/code&gt;가 값 자체를 복사해서 들고 있으므로 &lt;code&gt;UPROPERTY()&lt;/code&gt;가 없어도 동작에 치명적인 문제 없음 (직렬화 제외)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;언리얼 오브젝트 포인터 (&lt;code&gt;TArray&amp;lt;UObject*&amp;gt;&lt;/code&gt;)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UPROPERTY()&lt;/code&gt; 매크로 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;TMap&lt;/h1&gt;
&lt;h2&gt;STL map vs TMap&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;std::map (C++ STL)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TMap (Unreal)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자료구조&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;이진 트리 (Binary Tree)&lt;/td&gt;
&lt;td&gt;해시 테이블 (Hash Table)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;유사 STL&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;&lt;code&gt;std::unordered_map&lt;/code&gt;과 유사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;정렬&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Key 기준 자동 정렬됨&lt;/td&gt;
&lt;td&gt;정렬되지 않음 (입력 순서 보장 X)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;메모리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;노드 기반 (메모리 파편화 가능성)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;희소 배열 (Sparse Array)&lt;/strong&gt; 기반&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;성능&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;검색/삽입/삭제: $O(\log N)$&lt;/td&gt;
&lt;td&gt;검색/삽입/삭제: 평균 $O(1)$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;TMap 구조 및 특징&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image-5.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key-Value 쌍&lt;/strong&gt; Key와 Value의 튜플 데이터(&lt;code&gt;TPair&amp;lt;Key, Value&amp;gt;&lt;/code&gt;)를 관리&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;희소 배열 구조&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;데이터 삭제 시 메모리를 재할당(Realloc)하거나 당겨오지 않고, 해당 자리를 &apos;비어있음&apos;으로 표시&lt;/li&gt;
&lt;li&gt;이 덕분에 STL보다 **캐시 적중률(Cache Locality)**이 높고 순회 속도가 빠름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TMultiMap&lt;/strong&gt; 중복된 Key를 허용해야 할 경우 사용 (&lt;code&gt;TMap&lt;/code&gt;은 Key 중복 불가능, 덮어씌워짐)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;선언 및 사용&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;// Key: int32, Value: FString
TMap&amp;lt;int32, FString&amp;gt; StudentMap;

// 데이터 추가 (기존 Key가 있으면 덮어씀)
StudentMap.Add(1, TEXT(&quot;이름1&quot;));
StudentMap.Emplace(2, TEXT(&quot;이름2&quot;)); 
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;TMap&lt;/code&gt;이나 &lt;code&gt;TSet&lt;/code&gt;의 Key로 커스텀 구조체(&lt;code&gt;FStruct&lt;/code&gt;)를 쓰려면 전역 함수 &lt;code&gt;GetTypeHash&lt;/code&gt;를 반드시 오버로딩&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;시간복잡도 비교&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;구분&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TArray (동적 배열)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TSet / TMap (해시 테이블)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;인덱스 접근&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$O(1)$&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;검색 (Find)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$O(N)$&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;평균 $O(1)$&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;삽입 (Add/Emplace)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$O(N)$&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;평균 $O(1)$&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;삭제 (Remove)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$O(N)$&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;평균 $O(1)$&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;메모리 특징&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;연속된 메모리 (빈틈 없음)&lt;/td&gt;
&lt;td&gt;희소 배열 (중간에 빈틈 존재)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;순차 접근/순회에 유리&lt;/td&gt;
&lt;td&gt;빠른 검색에 유리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>[UE5/Data] 10. TArray &amp; TSet</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/3_datastructmemory/uepart1-10/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/3_datastructmemory/uepart1-10/</guid><description>언리얼의 TArray와 TSet은 C++ STL과 어떻게 다른지 알아보자</description><pubDate>Mon, 09 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 대표 컨테이너 라이브러리 &lt;code&gt;TArray&lt;/code&gt;, &lt;code&gt;TSet&lt;/code&gt;의 내부 구조 이해&lt;/li&gt;
&lt;li&gt;각 컨테이너의 장단점 파악, 알맞게 활용하는 방법 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;언리얼 C++ 주요 컨테이너 라이브러리&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;Set&lt;/code&gt;과 &lt;code&gt;Map&lt;/code&gt;은 C++ STL과 내부 구현 방식이 다르다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;STL&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;UCL&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;특징&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;vector&lt;/td&gt;
&lt;td&gt;TArray&lt;/td&gt;
&lt;td&gt;순차적, 가장 빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;set&lt;/td&gt;
&lt;td&gt;TSet&lt;/td&gt;
&lt;td&gt;중복 허용 X, 빠른 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;map&lt;/td&gt;
&lt;td&gt;TMap&lt;/td&gt;
&lt;td&gt;키-값 쌍, 빠른 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;TArray&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/array-containers-in-unreal-engine?application_version=5.1&quot;&gt;공식 문서: TArray&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;가변 배열(Dynamic Array) 자료 구조이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;STL의 vector와 동작 원리가 유사하다.&lt;/li&gt;
&lt;li&gt;게임 제작에서 가장 효율적인 컨테이너이다.
&lt;ul&gt;
&lt;li&gt;데이터가 메모리에 연속적으로 위치해 캐시 지역성(Locality) 효율이 매우 높다.&lt;/li&gt;
&lt;li&gt;임의 데이터 접근(Random Access)이 빠르다. O(1)&lt;/li&gt;
&lt;li&gt;요소 순회 속도가 가장 빠르다. O(N)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;맨 끝에 데이터를 추가하는 것은 가볍다. (Amortized Constant)&lt;/li&gt;
&lt;li&gt;단점
&lt;ul&gt;
&lt;li&gt;중간 요소의 추가/삭제는 뒤의 요소들을 이동시켜야 하므로 비용이 크다. O(N)&lt;/li&gt;
&lt;li&gt;데이터가 많아질수록 검색 작업이 느려진다. O(N) (이 경우 TSet 고려)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Add vs Emplace&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Add&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;외부에서 객체를 생성 후, 컨테이너 내부로 복사(Copy) 혹은 이동(Move)한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Emplace&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컨테이너 내부에서 직접 생성자를 호출하여 객체를 만든다.&lt;/li&gt;
&lt;li&gt;임시 객체 생성 및 복사/이동 비용을 절약할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;int와 같은 원시 타입(Primitive Type)은 차이가 없으나, 덩치가 큰 구조체나 클래스는 &lt;code&gt;Emplace&lt;/code&gt; 사용을 권장한다. 효율성 측면에서 &lt;code&gt;Emplace&lt;/code&gt;는 항상 &lt;code&gt;Add&lt;/code&gt;보다 같거나 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;정렬 (Sorting)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Sort&lt;/code&gt;: 일반적인 퀵 정렬 기반 (불안정 정렬)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HeapSort&lt;/code&gt;: 힙 정렬&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StableSort&lt;/code&gt;: 병합 정렬(Merge Sort) 기반. 요소의 상대적 순서가 유지된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;주요 쿼리 및 기능&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Num&lt;/code&gt;: 요소 개수 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GetData&lt;/code&gt;: 데이터의 원시 포인터 반환 (C 스타일 배열처럼 사용 가능)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;operator[]&lt;/code&gt;: 인덱스 접근 (유효성 검사 없음, 빠름)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IsValidIndex&lt;/code&gt;: 특정 인덱스의 유효성 검사&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Top&lt;/code&gt;, &lt;code&gt;Last&lt;/code&gt;: 마지막 요소 반환&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Find&lt;/code&gt;, &lt;code&gt;FindLast&lt;/code&gt;: 요소 검색 (O(N))&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RemoveAt&lt;/code&gt;: 특정 인덱스 제거 (O(N), 뒤의 요소를 당겨옴)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RemoveSwap&lt;/code&gt;: 제거 후 맨 끝 요소를 해당 자리로 옮김 (O(1), 순서가 중요하지 않을 때 매우 효율적)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;디버깅 팁&lt;/h2&gt;
&lt;p&gt;언리얼은 기본적으로 최적화 레벨이 높아 디버깅 시 변수 값이 보이지 않을 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Int32Array += { 2, 4, 6, 8, 10 };
// 변수가 최적화되어 사용할 수 없습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;해결: 솔루션 구성을 &lt;code&gt;Development Editor (O2)&lt;/code&gt; -&amp;gt; &lt;code&gt;DebugGame Editor(O0)&lt;/code&gt;로 변경&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;최적화가 꺼지며(O0) 정상적으로 메모리 뷰 확인 가능
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;메모리 직접 제어&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;AddUninitialized&lt;/code&gt;로 공간만 확보하고 &lt;code&gt;FMemory::Memcpy&lt;/code&gt;로 고속 복사가 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TArray&amp;lt;int32&amp;gt; Int32ArrayCompare;
int32 CArray[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 10 };

// 초기화 없이 메모리 공간만 확보 (생성자 호출 안 함)
Int32ArrayCompare.AddUninitialized(ArrayNum); 
FMemory::Memcpy(Int32ArrayCompare.GetData(), CArray, sizeof(int32) * ArrayNum);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;알고리즘 라이브러리&lt;/h2&gt;
&lt;p&gt;STL의 algorithm과 유사한 &lt;code&gt;Algo&lt;/code&gt; 라이브러리를 제공한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;Algo/Accumulate.h&quot;

// 배열의 합계 계산
int32 SumByAlgo = Algo::Accumulate(Int32Array, 0); 
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;TSet&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/set-containers-in-unreal-engine?application_version=5.1&quot;&gt;공식 문서: TSet&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;STL set vs UE TSet&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;STL &lt;code&gt;std::set&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이진 트리(Red-Black Tree) 구성&lt;/li&gt;
&lt;li&gt;정렬된 상태 유지&lt;/li&gt;
&lt;li&gt;메모리가 파편화되어 있을 수 있음&lt;/li&gt;
&lt;li&gt;순회 효율이 상대적으로 낮음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UE &lt;code&gt;TSet&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해시 테이블(Hash Table) 기반&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;희소 배열(Sparse Array)&lt;/strong&gt; 형태로 데이터 저장&lt;/li&gt;
&lt;li&gt;데이터가 비어있는 공간(Hole; Slack)이 존재할 수 있음&lt;/li&gt;
&lt;li&gt;정렬되지 않음&lt;/li&gt;
&lt;li&gt;검색 속도가 빠름 O(1)&lt;/li&gt;
&lt;li&gt;메모리가 상대적으로 모여 있어 순회 효율이 좋음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;주요 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;중복 없는 데이터 집합 구축에 사용&lt;/li&gt;
&lt;li&gt;요소가 삭제되어도 메모리를 즉시 재할당하지 않고 &lt;code&gt;빈칸(Invalid)&lt;/code&gt;으로 남겨둠&lt;/li&gt;
&lt;li&gt;데이터 중간중간 비어있는 공간을 &lt;code&gt;Slack&lt;/code&gt;이라 함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KeyFuncs&lt;/code&gt;를 통해 해시 생성을 커스터마이징 가능 (이번 강의에서는 다루지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;실습: 삭제 후 재추가 동작 확인&lt;/h2&gt;
&lt;p&gt;TSet은 내부적으로 희소 배열을 사용하므로, 요소 삭제 시 해당 인덱스에 플래그를 세운다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TSet&amp;lt;int32&amp;gt; Int32Set;
// 1~10 추가
for (int32 ix = 1; ix &amp;lt;= 10; ++ix) Int32Set.Add(ix);

// 짝수 제거 (2, 4, 6, 8, 10) -&amp;gt; 해당 인덱스는 Invalid 상태가 됨
Int32Set.Remove(2);
// ... 
Int32Set.Remove(10);

// 다시 짝수 추가
Int32Set.Add(2);
// ... 
Int32Set.Add(10);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;메모리 뷰 확인&lt;/h2&gt;
&lt;p&gt;삭제된 공간(Invalid)을 재활용하여 값이 채워진다. 이때 순서는 보장되지 않으며, 빈 공간의 뒤쪽부터 채워지는 경향이 있다.&lt;/p&gt;
&lt;p&gt;아래 표를 보면 인덱스&lt;code&gt;[9]&lt;/code&gt;에 값으로 &lt;strong&gt;2&lt;/strong&gt;부터 채워진걸 볼 수 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;이름&lt;/th&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;th&gt;형식&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Name&lt;/td&gt;
&lt;td&gt;Value&lt;/td&gt;
&lt;td&gt;Type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[0]&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[1]&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[2]&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[3]&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[4]&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[5]&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[6]&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[7]&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[8]&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[9]&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;h2&gt;TArray &amp;amp; TSet 시간 복잡도 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TArray&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;TSet&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;접근&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;td&gt;O(1)*&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;검색&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삽입&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;삭제&lt;/td&gt;
&lt;td&gt;O(N)&lt;/td&gt;
&lt;td&gt;O(1)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리&lt;/td&gt;
&lt;td&gt;연속된 메모리 (빈틈없음)&lt;/td&gt;
&lt;td&gt;희소 배열 (Invalid 존재)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;특징&lt;/td&gt;
&lt;td&gt;최고의 접근/순회 성능&lt;/td&gt;
&lt;td&gt;빠른 중복 감지 및 검색&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;TSet의 접근은 이터레이터를 통하거나 해시를 통한 조회를 의미하며, TSet을인덱스로 임의 접근하는 것은 일반적인 사용법이 아님.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;게임 로직의 대부분은 &lt;code&gt;TArray&lt;/code&gt;로 해결된다.&lt;/li&gt;
&lt;li&gt;하지만, 빈번한 검색과 중복 방지가 필요할때는 &lt;code&gt;TSet&lt;/code&gt;으로의 전환을 고려하자.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/OOP] 9. Delegate &amp; Pub/Sub</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/2_modernoop/uepart1-9/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/2_modernoop/uepart1-9/</guid><description>[OOP 3/3] - 발행-구독(Pub/Sub) 패턴의 이해, 델리게이트 매크로(DECLARE) 활용, 그리고 브로드캐스팅을 통한 이벤트 기반 설계를 알아보자.</description><pubDate>Sun, 08 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;느슨한 결합의 장점과 이를 편리하게 구현하도록 도와주는 델리게이트의 이해&lt;/li&gt;
&lt;li&gt;발행 구독 디자인 패턴의 이해&lt;/li&gt;
&lt;li&gt;언리얼 델리게이트를 활용한 느슨한 결합의 설계와 구현의 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Delegate&lt;/h1&gt;
&lt;p&gt;이전에 배운 컴포지션(Composition)은 강한 결합 관계를 가진다.&lt;br /&gt;
델리게이트(Delegate)는 약한 결합 관계를 가지며, 주로 구독자 패턴(Observer Pattern)을 구현할 때 사용한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 Delegate &lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/delegates-and-lamba-functions-in-unreal-engine?application_version=5.1&quot;&gt;공식 문서&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;발행 구독 디자인 패턴 (Pub/Sub)&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;푸시(Push) 형태의 알림(Notification)을 구현하는데 적합한 디자인 패턴&lt;/li&gt;
&lt;li&gt;&lt;code&gt;발행자(Publisher)&lt;/code&gt;와 &lt;code&gt;구독자(Subscriber)&lt;/code&gt;로 구분
&lt;ul&gt;
&lt;li&gt;콘텐츠 제작자는 콘텐츠를 생산한다.&lt;/li&gt;
&lt;li&gt;발행자는 콘텐츠를 배포한다.&lt;/li&gt;
&lt;li&gt;구독자는 배포된 콘텐츠를 받아 소비한다.&lt;/li&gt;
&lt;li&gt;제작자와 구독자가 서로를 몰라도, 발행자를 통해 콘텐츠를 생산하고 전달할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;장점
&lt;ul&gt;
&lt;li&gt;제작자, 구독자는 서로를 모르기 때문에 &lt;code&gt;느슨한 결합(Loose Coupling)&lt;/code&gt;으로 구성된다.&lt;/li&gt;
&lt;li&gt;유지 보수가 쉽고, 유연하게 활용될 수 있으며, 테스트가 쉬워진다.&lt;/li&gt;
&lt;li&gt;시스템 스케일을 유연하게 조절할 수 있으며(Scalability), 기능 확장(Extensibility)이 용이하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;실습 시나리오&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;image-5.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;학사정보 (CourseInfo)&lt;/code&gt; &lt;code&gt;학생(Student)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;학교는 학사 정보를 관리한다.&lt;/li&gt;
&lt;li&gt;학사 정보가 변경되면 자동으로 학생에게 공지한다.&lt;/li&gt;
&lt;li&gt;학생은 학사 정보의 알림 구독을 해지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;시나리오 흐름
&lt;ul&gt;
&lt;li&gt;학사 정보 시스템과 3명의 학생이 존재한다.&lt;/li&gt;
&lt;li&gt;시스템에서 학사 정보를 변경한다.&lt;/li&gt;
&lt;li&gt;학사 정보가 변경되면 알림을 구독한 학생들에게 변경 내용을 자동으로 전달한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;언리얼 델리게이트 선언 시 고려사항&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;인자의 수와 각각의 타입을 설계&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일대일?&lt;/li&gt;
&lt;li&gt;일대다?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;프로그래밍 환경 설정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Only C++?&lt;/li&gt;
&lt;li&gt;UFUNCTION으로 지정된 블루프린트(BP) 함수와 함께 사용?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;어떤 함수와 연결할 것인가?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클래스 외부에 설계된 C++ 함수?&lt;/li&gt;
&lt;li&gt;전역에 설계된 정적 함수?&lt;/li&gt;
&lt;li&gt;언리얼 오브젝트의 멤버 함수? (보통 이 방식을 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;매크로 구조&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;DECLARE_{델리게이트유형}DELEGATE{함수정보}&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;델리게이트 유형&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DECLARE_DELEGATE&lt;/code&gt; (일대일)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE_MULTICAST&lt;/code&gt; (일대다)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE_DYNAMIC&lt;/code&gt; (블루프린트 연동, 일대일)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE_DYNAMIC_MULTICAST&lt;/code&gt; (블루프린트 연동, 일대다)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;함수 정보&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DECLARE_DELEGATE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE_DELEGATE_OneParam&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE_DELEGATE_RetVal_ThreeParams&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;최대 9개 파라미터까지 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;h2&gt;설계 내용&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;학사정보 변경 시 &lt;code&gt;학교명&lt;/code&gt;, &lt;code&gt;내용&lt;/code&gt;을 학생에게 전달해야 함 -&amp;gt; 2개의 인자&lt;/li&gt;
&lt;li&gt;변경된 학사 정보는 다수 인원을 대상으로 발송해야 함 -&amp;gt; MULTICAST&lt;/li&gt;
&lt;li&gt;오직 C++ 프로그래밍에서만 사용 -&amp;gt; DYNAMIC 사용 X&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결론: &lt;code&gt;DECLARE_MULTICAST_DELEGATE_TwoParams&lt;/code&gt; 매크로 사용&lt;/p&gt;
&lt;h2&gt;1. CourseInfo&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;발행자 (Publisher)&lt;/strong&gt; 역할&lt;/li&gt;
&lt;li&gt;델리게이트를 정의하고, 학사 정보가 변경될 때 &lt;code&gt;Broadcast&lt;/code&gt;로 알림을 발행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;CourseInfo.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// delegate 매크로 선언
DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature, const FString&amp;amp;, const FString&amp;amp;);

UCLASS()
class UNREALDELEGATE_API UCourseInfo : public UObject
{
    GENERATED_BODY()
public:
    UCourseInfo();

    // 델리게이트 멤버 변수 선언
    FCourseInfoOnChangedSignature OnChanged;
    
    // 외부에서 학사정보 변경 요청 시 호출되는 함수
    void ChangeCourseInfo(const FString&amp;amp; InSchoolName, const FString&amp;amp; InNewContents);

private:
    FString Contents;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;CourseInfo.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCourseInfo::UCourseInfo()
{
    Contents = TEXT(&quot;기존 학사 정보&quot;);
}

void UCourseInfo::ChangeCourseInfo(const FString&amp;amp; InSchoolName, const FString&amp;amp; InNewContents)
{
    // 5. 학사 정보 내용 업데이트
    Contents = InNewContents;
    
    UE_LOG(LogTemp, Log, TEXT(&quot;[CourseInfo] 학사 정보가 변경되어 알림을 발송합니다.&quot;));
    
    // 6. 델리게이트를 통해 알림 발행 (구독자들에게 방송)
    OnChanged.Broadcast(InSchoolName, Contents);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. Student&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;구독자 (Subscriber)&lt;/strong&gt; 역할&lt;/li&gt;
&lt;li&gt;발행자(CourseInfo)가 알림을 보내면 자신이 등록한 함수(&lt;code&gt;GetNotification&lt;/code&gt;)를 자동으로 호출받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Student.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UNREALDELEGATE_API UStudent : public UObject
{
    GENERATED_BODY()

public:
    // 알림을 받을 함수
    void GetNotification(const FString&amp;amp; School, const FString&amp;amp; NewCourseInfo);
private:
    FString Name;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Student.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 7. 모든 구독자 학생들이 알림을 받음
void UStudent::GetNotification(const FString&amp;amp; School, const FString&amp;amp; NewCourseInfo)
{
    UE_LOG(LogTemp, Log, TEXT(&quot;[Student] %s 님이 %s로부터 받은 메시지 : %s&quot;), *Name, *School, *NewCourseInfo);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. MyGameInstance&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조율자 (Coordinator)&lt;/strong&gt; 역할&lt;/li&gt;
&lt;li&gt;발행자와 구독자를 연결해주는 역할을 한다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CourseInfo&lt;/code&gt;를 생성&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Student&lt;/code&gt;들을 생성한다. (여기서는 3개)&lt;/li&gt;
&lt;li&gt;Student들을 CourseInfo의 델리게이트에 바인딩(구독)시킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;유일하게 두 클래스(&lt;code&gt;CourseInfo&lt;/code&gt;, &lt;code&gt;Student&lt;/code&gt;)를 모두 &lt;code&gt;include&lt;/code&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private:
    UPROPERTY()
    TObjectPtr&amp;lt;class UCourseInfo&amp;gt; CourseInfo;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void UMyGameInstance::Init()
{
    Super::Init();

    // CourseInfo는 MyGameInstance의 SubObject가 되고
    // MyGameInstance는 CourseInfo의 Outer가 됨
    // 1. Composition 관계로 CourseInfo 생성 (발행자)
    CourseInfo = NewObject&amp;lt;UCourseInfo&amp;gt;(this); 

    UE_LOG(LogTemp, Log, TEXT(&quot;===============================&quot;));

    // 2. Student 객체 생성 (구독자)
    UStudent* Student1 = NewObject&amp;lt;UStudent&amp;gt;(this);
    Student1-&amp;gt;SetName(TEXT(&quot;학생1&quot;));
    UStudent* Student2 = NewObject&amp;lt;UStudent&amp;gt;(this);
    Student2-&amp;gt;SetName(TEXT(&quot;학생2&quot;));   
    UStudent* Student3 = NewObject&amp;lt;UStudent&amp;gt;(this);
    Student3-&amp;gt;SetName(TEXT(&quot;학생3&quot;));   
    
    // 3. 모든 학생을 구독자로 등록 (Binding)
    CourseInfo-&amp;gt;OnChanged.AddUObject(Student1, &amp;amp;UStudent::GetNotification);
    CourseInfo-&amp;gt;OnChanged.AddUObject(Student2, &amp;amp;UStudent::GetNotification);
    CourseInfo-&amp;gt;OnChanged.AddUObject(Student3, &amp;amp;UStudent::GetNotification);

    FString SchoolName = TEXT(&quot;언리얼대학교&quot;);
    
    // 4. 발행자에게 정보 변경 요청 (이벤트 발생 트리거)
    CourseInfo-&amp;gt;ChangeCourseInfo(SchoolName, TEXT(&quot;변경된 학사 정보&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;출력 결과&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: ===============================
LogTemp: [CourseInfo] 학사 정보가 변경되어 알림을 발송합니다.
LogTemp: [Student] 학생3 님이 언리얼대학교로부터 받은 메시지 : 변경된 학사 정보
LogTemp: [Student] 학생2 님이 언리얼대학교로부터 받은 메시지 : 변경된 학사 정보
LogTemp: [Student] 학생1 님이 언리얼대학교로부터 받은 메시지 : 변경된 학사 정보
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;전체 흐름 요약&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;MyGameInstance&lt;/code&gt;: &lt;code&gt;CourseInfo&lt;/code&gt; 생성 (발행자 준비)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MyGameInstance&lt;/code&gt;: &lt;code&gt;Student&lt;/code&gt; 객체 생성 (구독자 준비)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MyGameInstance&lt;/code&gt;: 학생들을 &lt;code&gt;CourseInfo.OnChanged&lt;/code&gt; 델리게이트에 구독자로 등록 (&lt;code&gt;AddUObject&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CourseInfo&lt;/code&gt;: 학사 정보 변경 시 델리게이트 &lt;code&gt;Broadcast&lt;/code&gt; 실행 (모든 학생에게 발행)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Student&lt;/code&gt;: 등록된 함수 &lt;code&gt;GetNotification&lt;/code&gt; 자동 호출 (구독자 알림 수신)&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;h3&gt;델리게이트 vs 컴포지션&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;델리게이트&lt;/strong&gt;: &lt;strong&gt;느슨한 결합&lt;/strong&gt; (기능적 의존성 제거, 구독자 패턴 활용)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;컴포지션&lt;/strong&gt;: &lt;strong&gt;강한 결합&lt;/strong&gt; (객체의 생성과 소멸 주기를 완전히 소유)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;느슨한 결합 (Loose Coupling)의 장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;향후 시스템 변경 사항에 대해 손쉽게 대처할 수 있다.&lt;/li&gt;
&lt;li&gt;발행-구독 모델의 이점
&lt;ul&gt;
&lt;li&gt;클래스는 자신이 해야 하는 작업에만 집중 가능&lt;/li&gt;
&lt;li&gt;외부 변경 사항에 영향을 받지 않음&lt;/li&gt;
&lt;li&gt;자신의 기능을 확장해도 다른 모듈에 영향을 주지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;언리얼 C++ 델리게이트 선언시 생각할 점&lt;/h3&gt;
&lt;p&gt;적합한 매크로를 선택하기 위해 다음을 고려한다&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;인자의 개수&lt;/strong&gt;: 파라미터가 몇 개 필요한가?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동작 방식&lt;/strong&gt;: 1:1 통신인가, 1:N(Multicast) 통신인가?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;블루프린트 연동&lt;/strong&gt;: 에디터/BP에서 접근이 필요한가? (Dynamic 필요 여부)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;기타: UML에서의 표기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;따로 번외편에서 정리.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/OOP] 8. Composition</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/2_modernoop/uepart1-8/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/2_modernoop/uepart1-8/</guid><description>[OOP 2/3] - 포함 관계에서 SubObject생성, 안전한 참조관리(TObjectPtr), Enum+리플렉션을 통한 메타데이터 활용을 알아보자</description><pubDate>Sat, 07 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 C++의 컴포지션 기법을 사용해 오브젝트의 포함 관계를 설계한다.&lt;/li&gt;
&lt;li&gt;언리얼 C++이 제공하는 열거형(Enum) 타입의 선언과 메타데이터 활용 방법을 학습한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;컴포지션 (Composition)&lt;/h1&gt;
&lt;p&gt;객체 지향 설계에서 상속이 가진 &lt;code&gt;Is-A&lt;/code&gt; 관계에만 의존해서는 복잡한 기능을 설계하고 유지 보수하기 어렵다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컴포지션은 &lt;code&gt;Has-A&lt;/code&gt; 관계를 구현하는 설계 방법이다. 복합적인 기능을 가진 거대한 클래스를 상속으로만 처리하지 않고, 필요한 기능을 가진 부품(오브젝트)을 조립해서 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;하나의 언리얼 오브젝트에는 항상 클래스 기본 오브젝트인 &lt;code&gt;CDO(Class Default Object)&lt;/code&gt;가 존재한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;언리얼 오브젝트에 다른 언리얼 오브젝트를 조합(Composition)할 때, CDO 생성 시점에 따라 두 가지 선택지가 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;생성자&lt;/code&gt; CDO 생성 시점에 미리 산하 오브젝트를 생성해 조합한다. (&lt;code&gt;CreateDefaultSubobject&lt;/code&gt; API 활용)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;런타임&lt;/code&gt; CDO에는 빈 포인터만 두고, 런타임(게임 실행 중)에 필요할 때 오브젝트를 생성해 조합한다. (&lt;code&gt;NewObject&lt;/code&gt; API 활용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;언리얼 오브젝트 간의 컴포지션 관계 용어&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SubObject&lt;/code&gt; 내가 소유한 산하 언리얼 오브젝트 (그림의 보라 상자 → 초록 상자)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Outer&lt;/code&gt; 나를 소유한 상위 언리얼 오브젝트 (그림의 초록 상자 → 보라 상자)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;h2&gt;1. Card 클래스 생성 (Enum 활용)&lt;/h2&gt;
&lt;p&gt;언리얼 엔진의 리플렉션 기능을 활용하기 위해 &lt;code&gt;UENUM&lt;/code&gt; 매크로와 &lt;code&gt;UMETA&lt;/code&gt;를 사용한다.
&lt;strong&gt;Card.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UENUM()
enum class ECardType : uint8
{
	Student = 1 UMETA(DisplayName = &quot;For Student&quot;),
	Teacher UMETA(DisplayName = &quot;For Teacher&quot;),
	Staff UMETA(DisplayName = &quot;For Staff&quot;),
	Invalid
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UCard&lt;/code&gt; 클래스 생성 및 &lt;code&gt;ECardType&lt;/code&gt; 멤버 변수 추가&lt;/li&gt;
&lt;li&gt;CardType에 대한 Getter/Setter 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. Person has-a Card (컴포지션 구현)&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Person&lt;/code&gt; 클래스가 &lt;code&gt;Card&lt;/code&gt; 객체를 소유하도록 구현한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Person.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UNREALCOMPOSITION_API UPerson : public UObject
{
	GENERATED_BODY()

public:
	UCard* GetCard() const { return Card; }
	void SetCard(UCard* InCard) { Card = InCard; }

protected:
	// Person has-a Card
	// 언리얼 5부터는 원시 포인터 대신 TObjectPtr&amp;lt;T&amp;gt; 사용 권장
	UPROPERTY()
	TObjectPtr&amp;lt;UCard&amp;gt; Card; 
	
	// class UCard* Card; // 언리얼 4 방식
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TObjectPtr&lt;/code&gt; 언리얼 5로 넘어오면서 &lt;code&gt;UPROPERTY&lt;/code&gt;로 지정된 멤버 변수는 원시 포인터 대신 &lt;code&gt;TObjectPtr&lt;/code&gt; 래퍼 클래스를 사용하는 것으로 변경되었다. (에디터 빌드 시 동적 로딩 등 이점 제공, 런타임에서는 원시 포인터와 동일)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. GameInstance에서 활용&lt;/h2&gt;
&lt;h3&gt;컴포지션 객체 접근&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const UCard* OwnCard = Person-&amp;gt;GetCard();
if (OwnCard)
{
	ECardType CardType = OwnCard-&amp;gt;GetCardType();
	UE_LOG(LogTemp, Log, TEXT(&quot;%s님이 소유한 카드 종류 %d&quot;), *Person-&amp;gt;GetName(), (int32)CardType);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Enum 메타데이터 접근 (리플렉션)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;FindObject&lt;/code&gt;를 사용해 열거형의 메타 정보(&lt;code&gt;UMETA&lt;/code&gt;로 지정한 DisplayName)를 가져올 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const UEnum* CardEnumType = FindObject&amp;lt;UEnum&amp;gt;(nullptr, TEXT(&quot;/Script/UnrealComposition.ECardType&quot;));
if (CardEnumType)
{
	// Enum의 Value를 통해 DisplayName(MetaData)을 문자열로 추출
	FString CardMetaData = CardEnumType-&amp;gt;GetDisplayNameTextByValue((int64)CardType).ToString();
	UE_LOG(LogTemp, Log, TEXT(&quot;%s님이 소유한 카드 종류: %s&quot;), *Person-&amp;gt;GetName(), *CardMetaData);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;출력 결과&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: ===============================
LogTemp: 학생님이 소유한 카드 종류 1
LogTemp: 선생님님이 소유한 카드 종류 For Teacher
LogTemp: 직원님이 소유한 카드 종류 For Staff
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 C++는 객체 간의 포함 관계(Has-A)를 구현하는 컴포지션 패턴을 지원한다.&lt;/li&gt;
&lt;li&gt;생성자 단계에서 &lt;code&gt;CreateDefaultSubobject&lt;/code&gt;를 사용해 필수적인 SubObject를 미리 생성하고 조립할 수 있다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SubObject&lt;/code&gt; 내가 소유한 하위 오브젝트&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Outer&lt;/code&gt; 나를 소유한 상위 오브젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TObjectPtr&lt;/code&gt;는 UE5부터 도입된 표준 객체 포인터 래퍼 클래스이다. (헤더에서 &lt;code&gt;TObjectPtr&amp;lt;UCard&amp;gt; Card;&lt;/code&gt; 형태로 선언하여 컴포지션 구현)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enum class&lt;/code&gt;와 &lt;code&gt;UMETA&lt;/code&gt;매크로를 조합하면 열거형의 메타 정보를 런타임에 활용 가능하다.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/OOP] 7. Interface</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/2_modernoop/uepart1-7/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/2_modernoop/uepart1-7/</guid><description>[OOP 1/3] - 객체 간 의존성을 줄여주는 느슨한 결합 인터페이스를 언리얼에서 사용해보자</description><pubDate>Fri, 06 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 C++ 인터페이스 클래스를 사용해 안정적으로 클래스를 설계하는 기법 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;이론&lt;/h1&gt;
&lt;h2&gt;Interface&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;객체가 반드시 구현해야 할 행동을 지정하는 데 활용하는 타입&lt;/li&gt;
&lt;li&gt;다형성(Polymorphism)의 구현, 의존성이 분리(Decouple)된 설계에 유용하게 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;UE 게임 콘텐츠 오브젝트 설계 예시&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;월드에 배치되는 모든 오브젝트(움직이지 않는 것도 포함) : Actor&lt;/li&gt;
&lt;li&gt;움직이는 오브젝트 : Pawn&lt;/li&gt;
&lt;li&gt;길찾기 시스템을 반드시 사용하면서 움직이는 오브젝트 : &lt;code&gt;INavAgentInterface&lt;/code&gt; 인터페이스를 구현한 Pawn&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;언리얼 Interface 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;인터페이스를 생성하면 두 개의 클래스가 생성됨
&lt;ul&gt;
&lt;li&gt;U로 시작하는 타입 : &lt;code&gt;UInterface&lt;/code&gt; 클래스 (타입 정보 제공)&lt;/li&gt;
&lt;li&gt;I로 시작하는 Interface 타입 : &lt;code&gt;IInterface&lt;/code&gt; (실질적인 설계 및 구현)-&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;U타입 클래스 정보는 런타임시 인터페이스 구현 여부 파악 용으로만 사용
&lt;ul&gt;
&lt;li&gt;실제로 U타입 클래스를 작업할 필요 X&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인터페이스 구성 및 구현은 I 클래스에서 진행&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;p&gt;아래 클래스 구조를 직접 구현해보자.
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;1. 전 프로젝트 소스 코드 불러오기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UnrealInterface&lt;/code&gt;라는 새 프로젝트 생성&lt;/li&gt;
&lt;li&gt;외부 프로젝트에 만들어둔 &lt;code&gt;MyGameInstance.h&lt;/code&gt; 와 &lt;code&gt;MyGameInstance.cpp&lt;/code&gt;를 현재 프로젝트 폴더로 옮긴 후, UE에서 &lt;strong&gt;Tools -&amp;gt; Refresh Visual Studio Project&lt;/strong&gt;를 하면 VS에 뜨긴 하지만 컴파일 에러가 발생&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MyGameInstance.h&lt;/code&gt; 코드 구조를 보면:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;class OBJECTREFLECTION_API UMyGameInstance : public UGameInstance

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;현재 프로젝트에 맞게 &lt;code&gt;_API&lt;/code&gt; 매크로를 변경할 필요가 있음
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OBJECTREFLECTION_API&lt;/code&gt; -&amp;gt; &lt;code&gt;UNREALINTERFACE_API&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;언리얼 엔진이 어떤 식으로 빌드하는지는 이후 강좌에서 다룸&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 클래스 설계&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Person 클래스 생성&lt;/li&gt;
&lt;li&gt;상속받는 클래스 3개 생성
&lt;ul&gt;
&lt;li&gt;Student&lt;/li&gt;
&lt;li&gt;Teacher&lt;/li&gt;
&lt;li&gt;Staff&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Person 객체는 생성자, getter/setter, Name 프로퍼티 보유&lt;br /&gt;
&lt;strong&gt;Person.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UNREALINTERFACE_API UPerson : public UObject
{
	GENERATED_BODY()

public:
	UPerson();

	FORCEINLINE FString&amp;amp; GetName() { return Name; }
	FORCEINLINE void SetName(const FString&amp;amp; InName) { Name = InName; } // 복사 1회 발생

protected:
	UPROPERTY()
	FString Name;
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TArray로 묶어주고 &lt;code&gt;Person&lt;/code&gt;을 상속받는 객체 3개를 출력해보자.&lt;br /&gt;
&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UE_LOG(LogTemp, Log, TEXT(&quot;===============================&quot;));
TArray&amp;lt;UPerson*&amp;gt; Persons = 
	{ NewObject&amp;lt;UStudent&amp;gt;(), NewObject&amp;lt;UTeacher&amp;gt;(), NewObject&amp;lt;UStaff&amp;gt;() };

for (const auto Person : Persons)
{
	UE_LOG(LogTemp, Log, TEXT(&quot;구성원 이름 : %s&quot;), *Person-&amp;gt;GetName());
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실행 결과&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: ===============================
LogTemp: 구성원 이름 : 학생
LogTemp: 구성원 이름 : 선생님
LogTemp: 구성원 이름 : 직원
LogTemp: ==============================

&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 인터페이스&lt;/h2&gt;
&lt;h3&gt;3-1. 인터페이스 정의&lt;/h3&gt;
&lt;p&gt;인터페이스를 추가해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인터페이스를 상속받으면, 추상화 부분을 무조건 구현해야 한다.&lt;/li&gt;
&lt;li&gt;C++ 인터페이스는 보통 순수 가상 함수(Pure Virtual Function)로 구현을 강제하지만, 언리얼 인터페이스는 내부에 기본 함수 구현을 넣을 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;LessonInterface.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UNREALINTERFACE_API ILessonInterface
{
	GENERATED_BODY()
public:
	virtual void DoLesson()
	{
		UE_LOG(LogTemp, Log, TEXT(&quot;수업에 입장합니다.&quot;));
	}
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-2. 인터페이스 구현 (상속 및 오버라이드)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Student&lt;/code&gt;와 &lt;code&gt;Teacher&lt;/code&gt; 클래스에 인터페이스를 상속받고 기능을 구현한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Staff&lt;/code&gt;는 인터페이스를 상속받지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Student.h / Teacher.h&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UNREALINTERFACE_API UStudent : public UPerson, public ILessonInterface
{
	GENERATED_BODY()
public:
	virtual void DoLesson() override
	{
		UE_LOG(LogTemp, Log, TEXT(&quot;%s님은 수업을 듣습니다.&quot;), *Name);
	}
};

UCLASS()
class UNREALINTERFACE_API UTeacher : public UPerson, public ILessonInterface
{
	GENERATED_BODY()
public:
	virtual void DoLesson() override
	{
		UE_LOG(LogTemp, Log, TEXT(&quot;%s님은 가르칩니다.&quot;), *Name);
	}
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3-3. 인터페이스 검사 : Cast&amp;lt;T&amp;gt;()&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Cast&amp;lt;T&amp;gt;()&lt;/code&gt;를 통해 인터페이스를 상속받고 있는지 검사가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MyGameInstance.cpp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 형변환으로 검사
for (const auto Person : Persons)
{
	ILessonInterface* LessonInterface = Cast&amp;lt;ILessonInterface&amp;gt;(Person);
	if (LessonInterface)
	{
		UE_LOG(LogTemp, Log, TEXT(&quot;%s님은 수업에 참여할 수 있습니다.&quot;), *Person-&amp;gt;GetName());
		LessonInterface-&amp;gt;DoLesson();
	}
	else
	{
		UE_LOG(LogTemp, Log, TEXT(&quot;%s님은 수업에 참여할 수 없습니다.&quot;), *Person-&amp;gt;GetName());
	}
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;실행 결과&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: 학생님은 수업에 참여할 수 있습니다.
LogTemp: 학생님은 수업을 듣습니다.

LogTemp: 선생님님은 수업에 참여할 수 있습니다.
LogTemp: 선생님님은 가르칩니다.

LogTemp: 직원님은 수업에 참여할 수 없습니다.

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;(참고: &lt;code&gt;DoLesson()&lt;/code&gt; 내부에서 &lt;code&gt;Super::DoLesson()&lt;/code&gt;을 호출하지 않았으므로 &quot;수업에 입장합니다&quot; 로그는 출력되지 않고 오버라이드된 로그만 출력됨. 만약, 인터페이스의 구현부 출력이 필요하면 &lt;code&gt;Super&lt;/code&gt;로 추가할 것)&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;interface&lt;/code&gt;는 클래스가 반드시 구현해야 하는 기능을 지정하는 데 사용된다.&lt;/li&gt;
&lt;li&gt;C++은 기본적으로 다중 상속을 지원하지만, 언리얼 C++에서는 &lt;code&gt;interface&lt;/code&gt;를 사용하여 축소된 다중상속(단일 상속 + 다중 인터페이스 구현) 형태로 설계하는 것이 향후 유지보수에 유리하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;interface&lt;/code&gt; 생성 시 두 개의 클래스(&lt;code&gt;UInterface&lt;/code&gt;, &lt;code&gt;IInterface&lt;/code&gt;)가 생성된다.&lt;/li&gt;
&lt;li&gt;언리얼 인터페이스는 &lt;code&gt;abstract&lt;/code&gt; 타입으로 강제되지 않으며, 내부에 기본 함수 동작을 구현할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/UObject] 6. UObject Reflection 2</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-6/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-6/</guid><description>언리얼 오브젝트의 리플렉션 시스템으로 프로퍼티와 함수를 접근하고 수정해보자.</description><pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;리플렉션 시스템을 이용해 언리얼 오브젝트를 다루는 방법
&lt;ul&gt;
&lt;li&gt;속성/함수 검색 및 호출/변경&lt;/li&gt;
&lt;li&gt;접근 지시자 우회&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;1. FProperty로 속성 접근/변경&lt;/h1&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Super::Init();

// 언리얼 오브젝트 생성 - NewObject&amp;lt;T&amp;gt;()
UStudent* Student = NewObject&amp;lt;UStudent&amp;gt;(this);
UTeacher* Teacher = NewObject&amp;lt;UTeacher&amp;gt;(this);

FString CurrentTeacherName;
FString NewTeacherName(TEXT(&quot;새이름&quot;));

FProperty* NameProp = UTeacher::StaticClass()-&amp;gt;FindPropertyByName(TEXT(&quot;Name&quot;));
if (NameProp)
{
	// 접근
	NameProp-&amp;gt;GetValue_InContainer(Teacher, &amp;amp;CurrentTeacherName);
	UE_LOG(LogTemp, Log, TEXT(&quot;현재 선생님 이름 %s&quot;), *CurrentTeacherName);
	
	// 변경
	NameProp-&amp;gt;SetValue_InContainer(Teacher, &amp;amp;NewTeacherName);
	UE_LOG(LogTemp, Log, TEXT(&quot;현재 선생님 이름 %s&quot;), *Teacher-&amp;gt;GetName());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Person.h&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected:
	UPROPERTY()
	FString Name; // 선생(학생) 이름
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output Log&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: 현재 선생님 이름 이선생
LogTemp: 현재 선생님 이름 새이름
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FProperty&lt;/code&gt;의 API를 이용해 프로퍼티의 접근 및 변경이 가능하다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GetValue_InContainer()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SetValue_InContainer()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이때, Protected로 보호받고 있는 프로퍼티 값인 선생님 이름을 Reflection을 이용해 그대로 출력 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;2. UFunction으로 함수 사용&lt;/h1&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Student-&amp;gt;DoLesson();
UFunction* DoLessonFunc = Teacher-&amp;gt;GetClass()-&amp;gt;FindFunctionByName(TEXT(&quot;DoLesson&quot;));
if (DoLessonFunc)
{
	Teacher-&amp;gt;ProcessEvent(DoLessonFunc, nullptr); // 인자가 없는 함수라 null 전달
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output Log&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: 새이름님이 수업에 참여합니다. // Person.cpp
LogTemp: 3년차 선생님 새이름님이 수업을 강의합니다. // Teacher.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UFunction&lt;/code&gt;의 API를 이용해 함수 사용이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Reflection System 활용
&lt;ul&gt;
&lt;li&gt;언리얼 오브젝트의 특정 속성과 함수를 이름으로 검색 가능&lt;/li&gt;
&lt;li&gt;접근 지시자와 무관하게 값을 설정&lt;/li&gt;
&lt;li&gt;언리얼 오브젝트의 함수 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;언리얼 엔진의 프레임워크는 리플렉션을 활용해서 구축되어 있으므로, 이해가 필요&lt;/li&gt;
&lt;li&gt;(주의) 컴파일러의 타입 체크 지원을 받지 못하므로, 런타임때 검증 로직을 직접 작성해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;번외&lt;/h1&gt;
&lt;h2&gt;타입 안정성 개선 : CastField&amp;lt;T&amp;gt;()&lt;/h2&gt;
&lt;h3&gt;문제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FindPropertyByName&lt;/code&gt;은 단순히 메모리 오프셋 정보만 가져온다.&lt;/li&gt;
&lt;li&gt;타입 일치 여부를 보장하지 않아, 메모리 오염(Memory Corruption) 위험이 존재&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;개선&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CastField&amp;lt;T&amp;gt;&lt;/code&gt;를 사용해 런타임 타입 검사(RTTI)를 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;FProperty* NameProp = UTeacher::StaticClass()-&amp;gt;FindPropertyByName(TEXT(&quot;Name&quot;));
if (NameProp)
{
	// FStrProperty인지 확인 (int, float 등 다른 타입일 경우 크래시 방지를 위해서)
	FStrProperty* StrProp = CastField&amp;lt;FStrProperty&amp;gt;(NameProp);
	if (StrProp)
	{
		StrProp-&amp;gt;SetValue_InContainer(Teacher, &amp;amp;NewTeacherName);
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;상속시 주의점 : generated.h&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;Student.generated.h&quot;
#include &quot;Person.h&quot; // generated.h 아래에 있으면 빌드 오류 발생
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;언리얼에서 다른 헤더를 상속 받을때 항상 &lt;code&gt;generated.h&lt;/code&gt;파일이 가장 밑에 include 되도록 주의하자.&lt;/li&gt;
&lt;li&gt;UHT가 &lt;code&gt;Student.h&lt;/code&gt;와 같은 C++ 헤더를 파싱하여, 리플렉션 정보가 담긴 &lt;code&gt;.generated.h&lt;/code&gt;를 만들기 때문에, 당연히 이 파일이 마지막에 있어야 &lt;code&gt;GENERATED_BODY&lt;/code&gt; 매크로가 정상 작동한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;GetValue_InContainer vs C++ 직접 접근&lt;/h2&gt;
&lt;h3&gt;문제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;리플렉션은 유연하지만 느리다. 매 프레임 호출되는 &lt;code&gt;Tick&lt;/code&gt;같은 곳에서는 지양해야한다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FindPropertyByName&lt;/code&gt;검색은 당연히 직접 접근하는  &lt;code&gt;Teacher-&amp;gt;SetName()&lt;/code&gt;보다 느리다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;써야 한다면 캐싱해서 쓰자&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;BeginPlay&lt;/code&gt;에서 &lt;code&gt;FindPropertyByName&lt;/code&gt;으로 &lt;code&gt;FProperty*&lt;/code&gt;를 찾아 변수에 저장(캐싱)해두고&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Tick&lt;/code&gt;에서는 저장된 포인터로 &lt;code&gt;SetValue_InContainer&lt;/code&gt;만 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;hr /&gt;
&lt;hr /&gt;
&lt;h1&gt;실습 코드 구조&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Student와 Teacher는 Person을 상속받음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MyGameInstance.h&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pragma once
#include &quot;CoreMinimal.h&quot;
#include &quot;Engine/GameInstance.h&quot;
#include &quot;MyGameInstance.generated.h&quot;
UCLASS()
class OBJECTREFLECTION_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
public:
	UMyGameInstance(); // 생성자

	virtual void Init() override;

private:
	UPROPERTY() // 이래야 UE가 관리해줌(GC, 직렬화, 리플렉션 등)
	FString SchoolName;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;MyGameInstance.h&quot; // 현재 언리얼 오브젝트 헤더가 가장 위에 있어야 함 (UHT 규칙)
#include &quot;Student.h&quot;
#include &quot;Teacher.h&quot;

UMyGameInstance::UMyGameInstance()
{
	SchoolName = TEXT(&quot;기본 학교&quot;); // 이 기본값은 CDO 템플릿 객체에 저장
}

void UMyGameInstance::Init()
{
	Super::Init();

	// 언리얼 오브젝트 생성 - NewObject&amp;lt;T&amp;gt;()
	UStudent* Student = NewObject&amp;lt;UStudent&amp;gt;();
	UTeacher* Teacher = NewObject&amp;lt;UTeacher&amp;gt;();

	Student-&amp;gt;SetName(TEXT(&quot;학생1&quot;));
	UE_LOG(LogTemp, Log, TEXT(&quot;새로운 학생 이름 %s&quot;), *Student-&amp;gt;GetName());

	// reflection을 이용해 속성 접근/변경
	FString CurrentTeacherName;
	FString NewTeacherName(TEXT(&quot;새이름&quot;));
	FProperty* NameProp = UTeacher::StaticClass()-&amp;gt;FindPropertyByName(TEXT(&quot;Name&quot;));
	if (NameProp)
	{
		NameProp-&amp;gt;GetValue_InContainer(Teacher, &amp;amp;CurrentTeacherName);
		UE_LOG(LogTemp, Log, TEXT(&quot;현재 선생님 이름 %s&quot;), *CurrentTeacherName);

		// FProperty API는 주소(void*)를 요구하며, 대상 객체 메모리에 값을 복사한다.
		// 단, FString은 내부적으로 버퍼 공유 + 참조 카운트 증가 방식이라 
		// 실제 문자열 데이터는 즉시 복제되지 않는다 (Copy-On-Write 발생 X → O(1), 발생 시 → O(n))
		NameProp-&amp;gt;SetValue_InContainer(Teacher, &amp;amp;NewTeacherName);
		UE_LOG(LogTemp, Log, TEXT(&quot;현재 선생님 이름 %s&quot;), *Teacher-&amp;gt;GetName());
	}

	UE_LOG(LogTemp, Log, TEXT(&quot;====================&quot;));

	// reflection을 이용해 함수 사용
	Student-&amp;gt;DoLesson();

	UFunction* DoLessonFunc = Teacher-&amp;gt;GetClass()-&amp;gt;FindFunctionByName(TEXT(&quot;DoLesson&quot;));
	if (DoLessonFunc)
	{
		Teacher-&amp;gt;ProcessEvent(DoLessonFunc, nullptr); // 인자가 없는 함수라 null 전달
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Person.h&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#pragma once
#include &quot;CoreMinimal.h&quot;
#include &quot;UObject/Object.h&quot;
#include &quot;Person.generated.h&quot;
UCLASS()
class OBJECTREFLECTION_API UPerson : public UObject
{
	GENERATED_BODY()
public:
	UPerson();

	UFUNCTION()
	virtual void DoLesson();

	const FString&amp;amp; GetName() const;
	void SetName(const FString&amp;amp; InName);

protected:
	UPROPERTY()
	FString Name;

	UPROPERTY()
	int32 Year;

private:
};
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[Algorithm] Network Flow</title><link>https://heparidayo.com/posts/algorithm/networkflow/</link><guid isPermaLink="true">https://heparidayo.com/posts/algorithm/networkflow/</guid><description>네트워크 유량: Network Flow &amp; Max-Flow Min-Cut(MFMC) 정리 및 BFS기반의 Edmonds-Karp 알고리즘을 사용해 최대 유량 구하기</description><pubDate>Wed, 04 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;조건&lt;/h1&gt;
&lt;p&gt;정점 u -&amp;gt; v 일때&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;간선의 용량 : &lt;code&gt;c(u, v)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;실제 흐르는 유량 : &lt;code&gt;f(u, v)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;전체 정점의 집합 : &lt;code&gt;V&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다음 3가지 속성을 만족해야한다.&lt;/p&gt;
&lt;h2&gt;1. 용량 제한 속성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;f(u, v)&lt;/code&gt; &amp;lt;= &lt;code&gt;c(u, v)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;자명한 속성&lt;/li&gt;
&lt;li&gt;그래프의 한 간선에서 흐르는 유량은 용량을 초과할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 유량의 대칭성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;f(u, v)&lt;/code&gt; == &lt;code&gt;-f(v, u)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;u -&amp;gt; v 로 양수의 유량을 보낸다는 것은, v -&amp;gt; u 로 음수의 유량을 보내는 것으로 간주&lt;/li&gt;
&lt;li&gt;역방향 간선에 음의 유량이 흐르는 것으로 처리(잔여 그래프; Residual Graph생성)하여, 나중에 경로를 취소할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 유량의 보존&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;∑f(in) == ∑f(out)&lt;/code&gt; (들어온 양 = 나간 양)
&lt;ul&gt;
&lt;li&gt;소스와 싱크를 제외한 모든 정점에는(파이프의 이음새)에는 물이 고일 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;소스&amp;amp;싱크 개념&lt;/h2&gt;
&lt;p&gt;소스는 유량이 시작되는 정점이고, 싱크는 유량이 끝나는 정점이기 때문에, 이 두가지 정점에 한해서 유량의 보존 성질이 성립하지 않는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;소스 (source) : 수원지&lt;/li&gt;
&lt;li&gt;싱크 (sink) : 목적지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;유량 그래프에서 &lt;code&gt;소스&lt;/code&gt; -&amp;gt; &lt;code&gt;싱크&lt;/code&gt;로 최대로 흐를 수 있는 유량을 구하는 문제를 &lt;code&gt;네트워크 유량&lt;/code&gt; 문제라고 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Ford-Fulkerson method&lt;/h1&gt;
&lt;h2&gt;동작 원리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;u -&amp;gt; v 로 보낼 수 있는 잔여 용량을 &lt;code&gt;r(u, v)&lt;/code&gt;라고 할때
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r(u, v)&lt;/code&gt; == &lt;code&gt;c(u, v) - f(u, v)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;일단 임의의 경로로 용량을 흘려보내고&lt;/li&gt;
&lt;li&gt;기존에 선택했던 경로를 취소하고, 새로운 경로로 더 개선된 답을 찾는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;최대 유량 최소 컷 정리 (Max-Flow Min-Cut)&lt;/h2&gt;
&lt;h3&gt;컷 (cut)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;그래프를 두개의 조각으로 토막낸 것&lt;/li&gt;
&lt;li&gt;유량 그래프에서의 컷은, 시작점인 소스와 종점인 싱크가 다른 집합에 속하도록 그래프를 쪼갠 것
&lt;ul&gt;
&lt;li&gt;소스가 있는 컷 : S&lt;/li&gt;
&lt;li&gt;싱크가 포함된 컷 : T&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;S -&amp;gt; T 로 가는 간선들의 용량 합 : 컷 S, T의 용량&lt;/li&gt;
&lt;li&gt;S -&amp;gt; T 로 가는 실제 유량 : 컷 S, T의 유량
&lt;ul&gt;
&lt;li&gt;(중요) 컷의 유량은 &lt;code&gt;역방향 간선&lt;/code&gt;의 -유량도 더해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;속성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;컷의 유량은 &lt;code&gt;소스 -&amp;gt; 싱크&lt;/code&gt;로 가는 총 유량과 같다.&lt;/li&gt;
&lt;li&gt;컷의 유량은 &lt;strong&gt;컷의 용량(capacity)보다 크지 않다&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;추론&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;(유량 보존) 임의의 컷을 통과하는 순 유량은 모두 동일하며, 이는 &lt;code&gt;소스 -&amp;gt; 싱크&lt;/code&gt;로 흐르는 총 유량과 같다.&lt;/li&gt;
&lt;li&gt;(컷 용량 제한) 컷을 통과하는 유량은 물리적으로 그 컷의 용량을 초과할 수 없다. &lt;code&gt;유량 &amp;lt;= 컷의 용량&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;(상한) 따라서 그래프의 모든 컷 중 &lt;strong&gt;가장 작은 용량을 가진 컷(최소 컷)&lt;/strong&gt; 의 용량이 전체 유량의 한계치가 된다.&lt;/li&gt;
&lt;li&gt;만약 &lt;code&gt;현재 유량 == 컷의 용량&lt;/code&gt;이 되는 상태(모든 간선이 꽉 찬 컷)를 하나라도 발견하면, 그 이상 유량을 늘리는 것은 불가능하므로 이때가 &lt;code&gt;최대 유량&lt;/code&gt;이다.
&lt;ul&gt;
&lt;li&gt;더 이상 증가 X -&amp;gt; 컷의 용량 포화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;결론&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;최대 유량&lt;/code&gt; == &lt;code&gt;최소 컷의 용량&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;즉, &lt;code&gt;Max-Flow&lt;/code&gt; == &lt;code&gt;Min-Cut&lt;/code&gt; 정리가 성립한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Edmonds-Karp Algorithm&lt;/h1&gt;
&lt;h2&gt;이론&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;BFS로 시작점에서 가장 간선 개수가 적은 경로(최단 경로)부터 찾는다.
&lt;ul&gt;
&lt;li&gt;이러면 유량을 흘려보내는 횟수가 O(VE)로 제한된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;알고리즘 흐름&lt;/h2&gt;
&lt;p&gt;아래의 과정을 경로가 없을 때까지 무한 반복한다.&lt;/p&gt;
&lt;h3&gt;1. 길 찾기 (BFS)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;소스(S) -&amp;gt; 싱크(T) 로 가는 경로를 BFS로 탐색
&lt;ul&gt;
&lt;li&gt;이동 조건: &lt;code&gt;현재 유량 &amp;lt; 용량&lt;/code&gt; 인 간선만 이동 가능 (물이 꽉 차면 이동 X)&lt;/li&gt;
&lt;li&gt;이때, &lt;code&gt;parent&lt;/code&gt;배열로 경로 역추적 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 병목 지점 찾기 (Bottleneck)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;싱크(T)에 도착했다면, &lt;code&gt;parent&lt;/code&gt;배열을 타고 소스(S)까지 올라간다&lt;/li&gt;
&lt;li&gt;이 경로상에 파이프 중 &lt;code&gt;잔여 용량(Capacity - Flow)&lt;/code&gt;이 가장 &lt;strong&gt;작은 값&lt;/strong&gt;을 찾는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;잔여 용량(amount)&lt;/code&gt; 이 값이 이번 턴에 흘려보낼 수 있는 유량 (가장 좁은 파이프만큼만 물이 흐르므로)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 유량 흘리기 (Update &amp;amp; Reverse Edge)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;다시 경로를 거슬러 올라가면서 유량 데이터를 갱신&lt;/li&gt;
&lt;li&gt;정방향 : &lt;code&gt;flow[u][v] += amount&lt;/code&gt; (물 채우기)&lt;/li&gt;
&lt;li&gt;역방향 : &lt;code&gt;flow[v][u] -= amount&lt;/code&gt; (반대 유량은 감소 -&amp;gt; 음수 유량)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;예제&lt;/h2&gt;
&lt;h3&gt;구조&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;

const int INF = 0x3f3f3f3f;
const int MAX = 1000;

// c: Capacity 용량
// f: Flow 현재 유량
int c[MAX][MAX]; // cur -&amp;gt; nxt
int f[MAX][MAX]; // cur -&amp;gt; nxt
vector&amp;lt;int&amp;gt; adj[MAX];

// 최대 유량 구하기 S(source) -&amp;gt; T(sink; target)
int getMaxFlow(int source, int sink)
{
    int totalFlow = 0;
    while (true)
    {
        // 1. 길 찾기 (BFS)
        vector&amp;lt;int&amp;gt; parent(MAX, -1);
        queue&amp;lt;int&amp;gt; q;
        q.push(source); // 시작점
        parent[source] = source; // 방문 표시

        while (!q.empty())
        {
            int cur = q.front(); q.pop();
            for (int nxt : adj[cur])
            {
                if ((c[cur][nxt] - f[cur][nxt]) &amp;lt;= 0) continue; // 잔여 용량이 남지 않음
                if (parent[nxt] != -1) continue; // 이미 방문함

                q.push(nxt);
                parent[nxt] = cur; // cur -&amp;gt; nxt 경로 기록
                if (nxt == sink) break; // 싱크 도착
            }
        }

        // 2. 싱크에 도달 못함. 경로가 없음
        if (parent[sink] == -1) break;

        // 3. 병목 지점 찾기 (Bottleneck) - 가장 작은 파이프
        int amount = INF;
        for (int p = sink; p != source; p= parent[p])
        {
	        int prv = parent[p];
	        amount = min(amount, c[prv][p] - f[prv][p]);
        }

        // 경로 역추적
        for (int p = sink; p != source; p = parent[p])
        {
            int prv = parent[p];
            f[prv][p] += amount; // u-&amp;gt;v 정방향
            f[p][prv] -= amount; // v-&amp;gt;u 역방향
        }
        
        totalFlow += amount;
    }
    
    return totalFlow;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;연습 문제 - BOJ 6086 최대 유량 구현&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;queue&amp;gt;
#include &amp;lt;algorithm&amp;gt;
using namespace std;

const int INF = 0x3f3f3f3f;

int c[150][150]; // capacity
int f[150][150]; // flow 0
vector&amp;lt;int&amp;gt; adj[150]; // 파이프 연결 list

int main()
{
    int n; cin &amp;gt;&amp;gt; n;
    for (int i = 0; i &amp;lt; n; i++)
    {
        char u, v;
        int w;
        cin &amp;gt;&amp;gt; u &amp;gt;&amp;gt; v &amp;gt;&amp;gt; w;
        
        // 양방향 그래프
        c[u][v] += w;
        c[v][u] += w;
        adj[u].push_back(v);
        adj[v].push_back(u);
    }

    int ans = 0;
    while (true)
    {
        // S(source) -&amp;gt; T(target; sink)
        int s = &apos;A&apos;;
        int t = &apos;Z&apos;;

        vector&amp;lt;int&amp;gt; p(150, -1); // BFS 기록용
        queue&amp;lt;int&amp;gt; q;
        q.push(s);
        p[s] = s; // 시작점 부모

        // BFS로 가능한 파이프 경로 찾기
        while (!q.empty())
        {
            int cur = q.front(); q.pop();
            for (int nxt : adj[cur])
            {
                if (p[nxt] != -1) continue; // 이미 방문
                if (c[cur][nxt] - f[cur][nxt] &amp;lt;= 0) continue; // 이미 다씀

                q.push(nxt);
                p[nxt] = cur; 
                if (nxt == t) break; // sink 도착
            }
        }

        // BFS로 sink에 도달 못함. (최대 유량임)
        if (p[t] == -1) break;

        // 거슬러 올라가면서 최소 찾기
        int amount = INF;
        for (int cur = t; cur != s; cur = p[cur])
            amount = min(amount, (c[p[cur]][cur] - f[p[cur]][cur]));
        for (int cur = t; cur != s; cur = p[cur])
        {
            f[p[cur]][cur] += amount; // 정방향 : 유량 늘림
            f[cur][p[cur]] -= amount; // 역방향 : 유량 줄임 (다시 빼는경우)
        }
        ans += amount;
    }

    cout &amp;lt;&amp;lt; ans;
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[UE5/UObject] 5. UObject Reflection 1</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-5/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-5/</guid><description>UObject&lt;-&gt;UClass객체 매칭, 메타데이터, CDO, Assertion함수의 개념을 알아보자</description><pubDate>Wed, 04 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;p&gt;문서 내용 정리 -&amp;gt; 강의 요약 -&amp;gt; 실습 &amp;amp; 분석 -&amp;gt; 번외 순으로 정리되어 있습니다.&lt;br /&gt;
복습할때는 실습&amp;amp;분석 파트를 읽는걸 권장&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 오브젝트의 특징과 리플렉션 시스템의 설명&lt;/li&gt;
&lt;li&gt;언리얼 오브젝트의 처리 방식의 이해&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;(문서) Unreal Property System&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://www.unrealengine.com/ko/blog/unreal-property-system-reflection&quot;&gt;링크&lt;/a&gt;
Reflection은 반사를 의미하는 그래픽 용어와 혼동되어 Unreal Property System이라고도 부른다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Reflection&lt;/code&gt;은 프로그램이 실행시간에 자기 자신을 조사하는 기능
&lt;ul&gt;
&lt;li&gt;but) C++은 지원하지 않아, 언리얼 자체적으로 구축되어 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;include &quot;FileName.generated.h&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;UENUM(), UCLASS(), USTRUCT(), UFUNCTION(), UPROPERTY() 등의 매크로를 추가하면 언리얼 헤더 툴이 적절한 코드들을 자동으로 Intermediate 폴더 내부에 생성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;모든 멤버 변수가 반드시 &lt;code&gt;UPROPERTY()&lt;/code&gt; 로 설정될 필요는 없음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;리플렉션된 프로퍼티가 아닌 것들은 UE시스템에서 관리를 받지 않음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GC가 레퍼런스를 확인 못함 (위험)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UPROPERTY()&lt;/code&gt; 붙이면 아넌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UHT는 실제 C++ 파서가 아님 (한계)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;너무 족잡한 유형은 UHT이 읽어드리지 못함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;프로퍼티 시스템의 계층 구조
&lt;ul&gt;
&lt;li&gt;추후 강의에서 자세히 다룸&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;UField
	UStruct - 기본적인 종합 구조체
		UClass (C++ class) - 자손으로 함수나 프로퍼티 포함 가능
		UScriptStruct (C++ struct) - 자손은 프로퍼티로만 제한
		UFunction (C++ function)   - 자손은 프로퍼티로만 제한
	UEnum (C++ enumeration)
	UProperty (C++ member variable or function parameter)
		(Many subclasses for different types)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 구축된 UnrealObject 클래스는 Static 클래스 함수나 Get 클래스 함수로 접근 가능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이 함수를 호출하면 이러한 Reflection, UHT이 분석해서 만들어 놓은 Reflection 정보들을 보관한 특정 객체에 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;for (TFieldIterator&amp;lt;UProperty&amp;gt; PropIt(GetClass()); PropIt; ++PropIt)
{
	UProperty* Property = *PropIt;
	// Do something with the property
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;UProperty&lt;/code&gt;라는 UnrealObject를 사용해서 순회하면, UnrealObject의 속성들을 우리가 조회하면서 거기에 대한 값이나 정보를 빼올 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;다음 강의 실습 예제로 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;(이론 강의)&lt;/h1&gt;
&lt;h2&gt;언리얼 오브젝트의 구성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 오브젝트에는 특별한 프로퍼티와 함수 지정 가능
&lt;ul&gt;
&lt;li&gt;UPROPERTY : 클래스 멤버 변수&lt;/li&gt;
&lt;li&gt;UFUNCTION : 클래스 멤버 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모든 언리얼 오브젝트는 클래스 정보와 함께 함
&lt;ul&gt;
&lt;li&gt;모든 프로퍼티 &amp;amp; 함수 정보를 언제든 조회 가능
&lt;ul&gt;
&lt;li&gt;컴파일 타임 : &lt;code&gt;UMyGameInstance::StaticClass();&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;런타임 : &lt;code&gt;GetClass();&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이런 기능을 제공하는 언리얼 오브젝트는 NewObject API를 사용해 생성해야 함.
&lt;img src=&quot;image.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/unreal-object-handling-in-unreal-engine?application_version=5.7&quot;&gt;언리얼 오브젝트 처리&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Class Default Object; CDO&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 클래스 정보에는 클래스 기본 오브젝트 &lt;code&gt;CDO&lt;/code&gt;가 포함됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CDO&lt;/code&gt;는 언리얼 객체가 가진 기본 값을 보관하는 템플릿 객체&lt;/li&gt;
&lt;li&gt;가정) 한 클래스로부터 다수의 물체를 생성해 게임 콘텐츠에 배치하는 상황
&lt;ul&gt;
&lt;li&gt;일관성 있게 기본 값을 조정하는데 유영하게 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클래스 정보&lt;code&gt;UClass&lt;/code&gt; -&amp;gt; &lt;code&gt;GetDefaultObject()&lt;/code&gt; = &lt;code&gt;CDO&lt;/code&gt; 추출 가능&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UClass&lt;/code&gt;, &lt;code&gt;CDO&lt;/code&gt;는 엔진 초기화 과정에서 생성
&lt;ul&gt;
&lt;li&gt;직접 디버그 걸어서 확인 가능 (엔진 로드 75%쯤에서 브레이크 걸림)
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UClass()와 CDO가 어떻게 동작하는지 실습을 통해 알아보자&lt;/p&gt;
&lt;h1&gt;0. 실습용 코드 구조&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;헤더 구조는 그대로 가져가고, cpp는 실습에 맞춰 수정
MyGameInstance.h&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#pragma once

#include &quot;CoreMinimal.h&quot;
#include &quot;Engine/GameInstance.h&quot;
#include &quot;MyGameInstance.generated.h&quot;

/**
 * 
 */
UCLASS()
class OBJECTREFLECTION_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
public:
	UMyGameInstance(); // 생성자

	virtual void Init() override;

private:
	UPROPERTY() // 이래야 UE가 관리해줌(GC, 직렬화, 리플렉션 등)
	FString SchoolName;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;MyGameInstance.h&quot;

UMyGameInstance::UMyGameInstance()
{
	SchoolName = TEXT(&quot;기본 학교&quot;); // 이 기본값은 CDO 템플릿 객체에 저장
}

void UMyGameInstance::Init()
{
	Super::Init();

	UE_LOG(LogTemp, Log, TEXT(&quot;====================&quot;));
	UClass* ClassRuntime = GetClass();
	UClass* ClassCompile = UMyGameInstance::StaticClass();
	UE_LOG(LogTemp, Log, TEXT(&quot;학교를 담당하는 클래스 이름 : %s&quot;), *ClassRuntime-&amp;gt;GetName());

	SchoolName = TEXT(&quot;임의의 학교&quot;);
	UE_LOG(LogTemp, Log, TEXT(&quot;학교 이름 : %s&quot;), *SchoolName);
	UE_LOG(LogTemp, Log, TEXT(&quot;학교 이름 기본값 : %s&quot;), *GetClass()-&amp;gt;GetDefaultObject&amp;lt;UMyGameInstance&amp;gt;()-&amp;gt;SchoolName);

	UE_LOG(LogTemp, Log, TEXT(&quot;====================&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;1. 검증 코드 (Assertion 함수)&lt;/h1&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;MyGameInstance.h&quot;
UMyGameInstance::UMyGameInstance()
{
}

void UMyGameInstance::Init()
{
	Super::Init();

	UE_LOG(LogTemp, Log, TEXT(&quot;====================&quot;));
	UClass* ClassRuntime = GetClass();
	UClass* ClassCompile = UMyGameInstance::StaticClass();
	check(ClassRuntime == ClassCompile);

	UE_LOG(LogTemp, Log, TEXT(&quot;학교를 담당하는 클래스 이름 : %s&quot;), *ClassRuntime-&amp;gt;GetName());

	UE_LOG(LogTemp, Log, TEXT(&quot;====================&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로그&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: ====================
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;정상적으로 &lt;code&gt;check()&lt;/code&gt; Assertion 함수를 통과하고, 로그에 MyGameInstance가 찍히는걸 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1-1. check()&lt;/h2&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;check(ClassRuntime != ClassCompile);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;이번에는, check를 부정이 뜨도록 바꿔보자.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unreal Crash Reporter&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LoginId:ec4d00644b8bfc8993dcb989a922f693
EpicAccountId:4d1d3208fcc147eba2964ad944fd2b63

Assertion failed: ClassRuntime != ClassCompile [File:F:\UE5\UE5Part1\ObjectReflection\Source\ObjectReflection\MyGameInstance.cpp] [Line: 17] 
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;컴파일 후, 언리얼을 실행해보면, 위와 같이 Crash Reporter가 뜨며 에디터가 다운된다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Assertion failed: ClassRuntime != ClassCompile&lt;/code&gt;에서 Assertion 실패&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1-2. ensure()&lt;/h2&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ensure(ClassRuntime != ClassCompile);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output Log
![[Pasted image 20260204093829.png]]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;실행하면, 에디터는 돌아가나 로그에 빨간 에러가 잡힌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1-3. ensureMsgf()&lt;/h2&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ensureMsgf(ClassRuntime != ClassCompile, TEXT(&quot;에러 발생 !!!&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output Log
![[Pasted image 20260204094409.png]]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;로그에, 우리가 입력한 TEXT도 함께 잡힌다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;check()&lt;/code&gt;와 같은 함수를 계속 넣어주며 검증하기
&lt;ul&gt;
&lt;li&gt;실제 게임으로 빌드할때는 모두 사라지니 안심하고 사용하자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;2. Class Default Object; CDO&lt;/h1&gt;
&lt;h2&gt;정의&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;UE엔진은 모든 &lt;code&gt;UClass&lt;/code&gt;타입에 대해 기본 객체 (Default Object)를 하나 자동으로 생성.
&lt;ul&gt;
&lt;li&gt;이 기본 객체를 &lt;code&gt;Class Default Object; CDO&lt;/code&gt;라고 부른다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;역할&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;해당 클래스의 기본값(Default Propery Values)을 저장하는 템플릿 역할&lt;/li&gt;
&lt;li&gt;새 인스터스를 만들 때, 이 CDO에 저장된 값들을 복사해서 초기화&lt;/li&gt;
&lt;li&gt;블루프린트 에디터에서 Default Values 창에 보이는 값들이 사실상 CDO에 들어있는 값&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;엔진이 자동으로 생성
&lt;ul&gt;
&lt;li&gt;개발자가 &lt;code&gt;new&lt;/code&gt;로 만드는 게 아님.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StaticClass()-&amp;gt;GetDefaultObject()&lt;/code&gt;로 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;실습&lt;/h2&gt;
&lt;h3&gt;2-1. CDO를 덮어주는 값 출력&lt;/h3&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;MyGameInstance.h&quot;

UMyGameInstance::UMyGameInstance()
{
	SchoolName = TEXT(&quot;기본 학교&quot;); // 이 기본값은 CDO 템플릿 객체에 저장
}

void UMyGameInstance::Init()
{
	Super::Init();

	UE_LOG(LogTemp, Log, TEXT(&quot;====================&quot;));
	UClass* ClassRuntime = GetClass();
	UClass* ClassCompile = UMyGameInstance::StaticClass();
	UE_LOG(LogTemp, Log, TEXT(&quot;학교를 담당하는 클래스 이름 : %s&quot;), *ClassRuntime-&amp;gt;GetName());

	SchoolName = TEXT(&quot;임의의 학교&quot;); // 현재 인스턴스의 멤버 변수를 덮는다. (CDO로 설정된 초기값이 덮어짐)
	UE_LOG(LogTemp, Log, TEXT(&quot;학교 이름 : %s&quot;), *SchoolName);

	UE_LOG(LogTemp, Log, TEXT(&quot;====================&quot;));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위와 같이 SchoolName = TEXT()로 CDO에 저장된 값을 덮어씌우면, 정상적으로 변경된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: 학교 이름 : 임의의 학교
LogTemp: ====================
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2-2. CDO 기본값 출력&lt;/h3&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SchoolName = TEXT(&quot;임의의 학교&quot;);
UE_LOG(LogTemp, Log, TEXT(&quot;학교 이름 : %s&quot;), *SchoolName);
UE_LOG(LogTemp, Log, TEXT(&quot;학교 이름 기본값 : %s&quot;), *GetClass()-&amp;gt;GetDefaultObject&amp;lt;UMyGameInstance&amp;gt;()-&amp;gt;SchoolName);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;이번에는, &lt;code&gt;*GetClass()-&amp;gt;GetDefaultObject&amp;lt;UMyGameInstance&amp;gt;()-&amp;gt;SchoolName&lt;/code&gt;로 CDO 기본값을 출력해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: 학교 이름 : 임의의 학교
LogTemp: 학교 이름 기본값 : 
LogTemp: ====================
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위와 같이 기본값이 안나오는데...&lt;/li&gt;
&lt;li&gt;Class Default Object를 고쳐주는 생성자 코드를 변경하는 경우에도 헤더 파일을 고치는 것과 똑같이 에디터를 꺼줘야한다.
&lt;ul&gt;
&lt;li&gt;헤더 파일의 리플렉션 정보에 구조를 변경&lt;/li&gt;
&lt;li&gt;생성자 코드에서 CDO의 기본값을 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: ====================
LogTemp: 학교를 담당하는 클래스 이름 : MyGameInstance
LogTemp: 학교 이름 : 임의의 학교
LogTemp: 학교 이름 기본값 : 기본 학교
LogTemp: ====================
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;에디터를 다시 실행하면 정상적으로 로그에 찍히는걸 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;3. 정리&lt;/h1&gt;
&lt;h2&gt;UObject &amp;lt;-&amp;gt; UClass 매칭&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;모든 &lt;code&gt;UObject&lt;/code&gt;기반 클래스는 자신을 설명하는 &lt;code&gt;UClass&lt;/code&gt;객체를 하나 가진다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;StaticClass()&lt;/code&gt;로 해당 클래스의 &lt;code&gt;UClass&lt;/code&gt;객체에 접근이 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UClass()&lt;/code&gt;aozmfhsms UHT가 해당 클래스를 리플렉션 시스테멩 등록하도록 표시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;UClass의 역할&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UClass&lt;/code&gt;는 다음 2가지의 요소를 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1. 클래스 메타데이터&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;클래스 이름, 함수 목록, 프로퍼티 목록 등
&lt;ul&gt;
&lt;li&gt;클래스 자체의 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. CDO (Class Default Object)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;기본값을 담은 템플릿 객체
&lt;ul&gt;
&lt;li&gt;인스턴스 생성 시 초기화 기준으로 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UClass&lt;/code&gt;는 CDO의 포인터를 가지고 있어 런타임에 접근 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;생성 및 반영&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;클래스 정보와 CDO는 엔진 초기화 과정에서 자동으로 생성되므로 안전하게 사용 가능&lt;/li&gt;
&lt;li&gt;헤더 변경 or 생정자에서 CDO 기본값 수정시 -&amp;gt; 에디터 종료 후 다시 컴파일해야 정상 반영&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;hr /&gt;
&lt;hr /&gt;
&lt;h1&gt;4. 번외&lt;/h1&gt;
&lt;h2&gt;UPROPERTY()&lt;/h2&gt;
&lt;h3&gt;매크로 분석&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// These macros wrap metadata parsed by the Unreal Header Tool, and are otherwise
// ignored when code containing them is compiled by the C++ compiler
#define UPROPERTY(...)
#define UFUNCTION(...)
#define USTRUCT(...)
#define UMETA(...)
#define UPARAM(...)
#define UENUM(...)
#define UDELEGATE(...)
#define RIGVM_METHOD(...)
#define VMODULE(...)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;주석 부분
&lt;ul&gt;
&lt;li&gt;이 매크로들은 Unreal Header Tool (UHT)가 파싱하는 메타데이터를 감싸는 역할을 한다.&lt;/li&gt;
&lt;li&gt;C++ 컴파일러가 코드를 컴파일할 때는 무시된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, &lt;code&gt;UPROPERTY()&lt;/code&gt;는 C++ 컴파일러 입장에서는 아무 의미 없는 빈 매크로이고, UHT가 소스 코드를 읽을 때만 의미를 가진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;매크로 정의 부분
&lt;ul&gt;
&lt;li&gt;#define UPROPERTY(...)라는 이름의 매크로를 정의했는데, 인자를 받아도 아무 코드로 치환되지 않는다.&lt;/li&gt;
&lt;li&gt;C++ 컴파일러가 볼 때는 그냥 &apos;없음!&apos; 으로 처리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UPROPERTY()&lt;/code&gt;는 C++ 컴파일러에는 무시되는 깡통 매크로&lt;/li&gt;
&lt;li&gt;하지만, UHT (Unreal Header Tool)이 소스 코드를 파싱할 때는 중요한 메타데이터로 인식
&lt;ul&gt;
&lt;li&gt;GC&lt;/li&gt;
&lt;li&gt;직렬화&lt;/li&gt;
&lt;li&gt;리플렉션&lt;/li&gt;
&lt;li&gt;블루프린트 등에 필요한 정보를 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;C++ 컴파일러는 위에서 생성된 &lt;code&gt;.generated.h&lt;/code&gt;에 메타데이터 코드를 포함해서 컴파일&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;Assertion (어설션) 함수&lt;/h2&gt;
&lt;h3&gt;정의&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;프로그램이 실행될 때 특정 조건이 참인지 검사하는 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;목적&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;잘못된 상태가 발생했을 때 즉시 알려주어 디버깅을 쉽게하고, 치명적인 오류를 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;종류&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;assert()&lt;/code&gt; 표준 C/C++
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;cassert&lt;/code&gt;헤더에 정의됨&lt;/li&gt;
&lt;li&gt;조건이 거짓이면 프로그램을 &lt;strong&gt;강제 종료&lt;/strong&gt;하고 오류 메시지를 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Unreal Engine의 &lt;code&gt;check()&lt;/code&gt; / &lt;code&gt;ensure()&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;check()&lt;/code&gt; : 조건이 거짓이면 &lt;strong&gt;즉시 크래시&lt;/strong&gt; 발생 (개발 중 치명적 오류 확인용)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ensure()&lt;/code&gt; : 조건이 거짓이면 경고 메시지 출력 후 계속 실행 (비치명적 오류 확인용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예시&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;void UMyGameInstance::Init()
{
    Super::Init();

    // 반드시 SchoolName이 비어있지 않아야 한다고 가정
    check(!SchoolName.IsEmpty()); // 조건이 거짓이면 크래시 발생

    // 혹은 덜 치명적인 경우
    ensure(!SchoolName.IsEmpty()); // 조건이 거짓이면 경고만 띄우고 실행은 계속
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>[UE5/UObject] 4. UObject DOCS</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-4/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-4/</guid><description>언리얼 오브젝트 DOCS를 읽어보며 필요성과 특징 및 UHT 등을 간단하게 살펴보자</description><pubDate>Tue, 03 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;p&gt;이미 번외에서 다룬 내용이 많아 간략하게 넘어갑니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 오브젝트의 필요성&lt;/li&gt;
&lt;li&gt;언리얼 오브젝트 선언 &amp;amp; 엔진 내부 컴파일 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;언리얼 오브젝트&lt;/h1&gt;
&lt;h2&gt;게임 프로그래밍의 특수성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;사용자 : 쾌적한 경험을 위해 최대 성능을 뽑아내야 함&lt;/li&gt;
&lt;li&gt;개발자 : 게임의 규모가 커질수록 개발 안정성이 중요함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;모덴 객체 지향 설계 원칙&lt;/h2&gt;
&lt;p&gt;현재 시점에서 모던하다는 뜻은 아님. 추후 강의에서 더 자세히 다룰 예정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SOLID 원칙&lt;/li&gt;
&lt;li&gt;후발 언어에서 보완한 새로운 기능
&lt;ul&gt;
&lt;li&gt;인터페이스 (Interface) : 객체 설계의 틀을 제공하는 추상 클래스&lt;/li&gt;
&lt;li&gt;리플렉션 (Reflection) : 런타임에서 객체의 구조를 파악하고 객체에 메타데이터 부여&lt;/li&gt;
&lt;li&gt;델리게이트 (Delegate) : 프로그램에서 발생한 이벤트를 다수의 객체에 효과적으로 전달하는데 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;언리얼 엔진은?&lt;/h2&gt;
&lt;p&gt;기존 C++ 언어를 확장해 모던 객체 지향 설계를 가능하도록 만듬&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;일반 C++ 오브젝트는 : F
&lt;ul&gt;
&lt;li&gt;저수준의 빠른 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;언리얼 오브젝트는 : U
&lt;ul&gt;
&lt;li&gt;콘텐츠 제작 관련 복잡한 설계 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;언리얼 오브젝트 문서&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/objects-in-unreal-engine?application_version=5.7&quot;&gt;언리얼 오브젝트&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;언리얼에는 게임 오브젝트 처리용 탄탄한 시스템이 있다.&lt;/li&gt;
&lt;li&gt;언리얼에서 오브젝트의 베이스 클래스는 UObject&lt;/li&gt;
&lt;li&gt;UCLASS 매크로를 사용하여 태그를 해주면 UObject 처리 시스템에서 인식&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;UCLASS 매크로&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CDO -&amp;gt; 다음 강의에서 더 자세히 다룰 예정&lt;/li&gt;
&lt;li&gt;UClass에는 클래스를 정의하는 프로퍼티와 함수 세트가 들어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UObject에 제공되는 함수성&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여러가지 매크로로 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;언리얼 헤더 툴&lt;/h3&gt;
&lt;p&gt;UHT (Unreal Header Tool)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;컴파일 과정에서 언리얼 오브젝트를 처리하기 위한 특수한 과정들이 들어감
헤더 파일 포맷&lt;/li&gt;
&lt;li&gt;언리얼이 가지는 독특한...&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;오브젝트 업데이트 및 소멸&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;현재 단계에서는 자세히 X&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;언리얼 오브젝트가 가지는 특징&lt;/h2&gt;
&lt;p&gt;추후 강의에서 예제를 통해 살펴볼 예정&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클래스 기본 객체 (CDO)&lt;/li&gt;
&lt;li&gt;리플렉션&lt;/li&gt;
&lt;li&gt;인터페이스&lt;/li&gt;
&lt;li&gt;향상된 열거형&lt;/li&gt;
&lt;li&gt;델리게이트&lt;/li&gt;
&lt;li&gt;GC&lt;/li&gt;
&lt;li&gt;향상된 구조체 (Struct)&lt;/li&gt;
&lt;li&gt;직렬화 (Serialization)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;번외편에서 이미 다룬 분석 내용이므로, 간략하게 요약합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;UHT : 언리얼 헤더 툴이 실행됨&lt;/li&gt;
&lt;li&gt;매크로들을 참조해서 언리얼 오브젝트에 관련된 generated.h 파일 같은걸 UHT이 자동으로 생성해줌&lt;/li&gt;
&lt;li&gt;그걸 포함해서 완벽한 프로그램이 되는것&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;게임이 대형화되면서 성능/유지보수 두가지가 모두 중요해짐&lt;/li&gt;
&lt;li&gt;언리얼 엔진은 C++을 확장한 U오브젝트라는 객체 구조를 고안&lt;/li&gt;
&lt;li&gt;지정된 매크로를 사용해 빌드를 수행하면, 추가 코드가 자동으로 만들어지는 구조&lt;/li&gt;
&lt;li&gt;언리얼 오브젝트를 사용해 대규모 게임 제작을 안정적으로 설계 및 구현 가능&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/UObject] 3. 기본타입과 문자열</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-3/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-3/</guid><description>언리얼 C++의 Int, TChar, FString, FName과 같은 타입과 문자열 처리 방식을 알아보자</description><pubDate>Mon, 02 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;p&gt;이번 노트는 언리얼 표준 컨벤션을 요약 정리한 내용입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 환경에서 알아두어야 할 기본 타입과 고려할 점&lt;/li&gt;
&lt;li&gt;캐릭터 인코딩 시스템에 대한 이해&lt;/li&gt;
&lt;li&gt;언리얼 C++이 제공하는 다양한 문자열 처리 방법과 내부 구성의 이해&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;언리얼 C++ 기본 타입&lt;/h1&gt;
&lt;h2&gt;int32&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;언리얼은 &lt;code&gt;int&lt;/code&gt;를 사용하지 않고 &lt;code&gt;int32&lt;/code&gt;를 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;float &amp;amp; double&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;국제 표준으로 4byte 8byte가 정의되어 있기때문에&lt;/li&gt;
&lt;li&gt;&lt;code&gt;float&lt;/code&gt; &lt;code&gt;double&lt;/code&gt; 그대로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;bool&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bool&lt;/code&gt;은 크기가 명확하지 않음&lt;/li&gt;
&lt;li&gt;헤더에는 가급적 &lt;code&gt;uint8&lt;/code&gt; 타입을 사용한다. but) 1byte는 너무 크다.
&lt;ul&gt;
&lt;li&gt;그래서 Bit Field 오퍼레이터를 사용 : &lt;code&gt;uint8 bNetTemporary:1;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;일반 &lt;code&gt;uint8&lt;/code&gt;과의 구분을 위해 b접두사 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cpp 로직에서는 자유롭게 &lt;code&gt;bool&lt;/code&gt; 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;언리얼 문자 인코딩&lt;/h1&gt;
&lt;h2&gt;UE 내부 문자열 표현&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FStrings&lt;/code&gt; : 내부적으로 &lt;code&gt;TArray&amp;lt;TCHAR&amp;gt;&lt;/code&gt;을 사용해 문자열을 관리하는 클래스&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TCHAR&lt;/code&gt; : 언리얼 표준 문자 타입
&lt;ul&gt;
&lt;li&gt;Windows 기준으로 &lt;code&gt;wchar_t&lt;/code&gt;와 매핑되어 &lt;code&gt;UTF-16&lt;/code&gt;으로 저장&lt;/li&gt;
&lt;li&gt;플랫폼에 따라 &lt;code&gt;UTF-8&lt;/code&gt;로 매핑될 수도 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UTF-16&lt;/code&gt; 인코딩 : 윈도우 기준 2byte로 사이즈가 균일&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TEXT()&lt;/code&gt; 매크로 : 문자열 리터럴을 항상 &lt;code&gt;TCHAR&lt;/code&gt;배열로 변환
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TEXT(&quot;unreal&quot;)&lt;/code&gt; -&amp;gt; &lt;code&gt;const TCHAR[] = {&apos;u&apos;, &apos;n&apos;, &apos;r&apos;, &apos;e&apos;, &apos;a&apos;, &apos;l&apos;, &apos;\0&apos;}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;c언어 스타일로 &lt;code&gt;\0&lt;/code&gt;이 붙음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FString&lt;/code&gt; : &lt;code&gt;TChar&lt;/code&gt; 배열을 포함하는 헬퍼 클래스
&lt;ul&gt;
&lt;li&gt;문자열 생성, 비교, 변환 등 다양한 기능 제공&lt;/li&gt;
&lt;li&gt;단순 포인터&lt;code&gt;TCHAR*&lt;/code&gt;보다 안전하고 편리하게 문자열을 다룰 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;UE에서 TEXT파일 로드 방식&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UTF-16&lt;/code&gt; 으로 파일을 로드한다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UnMisc.cpp&lt;/code&gt; 에 구현된 &lt;code&gt;appLoadFileToString()&lt;/code&gt; 함수로 처리&lt;/li&gt;
&lt;li&gt;위 함수는 파일에 포함된 유니코드 BOM을 인식하여 문자열을 로드&lt;/li&gt;
&lt;li&gt;만약, BOM이 없다면 플랫폼별 인코딩 규칙에 따라 해석
&lt;ul&gt;
&lt;li&gt;Windows라면 UTF-16&lt;/li&gt;
&lt;li&gt;다른 플랫폼은 UTF-8로 처리될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;UE에서 사용되는 TEXT 파일 추천 인코딩&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;INI/INT 파일
&lt;ul&gt;
&lt;li&gt;지역화(INI) 및 설정(INT) 파일은 &lt;code&gt;UTF-16&lt;/code&gt; 인코딩을 권장&lt;/li&gt;
&lt;li&gt;Windows환경에 안정적, 다국어 지원에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;소스 코드 내 문자열 처리
&lt;ul&gt;
&lt;li&gt;C++ 소스 코드 안에서 직접 문자열 하드코딩은 권장 X&lt;/li&gt;
&lt;li&gt;문자열은 별도의 INT/INI 파일로 분리하는걸 권장
&lt;ul&gt;
&lt;li&gt;지역화 &amp;amp; 유지보수에 유리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;소스 코드에서 한글 사용 시 주의점
&lt;ul&gt;
&lt;li&gt;소스에 꼭 한글을 사용하겠다면, &lt;code&gt;UTF-8&lt;/code&gt; 방식 저장 (권장x)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;실습&lt;/h1&gt;
&lt;h2&gt;1. TCHAR &amp;amp; TEXT() 매크로&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TCHAR&lt;/code&gt; 배열은 C 스타일 문자열 포인터 &lt;code&gt;const TCHAR*&lt;/code&gt; : &lt;code&gt;%s&lt;/code&gt; 를 충족한다.
&lt;ul&gt;
&lt;li&gt;내부적으로 &lt;code&gt;\0&lt;/code&gt;로 끝남&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, &lt;code&gt;*&lt;/code&gt;포인터 없이 &lt;code&gt;TCHAR[]&lt;/code&gt; -&amp;gt; &lt;code&gt;TCHAR*&lt;/code&gt;로 decay되므로 바로 전달 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;MyGameInstance.h&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UNREALSTRING_API UMyGameInstance : public UGameInstance
{
	GENERATED_BODY()
public:
	virtual void Init() override;

private:
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;MyGameInstance.h&quot;

void UMyGameInstance::Init()
{
	Super::Init();

	TCHAR LogCharArray[] = TEXT(&quot;Hello Unreal&quot;);
	UE_LOG(LogTemp, Log, TEXT(&quot;%s&quot;), LogCharArray);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output Log&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: Hello Unreal
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. FString 클래스&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/fstring-in-unreal-engine?application_version=5.7&quot;&gt;FString&lt;/a&gt; : 언리얼 문자열 클래스
&lt;ul&gt;
&lt;li&gt;내부적으로 &lt;code&gt;TArray&amp;lt;TCHAR&amp;gt;&lt;/code&gt;를 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FString&lt;/code&gt;자체를 &lt;code&gt;%s&lt;/code&gt;에 넘길 수 없다. &lt;code&gt;%s&lt;/code&gt;는 객체가 아니라 포인터&lt;code&gt;*&lt;/code&gt;를 요구하므로&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FString&lt;/code&gt;은 &lt;code&gt;operator*&lt;/code&gt;를 오버로드해서 내부 버퍼의 &lt;code&gt;const TCHAR*&lt;/code&gt;를 반환하도록 되어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;MyGameInstance.cpp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &quot;MyGameInstance.h&quot;

void UMyGameInstance::Init()
{
	Super::Init();

	TCHAR LogCharArray[] = TEXT(&quot;Hello Unreal&quot;);
	UE_LOG(LogTemp, Log, TEXT(&quot;%s&quot;), LogCharArray);

	FString LogCharString = LogCharArray;
	UE_LOG(LogTemp, Log, TEXT(&quot;%s&quot;), *LogCharString); // 포인터 필요
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Output Log&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LogTemp: Hello Unreal
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;FString&lt;/h1&gt;
&lt;p&gt;![[Pasted image 20260201225015.png]]&lt;/p&gt;
&lt;h1&gt;FName&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/fname-in-unreal-engine?application_version=5.7&quot;&gt;FName&lt;/a&gt; : 에셋 관리를 위해 사용되는 문자열 체계 (해시)
&lt;ul&gt;
&lt;li&gt;대소문자 구분 X&lt;/li&gt;
&lt;li&gt;한선 선언되면 변경 X&lt;/li&gt;
&lt;li&gt;가볍고 빠름&lt;/li&gt;
&lt;li&gt;문자표현용 X, 에셋 키를 지정하는 용도. 빌드시 해시값으로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/ftext-in-unreal-engine&quot;&gt;FText&lt;/a&gt; : 다국어 지원을 위한 문자열 관리 체계
&lt;ul&gt;
&lt;li&gt;일종의 키로 작용함&lt;/li&gt;
&lt;li&gt;별도의 문자열 테이블 정보가 추가로 요구됨&lt;/li&gt;
&lt;li&gt;게임 빌드 시 자동으로 국가별 언어로 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;구조&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;사진 첨부 필&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;언리얼이 C++ 타입 Int를 사용하지 않는 이유&lt;/li&gt;
&lt;li&gt;다양한 캐릭터 인코딩 시스템 이해&lt;/li&gt;
&lt;li&gt;언리얼의 문자열 처리&lt;/li&gt;
&lt;li&gt;FString의 구조와 사용 방법&lt;/li&gt;
&lt;li&gt;FName의 구조와 사용 방법&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>[UE5/UObject] 언리얼 클래스 문법과 필수 매크로</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-3ex/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-3ex/</guid><description>언리얼 C++에서 클래스 생성시에 보이는 UCLASS()와 XXXXX_API, GENERATED_BODY()는 무엇인지 기존 C++과 비교하면서 알아보자</description><pubDate>Mon, 02 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;강의 외적인 의문을 정리한 번외 파트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UNREALSTRING_API UMyGameInstance : public UGameInstance
{
    GENERATED_BODY()
public:
    virtual void Init() override;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 언리얼 C++을 처음 접하면 기존 C++과 다른 클래스 문법이 몇가지 보인다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UCLASS()&lt;/li&gt;
&lt;li&gt;UNREALSTRING_API&lt;/li&gt;
&lt;li&gt;GENERATED_BODY()&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;1. 기존 C++ 클래스 문법&lt;/h1&gt;
&lt;p&gt;C++에서 클래스는 아래와 같이 선언한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class 클래스명 : 접근제한자 부모클래스명
{
    // 멤버 변수와 함수
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예시&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class MyClass : public BaseClass
{
    void Hello();
};
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;2. UCLASS() 매크로&lt;/h1&gt;
&lt;h2&gt;2-1. UCLASS() 의미&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 엔진은 자체 오브젝트/리플렉션 시스템을 보유&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UCLASS()&lt;/code&gt; 매크로는 해당 클래스가 리플렉션 시스템에 등록된다는 표시
&lt;ul&gt;
&lt;li&gt;블루프린트에서 사용 가능&lt;/li&gt;
&lt;li&gt;직렬화(Serialization), 네트워킹, GC 등 엔진 기능과 연동
&lt;ul&gt;
&lt;li&gt;UHT가 생성하는 코드와 연결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2-2. generated.h와 UHT&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;UCLASS()&lt;/code&gt; 자체는 모든 기능을 담지 않고, UHT가 생성한 코드와 연결하는 hook 역할을 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;#include &quot;MyGameInstance.generated.h&quot;&lt;/code&gt; 는 UHT가 빌드 과정에서 자동 생성한 헤더&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UCLASS()&lt;/code&gt; 매크로는 내부적으로 &lt;code&gt;BODY_MACRO_COMBINE(...)&lt;/code&gt; 같은 토큰을 생성
&lt;ul&gt;
&lt;li&gt;실제 구현은 &lt;code&gt;generated.h&lt;/code&gt; 안에서 확장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2-3. UCLASS() 매크로 확장 과정&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;UCLASS()&lt;/code&gt;를 추적해보면 아래와 같이 define 되어있는데&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define UCLASS(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_PROLOG)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;code&gt;CURRENT_FILE_ID&lt;/code&gt; + &lt;code&gt;__LINE__&lt;/code&gt;  등이 합쳐져서 고유한 매크로 이름이 생성&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;예를 들어, &lt;code&gt;MyGameInstance.generated.h&lt;/code&gt; 안에서는 다음과 같이 확장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#define FID_UnrealString_Source_UnrealString_MyGameInstance_h_16_INCLASS_NO_PURE_DECLS \
private: \
	static void StaticRegisterNativesUMyGameInstance(); \
	friend struct ::Z_Construct_UClass_UMyGameInstance_Statics; \
	static UClass* GetPrivateStaticClass(); \
	friend UNREALSTRING_API UClass* ::Z_Construct_UClass_UMyGameInstance_NoRegister(); \
public: \
	DECLARE_CLASS2(UMyGameInstance, UGameInstance, COMPILED_IN_FLAGS(0 | CLASS_Transient), CASTCLASS_None, TEXT(&quot;/Script/UnrealString&quot;), Z_Construct_UClass_UMyGameInstance_NoRegister) \
	DECLARE_SERIALIZER(UMyGameInstance)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FID_UnrealString_Source_UnrealString_MyGameInstance_h_16_INCLASS_NO_PURE_DECLS&lt;/code&gt;와 같이 combine 되어 있고, 이는 다시 헤더 내부에서 define되고 있다.&lt;/p&gt;
&lt;h2&gt;2-4. 라인 번호 변화에 따른 FID 매크로 이름 변화&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;_h_16_&lt;/code&gt;의 숫자는 클래스 선언이 위치한 소스 파일의 라인 번호를 뜻한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define FID_UnrealString_Source_UnrealString_MyGameInstance_h_16_INCLASS_NO_PURE_DECLS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;만약 클래스 선언 위에 엔터를 하나 더 쳐서 줄이 바뀌면, &lt;code&gt;__LINE__&lt;/code&gt;값이 증가하여 다음과 같이 바뀐다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define FID_UnrealString_Source_UnrealString_MyGameInstance_h_17_INCLASS_NO_PURE_DECLS
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;3. UNREALSTRING_API 매크로&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;UNREALSTRING_API&lt;/code&gt;같은 매크로는 모듈 간 심볼 &lt;code&gt;export&lt;/code&gt; &lt;code&gt;import&lt;/code&gt;를 처리하기 위한 장치이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class UNREALSTRING_API UMyGameInstance : public UGameInstance
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드에 &lt;code&gt;UNREALSTRING_API&lt;/code&gt;를 VS에서 추적해보면 아래와 같이 잡힌다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Windows (MSVC)
#define UNREALSTRING_API DLLEXPORT
// -&amp;gt;
#define DLLEXPORT __declspec(dllexport)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 프로젝트는 여러 모듈로 나뉘어 있고, 각 모듈은 &lt;code&gt;DLL&lt;/code&gt;로 빌드된다.&lt;/li&gt;
&lt;li&gt;다른 모듈에서 정의한 클래스를 사용하려면 &lt;code&gt;export&lt;/code&gt; &lt;code&gt;import&lt;/code&gt; 가 필요하다.&lt;/li&gt;
&lt;li&gt;언리얼은 멀티 플랫폼을 지원하므로,  &lt;code&gt;XXXXX_API&lt;/code&gt; 매크로를 두고 환경에 맞게 아래와 같이 치환한다.
&lt;ul&gt;
&lt;li&gt;MSVC :  &lt;code&gt;__declspec(dllexport)&lt;/code&gt; / &lt;code&gt;__declspec(dllimport)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;GCC/Clang : &lt;code&gt;__attribute__((visibility(&quot;default&quot;)))&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;4. GENERATED_BODY() 매크로&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class UNREALSTRING_API UMyGameInstance : public UGameInstance
{
    GENERATED_BODY()
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;GENERATED_BODY()&lt;/code&gt;는 엔진이 자동으로 필요한 내부 코드를 삽입하게 해주는 매크로
&lt;ul&gt;
&lt;li&gt;리플렉션, 직렬화, 블루프린트 지원 등..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;클래스마다 고유한 함수/메타데이터를 생성하는 코드가 들어간다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;StaticRegisterNativesUMyGameInstance()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE_CLASS(...)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DECLARE_SERIALIZER(...)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;리플렉션&lt;/h2&gt;
&lt;p&gt;리플렉션(Reflection)은 프로그램이 자기 자신을 메타 데이터로 인식하고 다룰 수 있는 기능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;언리얼에서는 리플렉션을 통해 다음이 가능하다.
&lt;ul&gt;
&lt;li&gt;블루프린트 노출&lt;/li&gt;
&lt;li&gt;직렬화(저장/로드)&lt;/li&gt;
&lt;li&gt;에디터에서 프로퍼티 수정&lt;/li&gt;
&lt;li&gt;네트워킹/레플리케이션&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;5. 결론&lt;/h1&gt;
&lt;p&gt;언리얼 클래스 선언부는 아래와 같이 이해하면 된다&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UCLASS()
class [모듈 매크로] [클래스명] : [접근제한자] [상속클래스명]
{
    GENERATED_BODY()
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정리하면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UCLASS()&lt;/code&gt; : 언리얼 리플렉션 시스템 등록용 매크로&lt;/li&gt;
&lt;li&gt;&lt;code&gt;XXXXX_API&lt;/code&gt; : 모듈 간 심볼 export/import 처리용 매크로 (멀티 플랫폼 호환성)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GENERATED_BODY()&lt;/code&gt; : 엔진이 자동으로 필요한 내부 코드를 삽입하는 매크로&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[UE5/UObject] 2. UE C++ 표준 Docs</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-2/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-2/</guid><description>언리얼 C++ 코딩 표준을 공식 문서를 읽으면서 대략적으로 파악해보자</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;p&gt;이번 노트는 언리얼 표준 컨벤션을 요약 정리한 내용입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;프로그래밍을 시작하기 전에 알아두어야 할 언리얼 C++ 코딩 표준 이해&lt;/li&gt;
&lt;li&gt;언리얼 C++ 코딩 표준에서 주의할 점 확인&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;구글 &amp;amp; 언리얼 C++ 코딩 표준 URL&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://google.github.io/styleguide/cppguide.html&quot;&gt;구글&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/epic-cplusplus-coding-standard-for-unreal-engine?application_version=5.7&quot;&gt;언리얼&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;클래스 체계&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;public&lt;/code&gt; 구현을 먼저 선언한 후 클래스의 &lt;code&gt;private&lt;/code&gt; 구현이 뒤따라야 한다.&lt;/p&gt;
&lt;h1&gt;명명 규칙&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;파스칼 케이싱 (Pascal Casing) 사용&lt;/li&gt;
&lt;li&gt;U A S E b F 등의 접두어 활용
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;UObject&lt;/code&gt; &lt;code&gt;U&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AActor&lt;/code&gt; &lt;code&gt;A&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Swidget&lt;/code&gt; &lt;code&gt;S&lt;/code&gt; : Slate 라는 UI전용 클래스&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enum&lt;/code&gt; &lt;code&gt;E&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bool&lt;/code&gt; &lt;code&gt;b&lt;/code&gt; : 소문자
- ex: &lt;code&gt;bPendingDestrction&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그 외 대부분의 클래스는 접두사 &lt;code&gt;F&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;포터블 C++ 코드 (ex: int)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;int&lt;/code&gt; 및 부호 없는 &lt;code&gt;int&lt;/code&gt;타입은 플랫폼에 따라 크기가 다를 수 있다.&lt;/li&gt;
&lt;li&gt;주요 타입은 아래와 같이 크기를 &lt;a href=&quot;https://dev.epicgames.com/documentation/ko-kr/unreal-engine/epic-cplusplus-coding-standard-for-unreal-engine?application_version=5.7#%ED%8F%AC%ED%84%B0%EB%B8%94c++%EC%BD%94%EB%93%9C&quot;&gt;표기&lt;/a&gt;해준다.
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TCHAR&lt;/code&gt; - character(문자) (TCHAR 크기 추정 금지)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uint8&lt;/code&gt; - unsigned byte(부호 없는 바이트) (1바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int8&lt;/code&gt; - signed byte(부호 있는 바이트) (1바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uint16&lt;/code&gt; - unsigned shorts(부호 없는 short) (2바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int16&lt;/code&gt; - signed short(부호 있는 short) (2바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uint32&lt;/code&gt; - unsigned int(부호 없는 int) (4바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int32&lt;/code&gt; - signed int(부호 있는 int) (4바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uint64&lt;/code&gt; - unsigned quad word(부호 없는 쿼드 단어) (8바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;int64&lt;/code&gt; - signed quad word(부호 있는 쿼드 단어) (8바이트)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PTRINT&lt;/code&gt; - 포인터를 가질 수 있는 정수(PTRINT 크기 추정 금지)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Const 정확도&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt; 가능하면 붙여주기&lt;/li&gt;
&lt;li&gt;그 외에 아래와 같은 경우에도 &lt;code&gt;const&lt;/code&gt; 활용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 루프&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;TArray&amp;lt;FString&amp;gt; StringArray;
for (const FString&amp;amp; : StringArray)
{
    // 이 루프의 바디는 StringArray를 수정하지 않습니다.
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 포인터 자체에 const&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;	// 데이터 수정은 가능하나, 포인터에 증감 연산자 사용은 불가함
    T* const Ptr = ...;
    
    // X 틀림
    T&amp;amp; const Ref = ...;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 데이터에 const&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;	// X [복사] 나쁜 예 - const 배열 반환 
    const TArray&amp;lt;FString&amp;gt; GetSomeArray();
    
    // [읽기] 좋은 예 - const 배열로의 레퍼런스 반환
    const TArray&amp;lt;FString&amp;gt;&amp;amp; GetSomeArray();
    
    // [읽기] 좋은 예- const 배열로의 포인터 반환
    const TArray&amp;lt;FString&amp;gt;* GetSomeArray();
    
    // X [나쁜 예 - const 배열로의 const 포인터 반환 (포인터/데이터 모두 const)
    const TArray&amp;lt;FString&amp;gt;* const GetSomeArray();
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;예시 포맷&lt;/h1&gt;
&lt;p&gt;규칙에 맞춰 코드를 작성하면, &lt;code&gt;JavaDoc&lt;/code&gt;기반으로 자동 문서화 가능&lt;/p&gt;
&lt;h1&gt;최신 C++ 문법&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 엔진은 기본적으로 C++ 20 버전으로 컴파일&lt;/li&gt;
&lt;li&gt;아래에 지원되는 최신 C++ 컴파일러 기능으로 명시된 것 이외에는 신증하게 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;static_assert&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;static_assert&lt;/code&gt; 키워드는 컴파일 시간 조건 검증을 위해 사용 가능&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;조건이 거짓이면 컴파일 자체를 실패, 잘못된 코드가 실행 파일로 만들어지지 않도록 차단&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 기본 문법
static_assert(조건식, &quot;에러 메시지&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 1. [타입 크기 검증] 네트워크 패킷은 반드시 64바이트여야 한다
struct FMyPacket
{
    int32 Data[16];
};

static_assert(sizeof(FMyPacket) == 64, &quot;FMyPacket must be 64 bytes!&quot;);

// 2. [템플릿 타입 제약]
template&amp;lt;typename T&amp;gt;
void ProcessValue(T Value)
{
    static_assert(std::is_integral&amp;lt;T&amp;gt;::value, &quot;T must be an integral type&quot;);
    // 정수형 타입만 허용
}

// 3. [엔진 내부 규칙 강제] UObject 파생 클래스만 허용
template&amp;lt;typename T&amp;gt;
class TMyManager
{
    static_assert(TIsDerivedFrom&amp;lt;T, UObject&amp;gt;::IsDerived, &quot;T must derive from UObject&quot;);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;override &amp;amp; final&lt;/h2&gt;
&lt;p&gt;사용을 강력히 권합니다.&lt;/p&gt;
&lt;h2&gt;nullptr&lt;/h2&gt;
&lt;p&gt;C 스타일의 &lt;code&gt;NULL&lt;/code&gt; -&amp;gt; &lt;code&gt;nullptr&lt;/code&gt;을 사용&lt;/p&gt;
&lt;h2&gt;auto&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;auto&lt;/code&gt;는 왠만하면 사용하지 말기&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;변수에 람다 바인딩&lt;/li&gt;
&lt;li&gt;이터레이터 타입이 장황해 가독성에 악영향을 끼칠때 &lt;code&gt;auto it2 = names.begin(); &lt;/code&gt;&lt;/li&gt;
&lt;li&gt;(고급 ?) 템플릿 코드에서 표현식의 타입을 쉽게 식별 못할때&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;범위 기반 for&lt;/h2&gt;
&lt;p&gt;사용을 추천합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TMap&amp;lt;FString, int32&amp;gt; MyMap;
    
    // 기존 스타일
    for (auto It = MyMap.CreateIterator(); It; ++It)
    {
        UE_LOG(LogCategory, Log, TEXT(&quot;Key: %s, Value: %d&quot;), It.Key(), *It.Value());
    }
    
    // 새 스타일
    for (TPair&amp;lt;FString, int32&amp;gt;&amp;amp; Kvp : MyMap)
    {
        UE_LOG(LogCategory, Log, TEXT(&quot;Key: %s, Value: %d&quot;), *Kvp.Key, Kvp.Value);
    }

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;강 - 타입 Enum&lt;/h2&gt;
&lt;p&gt;가능하면 &lt;code&gt;enum class&lt;/code&gt; 를 사용&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; // 기존 열거형
    UENUM()
    namespace EThing
    {
        enum Type
        {
            Thing1,
            Thing2
        };
    }
    
    // 새 열거형
    UENUM()
    enum class EThing : uint8
    {
        Thing1,
        Thing2
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;디폴트 멤머 이니셜라이저&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class UTeaOptions : public UObject
{
    GENERATED_BODY()

public:
    UPROPERTY()
    int32 MaximumNumberOfCupsPerDay = 10;   // ← 여기서 바로 초기화
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같이 바로 초기화 하면, 생성자에서 멤버 초기화가 필요 없다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;엔진 코드: 디폴트 멤머 이니셜라이저 자주 사용 X&lt;/li&gt;
&lt;li&gt;게임 코드: 개발자가 직접 관리하니, 가독성 &amp;amp; 편의성 측면에서 자주 사용 O&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config&lt;/code&gt; &lt;code&gt;INI&lt;/code&gt;환경설정 파일을 통해 관리 하는것도 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;물리적 종속성 (Physical Dependencies)&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;파일 이름 : 접두사 X &lt;code&gt;UScene.cpp&lt;/code&gt; -&amp;gt; &lt;code&gt;Scene.cpp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;헤더 관리
&lt;ol&gt;
&lt;li&gt;모든 헤더에 &lt;code&gt;#pragma once&lt;/code&gt;넣어 중복 include 방지&lt;/li&gt;
&lt;li&gt;헤더가 늘어나면 컴파일 시간이 증가 (가능하면 적게 include)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;캡슐화&lt;/h2&gt;
&lt;p&gt;가능하면 &lt;code&gt;private&lt;/code&gt;으로 선언, &lt;code&gt;getter/setter&lt;/code&gt;로 접근&lt;/p&gt;
&lt;h2&gt;일반적인 스타일 문제&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;문자열은 꼭 &lt;code&gt;TEXT()&lt;/code&gt; 매크로 사용&lt;/li&gt;
&lt;li&gt;포인터 선언은 무조건 &lt;code&gt;FShaderType* Ptr&lt;/code&gt; 구조로
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Find In Files&lt;/code&gt;검색에 용이&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;헤더에 특수한 스태틱 변수 정의 X&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// SomeModule.h
static const FString GUsefulNamedString = TEXT(&quot;String&quot;); // 각 cpp파일에서 복사본이 생김

// 위 헤더를 참조하는 모든 인스턴스들이 컴파일 됨
// 아래와 같이 개선해야함

// SomeModule.h
extern SOMEMODULE_API const FString GUsefulNamedString; // 링크 단계에서 찾으라고 명시

// SomeModule.cpp
const FString GUsefulNamedString = TEXT(&quot;String&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;API 디자인 가이드라인&lt;/h2&gt;
&lt;h3&gt;부울 함수 파라미터 피하기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;호출하는 쪽에서 &lt;code&gt;flase&lt;/code&gt;, &lt;code&gt;ture&lt;/code&gt;가 각각 무엇을 의미하는지 알기 어려움&lt;/li&gt;
&lt;li&gt;API 확장성이 부족함&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;MakeCupOfTea(Tea, false, true, true);
MakeCupOfTea(Tea, false, true, true, false, true, ...);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;(개선) 열거형 플래그 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;enum class ETeaFlags
{
    None,
    Milk  = 0x01,
    Sugar = 0x02,
    Honey = 0x04,
    Lemon = 0x08
};
ENUM_CLASS_FLAGS(ETeaFlags)

FCup* MakeCupOfTea(FTea* Tea, ETeaFlags Flags = ETeaFlags::None);

// 호출 예시
FCup* Cup = MakeCupOfTea(Tea, ETeaFlags::Milk | ETeaFlags::Honey);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;인터페이스 클래스 규칙&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;접두사 &lt;code&gt;I&lt;/code&gt; 활용&lt;/li&gt;
&lt;li&gt;멤버 변수 X&lt;/li&gt;
&lt;li&gt;항상 추상형&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 리플렉션용 껍데기
UINTERFACE(MinimalAPI)
class UDrinkable : public UInterface
{
    GENERATED_BODY()
};

// 실제 인터페이스
class IDrinkable
{
public:
    virtual void Drink() = 0; // 반드시 구현해야 하는 계약
};


// 실제 구현 클래스 UCup
UCLASS()
class UCup : public UObject, public IDrinkable
{
    GENERATED_BODY()

public:
    virtual void Drink() override
    {
        UE_LOG(LogTemp, Log, TEXT(&quot;Cup is being drunk!&quot;));
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;public&lt;/code&gt; 에서 &lt;code&gt;private로&lt;/code&gt; 이어지는 클래스 체계 (Organization) 준수&lt;/li&gt;
&lt;li&gt;명명 규칙
&lt;ol&gt;
&lt;li&gt;파스칼 케이싱(Pascal Casing)&lt;/li&gt;
&lt;li&gt;소문자를 가급적 사용하지 말고, 공백 및 언더스코어 없음&lt;/li&gt;
&lt;li&gt;모든 클래스와 구조체에는 고유 접두사&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;코드의 명확성
&lt;ol&gt;
&lt;li&gt;파라미터에 가급적 &lt;code&gt;In&lt;/code&gt; &lt;code&gt;Out&lt;/code&gt; 접두사&lt;/li&gt;
&lt;li&gt;&lt;code&gt;const&lt;/code&gt; 지시자 (directive) 적극적 활용&lt;/li&gt;
&lt;li&gt;레퍼런스를 통한 복사 방지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auto&lt;/code&gt; 키워드 가급적 자제&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Find In Files 활용&lt;/li&gt;
&lt;li&gt;헤더 파일 및 &lt;code&gt;#include&lt;/code&gt;구문 의존성 최소화 시켜 주의 깊게 다루기&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>[UE5/UObject] 1. UE C++ 환경 설정</title><link>https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-1/</link><guid isPermaLink="true">https://heparidayo.com/posts/unrealengine/%EC%9D%B4%EB%93%9D%EC%9A%B0%EC%96%B8%EB%A6%AC%EC%96%BCpart1/1_uobject/uepart1-1/</guid><description>언리얼 C++ 개발을 위한 언리얼 및 IDE 환경 설정 후 Hello Unreal 로그를 출력해보자</description><pubDate>Sat, 31 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;&quot;이득우의 언리얼 프로그래밍 Part1 - 언리얼 C++의 이해&quot; 학습 내용을 정리한 강의 노트입니다.&lt;br /&gt;
옵시디언에 정리한 마크다운 문서라 블로그 마크다운 양식에 일부 맞지 않을 수 있습니다.&lt;/p&gt;
&lt;p&gt;강의 시작 전, 언리얼과 VS세팅에 관한 내용 정리입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;강의 목표&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;언리얼 C++ 프로그래밍을 위한 개발 환경 설정&lt;/li&gt;
&lt;li&gt;앞으로 강의에 사용할 프로그래밍 환경 구성 학습&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;개발 환경 설정&lt;/h1&gt;
&lt;h2&gt;Visual Studio 설정&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Visual Studio&lt;/li&gt;
&lt;li&gt;UE 5.7.2
&lt;ul&gt;
&lt;li&gt;디버깅을 위한 에디터 심볼 (현재는 필수 x)&lt;/li&gt;
&lt;li&gt;Rider가 VS보다 빠르다고 한다. (디버깅에는 VS가 유리)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Development Editor 좌측쯤을 우클릭해서 common으로 변경, 해당 부분 너비를 늘려준다.&lt;/li&gt;
&lt;li&gt;언리얼 새 프로젝트 -&amp;gt; Blank -&amp;gt; 이름은 &quot;HelloUnreal&quot; 에 C++로 생성&lt;/li&gt;
&lt;li&gt;C++ 매크로 자동 들여쓰기를 방지하기 위해 visualcommander와 ue4vstool 설치
&lt;ul&gt;
&lt;li&gt;(언리얼4 기준. 필수 아닌듯)&lt;/li&gt;
&lt;li&gt;https://vlasovstudio.com/visual-commander/&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;언리얼 클래스 추가&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;tools -&amp;gt; add c++ class 로 클래스를 추가하자.
&lt;img src=&quot;Pasted%20image%2020260130214206.png&quot; alt=&quot;alt text&quot; /&gt;
생성 후, VS로 가보면 Games/HelloUnreal에 &lt;code&gt;MyGameInstance.cpp&lt;/code&gt;와 &lt;code&gt;MyGameInstance.h&lt;/code&gt;가 생겨있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;언리얼 Maps 끄기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;게임을 시작할때 오픈월드 맵과 같은 연산은 아직 불필요하다.&lt;/li&gt;
&lt;li&gt;아래와 같이 전부 Clear 해서 날려주자.
&lt;img src=&quot;Pasted%20image%2020260131012949.png&quot; alt=&quot;alt text&quot; /&gt;
&lt;img src=&quot;Pasted%20image%2020260131013004.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;언리얼 Game Instance Class (싱글톤)&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260131012509.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;위 사진과 같이 Game Instance Class를 우리가 만든 MyGameInstance로 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;언리얼 코드 컴파일 방법&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;헤더 파일에 변경이 발생 - 에디터를 끄고 비주얼 스튜디오에서 컴파일&lt;/li&gt;
&lt;li&gt;소스 파일에만 변경이 발생 - 라이브 코딩으로 컴파일 (ctrl + alt + F11)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;GameInstance 클래스&lt;/h1&gt;
&lt;h2&gt;Init() 원형&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;Pasted%20image%2020260130232133.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;헤더 선언&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;public 으로 virtual void Init() override&lt;/li&gt;
&lt;li&gt;오버라이딩 해주자.
&lt;img src=&quot;Pasted%20image%2020260131013704.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;구현&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;먼저 &lt;code&gt;Super&lt;/code&gt; 키워드를 반드시 사용해서 부모 클래스 로직을 실행한다.&lt;/li&gt;
&lt;li&gt;그 후, &lt;code&gt;UE_LOG()&lt;/code&gt; 로직 찍는다.&lt;/li&gt;
&lt;li&gt;이때 &lt;code&gt;TEXT()&lt;/code&gt; 매크로를 이용해야 한글과 같은 2byte 문자를 사용할때 문제가 안생긴다.
&lt;img src=&quot;Pasted%20image%2020260131013753.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;결과&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;아래와 같이 LogTemp: 에 로그가 찍힌걸 볼 수 있다.
&lt;img src=&quot;Pasted%20image%2020260131013320.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;정리&lt;/h1&gt;
&lt;p&gt;&quot;Hello Unreal!&quot; 로그를 출력하기까지의 과정은 다음과 같았다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;언리얼 엔진 설치 &amp;amp; 프로그래밍 환경 구축&lt;/li&gt;
&lt;li&gt;언리얼 에디터 클래스 추가&lt;/li&gt;
&lt;li&gt;언리얼 C++ 클래스 상속 &amp;amp; 오버라이딩&lt;/li&gt;
&lt;li&gt;언리얼 엔진 문자열 처리 : &lt;code&gt;TEXT()&lt;/code&gt; 매크로&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GameInstance&lt;/code&gt;클래스 적용 &amp;amp; 로그 확인&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>2025년 회고</title><link>https://heparidayo.com/posts/memoir/2025%EC%A0%95%EB%A6%AC/</link><guid isPermaLink="true">https://heparidayo.com/posts/memoir/2025%EC%A0%95%EB%A6%AC/</guid><description>2026년에는 열심히 살테야</description><pubDate>Wed, 07 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;2025년을 보내며&lt;/h1&gt;
&lt;p&gt;미루고 미루던 블로그. 더 이상 방치하면 안 될 것 같아 2025년 회고록이라도 남겨본다. 올해는 하계부터 대외 활동과 프로젝트가 몰리면서, 1학기에 비해 2학기는 정말 빠르게 지나갔다.&lt;/p&gt;
&lt;h2&gt;1. 알고리즘 스터디 및 CS 공부&lt;/h2&gt;
&lt;p&gt;2025년은 CS 지식의 뿌리를 다시 단단히 하는 시기였다.&lt;/p&gt;
&lt;h3&gt;알고리즘 스터디와 백준 플래티넘&lt;/h3&gt;
&lt;p&gt;연초부터 알고리즘 &amp;amp; 자료구조 스터디에 참가했다. 실력이 뛰어난 분들을 많이 알게 되었는데, 마음속의 라이벌이자 롤모델로 삼을만한 분들을 만난 것이 가장 큰 수확이다.&lt;br /&gt;
(사실 지금 블로그의 Astro 템플릿도 스터디원분 덕분에 알게 된 것이다. 여러모로 정말 감사한 분이다.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-8.png&quot; alt=&quot;alt text&quot; /&gt;
10월부터는 다른 활동 및 수업과 시간이 겹쳐 아쉽게도 하차하게 되었지만, 정말 유익한 시간이었다. 몇 개월이나 지치지 않고 함께 해준 스터디원분들과 스터디장님께 감사한 마음이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;그 열정을 이어 2학기에는 교내에서 직접 알고리즘 스터디를 개설해 보기도 했다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-9.png&quot; alt=&quot;alt text&quot; /&gt;
야심 차게 시작했으나, 참가율 저조와 나의 주말 수업 일정이 겹치는 악재로 사실상 흐지부지되었다. 운영이란 게 쉽지 않음을 느꼈던 경험.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;백준 티어는 플래티넘을 달성했다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-2.png&quot; alt=&quot;alt text&quot; /&gt;
티어 상승을 계기로 예전에 알던 몇몇 분들이 연락을 주시기도 했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;대회 참가 기록과 아쉬움&lt;/h3&gt;
&lt;p&gt;여름 시즌에는 실력 점검 차원에서 몇몇 대회에 참가했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;교내 알고리즘 대회&lt;/strong&gt;: 문제 난이도는 전체적으로 평이했다(3차원 BFS 정도가 변별력 문제). 하지만 진행 방식이 좀 아쉬웠다. 2회차라 그런지 규칙 공지가 미흡했고, 제출 시간이 성적에 반영되는 줄 모르고 여유 있게 제출했다가 대상을 놓치고 2위(최우수상)에 머물렀다. 다음 대회는 변별력을 위해 난이도가 조금 더 상향되었으면 좋겠다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;삼성 SCPC&lt;/strong&gt;: 1차 예선에서 1번, 3번 문제를 풀어 2솔브로 2차 진출에 성공했다. 하지만, 고질적인 밤샘 습관 때문에 2차 대회 날짜를 착각해 늦잠을 자버렸다. 문제 구경도 못 하고 불참이라니... 뼈아픈 실책이었지만, 학교에서 NYPC 성적과 합산해 장학금을 챙겨준 것으로 위안을 삼았다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2025 넥슨 NYPC 대학생 코드배틀&lt;/strong&gt;: 요트 게임 AI를 제작하는 대회였다. 게임 AI에 대한 도메인 지식이 부족해 성적 자체는 좋지 않았으나, 룰 기반이 아닌 AI 로직을 고민해 보는 과정에서 많은 공부가 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;그 외에 읽은 책들&lt;/h3&gt;
&lt;p&gt;네트워크, 알고리즘, 리버싱, 운영체제등..&lt;/p&gt;
&lt;h2&gt;2. 개발 및 기술 연구 (토이 프로젝트)&lt;/h2&gt;
&lt;h3&gt;AWS 인프라 비용&lt;/h3&gt;
&lt;p&gt;개인 토이 프로젝트로 &lt;strong&gt;&apos;로즈몬티스&apos;&lt;/strong&gt; 캐릭터와 채팅이 가능한 웹사이트/디스코드 봇을 운영했었다. AWS EC2 기반으로 호스팅 중이었는데, 프리 티어가 만료되자 청구되는 비용이 학생 입장에서 꽤 부담스러웠다. 결국 눈물을 머금고 서버를 내렸다. 집에 있는 라즈베리파이로 마이그레이션해서 나중에 다시 살려낼 계획이다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-10.png&quot; alt=&quot;alt text&quot; /&gt;
디코에서 다른사람들과 대화중에 편하게 쓸 수 있어서 꽤나 애용했었다.
명일방주 일퀘 자동화, 일정관리, 시간측정, 녹음, 마크서버관리 등.. 잡다한 기능을 통합해 놨었다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;게임 엔진과 그래픽스&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;C++ Asio 분석&lt;/strong&gt;: 네트워크 프로그래밍의 기초를 복습하기 위해 C++ Asio 라이브러리의 공식 튜토리얼을 분석하며 비동기 I/O 모델을 학습했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unreal Engine 5 리슨 서버 분석&lt;/strong&gt;: 언리얼 5의 리슨(Listen) 서버를 기준으로 제공되는 API와 서버 구조를 뜯어보았다. &lt;code&gt;GameMode&lt;/code&gt;, &lt;code&gt;GameState&lt;/code&gt;, &lt;code&gt;PlayerState&lt;/code&gt; 같은 핵심 클래스와 RPC(Remote Procedure Call)의 작동 원리 등을 분석하고, 이를 검증하기 위한 간단한 프로젝트를 제작하며 개념을 확립했다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI × Unity 클리커 토이 프로젝트&lt;/strong&gt;: &apos;나노바나나 AI&apos;와 &apos;Grok&apos;을 활용해 세로 모드 클리커 게임을 만들어보았다. 원래 유니티 UI 계층 구조를 깔끔하게 설계하는 아키텍처 연습용 프로젝트였으나, 게임에 필요한 이미지 리소스들을 AI로 생성해 보면서 나노 바나나의 위력의 위력을 몸소 체감한 프로젝트였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-11.png&quot; alt=&quot;alt text&quot; /&gt;
&lt;img src=&quot;sadLoop-2.gif&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;귀의 움직임이 나오는게 특히나 신기했던 리소스&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OpenGL &amp;amp; Shader Sandbox&lt;/strong&gt;: 학교 발표 과제를 계기로 OpenGL과 C++ GUI 라이브러리인 &lt;code&gt;glad&lt;/code&gt;를 활용한 샌드박스 프로젝트를 진행했다. 큐브를 렌더링하고 회전시키며 쉐이더 코드, 텍스처, 조명 색상 등을 런타임에 변경하는 툴을 만들었다. 엔진이 다 해주는 걸 직접 짜보니 공부가 많이 됐다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;temp3.gif&quot; alt=&quot;alt text&quot; /&gt;
빙글빙글~&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. 커리어 및 활동&lt;/h2&gt;
&lt;p&gt;올해는 다양한 활동 덕분에 경제적인 부분(등록금, 생활비)을 어느정도 해결하며, 공부에 집중할 수 있는 환경을 만들 수 있었다.&lt;/p&gt;
&lt;h3&gt;스타트업 근무 및 연구생&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;작년말부터 여름까지 작은 스타트업에서 유니티 클라이언트 개발로 근무했다. 2학기부터는 개인 프로젝트와 깊이 있는 공부에 더 시간을 쏟고 싶어 아쉽지만 퇴사했다. 퇴사 후, 교수님의 추천으로 2학기 동안은 학생 연구원 신분으로 소속되어 있었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;영호남 대학 학술대회 (최우수상 &amp;amp; 리버스 엔지니어링상)&lt;/h3&gt;
&lt;p&gt;2박 3일 일정으로 참가해 프로젝트를 발표했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;성과&lt;/strong&gt;: 최우수상과 동료 평가상인 &apos;리버스 엔지니어링상&apos;을 동시 수상했다. 단순한 발표가 아니라, 포스트 디펜스 형식으로 다른 참가자들과 양방향으로 소통하며 내 프로젝트를 이해시키는 과정이 꽤 즐거웠다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배운 점&lt;/strong&gt;: PyPI 배포, AWS 및 Terraform 활용 등 인프라 지식이 확실히 늘었다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;에피소드&lt;/strong&gt;: 팀명을 명일방주의 &apos;로즈몬티스&apos;에서 따와 &apos;로즈마리&apos;로 지었으나... 아무도 못 알아봤다. 우리 팀을 제외하면 전원 4학년이었는데, 2학년 팀으로서 유의미한 상금과 성과를 거둬 뿌듯했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;생산성 도구와 자격증 (GenAI)&lt;/h3&gt;
&lt;p&gt;학교 생성형 AI 수업을 통해 NotebookLM, Replit 등 다양한 툴을 익혔고, 커리큘럼에 포함된 &lt;strong&gt;AWS Certified AI Practitioner&lt;/strong&gt; 자격증까지 취득했다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-4.png&quot; alt=&quot;alt text&quot; /&gt;
실무 툴 사용법도 익히고, 학교에서 자격증 취득 장학금도 챙겨주니 일석이조였다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;학업 성취와 장학금 해프닝&lt;/h3&gt;
&lt;p&gt;전공 변경과 복수전공으로 걱정이 많았으나, 다행히 과 수석을 차지하며 &lt;strong&gt;등록금 전액 면제&lt;/strong&gt;를 받았다. 여기에 각종 대외 활동과 SW중심대학 마일리지까지 더해져 꽤 많은 장학금을 기대했는데...&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-3.png&quot; alt=&quot;alt text&quot; /&gt;
&lt;img src=&quot;image-1.png&quot; alt=&quot;alt text&quot; /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;학교 규정상 한 학기 최대 수혜 가능 마일리지는 2,500점이었고, 2학기에만 무려 &lt;strong&gt;3,120마일리지&lt;/strong&gt;를 모아버린 나는 강제로 장학금이 삭감당하는(?) 웃지 못할 일을 겪었다.
결과적으로 급여, 각종 대회 상금, 그리고 (깎였지만) 넉넉한 장학금과 등록금 면제 덕분에 부모님 손을 빌리지 않고 한 해를 마무리했다. 덕분에 2026년에는 알바 걱정 없이 온전히 개발과 공부에만 몰입할 수 있는 시간을 벌어둔 것 같아 든든하다.&lt;/p&gt;
&lt;h2&gt;4. 코딩력 채우기&lt;/h2&gt;
&lt;p&gt;올해는 유독 비주얼 노벨 장르를 많이 즐겼다. &lt;strong&gt;&amp;lt;활협전&amp;gt;, &amp;lt;명말천리행&amp;gt;, &amp;lt;당신과 나의 존재불명&amp;gt;, &amp;lt;마법소녀의 마녀재판&amp;gt;&lt;/strong&gt; 등... 스토리 게임들에 푹 빠져 지냈다.
특이한 점은 이런 스토리 게임의 OST를 들으면 묘하게 코딩 욕구가 솟구친다는 것이다. 벅차오르는 감정 때문인지, 몰입감 때문인지는 모르겠지만 개발 생산성에 긍정적인 도움이 된다고 믿고 있다. (단순한 자기합리화일지도 모르겠지만, 일단 나는 그렇다.)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img src=&quot;image-5.png&quot; alt=&quot;alt text&quot; /&gt;
가장 잔인하고 상냥한 복수.
&lt;img src=&quot;image-6.png&quot; alt=&quot;alt text&quot; /&gt;
릴리스는 존재하며
&lt;img src=&quot;image-7.png&quot; alt=&quot;alt text&quot; /&gt;
병을 구우면 병 구이가 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 2026년 계획&lt;/h2&gt;
&lt;p&gt;26년에는 아래와 같은 분야에 관심을 가져볼 예정이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Unity NGO 프로젝트: 26년도부터 지인과 함께 유니티 NGO(Netcode for GameObjects)를 활용한 게임을 개발해 보려고 한다. 과거에 비해 유니티 네트워크 생태계가 어떻게 변했는지 직접 부딪혀보며 체험해볼 생각이다.&lt;/li&gt;
&lt;li&gt;UE5 공부: 최근에 공개한 언리얼의 네트워크(서버) 관련 로드맵이 심상치않다. 지금이라도 언리얼을 공부해서 UE6즈음에 서버 코드를 분석이 가능한 기초 체력을 만들어 놔야 될거 같다.&lt;/li&gt;
&lt;li&gt;AI 인식 변화: 2025년 2학기 GenAI수업을 통해 여러 노코드툴과 AI를 직접 접하고, 토이 프로젝트에 직접 사용해보면서 내 생각보다 AI의 발전이 매우 빠르다는걸 체감했다. 이후로는 AI툴을 적극적으로 사용하고 관련 정보를 놓치지 않도록 노력해 볼 것이다.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[TIL] ++i vs i++ 어셈블리 비교 메모</title><link>https://heparidayo.com/posts/til/%EC%A0%84%EC%9C%84%ED%9B%84%EC%9C%84%EB%B9%84%EA%B5%90/</link><guid isPermaLink="true">https://heparidayo.com/posts/til/%EC%A0%84%EC%9C%84%ED%9B%84%EC%9C%84%EB%B9%84%EA%B5%90/</guid><pubDate>Wed, 03 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h3&gt;전위 증가 (++i) vs 후위 증가 (i++)&lt;/h3&gt;
&lt;p&gt;후위 증가는 임시 변수가 필요해 전위 증가보다 느리다고 하는데, 어셈블리 레벨에서 확인해보자.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;환경: MSVC (Visual Studio) x64 C++ 컴파일러 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;h5&gt;전위증가&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;mov         eax,dword ptr [b]                   ; eax = b
inc         eax                                 ; eax++
mov         dword ptr [b],eax                   ; b = eax
mov         eax,dword ptr [b]                   ; eax = b
mov         dword ptr [d],eax                   ; d = eax
&lt;/code&gt;&lt;/pre&gt;
&lt;h5&gt;후위증가&lt;/h5&gt;
&lt;pre&gt;&lt;code&gt;mov         eax,dword ptr [a]                   ; eax = a
mov         dword ptr [rbp+134h],eax            ; ! temp = a
mov         eax,dword ptr [a]                   ; eax = a
inc         eax                                 ; eax++
mov         dword ptr [a],eax                   ; a = eax
mov         eax,dword ptr [rbp+134h]            ; ! eax = temp
mov         dword ptr [c],eax                   ; c = eax
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;후위연산은 임시로 base pointer + 0x134 (+308byte) 떨어진 곳에 a를 저장(&lt;code&gt;mov&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;다시 불러오는(&lt;code&gt;mov&lt;/code&gt;) 과정이 추가&lt;/li&gt;
&lt;li&gt;결론적으로 &lt;code&gt;mov&lt;/code&gt;연산이 2회 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h3&gt;코드 전문&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;	int a = 1;
00007FF650F8DF8C  mov         dword ptr [a],1  
	int b = 1;
00007FF650F8DF93  mov         dword ptr [b],1  

	a++;
00007FF650F8DF9A  mov         eax,dword ptr [a]  
00007FF650F8DF9D  inc         eax  
00007FF650F8DF9F  mov         dword ptr [a],eax  
	++b;
00007FF650F8DFA2  mov         eax,dword ptr [b]  
00007FF650F8DFA5  inc         eax  
00007FF650F8DFA7  mov         dword ptr [b],eax  

	int c = a++;   // 후위 증가
00007FF650F8DFAA  mov         eax,dword ptr [a]  
00007FF650F8DFAD  mov         dword ptr [rbp+134h],eax  
00007FF650F8DFB3  mov         eax,dword ptr [a]  
00007FF650F8DFB6  inc         eax  
00007FF650F8DFB8  mov         dword ptr [a],eax  
00007FF650F8DFBB  mov         eax,dword ptr [rbp+134h]  
00007FF650F8DFC1  mov         dword ptr [c],eax  
	int d = ++b;   // 전위 증가
00007FF650F8DFC4  mov         eax,dword ptr [b]  
00007FF650F8DFC7  inc         eax  
00007FF650F8DFC9  mov         dword ptr [b],eax  
00007FF650F8DFCC  mov         eax,dword ptr [b]  
00007FF650F8DFCF  mov         dword ptr [d],eax  
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>