<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jupiter_0609.log</title>
        <link>https://velog.io/</link>
        <description>게임개발 및 아트, TA</description>
        <lastBuildDate>Fri, 04 Sep 2026 13:03:07 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jupiter_0609.log</title>
            <url>https://velog.velcdn.com/images/jupiter_0609/profile/74762561-b273-4e3d-92b3-7af7d055194b/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jupiter_0609.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jupiter_0609" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[슈터 게임 기획 셋업]]></title>
            <link>https://velog.io/@jupiter_0609/%EC%8A%88%ED%84%B0-%EA%B2%8C%EC%9E%84-%EA%B8%B0%ED%9A%8D-%EC%85%8B%EC%97%85</link>
            <guid>https://velog.io/@jupiter_0609/%EC%8A%88%ED%84%B0-%EA%B2%8C%EC%9E%84-%EA%B8%B0%ED%9A%8D-%EC%85%8B%EC%97%85</guid>
            <pubDate>Fri, 04 Sep 2026 13:03:07 GMT</pubDate>
            <description><![CDATA[<h1 id="게임-이름-veilbreak베일브릭"><strong>게임 이름 :VeilBreak(베일브릭)</strong></h1>
<h3 id="1-기본-개요--3인칭-기반의-fps-보스-레이드"><strong>1. 기본 개요 : 3인칭 기반의 FPS 보스 레이드</strong></h3>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/a1e7eda5-5230-4534-a6ba-265335f29807/image.png" alt=""></p>
<h3 id="2-전투로직"><strong>2. 전투로직</strong></h3>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/4ffb6207-2183-4332-b4e4-dfae28e29c94/image.png" alt="">
<img src="https://velog.velcdn.com/images/jupiter_0609/post/34b44938-e50e-4e44-a389-644d80d87644/image.png" alt=""></p>
<h3 id="3-게임-루프"><strong>3. 게임 루프</strong></h3>
<p><strong>1. 프로그램 실행</strong></p>
<ul>
<li>세이브 데이터 로드(클리어 기록)</li>
<li>메인 메뉴 UI(인게임에 입장하는 UI)</li>
</ul>
<p><strong>2. 인게임 레벨 초기화</strong>
GameMode 실행</p>
<ul>
<li>플레이어와 보스 스폰</li>
<li>HP, 탄약 등 HUD 가져오기</li>
<li>보스 AI 가동</li>
</ul>
<p><strong>3. 인게임 메인 루프</strong>
플레이어 액션</p>
<ul>
<li>이동, 달리기, 구르기</li>
<li>사격 → 보스 피격 판정 및 데미지</li>
<li>특수 기술 → 그에 대한 효과(보스에게 데미지 등)</li>
<li>재장전 및 탄약·체력 관리
보스 AI 루프</li>
<li>플레이어 감지 및 추적</li>
<li>거리 및 체력 등의 조건에 따라 패턴 발동</li>
<li>피격 애니메이션 및 체력 관리
틱(Tick)</li>
<li>경과 시간 업데이트</li>
<li>히트 마커나 데미지 텍스트 등의 연출</li>
</ul>
<p><strong>4. 게임 종료 및 저장 단계</strong>
승리 조건: 보스 체력 0</p>
<ul>
<li>게임 정지 → 클리어 기록 출력 및 저장</li>
<li>클리어 UI 출력(점수)
패배 조건: 플레이어 체력 0</li>
<li>게임 정지 및 게임오버 UI 호출</li>
<li>재시작/메인 메뉴 이동 UI</li>
</ul>
<p><strong>4. UI 레퍼런스 분석 및 기본 배치</strong></p>
<ul>
<li>UI 레퍼런스 분석
<img src="https://velog.velcdn.com/images/jupiter_0609/post/5753f226-3027-4c27-a2c5-cd68dfb0c48c/image.png" alt=""></li>
<li>기본 배치
<img src="https://velog.velcdn.com/images/jupiter_0609/post/ac969a8c-5ff0-4201-8e41-b0a1c5de9613/image.png" alt=""></li>
</ul>
<p><strong>5. 사용 에셋 목록</strong>
<img src="https://velog.velcdn.com/images/jupiter_0609/post/20322bac-0b99-449b-b08c-1ed92783f064/image.png" alt=""></p>
<p><strong>6. 레퍼런스 게임의 몬스터 기믹 분석 후 사용 기믹 확정</strong></p>
<ul>
<li>게임 분석
<img src="https://velog.velcdn.com/images/jupiter_0609/post/4e147f1d-2499-4f98-8bd1-6253a422fe1f/image.png" alt=""></li>
<li>보스 확정
<img src="https://velog.velcdn.com/images/jupiter_0609/post/5e0f7924-9d02-40e7-9867-8ca07de5ecde/image.png" alt=""></li>
</ul>
<p><strong>7. PC 구체화 및 스킬 확정</strong>
<img src="https://velog.velcdn.com/images/jupiter_0609/post/0d75cd8b-76fa-42c9-abbb-6ede565d69da/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] BlueprintReadWhite]]></title>
            <link>https://velog.io/@jupiter_0609/TIL-BlueprintReadWhite</link>
            <guid>https://velog.io/@jupiter_0609/TIL-BlueprintReadWhite</guid>
            <pubDate>Fri, 28 Aug 2026 12:04:37 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/ae73cec6-9bda-45d6-9195-827675ba8df6/image.png" alt="">
블루 프린트에서 읽을 수 있고 하얗다...
제 머릿속 같군요.
희게 질려 누구든 알 수 있겠습니다.</p>
<p>일단 BlueprintReadWrite로 다시 바꿔주죠...
빌드 후 클래스를 검색하면 이렇게 잘 들어온 걸 확인할 수 있습니다. 야호.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/9ea9918a-970e-48fe-8a16-b99a206bedd2/image.png" alt="">
이렇게 BaseItem을 상속받은 CoinItem을 확인 할 수 있습니다. 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/e380ccc8-7f6d-40bc-a58c-863ba0f5c406/image.png" alt="">
그리고 다시 확인해보면 코인 아이템이 잘 생성되었습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/265bc0a0-d4cf-42af-8972-f8b3f210551f/image.png" alt="">
작업하던 도중 원인을 추적하기 어려운 오류가 발생할 때가 가끔 있었습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/86e14e6b-a543-466b-9884-ae0f9b8a847b/image.png" alt=""></p>
<p>이런식으로 알 수 없는 위치에 붉은 줄이 표시되고, 빌드 또한 되지 않았는데요.
이럴 때 의심해 볼 수 있는 요인이 몇 가지 있습니다.</p>
<blockquote>
<p>① IntelliSense가 UE include 경로를 잃어버린 경우
② ItemInterface.generated.h가 아직 생성되지 않은 경우
③ .generated.h의 위치 문제
④ 프로젝트 파일이 꼬인 경우</p>
</blockquote>
<p>입니다.
우선 1번부터 시도해보죠.
해결 방법은 다음과 같습니다.</p>
<blockquote>
<p>① Unreal Editor와 Visual Studio를 모두 종료합니다.
② 프로젝트 폴더에서 프로젝트이름.uproject 파일을 찾아 우클릭합니다.
③ Generate Visual Studio project files를 선택합니다.
④ 생성이 끝나면 .sln 파일을 다시 열어 Ctrl + Shift + B로 빌드합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/033ee290-25db-4dcf-8bf6-da343c8a3750/image.png" alt="">
이러고 확인해보니 이상한 붉은 줄이 모두 없어졌습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/2f8f5bc0-cb92-4b3c-b032-472e40db05ce/image.png" alt="">
프로젝트 메타정보를 재생성해서 Visual Studio와 UE 프로젝트를 다시 동기화하는 작업이라고 하는군요...</p>
<hr>
<p>+팀원분들과 면담을 마쳤습니다. 기존에 알던 사실 외에도 조율할 부분을 가볍게 얘기해 보았습니다. 잘 해볼 수 있을 것 같습니다. 파이팅~!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CH3 게임 루프 및 UI 재설계하기]]></title>
            <link>https://velog.io/@jupiter_0609/CH3-%EA%B2%8C%EC%9E%84-%EB%A3%A8%ED%94%84-%EB%B0%8F-UI-%EC%9E%AC%EC%84%A4%EA%B3%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jupiter_0609/CH3-%EA%B2%8C%EC%9E%84-%EB%A3%A8%ED%94%84-%EB%B0%8F-UI-%EC%9E%AC%EC%84%A4%EA%B3%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 25 Aug 2026 13:58:51 GMT</pubDate>
            <description><![CDATA[<p>과제를 진행하기 앞서 해당 과제를 어떻게 해석해서 풀이할까 고민해보면 좋겠습니다.</p>
<p>현재 TA 채용에서 반복해서 나오는 역할은 <strong>아트와 프로그래밍 사이의 연결, 제작 환경/툴 개선, 시각 효과 구현, 최적화와 프로파일링, 기술 문서화</strong>입니다. 실제 최근 공고에서도 UE5, C++/Python, 아트 제작 환경 구축, 툴·자동화, 머티리얼/라이팅, 성능 분석과 최적화가 반복됩니다.</p>
<p> 따라서 「멀티 웨이브 게임 제작」이 아니라 「디자이너 친화적인 Data-Driven Wave &amp; Visual Feedback System」로 제작해보는 건 어떨까 싶습니다.</p>
<p> 우선 들어갈 필수 구현 항목을 정리해봅시다.</p>
<hr>
<h2 id="필수-구현-항목">[필수 구현 항목]</h2>
<h3 id="1-멀티-웨이브-및-레벨-시스템-구축">1. 멀티 웨이브 및 레벨 시스템 구축</h3>
<blockquote>
</blockquote>
<ul>
<li>구현했던 SpawnVolume, 충돌(Overlap), GameState/GameMode 등을 재사용 합니다.</li>
<li>한 레벨 안에서 레벨 전환 없이 1~3단계의 웨이브 (게임 진행)을 진행합니다.</li>
<li>각 웨이브마다 <strong>제한 시간, 아이템 스폰 횟수</strong>의 베리에이션을 설정합니다.</li>
</ul>
<hr>
<ul>
<li><input disabled="" type="checkbox"> 전체 게임 진행: 3번의 레벨이 전환이 됩니다.<ul>
<li>각 레벨 안에서 최소 <strong>3단계 이상의 웨이브</strong>를 만들어봅니다.</li>
<li>각 웨이브는 난이도 조정을 위해, 시간과 아이템의 갯수가 달라집니다.</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> 웨이브 시작시  <code>UE_LOG</code>나 <code>GEngine-&gt;AddOnScreenDebugMessage</code> 등의 방법으로
“Wave 1 시작!”  알림을 출력합니다.</li>
</ul>
<hr>
<h3 id="2-uiux-리뉴얼-및-기능-연결">2. UI/UX 리뉴얼 및 기능 연결</h3>
<blockquote>
<p> <strong>UI 분석 및 요구사항 정리  **
        - <code>WBP_HUD</code>, <code>WBP_MainMenu</code> , <code>HP</code> 위젯을 **기능과</strong> <strong>디자인</strong> 관점에서 <strong>요약/분석</strong>합니다.
        - 분석한 정보를 바탕으로, 기획안에 개선점을 녹여냅니다.
        - ex) “HUD에 표시하고 싶은 정보가 무엇인지”,
        - ex2) “메인 메뉴/종료 메뉴에 필요한 버튼 및 레이아웃은 어떠한지”</p>
</blockquote>
<hr>
<p> ** HUD 및 Menu UI 재설계**</p>
<ul>
<li><strong>HUD 위젯 재디자인</strong><ul>
<li><strong>캔버스 구조</strong> (CanvasPanel, VerticalBox 등)를 적절히 배치합니다.</li>
<li>점수, 타이머, 레벨 등 정보를 보기 좋게 표시합니다.</li>
<li>텍스트 스타일 (폰트, 색상, 테두리 등)과 아이콘, 게이지 바 등을 활용해 <strong>디자인 완성도</strong>를 높입니다.<ul>
<li><strong>메뉴 UI 재디자인</strong></li>
</ul>
</li>
<li>게임 시작, 재시작, 종료 버튼을 재배치하고, 배경 이미지·반투명 블러 등 시각적 요소를 개선합니다.</li>
<li>버튼 hover, clicked 등 <strong>인터랙션 효과</strong> (색/이미지 변화)를 적용합니다. (UButton Style)</li>
</ul>
</li>
</ul>
<ul>
<li><input disabled="" type="checkbox"> <strong>HUD에 표시할 정보를 표시합니다.</strong><ul>
<li>점수, 시간, 체력</li>
<li>전부 한 화면에서 볼 수 있도록 배치합니다.</li>
<li><input disabled="" type="checkbox"> <strong>아래의 UI를 재설계 합니다.</strong></li>
<li>메인 메뉴 (시작, 종료)</li>
<li>게임 오버 메뉴 (재시작, 메인 메뉴로 돌아가기)</li>
<li><input disabled="" type="checkbox"> <strong>디자인을 업그레이드 합니다.</strong></li>
<li>폰트, 색상, 배경 등을 적절히 조정하여 일관된 디자인을 적용합니다.</li>
<li>각자 원하는 스타일로, 멋지고 직관적인 UI를 구현합니다.</li>
<li><input disabled="" type="checkbox"> <strong>기능 구현</strong></li>
<li>****레벨 이동 또는 게임 종료 등
각 버튼 클릭 시 C++ / 블루프린트 함수를 호출합니다.</li>
</ul>
</li>
</ul>
<hr>
<p> 할 일이 많군요.
 일단 할 수 있는 일을 최대한 해봅시다.
 필수 기능 구현을 변주하면 될 일입니다.</p>
<table>
<thead>
<tr>
<th>과제 요구사항</th>
<th>TA 포트폴리오식 구현</th>
</tr>
</thead>
<tbody><tr>
<td>웨이브마다 시간 변경</td>
<td><code>FWaveConfig</code> 데이터화</td>
</tr>
<tr>
<td>아이템 스폰 수 변경</td>
<td>DataAsset/DataTable에서 조절</td>
</tr>
<tr>
<td>HUD</td>
<td>이벤트 기반 HUD</td>
</tr>
<tr>
<td>웨이브 시작 알림</td>
<td>UI Animation + VFX</td>
</tr>
<tr>
<td>환경 변화</td>
<td>Material/VFX와 웨이브 연동</td>
</tr>
<tr>
<td>디버프</td>
<td>공통 Effect 구조 + 다형성</td>
</tr>
<tr>
<td>UI</td>
<td>상태에 따라 자동 갱신</td>
</tr>
<tr>
<td>최적화</td>
<td>Profiler로 검증</td>
</tr>
</tbody></table>
<p>이정도면 마감 내에 할 수 있지 않을까 기대해봅니다. </p>
<p>아자아자 파이팅.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[VOD 2강 복습]]></title>
            <link>https://velog.io/@jupiter_0609/VOD-2%EA%B0%95-%EB%B3%B5%EC%8A%B5</link>
            <guid>https://velog.io/@jupiter_0609/VOD-2%EA%B0%95-%EB%B3%B5%EC%8A%B5</guid>
            <pubDate>Fri, 21 Aug 2026 10:39:38 GMT</pubDate>
            <description><![CDATA[<h2 id="c에서-헷갈리는-연산자-정리---vs--vs-">C++에서 헷갈리는 연산자 정리: <code>-&gt;</code> vs <code>.</code> vs <code>::</code></h2>
<p>언리얼 C++ 코드를 읽다 보면 <code>-&gt;</code>, <code>.</code>, <code>::</code>가 굉장히 자주 등장합니다.</p>
<p>처음에는 셋 다 대충 <strong>&quot;~의&quot;</strong> 정도로 읽혀서 헷갈리기 쉬운데, 실제로는 접근하는 대상이 다릅니다.</p>
<table>
<thead>
<tr>
<th>연산자</th>
<th>간단하게 읽으면</th>
<th>언제 사용하는가</th>
</tr>
</thead>
<tbody><tr>
<td><code>-&gt;</code></td>
<td>이 포인터가 가리키는 객체의</td>
<td><strong>포인터</strong>를 통해 멤버에 접근</td>
</tr>
<tr>
<td><code>.</code></td>
<td>이 객체의</td>
<td><strong>객체 자체</strong>에서 멤버에 접근</td>
</tr>
<tr>
<td><code>::</code></td>
<td>이 클래스/범위에 속한</td>
<td>특정 <strong>클래스나 범위</strong>를 지정</td>
</tr>
</tbody></table>
<hr>
<h2 id="1----포인터가-가리키는-객체의">1. <code>-&gt;</code> : 포인터가 가리키는 객체의</h2>
<p><code>-&gt;</code>는 <strong>포인터를 통해 그 객체의 변수나 함수에 접근할 때</strong> 사용합니다.</p>
<pre><code class="language-cpp">PlayerController-&gt;MoveAction</code></pre>
<p>말로 풀어 읽으면 다음과 같습니다.</p>
<blockquote>
<p><code>PlayerController</code> 포인터가 가리키는 객체의 <code>MoveAction</code></p>
</blockquote>
<p>즉, <code>PlayerController</code>가 객체 그 자체가 아니라 <strong>객체를 가리키는 포인터</strong>이기 때문에 <code>.</code>이 아니라 <code>-&gt;</code>를 사용합니다.</p>
<pre><code class="language-cpp">AJupiterPlayerController* PlayerController;</code></pre>
<p>여기서 <code>*</code>가 붙어 있으므로 <code>PlayerController</code>는 <code>AJupiterPlayerController</code> 객체를 가리킬 수 있는 <strong>포인터 변수</strong>입니다.</p>
<p>따라서 다음과 같이 사용합니다.</p>
<pre><code class="language-cpp">PlayerController-&gt;MoveAction</code></pre>
<hr>
<h2 id="2---객체-자체의">2. <code>.</code> : 객체 자체의</h2>
<p><code>.</code>은 포인터가 아니라 <strong>객체 자체를 가지고 있을 때</strong> 그 객체의 멤버에 접근하는 연산자입니다.</p>
<p>예를 들어 다음과 같은 객체가 있다고 가정해보겠습니다.</p>
<pre><code class="language-cpp">AJupiterPlayerController PlayerController;</code></pre>
<p>이 경우 <code>PlayerController</code>는 포인터가 아니라 객체 자체이므로 다음과 같이 접근합니다.</p>
<pre><code class="language-cpp">PlayerController.MoveAction</code></pre>
<p>즉,</p>
<pre><code class="language-cpp">PlayerController.MoveAction</code></pre>
<p>은</p>
<blockquote>
<p><code>PlayerController</code> 객체의 <code>MoveAction</code></p>
</blockquote>
<p>이라고 읽을 수 있습니다.</p>
<p>반면,</p>
<pre><code class="language-cpp">PlayerController-&gt;MoveAction</code></pre>
<p>은</p>
<blockquote>
<p><code>PlayerController</code> 포인터가 가리키는 객체의 <code>MoveAction</code></p>
</blockquote>
<p>이라고 읽을 수 있습니다.</p>
<h3 id="한-줄-정리">한 줄 정리</h3>
<pre><code class="language-cpp">객체.멤버
포인터-&gt;멤버</code></pre>
<p>이렇게 기억하면 비교적 간단합니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/5bef25ea-eb7a-421e-9d60-7372e33cfb09/image.png" alt=""></p>
<hr>
<h2 id="3---특정-클래스나-범위에-속한-것">3. <code>::</code> : 특정 클래스나 범위에 속한 것</h2>
<p><code>::</code>는 <strong>범위 지정 연산자(Scope Resolution Operator)</strong>입니다.</p>
<p>쉽게 말하면,</p>
<blockquote>
<p>&quot;<code>::</code> 앞에 있는 범위에 속한 것을 사용하겠다.&quot;</p>
</blockquote>
<p>라고 읽을 수 있습니다.</p>
<p>예를 들어 다음 코드를 보겠습니다.</p>
<pre><code class="language-cpp">AJupiterCharacter::Move</code></pre>
<p>이는</p>
<blockquote>
<p><code>AJupiterCharacter</code> 클래스에 속한 <code>Move</code></p>
</blockquote>
<p>라는 뜻입니다.</p>
<p>함수를 클래스 밖에서 정의할 때도 사용합니다.</p>
<pre><code class="language-cpp">void AJupiterCharacter::Move()
{
}</code></pre>
<p>이는</p>
<blockquote>
<p>지금 정의하고 있는 <code>Move()</code>는 <code>AJupiterCharacter</code> 클래스의 <code>Move()</code>다.</p>
</blockquote>
<p>라는 의미입니다.</p>
<p>따라서 세 연산자를 아주 단순화하면 다음처럼 구분할 수 있습니다.</p>
<pre><code class="language-cpp">Object.Member             // 객체의 Member
Pointer-&gt;Member           // 포인터가 가리키는 객체의 Member
ClassName::Member         // 해당 클래스/범위에 속한 Member</code></pre>
<hr>
<h1 id="실제-코드-읽어보기">실제 코드 읽어보기</h1>
<p>이번에는 Enhanced Input을 연결하는 코드를 한 줄씩 읽어보겠습니다.</p>
<pre><code class="language-cpp">void AJupiterCharacter::SetupPlayerInputComponent(
    UInputComponent* PlayerInputComponent)</code></pre>
<p>먼저 <code>AJupiterCharacter::SetupPlayerInputComponent</code>는</p>
<blockquote>
<p><code>AJupiterCharacter</code> 클래스에 속한 <code>SetupPlayerInputComponent</code> 함수를 정의한다.</p>
</blockquote>
<p>라고 읽을 수 있습니다.</p>
<p>그리고</p>
<pre><code class="language-cpp">UInputComponent* PlayerInputComponent</code></pre>
<p>는 함수의 <strong>매개변수</strong>입니다.</p>
<p><code>UInputComponent</code> 객체를 가리킬 수 있는 포인터를 <code>PlayerInputComponent</code>라는 이름으로 받아옵니다.</p>
<p>따라서 전체적으로는 대략 다음과 같이 읽을 수 있습니다.</p>
<blockquote>
<p><code>AJupiterCharacter</code>의 <code>SetupPlayerInputComponent</code> 함수를 정의한다.
이 함수는 <code>UInputComponent</code>를 가리키는 포인터를 <code>PlayerInputComponent</code>라는 이름으로 전달받는다.</p>
</blockquote>
<hr>
<h2 id="부모-클래스의-함수-호출">부모 클래스의 함수 호출</h2>
<pre><code class="language-cpp">Super::SetupPlayerInputComponent(PlayerInputComponent);</code></pre>
<p>여기서 <code>Super</code>는 언리얼에서 <strong>부모 클래스를 가리키는 별칭</strong>입니다.</p>
<p>따라서 다음과 같이 이해할 수 있습니다.</p>
<blockquote>
<p>부모 클래스의 <code>SetupPlayerInputComponent()</code>를 먼저 실행한다.</p>
</blockquote>
<p><code>AJupiterCharacter</code>가 <code>ACharacter</code>를 상속받았다면, 여기서 <code>Super</code>는 부모인 <code>ACharacter</code>를 의미하게 됩니다.</p>
<hr>
<h2 id="enhanced-input으로-캐스팅">Enhanced Input으로 캐스팅</h2>
<pre><code class="language-cpp">if (UEnhancedInputComponent* EnhancedInput =
    Cast&lt;UEnhancedInputComponent&gt;(PlayerInputComponent))</code></pre>
<p>캐스팅(Casting)은 여기서는 쉽게 말해,</p>
<blockquote>
<p>&quot;이 객체를 <code>UEnhancedInputComponent</code>로 다룰 수 있는지 확인하고, 가능하다면 그 타입의 포인터를 얻는다.&quot;</p>
</blockquote>
<p>라고 이해할 수 있습니다.</p>
<p><code>Cast&lt;UEnhancedInputComponent&gt;(PlayerInputComponent)</code>가 성공하면 결과로 유효한 포인터가 반환되고, 그것을</p>
<pre><code class="language-cpp">UEnhancedInputComponent* EnhancedInput</code></pre>
<p>에 저장합니다.</p>
<p>실패하면 <code>nullptr</code>가 반환되므로 <code>if</code>의 내부가 실행되지 않습니다.</p>
<p>즉,</p>
<blockquote>
<p><code>PlayerInputComponent</code>가 <code>UEnhancedInputComponent</code>로 캐스팅 가능한지 확인한다. 성공하면 그 결과를 <code>EnhancedInput</code>이라는 지역 포인터 변수에 담고 아래 코드를 실행한다.</p>
</blockquote>
<p>라고 읽을 수 있습니다.</p>
<hr>
<h2 id="playercontroller-캐스팅">PlayerController 캐스팅</h2>
<pre><code class="language-cpp">if (AJupiterPlayerController* PlayerController =
    Cast&lt;AJupiterPlayerController&gt;(GetController()))</code></pre>
<p>이것도 같은 구조입니다.</p>
<p><code>GetController()</code>로 현재 Pawn/Character를 제어하고 있는 Controller를 가져온 뒤, 그것이 <code>AJupiterPlayerController</code>로 다룰 수 있는지 확인합니다.</p>
<p>성공하면 그 포인터를</p>
<pre><code class="language-cpp">AJupiterPlayerController* PlayerController</code></pre>
<p>에 저장합니다.</p>
<p>따라서 다음처럼 읽을 수 있습니다.</p>
<blockquote>
<p><code>GetController()</code>가 반환한 Controller를 <code>AJupiterPlayerController</code>로 캐스팅한다. 성공하면 그 포인터를 <code>PlayerController</code>라는 지역 변수에 저장하고 <code>if</code>문 내부를 실행한다.</p>
</blockquote>
<p>실패하면 <code>nullptr</code>이 반환되므로 내부 코드가 실행되지 않습니다.</p>
<hr>
<h2 id="playercontroller-moveaction"><code>PlayerController-&gt;MoveAction</code></h2>
<pre><code class="language-cpp">if (PlayerController-&gt;MoveAction)</code></pre>
<p>앞에서 <code>PlayerController</code>를 다음과 같이 선언했습니다.</p>
<pre><code class="language-cpp">AJupiterPlayerController* PlayerController</code></pre>
<p>즉, <strong>포인터</strong>입니다.</p>
<p>따라서 <code>.</code>이 아니라 <code>-&gt;</code>를 사용합니다.</p>
<pre><code class="language-cpp">PlayerController-&gt;MoveAction</code></pre>
<p>은</p>
<blockquote>
<p><code>PlayerController</code> 포인터가 가리키는 객체의 <code>MoveAction</code></p>
</blockquote>
<p>이라고 읽습니다.</p>
<p>그리고 <code>if</code>에 넣었으므로 여기서는 <code>MoveAction</code>이 유효한지 확인하는 역할을 합니다.</p>
<blockquote>
<p><code>MoveAction</code>이 실제로 설정되어 있다면 아래 코드를 실행한다.</p>
</blockquote>
<hr>
<h1 id="bindaction-읽어보기"><code>BindAction()</code> 읽어보기</h1>
<p>마지막으로 가장 복잡해 보이는 부분입니다.</p>
<pre><code class="language-cpp">EnhancedInput-&gt;BindAction(
    PlayerController-&gt;MoveAction,
    ETriggerEvent::Triggered,
    this,
    &amp;AJupiterCharacter::Move
);</code></pre>
<p>한꺼번에 보면 복잡하지만 하나씩 분리하면 생각보다 단순합니다.</p>
<h3 id="enhancedinput-bindaction"><code>EnhancedInput-&gt;BindAction</code></h3>
<pre><code class="language-cpp">EnhancedInput-&gt;BindAction(...)</code></pre>
<p><code>EnhancedInput</code>은 포인터이므로 <code>-&gt;</code>를 사용합니다.</p>
<blockquote>
<p><code>EnhancedInput</code> 포인터가 가리키는 객체의 <code>BindAction()</code> 함수를 실행한다.</p>
</blockquote>
<hr>
<h3 id="playercontroller-moveaction-1"><code>PlayerController-&gt;MoveAction</code></h3>
<pre><code class="language-cpp">PlayerController-&gt;MoveAction</code></pre>
<blockquote>
<p><code>PlayerController</code> 포인터가 가리키는 객체의 <code>MoveAction</code></p>
</blockquote>
<p>즉, <strong>어떤 Input Action을 연결할 것인지</strong> 전달합니다.</p>
<hr>
<h3 id="etriggereventtriggered"><code>ETriggerEvent::Triggered</code></h3>
<pre><code class="language-cpp">ETriggerEvent::Triggered</code></pre>
<p>여기서는 <code>::</code>가 사용되었습니다.</p>
<blockquote>
<p><code>ETriggerEvent</code>라는 범위에 정의되어 있는 <code>Triggered</code></p>
</blockquote>
<p>즉, 해당 Input Action이 <strong>Triggered 상태일 때</strong> 함수를 실행하도록 지정합니다.</p>
<hr>
<h3 id="this"><code>this</code></h3>
<pre><code class="language-cpp">this</code></pre>
<p><code>this</code>는 <strong>현재 객체 자신을 가리키는 포인터</strong>입니다.</p>
<p>현재 코드가 <code>AJupiterCharacter</code> 객체에서 실행되고 있으므로,</p>
<blockquote>
<p>&quot;바로 이 <code>AJupiterCharacter</code> 객체에서&quot;</p>
</blockquote>
<p>정도로 우선 이해할 수 있습니다.</p>
<hr>
<h3 id="ajupitercharactermove"><code>&amp;AJupiterCharacter::Move</code></h3>
<pre><code class="language-cpp">&amp;AJupiterCharacter::Move</code></pre>
<p>여기서 <code>::</code>가 다시 등장합니다.</p>
<pre><code class="language-cpp">AJupiterCharacter::Move</code></pre>
<p>는</p>
<blockquote>
<p><code>AJupiterCharacter</code> 클래스에 속한 <code>Move</code> 함수</p>
</blockquote>
<p>라는 의미입니다.</p>
<p>앞의 <code>&amp;</code>까지 포함하면 해당 멤버 함수를 <code>BindAction()</code>에 <strong>연결할 함수로 전달</strong>하는 형태가 됩니다.</p>
<p>즉, 여기서는</p>
<blockquote>
<p>실제로 실행할 함수는 <code>AJupiterCharacter</code>의 <code>Move</code> 함수다.</p>
</blockquote>
<p>정도로 이해하면 됩니다.</p>
<hr>
<h1 id="전체-코드를-문장으로-읽어보기">전체 코드를 문장으로 읽어보기</h1>
<pre><code class="language-cpp">if (PlayerController-&gt;MoveAction)
{
    EnhancedInput-&gt;BindAction(
        PlayerController-&gt;MoveAction,
        ETriggerEvent::Triggered,
        this,
        &amp;AJupiterCharacter::Move
    );
}</code></pre>
<p>이를 자연어로 바꾸면 다음과 같습니다.</p>
<blockquote>
<p><code>PlayerController</code>가 가지고 있는 <code>MoveAction</code>이 유효하다면, 그 액션을 Enhanced Input에 연결한다.
<code>MoveAction</code>이 <code>Triggered</code>될 때 현재 <code>AJupiterCharacter</code> 객체의 <code>Move</code> 함수가 실행되도록 바인딩한다.</p>
</blockquote>
<p>결국 이 코드의 핵심은 <strong>입력 액션과 실제 C++ 함수를 연결하는 것</strong>입니다.</p>
<hr>
<h2 id="최종-정리">최종 정리</h2>
<p>코드를 읽을 때 세 연산자를 전부 똑같은 &quot;<code>~의</code>&quot;라고 생각하면 헷갈리기 쉽습니다.</p>
<p>다음처럼 구분해서 읽는 편이 좋습니다.</p>
<pre><code class="language-cpp">PlayerController.MoveAction
// PlayerController 객체의 MoveAction

PlayerController-&gt;MoveAction
// PlayerController 포인터가 가리키는 객체의 MoveAction

AJupiterCharacter::Move
// AJupiterCharacter 클래스에 속한 Move</code></pre>
<p>따라서 기억할 핵심은 다음 세 가지입니다.</p>
<table>
<thead>
<tr>
<th>코드</th>
<th>읽는 방법</th>
</tr>
</thead>
<tbody><tr>
<td><code>A.B</code></td>
<td>A 객체의 B</td>
</tr>
<tr>
<td><code>A-&gt;B</code></td>
<td>A 포인터가 가리키는 객체의 B</td>
</tr>
<tr>
<td><code>A::B</code></td>
<td>A라는 범위에 속한 B</td>
</tr>
</tbody></table>
<p>특히 언리얼 C++에서는 객체를 포인터 형태로 다루는 경우가 많기 때문에 <code>-&gt;</code>를 매우 자주 만나게 됩니다.</p>
<p>처음에는 기호 자체를 외우기보다는 코드를 볼 때마다 <strong>&quot;지금 왼쪽에 있는 것이 객체인가, 포인터인가, 아니면 클래스/범위 이름인가?&quot;</strong>를 확인하는 방식으로 읽는 것이 이해하기 쉽습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[강의 2 복습하기]]></title>
            <link>https://velog.io/@jupiter_0609/%EA%B0%95%EC%9D%98-2-%EB%B3%B5%EC%8A%B5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jupiter_0609/%EA%B0%95%EC%9D%98-2-%EB%B3%B5%EC%8A%B5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 20 Aug 2026 12:36:32 GMT</pubDate>
            <description><![CDATA[<p>이대로 진도 나가면 안될 듯 하여 강의 2 다시 짚고 넘어갑니다. </p>
<h1 id="헷갈리는-개념-정리">헷갈리는 개념 정리</h1>
<h2 id="1-uproperty에서-사용하는-지정자의-범위">1. <code>UPROPERTY()</code>에서 사용하는 지정자의 범위</h2>
<p><code>UPROPERTY()</code>에는 여러 지정자가 들어갈 수 있는데, 처음에는 <code>VisibleAnywhere</code>, <code>BlueprintReadOnly</code>, <code>EditAnywhere</code> 등이 비슷해 보여 꽤 헷갈렸습니다.</p>
<p>우선 크게 나누면 다음과 같습니다.</p>
<h3 id="에디터에서의-표시-및-수정-범위">에디터에서의 표시 및 수정 범위</h3>
<ul>
<li><code>VisibleAnywhere</code></li>
<li><code>VisibleDefaultsOnly</code></li>
<li><code>VisibleInstanceOnly</code></li>
<li><code>EditAnywhere</code></li>
<li><code>EditDefaultsOnly</code></li>
<li><code>EditInstanceOnly</code></li>
</ul>
<h3 id="블루프린트에서의-접근-권한">블루프린트에서의 접근 권한</h3>
<ul>
<li><code>BlueprintReadOnly</code></li>
<li><code>BlueprintReadWrite</code></li>
</ul>
<table>
<thead>
<tr>
<th>지정자</th>
<th align="right">Details 패널에 표시</th>
<th align="right">수정 가능</th>
<th>적용 범위</th>
</tr>
</thead>
<tbody><tr>
<td><code>VisibleAnywhere</code></td>
<td align="right">O</td>
<td align="right">X</td>
<td>클래스 기본값 + 배치된 인스턴스</td>
</tr>
<tr>
<td><code>VisibleDefaultsOnly</code></td>
<td align="right">O</td>
<td align="right">X</td>
<td>클래스 기본값</td>
</tr>
<tr>
<td><code>VisibleInstanceOnly</code></td>
<td align="right">O</td>
<td align="right">X</td>
<td>배치된 인스턴스</td>
</tr>
<tr>
<td><code>EditAnywhere</code></td>
<td align="right">O</td>
<td align="right">O</td>
<td>클래스 기본값 + 배치된 인스턴스</td>
</tr>
<tr>
<td><code>EditDefaultsOnly</code></td>
<td align="right">O</td>
<td align="right">O</td>
<td>클래스 기본값</td>
</tr>
<tr>
<td><code>EditInstanceOnly</code></td>
<td align="right">O</td>
<td align="right">O</td>
<td>배치된 인스턴스</td>
</tr>
</tbody></table>
<p>여기서 중요한 점은 <strong>에디터에서의 접근 권한과 블루프린트에서의 접근 권한은 서로 다른 문제</strong>라는 것입니다.</p>
<p>예를 들어 다음과 같이 선언할 수 있습니다.</p>
<pre><code class="language-cpp">UPROPERTY(VisibleAnywhere, BlueprintReadOnly)
USpringArmComponent* SpringArmComp;</code></pre>
<p><code>VisibleAnywhere</code>는 해당 프로퍼티를 에디터에서 볼 수 있지만 직접 다른 값으로 교체할 수 없도록 합니다.</p>
<p><code>BlueprintReadOnly</code>는 블루프린트에서 해당 프로퍼티를 읽을 수는 있지만 직접 새로운 값을 대입할 수 없도록 합니다.</p>
<h3 id="그렇다면-컴포넌트-내부-속성도-수정할-수-없을까요">그렇다면 컴포넌트 내부 속성도 수정할 수 없을까요?</h3>
<p>이 부분이 특히 헷갈렸습니다.</p>
<p>결론부터 말하면 <strong>컴포넌트 자체를 가리키는 참조를 변경하는 것</strong>과 <strong>그 컴포넌트가 가지고 있는 내부 속성을 변경하는 것</strong>은 별개의 문제입니다.</p>
<p>예를 들어 <code>SpringArmComp</code>를 <code>VisibleAnywhere</code>로 선언했다면 <code>SpringArmComp</code>라는 변수에 다른 컴포넌트를 마음대로 집어넣는 것은 제한됩니다.</p>
<p>하지만 <code>SpringArmComp</code> 안에 있는 <code>Target Arm Length</code>, 회전 관련 설정 등 <strong>컴포넌트 자체가 제공하는 편집 가능한 속성</strong>은 Details 패널에서 변경할 수 있습니다.</p>
<p>즉, 대략 다음과 같이 이해할 수 있을 것 같습니다.</p>
<blockquote>
<p><strong>VisibleAnywhere</strong>
&quot;이 컴포넌트가 존재한다는 것은 보여드리지만, 컴포넌트 자체를 다른 것으로 갈아끼우지는 마세요.&quot;</p>
</blockquote>
<blockquote>
<p><strong>BlueprintReadOnly</strong>
&quot;블루프린트에서 이 변수를 가져다 읽을 수는 있지만, 변수 자체에 새로운 값을 직접 대입하지는 마세요.&quot;</p>
</blockquote>
<p>아직은 <strong>어디까지 C++에서 결정하고 어디까지 에디터에서 조절하도록 만드는 것이 효율적인가?</strong>에 대한 감각이 완전히 잡히지는 않았습니다.</p>
<p>현재는 대략 다음 정도로 이해하고 있습니다.</p>
<ul>
<li>게임의 구조나 반드시 지켜져야 하는 규칙 → C++</li>
<li>이동 속도, 카메라 거리, 이펙트 크기처럼 자주 조정할 값 → 에디터</li>
<li>기획자가 직접 조정할 가능성이 높은 값 → <code>EditAnywhere</code>, <code>EditDefaultsOnly</code> 등을 이용해 노출</li>
<li>외부에서 함부로 변경하면 안 되는 컴포넌트 → <code>VisibleAnywhere</code> 등으로 제한</li>
</ul>
<p>결국 C++과 에디터 중 하나만 사용하는 것이 아니라, <strong>변경되어도 되는 값과 변경되면 안 되는 구조를 구분하는 것이 중요해 보입니다.</strong></p>
<hr>
<h1 id="서바이벌-아케이드-게임-구현하기">서바이벌 아케이드 게임 구현하기</h1>
<p>이번 프로젝트에서는 기본적인 캐릭터 조작부터 아이템, 웨이브, HUD, 이펙트까지 단계적으로 구현해 볼 예정입니다.</p>
<h2 id="할-일-목록">할 일 목록</h2>
<ol>
<li><p>캐릭터 구현</p>
</li>
<li><p>캐릭터 입력 매핑 구현</p>
</li>
<li><p>캐릭터 기본 동작 구현</p>
<ul>
<li>WASD 이동</li>
<li>점프</li>
<li>스프린트</li>
<li>카메라 회전</li>
</ul>
</li>
<li><p>캐릭터 애니메이션 적용</p>
<ul>
<li>Idle</li>
<li>Walk</li>
<li>Jump</li>
<li>Sprint</li>
</ul>
</li>
<li><p>아이템 상호작용 구현</p>
<ul>
<li>범위 내에 있을 경우 체력 감소</li>
<li>획득 시 점수 증가</li>
<li>획득 시 체력 증가</li>
</ul>
</li>
<li><p>아이템 랜덤 스폰 및 레벨별 아이템 개수 설정</p>
</li>
<li><p>캐릭터 데미지 및 아이템 점수 관리 시스템 구현</p>
</li>
<li><p>웨이브 시스템을 통한 게임 흐름 제어</p>
</li>
<li><p>HUD에 실시간 정보 반영</p>
</li>
<li><p>게임 흐름에 맞춘 메뉴 UI 구현</p>
</li>
<li><p>UI 애니메이션 효과 및 3D 위젯 UI 구현</p>
</li>
<li><p>파티클 효과 적용</p>
<ul>
<li>지뢰 폭발</li>
<li>아이템 습득</li>
</ul>
</li>
<li><p>사운드 효과 적용</p>
<ul>
<li>지뢰 폭발</li>
<li>아이템 습득</li>
<li>발걸음</li>
</ul>
</li>
<li><p>프로젝트 패키징 및 배포</p>
</li>
</ol>
<hr>
<h1 id="1-gamemode---게임의-규칙을-관리하는-역할">1. GameMode - 게임의 규칙을 관리하는 역할</h1>
<p>GameMode는 게임 전체의 규칙과 기본 구성을 결정하는 클래스입니다.</p>
<p>현재 공부한 내용을 기준으로 보면 다음과 같은 클래스들을 지정할 수 있습니다.</p>
<ol>
<li><p><strong>Default Pawn / Character</strong></p>
<ul>
<li>플레이어가 기본적으로 조종하게 될 <code>Pawn</code> 또는 <code>Character</code> 클래스를 지정합니다.</li>
</ul>
</li>
<li><p><strong>PlayerController</strong></p>
<ul>
<li>플레이어가 사용할 <code>PlayerController</code> 클래스를 지정합니다.</li>
<li>Controller는 Pawn 또는 Character를 Possess하여 조종할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>게임 규칙</strong></p>
<ul>
<li>게임 시작, 종료, 승패 조건 등의 게임 로직을 관리할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>GameState</strong></p>
<ul>
<li>현재 게임 전체에서 공유해야 하는 상태를 관리할 때 사용합니다.</li>
</ul>
</li>
<li><p><strong>PlayerState</strong></p>
<ul>
<li>각 플레이어별로 관리해야 하는 상태를 저장할 때 사용합니다.</li>
</ul>
</li>
</ol>
<h2 id="gamemodebase와-gamemode"><code>GameModeBase</code>와 <code>GameMode</code></h2>
<p>GameMode 클래스를 생성할 때 보면 <code>GameModeBase</code>와 <code>GameMode</code>라는 두 종류가 있습니다.</p>
<p><code>GameModeBase</code>는 비교적 단순화된 기본 GameMode이고, <code>GameMode</code>는 여기에 <strong>Match State와 같은 경기 진행 상태 관리 기능이 추가된 클래스</strong>입니다.</p>
<p>따라서 단순한 게임에서는 <code>GameModeBase</code>만으로도 충분할 수 있고, 경기 시작·진행·종료와 같은 명확한 매치 흐름이 필요한 게임에서는 <code>GameMode</code>가 유용합니다.</p>
<p>GameMode는 프로젝트 설정에서 기본값을 지정할 수도 있고, 각 레벨의 <strong>World Settings</strong>에서 별도로 지정할 수도 있습니다.</p>
<p>이 경우 레벨의 World Settings에서 GameMode를 따로 지정했다면 해당 설정이 우선 적용됩니다.</p>
<p>GameMode에서는 대표적으로 다음 클래스들을 지정할 수 있습니다.</p>
<ul>
<li>Default Pawn Class</li>
<li>Player Controller Class</li>
<li>Player State Class</li>
<li>Game State Class</li>
<li>HUD Class</li>
<li>Spectator Class</li>
</ul>
<p>즉, GameMode는</p>
<blockquote>
<p><strong>&quot;이 게임 또는 이 레벨에서는 어떤 규칙과 기본 클래스들을 사용할 것인가?&quot;</strong></p>
</blockquote>
<p>를 결정하는 역할에 가깝다고 이해했습니다.</p>
<hr>
<h1 id="pawn과-character의-차이">Pawn과 Character의 차이</h1>
<p><code>Pawn</code>과 <code>Character</code>의 차이도 처음에는 상당히 헷갈렸습니다.</p>
<p><code>Character</code>는 <code>Pawn</code>을 상속받은 클래스입니다.</p>
<p>특히 <code>CharacterMovementComponent</code>, Capsule Collision 등 <strong>걷고 뛰는 캐릭터를 구현하기 편리한 기능들이 기본으로 준비되어 있습니다.</strong></p>
<p>따라서 사람이나 몬스터처럼 지면 위를 걸어다니는 캐릭터를 만들 때 상당히 편리합니다.</p>
<p>반면 <code>Pawn</code>은 Character보다 더 기본적인 형태입니다.</p>
<p>이동 방법이 미리 강하게 정해져 있지 않기 때문에 자동차, 비행기, 드론, 특수한 이동체처럼 <strong>직접 이동 방식을 설계하고 싶은 경우 더 자유롭게 사용할 수 있습니다.</strong></p>
<p>다만,</p>
<blockquote>
<p>&quot;사람이 아니면 무조건 Pawn을 사용한다.&quot;</p>
</blockquote>
<p>라고 이해하는 것은 정확하지 않습니다.</p>
<p>인간이 아닌 몬스터라고 하더라도 Character의 이동 방식이 적합하다면 Character를 사용할 수 있습니다.</p>
<p>반대로 인간형 캐릭터라고 하더라도 매우 특수한 움직임이 필요하다면 Pawn을 사용할 수도 있습니다.</p>
<p>결국 기준은 외형보다는</p>
<blockquote>
<p><strong>&quot;Character가 기본으로 제공하는 이동 시스템을 활용할 것인가?&quot;</strong></p>
</blockquote>
<p>에 더 가깝습니다.</p>
<hr>
<h2 id="전방-선언forward-declaration">전방 선언(Forward Declaration)</h2>
<p>코드를 보다 보면 다음과 같은 형태가 자주 등장합니다.</p>
<pre><code class="language-cpp">class USpringArmComponent;</code></pre>
<p>처음에는 <code>#include</code>도 아닌데 갑자기 클래스를 선언해서 무엇을 하는 것인지 알기 어려웠습니다.</p>
<p>이것을 <strong>전방 선언(Forward Declaration)</strong>이라고 합니다.</p>
<p>쉽게 말하면 컴파일러에게</p>
<blockquote>
<p>&quot;<code>USpringArmComponent</code>라는 클래스가 어딘가에 존재합니다. 자세한 내용은 나중에 알려드리겠습니다.&quot;</p>
</blockquote>
<p>라고 미리 알려주는 것입니다.</p>
<p>헤더 파일에서 클래스의 포인터나 참조만 필요하다면 굳이 해당 클래스의 헤더 전체를 <code>#include</code>하지 않고 전방 선언만 사용할 수 있습니다.</p>
<p>예를 들어,</p>
<pre><code class="language-cpp">class USpringArmComponent;

USpringArmComponent* SpringArmComp;</code></pre>
<p>처럼 사용할 수 있습니다.</p>
<p>그리고 실제로 <code>USpringArmComponent</code>의 함수나 내부 기능을 사용하는 <code>.cpp</code> 파일에서는 필요한 헤더를 <code>#include</code>합니다.</p>
<p>이렇게 하면 헤더끼리 불필요하게 많은 파일을 불러오는 것을 줄일 수 있습니다.</p>
<hr>
<h1 id="2-playercontroller">2. PlayerController</h1>
<p><code>PlayerController</code>는 <strong>플레이어와 게임 속 Pawn을 연결하는 Controller</strong>입니다.</p>
<p>이번 프로젝트에서는 PlayerController를 중심으로 입력을 처리하도록 구성했습니다.</p>
<p>PlayerController에서 담당할 수 있는 대표적인 기능은 다음과 같습니다.</p>
<ol>
<li>플레이어 입력 처리</li>
<li>카메라 제어 로직</li>
<li>UI와의 상호작용</li>
<li>Pawn / Character에 대한 <code>Possess</code>, <code>UnPossess</code></li>
</ol>
<p>여기서 한 가지 주의할 점은</p>
<blockquote>
<p><strong>&quot;언리얼에서는 모든 입력을 반드시 PlayerController에서 처리해야 한다.&quot;</strong></p>
</blockquote>
<p>는 의미는 아닙니다.</p>
<p>Character나 Pawn에서도 입력을 바인딩할 수 있습니다.</p>
<p>프로젝트 구조에 따라서 PlayerController에서 입력을 관리할 수도 있고, Character 또는 Pawn에서 실제 움직임과 관련된 입력을 처리할 수도 있습니다.</p>
<p>이번에는 <strong>플레이어의 입력을 PlayerController 쪽에서 분리해서 관리하는 구조</strong>를 공부했습니다.</p>
<hr>
<h1 id="enhanced-input-system">Enhanced Input System</h1>
<p>Enhanced Input은 기존 입력 시스템보다 Input Action, Mapping Context, Trigger, Modifier 등을 이용해 입력을 더 구조적으로 관리할 수 있게 해주는 시스템입니다.</p>
<p>UE5에서 일반적으로 사용하는 입력 시스템이기도 합니다.</p>
<h2 id="input-mapping-context-imc">Input Mapping Context (IMC)</h2>
<p><code>Input Mapping Context</code>, 줄여서 IMC는 <strong>어떤 입력 설정들을 현재 사용할 것인지 묶어 놓은 컨텍스트</strong>입니다.</p>
<p>처음에는 스위치와 비슷하다고 이해했습니다.</p>
<p>예를 들어 다음과 같이 나눌 수 있습니다.</p>
<pre><code class="language-text">IMC_Human
 ├─ IA_Move
 ├─ IA_Look
 └─ IA_Jump

IMC_Car
 ├─ IA_Accelerate
 ├─ IA_Brake
 └─ IA_Steer</code></pre>
<p>사람을 조종할 때는 <code>IMC_Human</code>을 사용하고, 자동차에 탑승하면 <code>IMC_Car</code>를 적용하는 식으로 입력 구성을 전환할 수 있습니다.</p>
<p>따라서 IMC는 단순히 키 하나를 지정하는 것이 아니라,</p>
<blockquote>
<p><strong>여러 Input Action과 실제 키 입력의 대응 관계를 묶어서 관리하는 설정</strong></p>
</blockquote>
<p>이라고 보는 편이 더 정확합니다.</p>
<hr>
<h1 id="input-action-ia">Input Action (IA)</h1>
<p>Input Action은 특정한 행동을 추상화한 것입니다.</p>
<p>예를 들어</p>
<pre><code class="language-text">점프 → IA_Jump
카메라 회전 → IA_Look
이동 → IA_Move</code></pre>
<p>와 같이 만들 수 있습니다.</p>
<p>여기서 중요한 점은 <code>IA_Move</code> 자체가 W키를 의미하는 것은 아니라는 것입니다.</p>
<p><code>IA_Move</code>는 단순히 <strong>&quot;이동이라는 행동&quot;</strong>을 의미하고,</p>
<p>실제로 어떤 키가 이동을 발생시키는지는 IMC에서 연결합니다.</p>
<p>이 구조 덕분에 같은 <code>IA_Move</code>를 사용하면서도 키보드, 게임패드 등 서로 다른 입력 장치를 연결할 수 있습니다.</p>
<hr>
<h2 id="이번-캐릭터에서-필요한-행동">이번 캐릭터에서 필요한 행동</h2>
<p>이번 캐릭터에는 다음 입력을 구현합니다.</p>
<ol>
<li>WASD 이동</li>
<li>마우스 회전</li>
<li>점프</li>
<li>스프린트</li>
</ol>
<p>Input Action을 생성한 뒤 각각의 행동에 필요한 Value Type을 지정합니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/71a26f7d-1777-45ce-831b-7b2e174e0108/image.png" alt=""></p>
<hr>
<h1 id="value-type">Value Type</h1>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/638514e0-bcbe-4d7e-a2f3-d31a08bb11c0/image.png" alt=""></p>
<p>Input Action에서 특히 중요한 설정 중 하나가 <strong>Value Type</strong>입니다.</p>
<p>해당 Input Action이 실행될 때 <strong>어떤 형태의 값을 전달할 것인지</strong>를 결정합니다.</p>
<h3 id="bool">Bool</h3>
<p>참 또는 거짓을 전달합니다.</p>
<p>단순히 눌렸는지 아닌지를 확인할 때 사용하기 좋습니다.</p>
<p>예:</p>
<ul>
<li>점프</li>
<li>공격</li>
<li>상호작용</li>
</ul>
<h3 id="axis1d">Axis1D</h3>
<p>하나의 축 값을 전달합니다.</p>
<p>예를 들어 <code>-1 ~ 1</code> 범위의 값처럼 한 방향의 정도를 표현할 수 있습니다.</p>
<p>예:</p>
<ul>
<li>게임패드 트리거</li>
<li>전진 / 후진</li>
<li>가속 페달</li>
</ul>
<h3 id="axis2d">Axis2D</h3>
<p>X, Y 두 개의 축 값을 동시에 전달합니다.</p>
<p>예:</p>
<ul>
<li>WASD 이동</li>
<li>마우스 이동</li>
<li>게임패드 스틱</li>
</ul>
<p>예를 들어 캐릭터 이동이라면 다음처럼 생각할 수 있습니다.</p>
<pre><code class="language-text">X = 좌우 이동
Y = 앞뒤 이동</code></pre>
<h3 id="axis3d">Axis3D</h3>
<p>X, Y, Z 세 개의 축 값을 사용합니다.</p>
<p>3차원 방향 입력이 필요한 경우 사용할 수 있습니다.</p>
<p>예:</p>
<ul>
<li>비행체의 3축 입력</li>
<li>특수한 3차원 이동 시스템</li>
</ul>
<hr>
<h1 id="trigger">Trigger</h1>
<p>Trigger는</p>
<blockquote>
<p><strong>&quot;입력이 어느 조건을 만족했을 때 Input Action을 발동시킬 것인가?&quot;</strong></p>
</blockquote>
<p>를 결정합니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/9a0f58e2-5d96-49fd-8ad9-148c778932ad/image.png" alt=""></p>
<p>예를 들어 단순히 키를 누르는 것뿐만 아니라 일정 시간 누르고 있거나, 키를 떼는 순간 등의 조건을 만들 수 있습니다.</p>
<h3 id="pressed">Pressed</h3>
<p>키가 눌리는 순간을 감지합니다.</p>
<p>예:</p>
<pre><code class="language-text">스페이스바를 누르는 순간 → 점프</code></pre>
<h3 id="hold">Hold</h3>
<p>키를 일정 시간 이상 누르고 있는 경우 작동합니다.</p>
<p>예:</p>
<pre><code class="language-text">상호작용 키를 1초간 누름 → 문 열기</code></pre>
<h3 id="released">Released</h3>
<p>누르고 있던 키를 놓는 순간 작동합니다.</p>
<p>예:</p>
<pre><code class="language-text">활시위를 당기고 있다가 버튼을 놓음 → 화살 발사</code></pre>
<p>이외에도 Tap, Chorded Action 등 여러 종류의 Trigger를 조합할 수 있습니다.</p>
<hr>
<h1 id="modifier">Modifier</h1>
<p>처음에는 Modifier가 Trigger보다 훨씬 이해하기 어려웠습니다.</p>
<p>정리해 보니 Trigger와 Modifier의 차이는 다음과 같습니다.</p>
<blockquote>
<p><strong>Trigger</strong>
&quot;언제 실행할 것인가?&quot;</p>
</blockquote>
<blockquote>
<p><strong>Modifier</strong>
&quot;들어온 입력 값을 어떤 값으로 바꿀 것인가?&quot;</p>
</blockquote>
<p>예를 들어 마우스를 오른쪽으로 움직였을 때 <code>1</code>이라는 값이 들어왔다고 생각해 보겠습니다.</p>
<p>Modifier를 사용하면 이 <code>1</code>이라는 입력값을 실제 게임 로직으로 보내기 전에 가공할 수 있습니다.</p>
<h3 id="scalar">Scalar</h3>
<p>입력 값에 특정 배율을 곱합니다.</p>
<pre><code class="language-text">원래 입력 : 1
Scalar × 2
결과 : 2</code></pre>
<p>마우스 감도를 높이는 것과 비슷하게 사용할 수 있습니다.</p>
<h3 id="negate">Negate</h3>
<p>입력값의 방향을 반대로 바꿉니다.</p>
<pre><code class="language-text">1 → -1</code></pre>
<p>카메라 축을 반전하거나 WASD에서 왼쪽/뒤쪽 방향 값을 만드는 데 사용할 수 있습니다.</p>
<h3 id="dead-zone">Dead Zone</h3>
<p>일정 크기보다 작은 입력값을 무시합니다.</p>
<p>게임패드의 아날로그 스틱은 손을 놓고 있어도 아주 작은 입력값이 발생할 수 있습니다.</p>
<p>예를 들어,</p>
<pre><code class="language-text">0.01
0.02
0.03</code></pre>
<p>같은 작은 입력을 무시하도록 만들어 캐릭터가 가만히 있는데 조금씩 움직이는 현상을 방지할 수 있습니다.</p>
<p>즉, Modifier는</p>
<blockquote>
<p><strong>입력 장치에서 들어온 값을 게임에서 사용하기 좋은 형태로 가공하는 과정</strong></p>
</blockquote>
<p>이라고 이해하면 조금 더 와닿습니다.</p>
<hr>
<h1 id="input-mapping-context에서-키-매핑하기">Input Mapping Context에서 키 매핑하기</h1>
<h2 id="swizzle-input-axis-values란"><code>Swizzle Input Axis Values</code>란?</h2>
<p><code>Swizzle Input Axis Values</code>는 입력 값의 <strong>축 순서를 바꾸는 Modifier</strong>입니다.</p>
<p>처음에는 이것이 상당히 이해하기 어려웠습니다.</p>
<p>WASD를 예로 들어 보겠습니다.</p>
<p>키보드의 W키 하나는 기본적으로 하나의 값만 전달하는 1차원 입력입니다.</p>
<p>하지만 <code>IA_Move</code>를 <code>Axis2D</code>로 만들었다면 다음과 같은 값이 필요합니다.</p>
<pre><code class="language-text">X = 좌우
Y = 앞뒤</code></pre>
<p>여기서 D키의 입력값 <code>1</code>은 X축에 그대로 넣으면 됩니다.</p>
<pre><code class="language-text">D → X = +1</code></pre>
<p>A키는 반대 방향이므로 <code>Negate</code>를 사용합니다.</p>
<pre><code class="language-text">A → X = -1</code></pre>
<p>문제는 W와 S입니다.</p>
<p>W를 눌렀을 때 들어오는 1차원 입력도 기본적으로 X 위치에 들어오기 때문에, 이것을 <strong>Y축 값으로 옮겨야 합니다.</strong></p>
<p>이때 사용하는 것이 <code>Swizzle Input Axis Values</code>입니다.</p>
<pre><code class="language-text">W
1차원 입력 +1
↓
Swizzle
↓
Y = +1</code></pre>
<p>S는 여기에 <code>Negate</code>까지 적용합니다.</p>
<pre><code class="language-text">S
1차원 입력 +1
↓
Swizzle
↓
Y = +1
↓
Negate
↓
Y = -1</code></pre>
<p>따라서 WASD를 하나의 <code>Axis2D</code> Input Action으로 묶으면 대략 다음과 같이 정리할 수 있습니다.</p>
<pre><code class="language-text">W → Swizzle → Y +1
S → Swizzle + Negate → Y -1
A → Negate → X -1
D → 그대로 → X +1</code></pre>
<p>처음에는 Swizzle이라는 이름 때문에 굉장히 복잡한 기능처럼 느껴졌지만,</p>
<blockquote>
<p><strong>&quot;지금 X 자리에 들어온 값을 Y 자리로 옮겨 주세요.&quot;</strong></p>
</blockquote>
<p>정도로 이해하면 훨씬 간단합니다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/b7e0e733-1291-4c0f-8516-57847b703495/image.png" alt=""></p>
<p><code>Look</code> 입력에서도 프로젝트에서 원하는 마우스 조작 방향에 따라 특정 축에 <code>Negate</code>를 적용하여 상하 또는 좌우 방향을 반전시킬 수 있습니다.</p>
<p>중요한 것은 <strong>카메라 입력이라서 반드시 Y축을 반전해야 하는 것은 아니며</strong>, 원하는 조작 방향과 현재 입력값의 방향에 따라 결정해야 한다는 점입니다.</p>
<hr>
<h2 id="trigger와-modifier-다시-정리">Trigger와 Modifier 다시 정리</h2>
<p>둘이 자꾸 헷갈려서 한 문장으로 정리했습니다.</p>
<pre><code class="language-text">Modifier → 입력 값을 가공합니다.
Trigger  → 그 입력을 언제 실행할지 판단합니다.</code></pre>
<p>예를 들어,</p>
<pre><code class="language-text">마우스 Y 입력
↓
Negate Modifier
↓
방향 반전
↓
Trigger 조건 확인
↓
IA_Look 실행</code></pre>
<p>과 같은 흐름으로 이해할 수 있습니다.</p>
<p>Enhanced Input의 구조를 전체적으로 보면 다음과 같습니다.</p>
<pre><code class="language-text">키보드 / 마우스 / 게임패드
        ↓
Input Mapping Context
        ↓
Modifier
        ↓
Trigger
        ↓
Input Action
        ↓
C++ / Blueprint 로직</code></pre>
<p>정확한 내부 처리 과정을 모두 표현한 그림은 아니지만, 각 요소가 어떤 역할을 담당하는지 구분하기 위한 학습용으로는 이렇게 기억해 두려고 합니다.</p>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<p><a href="https://shjz.tistory.com/32">https://shjz.tistory.com/32</a></p>
<hr>
<h1 id="덤-다중-커서-사용법">덤: 다중 커서 사용법</h1>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/e6523ac3-80d7-428e-973b-b460afc484dc/image.png" alt=""></p>
<p>코드를 작성하다가 다중 커서를 사용하는 방법도 알게 되었습니다.</p>
<p><code>Alt + Ctrl + 클릭</code></p>
<p>여러 위치를 동시에 수정할 때 꽤 편리하게 사용할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[언리얼 C++ 캐릭터 코드 뜯어보기 — 리플렉션부터 Enhanced Input까지]]></title>
            <link>https://velog.io/@jupiter_0609/%EC%96%B8%EB%A6%AC%EC%96%BC-C-%EC%BA%90%EB%A6%AD%ED%84%B0-%EC%BD%94%EB%93%9C-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EB%B6%80%ED%84%B0-Enhanced-Input%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@jupiter_0609/%EC%96%B8%EB%A6%AC%EC%96%BC-C-%EC%BA%90%EB%A6%AD%ED%84%B0-%EC%BD%94%EB%93%9C-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98%EB%B6%80%ED%84%B0-Enhanced-Input%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Wed, 19 Aug 2026 14:56:44 GMT</pubDate>
            <description><![CDATA[<p>강의를 따라 코드를 작성하는 것만으로는 개념이 잘 남지 않아, 이번에는 작성한 코드 한 줄 한 줄에 직접 주석을 달아보았습니다.</p>
<p>막상 설명하려고 하니 <code>UCLASS</code>, 컴포넌트 생성처럼 어느 정도 이해하고 있던 부분도 있었지만, <code>Enhanced Input</code>, <code>Cast</code>, <code>AddMappingContext()</code> 쪽은 코드를 따라 작성했을 뿐 제대로 설명하지 못하는 부분이 많았습니다.</p>
<p>그래서 이번 글에서는 다음 코드를 기준으로 <strong>“이 한 줄이 왜 필요한가?”</strong>를 다시 정리해보겠습니다.</p>
<hr>
<h1 id="1-uclass와-generated_body">1. <code>UCLASS()</code>와 <code>GENERATED_BODY()</code></h1>
<pre><code class="language-cpp">UCLASS()
class SPARTAPROJECT_API ASpartaCharacter : public ACharacter
{
    GENERATED_BODY()
};</code></pre>
<h2 id="uclass"><code>UCLASS()</code></h2>
<pre><code class="language-cpp">UCLASS()</code></pre>
<p>일반 C++ 클래스와 달리, 언리얼 엔진은 클래스에 대한 여러 정보를 엔진 차원에서 관리합니다.</p>
<p><code>UCLASS()</code>는 이 클래스를 언리얼의 객체 및 리플렉션 시스템이 인식할 수 있는 클래스로 선언하는 매크로입니다.</p>
<p>초보자 관점에서는 우선 다음 정도로 기억하면 이해하기 쉽습니다.</p>
<blockquote>
<p><strong>“이 클래스는 그냥 C++ 클래스가 아니라 언리얼 엔진도 알아야 하는 클래스입니다.”</strong></p>
</blockquote>
<hr>
<h2 id="spartaproject_api"><code>SPARTAPROJECT_API</code></h2>
<pre><code class="language-cpp">class SPARTAPROJECT_API ASpartaCharacter</code></pre>
<p><code>SPARTAPROJECT_API</code>는 이 클래스를 프로젝트의 다른 모듈에서도 사용할 수 있도록 심볼을 노출하는 매크로입니다.</p>
<p>현재 단계에서는 DLL Export/Import 구조까지 깊게 들어가기보다는,</p>
<blockquote>
<p><strong>“이 클래스를 다른 모듈에서도 사용할 수 있도록 열어주는 표시입니다.”</strong></p>
</blockquote>
<p>정도로 이해했습니다.</p>
<hr>
<h2 id="-public-acharacter"><code>: public ACharacter</code></h2>
<pre><code class="language-cpp">ASpartaCharacter : public ACharacter</code></pre>
<p><code>ASpartaCharacter</code>가 언리얼의 <code>ACharacter</code> 클래스를 <strong>상속받는다</strong>는 의미입니다.</p>
<p>즉,</p>
<blockquote>
<p><code>ACharacter</code>가 원래 가지고 있던 기능을 물려받아
<code>ASpartaCharacter</code>라는 새로운 캐릭터 클래스를 만듭니다.</p>
</blockquote>
<p>라고 이해할 수 있습니다.</p>
<hr>
<h2 id="generated_body"><code>GENERATED_BODY()</code></h2>
<pre><code class="language-cpp">GENERATED_BODY()</code></pre>
<p>처음에는 &quot;<code>UCLASS()</code>와 한 쌍인 매크로&quot; 정도로 외웠습니다.</p>
<p>방향 자체는 크게 틀리지 않지만, 조금 더 정확하게 보면 <code>GENERATED_BODY()</code>는 언리얼의 클래스 시스템이 동작하는 데 필요한 코드를 클래스 내부에 생성해주는 역할을 합니다.</p>
<p>따라서 저는 다음과 같이 기억하기로 했습니다.</p>
<blockquote>
<p><code>UCLASS()</code>
→ “언리얼 엔진에서 이 클래스를 관리해주세요.”</p>
<p><code>GENERATED_BODY()</code>
→ “그러면 관리에 필요한 내부 코드도 여기에 넣어주세요.”</p>
</blockquote>
<hr>
<h1 id="2-전방-선언">2. 전방 선언</h1>
<pre><code class="language-cpp">class USpringArmComponent;
struct FInputActionValue;</code></pre>
<p>처음에는 단순히</p>
<blockquote>
<p>&quot;<code>USpringArmComponent</code>라는 클래스를 선언합니다.&quot;</p>
</blockquote>
<p>정도로 생각했습니다.</p>
<p>하지만 이것은 정확히 말하면 <strong>전방 선언(Forward Declaration)</strong>입니다.</p>
<pre><code class="language-cpp">class USpringArmComponent;</code></pre>
<p>컴파일러에게</p>
<blockquote>
<p>&quot;<code>USpringArmComponent</code>라는 타입이 어딘가에 존재합니다.&quot;</p>
</blockquote>
<p>라는 사실만 먼저 알려주는 것입니다.</p>
<p>아직 그 클래스 내부에 어떤 변수와 함수가 들어 있는지는 알려주지 않습니다.</p>
<p>그래서 헤더에서 다음과 같이 <strong>포인터 타입만 선언할 때</strong>는 전체 헤더를 바로 포함하지 않고도 사용할 수 있습니다.</p>
<pre><code class="language-cpp">USpringArmComponent* SpringArmComp;</code></pre>
<p>비유하면 다음과 같습니다.</p>
<blockquote>
<p><strong>“자세한 정보는 나중에 알려드리겠지만, 일단 이런 이름을 가진 대상이 존재한다는 것만 알아두세요.”</strong></p>
</blockquote>
<p>반대로 <code>.cpp</code>에서 실제로 <code>USpringArmComponent</code>의 함수나 기능을 사용하려면 해당 클래스의 헤더를 <code>#include</code>하여 전체 정의를 알아야 합니다.</p>
<p>전방 선언을 사용하는 이유 중 하나는 헤더 간 의존성을 줄이고 불필요한 <code>include</code>를 줄이기 위해서입니다.</p>
<hr>
<h1 id="3-uproperty--editor와-blueprint-권한은-따로-생각하기">3. <code>UPROPERTY</code> — Editor와 Blueprint 권한은 따로 생각하기</h1>
<pre><code class="language-cpp">UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = &quot;Camera&quot;)
USpringArmComponent* SpringArmComp;</code></pre>
<p>처음에는 다음과 같이 이해했습니다.</p>
<blockquote>
<p><code>VisibleAnywhere</code> = 읽기만 가능
<code>BlueprintReadOnly</code> = 블루프린트에서 읽기만 가능</p>
</blockquote>
<p>그런데 여기서 중요한 점이 있었습니다.</p>
<p><strong><code>VisibleAnywhere</code>와 <code>BlueprintReadOnly</code>는 서로 다른 영역을 제어합니다.</strong></p>
<table>
<thead>
<tr>
<th>지정자</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>VisibleAnywhere</code></td>
<td>Details 패널에서 볼 수 있지만 편집할 수 없습니다.</td>
</tr>
<tr>
<td><code>EditAnywhere</code></td>
<td>Details 패널에서 편집할 수 있습니다.</td>
</tr>
<tr>
<td><code>BlueprintReadOnly</code></td>
<td>Blueprint에서 읽을 수 있지만 쓸 수 없습니다.</td>
</tr>
<tr>
<td><code>BlueprintReadWrite</code></td>
<td>Blueprint에서 읽기와 쓰기가 모두 가능합니다.</td>
</tr>
<tr>
<td><code>Category</code></td>
<td>에디터에서 프로퍼티를 묶어 표시할 카테고리입니다.</td>
</tr>
</tbody></table>
<p>따라서</p>
<pre><code class="language-cpp">UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = &quot;Camera&quot;)
USpringArmComponent* SpringArmComp;</code></pre>
<p>는 다음과 같이 이해할 수 있습니다.</p>
<blockquote>
<p><code>SpringArmComp</code>를 언리얼 프로퍼티 시스템에 노출하고,
에디터에서는 Camera 카테고리에서 확인할 수 있지만 수정할 수 없으며,
Blueprint에서도 읽기만 허용합니다.</p>
</blockquote>
<p>반면</p>
<pre><code class="language-cpp">UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = &quot;Movement&quot;)
float NormalSpeed;</code></pre>
<p>는 에디터에서도 수정할 수 있고, Blueprint에서도 읽고 쓸 수 있습니다.</p>
<h3 id="이번에-바로잡은-부분">이번에 바로잡은 부분</h3>
<p><code>VisibleAnywhere</code>를 단순히 <strong>“읽기 전용 변수”</strong>라고 외우면 안 됩니다.</p>
<p>정확히는</p>
<blockquote>
<p><strong>에디터의 프로퍼티 창에서 보이지만 직접 편집할 수 없도록 하는 지정자입니다.</strong></p>
</blockquote>
<hr>
<h1 id="4-gamemode와-staticclass">4. GameMode와 <code>StaticClass()</code></h1>
<pre><code class="language-cpp">DefaultPawnClass = ASpartaCharacter::StaticClass();
PlayerControllerClass = ASpartaPlayerController::StaticClass();</code></pre>
<p>처음에는 다음과 같이 주석을 달았습니다.</p>
<blockquote>
<p>&quot;<code>ASpartaCharacter</code>를 이 게임 모드의 PC로 사용합니다.&quot;</p>
</blockquote>
<p>하지만 이것은 잘못된 표현이었습니다.</p>
<p><code>Pawn</code>과 <code>PlayerController</code>는 서로 다른 역할을 담당합니다.</p>
<pre><code class="language-cpp">DefaultPawnClass</code></pre>
<p>에는 플레이어가 기본적으로 사용할 <strong>Pawn 또는 Character 클래스</strong>를 지정하고,</p>
<pre><code class="language-cpp">PlayerControllerClass</code></pre>
<p>에는 플레이어를 조종하는 <strong>PlayerController 클래스</strong>를 지정합니다.</p>
<p>따라서</p>
<pre><code class="language-cpp">DefaultPawnClass = ASpartaCharacter::StaticClass();</code></pre>
<p>는</p>
<blockquote>
<p>이 GameMode에서는 기본 플레이어 캐릭터로 <code>ASpartaCharacter</code> 클래스를 사용합니다.</p>
</blockquote>
<p>라는 의미이고,</p>
<pre><code class="language-cpp">PlayerControllerClass = ASpartaPlayerController::StaticClass();</code></pre>
<p>는</p>
<blockquote>
<p>기본 PlayerController로 <code>ASpartaPlayerController</code> 클래스를 사용합니다.</p>
</blockquote>
<p>라는 의미입니다.</p>
<hr>
<h2 id="그런데-staticclass는-무엇인가요">그런데 <code>StaticClass()</code>는 무엇인가요?</h2>
<pre><code class="language-cpp">ASpartaCharacter::StaticClass()</code></pre>
<p>여기서는 <code>ASpartaCharacter</code> 객체를 직접 생성하는 것이 아닙니다.</p>
<p>언리얼 엔진에게</p>
<blockquote>
<p><strong>&quot;<code>ASpartaCharacter</code>라는 클래스 자체의 정보를 주세요.&quot;</strong></p>
</blockquote>
<p>라고 요청하는 것입니다.</p>
<p>언리얼에서는 클래스 자체도 <code>UClass</code>라는 형태의 객체로 관리합니다.</p>
<p>따라서 <code>StaticClass()</code>는 해당 클래스의 <code>UClass</code> 정보를 가져오는 함수라고 이해할 수 있습니다.</p>
<p>게임식으로 비유하면,</p>
<pre><code class="language-cpp">ASpartaCharacter</code></pre>
<p>가 <strong>캐릭터 설계도의 종류</strong>라면,</p>
<pre><code class="language-cpp">ASpartaCharacter::StaticClass()</code></pre>
<p>는</p>
<blockquote>
<p><strong>“ASpartaCharacter라는 설계도 정보를 넘겨주세요.”</strong></p>
</blockquote>
<p>에 가깝습니다.</p>
<p>그래서 <code>DefaultPawnClass</code>에는 실제 캐릭터 한 명을 넣는 것이 아니라</p>
<blockquote>
<p><strong>앞으로 어떤 종류의 캐릭터를 생성해야 하는지</strong></p>
</blockquote>
<p>를 알려주는 클래스 정보를 넣습니다.</p>
<hr>
<h1 id="5-springarm과-camera-컴포넌트-생성">5. SpringArm과 Camera 컴포넌트 생성</h1>
<pre><code class="language-cpp">SpringArmComp =
    CreateDefaultSubobject&lt;USpringArmComponent&gt;(TEXT(&quot;SpringArm&quot;));</code></pre>
<p><code>CreateDefaultSubobject</code>는 생성자에서 캐릭터가 기본적으로 가지고 있을 <strong>서브 오브젝트 또는 컴포넌트</strong>를 생성할 때 사용합니다.</p>
<p>여기서는</p>
<pre><code class="language-cpp">&lt;USpringArmComponent&gt;</code></pre>
<p>를 통해</p>
<blockquote>
<p><code>USpringArmComponent</code> 타입의 컴포넌트를 만들겠습니다.</p>
</blockquote>
<p>라고 지정하고,</p>
<pre><code class="language-cpp">TEXT(&quot;SpringArm&quot;)</code></pre>
<p>을 통해 생성되는 컴포넌트에 <code>&quot;SpringArm&quot;</code>이라는 이름을 붙입니다.</p>
<p>즉,</p>
<blockquote>
<p><strong>SpringArm이라는 이름의 스프링 암 컴포넌트를 하나 생성합니다.</strong></p>
</blockquote>
<hr>
<h2 id="부모-컴포넌트에-부착하기">부모 컴포넌트에 부착하기</h2>
<pre><code class="language-cpp">SpringArmComp-&gt;SetupAttachment(RootComponent);</code></pre>
<p>스프링 암을 캐릭터의 <code>RootComponent</code> 아래에 붙입니다.</p>
<p>구조로 보면 다음과 같습니다.</p>
<pre><code class="language-text">Character
└─ RootComponent
   └─ SpringArmComp</code></pre>
<hr>
<h2 id="springarm-길이">SpringArm 길이</h2>
<pre><code class="language-cpp">SpringArmComp-&gt;TargetArmLength = 300.0f;</code></pre>
<p>스프링 암의 목표 길이를 300으로 설정합니다.</p>
<p>3인칭 카메라를 생각하면 캐릭터와 카메라 사이에 막대를 하나 두고, 그 막대 길이를 300으로 설정한다고 생각하면 이해하기 쉽습니다.</p>
<hr>
<h2 id="controller-회전을-springarm이-사용하도록-설정하기">Controller 회전을 SpringArm이 사용하도록 설정하기</h2>
<pre><code class="language-cpp">SpringArmComp-&gt;bUsePawnControlRotation = true;</code></pre>
<p>컨트롤러의 회전값을 스프링 암이 사용하도록 설정합니다.</p>
<p>즉 플레이어가 마우스로 시점을 돌려 Controller Rotation이 변경되면, SpringArm 역시 그 회전을 따라가도록 만드는 구조입니다.</p>
<hr>
<h1 id="6-camera를-springarm-끝에-달기">6. Camera를 SpringArm 끝에 달기</h1>
<pre><code class="language-cpp">CameraComp =
    CreateDefaultSubobject&lt;UCameraComponent&gt;(TEXT(&quot;Camera&quot;));

CameraComp-&gt;SetupAttachment(
    SpringArmComp,
    USpringArmComponent::SocketName
);</code></pre>
<p>카메라 컴포넌트를 생성하고 스프링 암에 부착합니다.</p>
<p>전체 구조는 대략 다음과 같습니다.</p>
<pre><code class="language-text">Character
└─ RootComponent
   └─ SpringArm
      └─ Camera</code></pre>
<p>즉 캐릭터에 카메라를 바로 붙이는 것이 아니라,</p>
<blockquote>
<p><strong>캐릭터 → 스프링 암 → 카메라</strong></p>
</blockquote>
<p>순서로 연결합니다.</p>
<hr>
<h2 id="왜-camera의-busepawncontrolrotation은-false인가요">왜 Camera의 <code>bUsePawnControlRotation</code>은 <code>false</code>인가요?</h2>
<pre><code class="language-cpp">CameraComp-&gt;bUsePawnControlRotation = false;</code></pre>
<p>처음 달았던 주석은 대체로 맞았습니다.</p>
<p>이미</p>
<pre><code class="language-cpp">SpringArmComp-&gt;bUsePawnControlRotation = true;</code></pre>
<p>로 설정했기 때문에 Controller의 회전은 SpringArm이 담당합니다.</p>
<p>Camera는 SpringArm에 붙어 있으므로 SpringArm이 회전하면 Camera 역시 함께 따라갑니다.</p>
<p>따라서 카메라까지 별도로 Pawn Control Rotation을 직접 사용할 필요가 없습니다.</p>
<p>구조를 정리하면 다음과 같습니다.</p>
<pre><code class="language-text">Controller Rotation
        ↓
    SpringArm 회전
        ↓
   Camera가 따라감</code></pre>
<p>즉 <strong>회전 명령은 SpringArm이 받고, Camera는 그 끝에 붙어서 따라갑니다.</strong></p>
<hr>
<h1 id="7-enhanced-input--imc-활성화">7. Enhanced Input — IMC 활성화</h1>
<p>여기부터는 처음 학습했을 때 가장 기억이 부실했던 부분입니다.</p>
<pre><code class="language-cpp">if (ULocalPlayer* LocalPlayer = GetLocalPlayer())
{
    if (UEnhancedInputLocalPlayerSubsystem* Subsystem =
        LocalPlayer-&gt;GetSubsystem&lt;UEnhancedInputLocalPlayerSubsystem&gt;())
    {
        if (InputMappingContext)
        {
            Subsystem-&gt;AddMappingContext(InputMappingContext, 0);
        }
    }
}</code></pre>
<p>처음에는</p>
<blockquote>
<p>“입력 매핑이 잘되었는지 검사하는 코드인가?”</p>
</blockquote>
<p>라고 생각했습니다.</p>
<p>하지만 정확히는 다릅니다.</p>
<p>이 코드는 <strong>Input Mapping Context를 실제 플레이어의 Enhanced Input 시스템에 등록하는 과정</strong>입니다.</p>
<hr>
<h2 id="①-local-player-가져오기">① Local Player 가져오기</h2>
<pre><code class="language-cpp">ULocalPlayer* LocalPlayer = GetLocalPlayer();</code></pre>
<p>현재 <code>PlayerController</code>와 연결된 로컬 플레이어 정보를 가져옵니다.</p>
<p>그리고 이를 <code>if</code>문 안에서 사용했으므로, 정상적으로 가져왔을 때만 내부 코드가 실행됩니다.</p>
<p>쉽게 말하면,</p>
<blockquote>
<p><strong>“현재 이 컴퓨터에서 플레이 중인 플레이어가 존재하나요?”</strong></p>
</blockquote>
<p>를 확인하는 과정이라고 볼 수 있습니다.</p>
<hr>
<h2 id="②-enhanced-input-subsystem-가져오기">② Enhanced Input Subsystem 가져오기</h2>
<pre><code class="language-cpp">LocalPlayer-&gt;GetSubsystem&lt;
    UEnhancedInputLocalPlayerSubsystem
&gt;()</code></pre>
<p>LocalPlayer가 가지고 있는 여러 시스템 가운데 <strong>Enhanced Input을 담당하는 Subsystem</strong>을 가져옵니다.</p>
<p>이 역시 <code>if</code>문 안에서 사용되므로, 정상적으로 가져왔을 때만 다음 단계로 넘어갑니다.</p>
<pre><code class="language-text">LocalPlayer
    ↓
Enhanced Input Subsystem</code></pre>
<p>순서로 찾아가는 것입니다.</p>
<hr>
<h2 id="③-inputmappingcontext가-존재하는지-확인하기">③ InputMappingContext가 존재하는지 확인하기</h2>
<pre><code class="language-cpp">if (InputMappingContext)</code></pre>
<p>포인터가 유효한지 확인합니다.</p>
<p>쉽게 말하면,</p>
<blockquote>
<p><strong>“등록하려고 하는 IMC가 실제로 존재하나요?”</strong></p>
</blockquote>
<p>를 확인하는 것입니다.</p>
<hr>
<h2 id="④-mapping-context-등록하기">④ Mapping Context 등록하기</h2>
<pre><code class="language-cpp">Subsystem-&gt;AddMappingContext(InputMappingContext, 0);</code></pre>
<p>이제 실제로 해당 <code>InputMappingContext</code>를 Enhanced Input 시스템에 추가합니다.</p>
<p>즉 이것은 <strong>검사 코드가 아니라 활성화 코드</strong>입니다.</p>
<p>두 번째 값인</p>
<pre><code class="language-cpp">0</code></pre>
<p>은 Mapping Context의 <strong>Priority</strong>입니다.</p>
<p>여러 Mapping Context가 동시에 존재할 때 우선순위를 결정하는 데 사용됩니다.</p>
<p>이 부분 전체는 다음 한 문장으로 기억할 수 있습니다.</p>
<blockquote>
<p><strong>“현재 플레이어의 Enhanced Input 시스템을 찾아서 제가 만든 IMC를 등록합니다.”</strong></p>
</blockquote>
<hr>
<h1 id="8-cast--원하는-타입이-맞는지-확인하기">8. <code>Cast</code> — 원하는 타입이 맞는지 확인하기</h1>
<pre><code class="language-cpp">if (UEnhancedInputComponent* EnhancedInput =
    Cast&lt;UEnhancedInputComponent&gt;(PlayerInputComponent))
{</code></pre>
<p>여기서 핵심은 <code>Cast</code>입니다.</p>
<p><code>PlayerInputComponent</code>는 부모 타입인 <code>UInputComponent</code> 계열로 전달되어 있습니다.</p>
<p>하지만 Enhanced Input 전용 함수인</p>
<pre><code class="language-cpp">BindAction()</code></pre>
<p>을 사용하려면 <code>UEnhancedInputComponent</code>로 접근할 수 있어야 합니다.</p>
<p>그래서</p>
<pre><code class="language-cpp">Cast&lt;UEnhancedInputComponent&gt;(PlayerInputComponent)</code></pre>
<p>를 사용합니다.</p>
<p>개념적으로는</p>
<blockquote>
<p><strong>“이 <code>PlayerInputComponent</code>가 실제로 <code>UEnhancedInputComponent</code>로 사용할 수 있는 객체인가요?”</strong></p>
</blockquote>
<p>라고 확인하는 과정입니다.</p>
<p>캐스팅에 성공하면 해당 타입의 포인터를 얻고, 실패하면 유효한 포인터를 얻지 못합니다.</p>
<p>그래서 다음과 같이 <code>if</code>문과 함께 사용하면,</p>
<pre><code class="language-cpp">if (UEnhancedInputComponent* EnhancedInput = Cast&lt;...&gt;())</code></pre>
<blockquote>
<p>캐스팅에 성공했을 때만 내부 코드를 실행합니다.</p>
</blockquote>
<p>라는 구조를 만들 수 있습니다.</p>
<hr>
<h1 id="9-playercontroller도-원하는-클래스인지-확인하기">9. PlayerController도 원하는 클래스인지 확인하기</h1>
<pre><code class="language-cpp">if (ASpartaPlayerController* PlayerController =
    Cast&lt;ASpartaPlayerController&gt;(GetController()))
{
}</code></pre>
<p>이번에는 캐릭터를 조종하고 있는 Controller가</p>
<pre><code class="language-cpp">ASpartaPlayerController</code></pre>
<p>타입인지 확인합니다.</p>
<p>왜 이것이 필요할까요?</p>
<p>이후 코드에서</p>
<pre><code class="language-cpp">PlayerController-&gt;JumpAction</code></pre>
<p>처럼 <strong><code>ASpartaPlayerController</code>에 정의해둔 변수</strong>를 사용하기 때문입니다.</p>
<p>일반 <code>AController</code> 타입으로는 <code>JumpAction</code>이라는 멤버가 있다는 사실을 알 수 없습니다.</p>
<p>따라서 흐름은 다음과 같습니다.</p>
<pre><code class="language-text">GetController()
        ↓
ASpartaPlayerController가 맞는지 확인
        ↓
맞으면 PlayerController 포인터 획득
        ↓
JumpAction, MoveAction 등에 접근</code></pre>
<hr>
<h1 id="10-input-action과-함수를-연결하는-bindaction">10. Input Action과 함수를 연결하는 <code>BindAction</code></h1>
<pre><code class="language-cpp">EnhancedInput-&gt;BindAction(
    PlayerController-&gt;JumpAction,
    ETriggerEvent::Triggered,
    this,
    &amp;ASpartaCharacter::StartJump
);</code></pre>
<p>이제 Input Action과 실제 C++ 함수를 연결합니다.</p>
<p>각 인자를 나누어보면 다음과 같습니다.</p>
<pre><code class="language-cpp">PlayerController-&gt;JumpAction</code></pre>
<p>어떤 Input Action을 감시할 것인지 지정합니다.</p>
<pre><code class="language-cpp">ETriggerEvent::Triggered</code></pre>
<p>그 Input Action이 어떤 상태가 되었을 때 실행할지 지정합니다.</p>
<pre><code class="language-cpp">this</code></pre>
<p>현재 <code>ASpartaCharacter</code> 객체를 의미합니다.</p>
<pre><code class="language-cpp">&amp;ASpartaCharacter::StartJump</code></pre>
<p>실제로 실행할 함수입니다.</p>
<p>그래서 전체 문장을 사람 말로 바꾸면,</p>
<blockquote>
<p><strong>“JumpAction이 Triggered 상태가 되면 현재 SpartaCharacter의 StartJump 함수를 실행해주세요.”</strong></p>
</blockquote>
<p>가 됩니다.</p>
<p><code>BindAction</code>은 결국</p>
<blockquote>
<p><strong>입력 이벤트와 실제 게임 로직 함수를 연결하는 과정입니다.</strong></p>
</blockquote>
<hr>
<h1 id="11-triggered와-completed">11. <code>Triggered</code>와 <code>Completed</code></h1>
<p>점프에는 두 개의 바인딩을 사용했습니다.</p>
<pre><code class="language-cpp">EnhancedInput-&gt;BindAction(
    PlayerController-&gt;JumpAction,
    ETriggerEvent::Triggered,
    this,
    &amp;ASpartaCharacter::StartJump
);</code></pre>
<p>그리고</p>
<pre><code class="language-cpp">EnhancedInput-&gt;BindAction(
    PlayerController-&gt;JumpAction,
    ETriggerEvent::Completed,
    this,
    &amp;ASpartaCharacter::StopJump
);</code></pre>
<p>현재 코드에서는</p>
<pre><code class="language-text">Triggered
    ↓
StartJump()

Completed
    ↓
StopJump()</code></pre>
<p>구조로 점프 시작과 종료를 연결합니다.</p>
<p>다만 <code>Triggered</code>를 단순히 <strong>“키를 눌렀을 때 딱 한 번 실행됩니다.”</strong>라고 외우면 곤란합니다.</p>
<p>Input Action에 설정된 Trigger 조건에 따라 <code>Triggered</code>가 발생하는 방식이 달라질 수 있습니다.</p>
<p>버튼을 누르는 순간 한 번만 처리하고 싶다면 상황에 따라</p>
<pre><code class="language-cpp">ETriggerEvent::Started</code></pre>
<p>를 사용하는 경우도 있습니다.</p>
<p>따라서 중요한 것은 이벤트 이름만 외우는 것이 아니라, <strong>Input Action에 어떤 Trigger가 설정되어 있는지 함께 확인하는 것</strong>입니다.</p>
<hr>
<h1 id="12-input-action의-값을-fvector2d로-꺼내기">12. Input Action의 값을 <code>FVector2D</code>로 꺼내기</h1>
<p>이동 코드입니다.</p>
<pre><code class="language-cpp">const FVector2D MoveInput =
    Value.Get&lt;FVector2D&gt;();</code></pre>
<p><code>Value</code>에는 Input Action을 통해 전달된 입력값이 들어 있습니다.</p>
<p>Move Action을 2D Axis로 만들었다면,</p>
<pre><code class="language-cpp">Value.Get&lt;FVector2D&gt;()</code></pre>
<p>를 이용하여 그 값을 <code>FVector2D</code> 형태로 꺼낼 수 있습니다.</p>
<p>예를 들면 다음과 같은 값이 들어올 수 있습니다.</p>
<pre><code class="language-text">앞으로 이동   → X = 1
뒤로 이동     → X = -1
오른쪽 이동   → Y = 1
왼쪽 이동     → Y = -1</code></pre>
<p>다만 <strong>어느 축이 앞뒤이고 어느 축이 좌우인지는 실제 Input Mapping 설정에 따라 달라질 수 있습니다.</strong></p>
<p>따라서 코드를 볼 때는 무조건</p>
<blockquote>
<p><code>X = 앞뒤</code></p>
</blockquote>
<p>라고 암기하기보다는 IMC 설정까지 함께 확인하는 것이 좋습니다.</p>
<hr>
<h1 id="13-isnearlyzero--왜-그냥--0을-사용하지-않나요">13. <code>IsNearlyZero()</code> — 왜 그냥 <code>!= 0</code>을 사용하지 않나요?</h1>
<pre><code class="language-cpp">if (!FMath::IsNearlyZero(MoveInput.X))
{</code></pre>
<p>처음 보면 다음처럼 작성해도 될 것처럼 보입니다.</p>
<pre><code class="language-cpp">if (MoveInput.X != 0)</code></pre>
<p>하지만 <code>float</code>는 계산 과정에서 아주 작은 오차가 생길 수 있습니다.</p>
<p>예를 들어 실제로는 0에 가까운 값인데</p>
<pre><code class="language-text">0.00000001</code></pre>
<p>같은 값이 들어올 수도 있습니다.</p>
<p>따라서</p>
<pre><code class="language-cpp">FMath::IsNearlyZero()</code></pre>
<p>를 사용하여</p>
<blockquote>
<p><strong>“완전히 정확한 0은 아니더라도 충분히 0에 가까운가요?”</strong></p>
</blockquote>
<p>를 검사합니다.</p>
<p>그리고 코드에는 앞에 <code>!</code>가 붙어 있습니다.</p>
<pre><code class="language-cpp">!FMath::IsNearlyZero(MoveInput.X)</code></pre>
<p>따라서 의미는</p>
<blockquote>
<p><strong>“MoveInput.X가 거의 0이 아니라면”</strong></p>
</blockquote>
<p>이 됩니다.</p>
<p>즉 실제 이동 입력이 있을 때만 아래 코드를 실행합니다.</p>
<hr>
<h1 id="14-addmovementinput으로-캐릭터-이동시키기">14. <code>AddMovementInput()</code>으로 캐릭터 이동시키기</h1>
<pre><code class="language-cpp">AddMovementInput(
    GetActorForwardVector(),
    MoveInput.X
);</code></pre>
<p>두 값을 전달하고 있습니다.</p>
<pre><code class="language-cpp">GetActorForwardVector()</code></pre>
<p>현재 캐릭터가 바라보고 있는 <strong>앞 방향</strong>을 가져옵니다.</p>
<p>그리고</p>
<pre><code class="language-cpp">MoveInput.X</code></pre>
<p>는 그 방향으로 어느 정도의 입력을 줄 것인지 전달합니다.</p>
<p>예를 들어</p>
<pre><code class="language-text">MoveInput.X = 1</code></pre>
<p>이라면 앞 방향,</p>
<pre><code class="language-text">MoveInput.X = -1</code></pre>
<p>이라면 반대 방향으로 입력하게 됩니다.</p>
<p>따라서 개념적으로는</p>
<pre><code class="language-text">방향
+
입력값
=
Movement Input</code></pre>
<p>이라고 볼 수 있습니다.</p>
<hr>
<h1 id="15-마우스-입력으로-controller-회전시키기">15. 마우스 입력으로 Controller 회전시키기</h1>
<p>마지막은 시점 회전입니다.</p>
<pre><code class="language-cpp">FVector2D LookInput =
    Value.Get&lt;FVector2D&gt;();</code></pre>
<p>Look Action 역시 2D Axis 값이므로 <code>FVector2D</code>로 받아옵니다.</p>
<p>그리고</p>
<pre><code class="language-cpp">AddControllerYawInput(LookInput.X);</code></pre>
<p>으로 좌우 회전값을 Controller에 넣습니다.</p>
<p><code>Yaw</code>는 쉽게 말하면 <strong>고개를 좌우로 돌리는 회전</strong>입니다.</p>
<p>반면</p>
<pre><code class="language-cpp">AddControllerPitchInput(LookInput.Y);</code></pre>
<p>은 위아래 방향의 회전입니다.</p>
<p>정리하면 다음과 같습니다.</p>
<pre><code class="language-text">LookInput.X
    ↓
Yaw
    ↓
좌우 시점 회전

LookInput.Y
    ↓
Pitch
    ↓
상하 시점 회전</code></pre>
<hr>
<h1 id="16-그런데-controller를-돌렸는데-왜-camera가-움직이나요">16. 그런데 Controller를 돌렸는데 왜 Camera가 움직이나요?</h1>
<p>여기서 앞서 설정했던 코드가 다시 연결됩니다.</p>
<pre><code class="language-cpp">SpringArmComp-&gt;bUsePawnControlRotation = true;</code></pre>
<p>Look 입력은</p>
<pre><code class="language-cpp">AddControllerYawInput()
AddControllerPitchInput()</code></pre>
<p>을 이용하여 <strong>Controller Rotation</strong>을 변경합니다.</p>
<p>그리고 SpringArm은 Controller Rotation을 사용하도록 설정되어 있습니다.</p>
<p>따라서 전체 과정은 다음과 같이 이어집니다.</p>
<pre><code class="language-text">마우스 움직임
    ↓
LookAction
    ↓
FVector2D LookInput
    ↓
AddControllerYawInput()
AddControllerPitchInput()
    ↓
Controller Rotation 변경
    ↓
SpringArm이 회전을 따라감
    ↓
SpringArm 끝에 붙은 Camera도 따라감</code></pre>
<p>처음에는 각각 따로 존재하는 코드처럼 보였지만, 실제로는 서로 연결되어 있었습니다.</p>
<hr>
<h1 id="17-전체-입력-시스템-한-번에-정리하기">17. 전체 입력 시스템 한 번에 정리하기</h1>
<p>이번 코드에서 가장 중요하게 정리한 것은 각각의 함수를 외우는 것보다 <strong>입력이 캐릭터에게 전달되는 과정</strong>이었습니다.</p>
<p>전체 흐름을 그려보면 다음과 같습니다.</p>
<pre><code class="language-text">[키보드 / 마우스]
        ↓
Input Mapping Context
        ↓
Input Action
        ↓
Enhanced Input
        ↓
BindAction
        ↓
캐릭터 함수
        ↓
이동 / 점프 / 회전</code></pre>
<p>조금 더 코드에 가깝게 보면 다음과 같습니다.</p>
<pre><code class="language-text">BeginPlay()
    ↓
LocalPlayer 획득
    ↓
EnhancedInput Subsystem 획득
    ↓
AddMappingContext()
    ↓
IMC 활성화


SetupPlayerInputComponent()
    ↓
PlayerInputComponent 획득
    ↓
UEnhancedInputComponent로 Cast
    ↓
PlayerController Cast
    ↓
BindAction()
    ↓
Move / Look / Jump 함수 연결</code></pre>
<p>실제 플레이 중에는 다음과 같이 동작합니다.</p>
<pre><code class="language-text">W 키 또는 마우스 입력
    ↓
Input Action 발생
    ↓
BindAction으로 연결된 함수 실행
    ↓
FInputActionValue에서 값 추출
    ↓
AddMovementInput()
또는
AddControllerYawInput()</code></pre>
<hr>
<h1 id="이번-학습에서-수정한-오개념">이번 학습에서 수정한 오개념</h1>
<p>주석을 직접 달아보니 <strong>알고 있다고 생각했지만 실제로는 애매하게 이해하고 있던 부분</strong>이 꽤 많았습니다.</p>
<h2 id="1-visibleanywhere">1. <code>VisibleAnywhere</code></h2>
<p>잘못된 이해:</p>
<blockquote>
<p>읽기 전용 변수입니다.</p>
</blockquote>
<p>수정:</p>
<blockquote>
<p><strong>언리얼 에디터의 프로퍼티 창에서 보이지만 수정할 수 없도록 하는 지정자입니다.</strong>
Blueprint에서의 읽기 및 쓰기 여부는 <code>BlueprintReadOnly</code>, <code>BlueprintReadWrite</code>가 따로 결정합니다.</p>
</blockquote>
<hr>
<h2 id="2-전방-선언-1">2. 전방 선언</h2>
<p>잘못된 이해:</p>
<blockquote>
<p>이 클래스를 여기에서 선언합니다.</p>
</blockquote>
<p>수정:</p>
<blockquote>
<p><strong>이 이름을 가진 타입이 존재한다는 사실만 컴파일러에게 미리 알려줍니다.</strong></p>
</blockquote>
<hr>
<h2 id="3-staticclass">3. <code>StaticClass()</code></h2>
<p>처음에는 역할 자체가 명확하지 않았습니다.</p>
<p>수정:</p>
<blockquote>
<p>객체를 생성하는 함수가 아니라 <strong>해당 Unreal 클래스의 <code>UClass</code> 정보를 가져오는 함수</strong>입니다.</p>
</blockquote>
<hr>
<h2 id="4-addmappingcontext">4. <code>AddMappingContext()</code></h2>
<p>잘못된 이해:</p>
<blockquote>
<p>입력이 제대로 설정되었는지 검사합니다.</p>
</blockquote>
<p>수정:</p>
<blockquote>
<p><strong>Input Mapping Context를 Local Player의 Enhanced Input 시스템에 실제로 추가하고 활성화합니다.</strong></p>
</blockquote>
<hr>
<h2 id="5-cast">5. <code>Cast</code></h2>
<p>처음에는 코드를 그대로 따라 작성하기만 했습니다.</p>
<p>수정:</p>
<blockquote>
<p><strong>현재 가지고 있는 객체가 제가 원하는 클래스 타입으로 사용할 수 있는 객체인지 확인하고, 가능하다면 해당 타입의 포인터를 얻습니다.</strong></p>
</blockquote>
<hr>
<h2 id="6-bindaction">6. <code>BindAction</code></h2>
<p>단순히</p>
<blockquote>
<p>입력을 연결합니다.</p>
</blockquote>
<p>정도로 알고 있었습니다.</p>
<p>수정:</p>
<blockquote>
<p><strong>특정 Input Action에서 특정 Trigger Event가 발생했을 때 어떤 객체의 어떤 함수를 실행할 것인지 연결합니다.</strong></p>
</blockquote>
<hr>
<h1 id="마무리">마무리</h1>
<p>이번 코드에서 개별 문법도 중요했지만, 가장 큰 수확은 코드들이 서로 어떤 관계를 가지고 있는지 이해하기 시작했다는 점입니다.</p>
<p>처음에는</p>
<pre><code class="language-cpp">AddMappingContext()
Cast()
BindAction()
Value.Get&lt;FVector2D&gt;()</code></pre>
<p>가 모두 별개의 코드처럼 보였습니다.</p>
<p>하지만 흐름을 연결해보면 결국 하나의 과정입니다.</p>
<blockquote>
<p><strong>입력 설정을 플레이어에게 활성화하고 →
입력을 받을 컴포넌트를 찾고 →
Input Action과 캐릭터 함수를 연결하고 →
전달받은 값을 이동과 회전에 사용합니다.</strong></p>
</blockquote>
<p>카메라 코드 역시 마찬가지였습니다.</p>
<pre><code class="language-text">Controller
→ SpringArm
→ Camera</code></pre>
<p>라는 관계를 먼저 이해하고 나니,</p>
<pre><code class="language-cpp">bUsePawnControlRotation = true;
bUsePawnControlRotation = false;</code></pre>
<p>가 왜 서로 다르게 설정되어 있는지도 자연스럽게 이해할 수 있었습니다.</p>
<p>결국 현재 단계에서 중요한 것은 코드를 통째로 외우는 것보다,</p>
<blockquote>
<p><strong>“누가 누구를 가지고 있고, 누가 누구에게 값을 전달하는가?”</strong></p>
</blockquote>
<p>를 추적하는 것이라고 생각합니다.</p>
<p>앞으로 새로운 언리얼 C++ 코드를 볼 때도 함수 이름부터 외우기보다는 <strong>객체 간의 관계와 데이터가 흘러가는 방향부터 그려보는 방식으로 읽어보려고 합니다.</strong></p>
<p>#UnrealEngine #UnrealCPP #CPlusPlus #EnhancedInput #게임개발 #언리얼엔진 #개발공부</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CH 3 개인 과제 KPT]]></title>
            <link>https://velog.io/@jupiter_0609/CH-3-%EA%B0%9C%EC%9D%B8-%EA%B3%BC%EC%A0%9C-KPT</link>
            <guid>https://velog.io/@jupiter_0609/CH-3-%EA%B0%9C%EC%9D%B8-%EA%B3%BC%EC%A0%9C-KPT</guid>
            <pubDate>Tue, 18 Aug 2026 11:26:43 GMT</pubDate>
            <description><![CDATA[<h3 id="ch3-kpt"><strong>CH3 KPT</strong></h3>
<p><strong>Keep</strong>
강의를 하나씩 충분히 이해하고 넘어가는 학습 방식 자체는 유지하고 싶다. 특히 동료분들과 함께 코드에 주석을 달고, 각 개념을 설명하면서 복습하는 과정이 개념을 확실하게 이해하는 데 효과적으로 작용했다.</p>
<p><strong>Problem</strong>
다만 모든 내용을 꼼꼼하게 이해하려다 보니 학습 진도가 지나치게 느려지는 문제가 있다. 부족한 진도를 철야나 추가 노동으로 해결하는 방식은 지속 가능하지 않다. 모든 내용을 한 번에 완벽하게 이해하려 하기보다, 중요한 개념과 그렇지 않은 부분을 구분하고 일단 넘어갔다가 필요할 때 다시 확인하는 방식이 필요하다.</p>
<p><strong>Try</strong>
집중 학습 시간이 필요할 때는 팀원들에게 미리 양해를 구하고 방해 금지 모드를 활용해 몰입할 수 있는 시간을 확보한다. 또한 강의를 들으며 모든 의문을 즉시 해결하려 하지 않고, ① 지금 반드시 이해해야 하는 내용 / ② 표시만 해두고 나중에 복습할 내용으로 나눠 학습 속도를 높여본다.</p>
<p><strong>Feel</strong>
하나의 개념을 온전히 이해했을 때 느끼는 만족감은 분명 크다. 지금의 깊이 있는 학습 방식 자체를 버리기보다는, 이해의 깊이는 유지하면서 학습 속도를 높이는 방법을 찾고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[분반 강의 세션]]></title>
            <link>https://velog.io/@jupiter_0609/%EB%B6%84%EB%B0%98-%EB%9D%BC%EC%9D%B4%EB%B8%8C-%EC%84%B8%EC%85%98</link>
            <guid>https://velog.io/@jupiter_0609/%EB%B6%84%EB%B0%98-%EB%9D%BC%EC%9D%B4%EB%B8%8C-%EC%84%B8%EC%85%98</guid>
            <pubDate>Wed, 12 Aug 2026 14:59:24 GMT</pubDate>
            <description><![CDATA[<h1 id="unreal-c-학습-정리--컴파일부터-리플렉션-시스템까지">Unreal C++ 학습 정리 — 컴파일부터 리플렉션 시스템까지</h1>
<p>언리얼 엔진을 공부하다 보면 <strong>컴파일, 빌드, 쿠킹, 패키징, 로드</strong>처럼 비슷해 보이는 용어를 자주 접하게 된다.</p>
<p>또 C++ 코드를 작성하다 보면 <code>UCLASS()</code>, <code>GENERATED_BODY()</code>, <code>.generated.h</code>처럼 일반 C++에서는 볼 수 없었던 코드도 등장한다.</p>
<p>이번에는 이 개념들이 각각 무엇을 의미하고, 언리얼의 <strong>리플렉션(Reflection) 시스템</strong>과 어떻게 연결되는지 정리해 보았다.</p>
<hr>
<h2 id="1-컴파일-쿠킹-패키징-로드는-서로-다르다">1. 컴파일, 쿠킹, 패키징, 로드는 서로 다르다</h2>
<p>처음에는 모두 &quot;내가 만든 것을 게임에서 사용할 수 있게 만드는 과정&quot;처럼 보이지만 역할이 다르다.</p>
<h3 id="컴파일compile">컴파일(Compile)</h3>
<p>컴파일은 작성한 <strong>코드나 블루프린트를 실행할 수 있는 형태로 변환하는 과정</strong>이다.</p>
<p>C++에서는 작성한 소스 코드를 컴퓨터가 실행할 수 있는 형태로 변환하는 과정이 필요하고, 블루프린트 역시 노드와 변수, 함수, 이벤트 그래프 등을 검사하고 실행 가능한 형태로 만드는 컴파일 과정이 존재한다.</p>
<h3 id="쿠킹cook">쿠킹(Cook)</h3>
<p>쿠킹은 코드보다는 <strong>게임 콘텐츠를 대상 플랫폼에서 사용할 수 있는 형태로 변환하는 과정</strong>에 가깝다.</p>
<p>예를 들면 다음과 같은 콘텐츠가 대상이 된다.</p>
<ul>
<li>Texture</li>
<li>Mesh</li>
<li>Sound</li>
<li>Blueprint</li>
<li>Level</li>
</ul>
<p>PC, 콘솔, 모바일 등 실제 게임이 실행될 플랫폼에 맞게 콘텐츠를 준비하는 과정이라고 이해할 수 있다.</p>
<h3 id="패키징package">패키징(Package)</h3>
<p>패키징은 <strong>컴파일된 코드와 쿠킹된 콘텐츠를 실제 배포 가능한 형태로 묶는 과정</strong>이다.</p>
<p>언리얼의 전체 패키징 과정에서는 Build, Cook, Stage, Package, Deploy, Run 등의 단계가 사용된다.</p>
<p>따라서 단순하게 보면,</p>
<blockquote>
<p><strong>Compile → 코드를 실행할 수 있도록 준비</strong>
<strong>Cook → 콘텐츠를 플랫폼에 맞게 준비</strong>
<strong>Package → 준비된 것들을 배포 가능한 게임으로 묶기</strong></p>
</blockquote>
<p>정도로 구별할 수 있다.</p>
<h3 id="로드load">로드(Load)</h3>
<p>로드는 앞의 과정과 성격이 조금 다르다.</p>
<p><strong>게임 실행 중 필요한 에셋이나 레벨 등의 데이터를 저장장치에서 메모리로 가져오는 과정</strong>이다.</p>
<p>예를 들어 SSD에 저장되어 있는 맵이나 텍스처가 필요하다면 게임이 이를 메모리로 가져와 사용할 수 있도록 해야 한다.</p>
<p>그런데 게임에 존재하는 모든 것을 한 번에 로드한다면 메모리와 로딩 시간 측면에서 비효율적이다.</p>
<p>그래서 실제 게임에서는 필요한 데이터를 어떻게, 언제 가져올 것인지가 중요하며 <strong>동기 로딩과 비동기 로딩</strong> 같은 개념도 등장한다.</p>
<hr>
<h1 id="2-unreal-c의-빌드는-일반-c과-조금-다르다">2. Unreal C++의 빌드는 일반 C++과 조금 다르다</h1>
<p>언리얼 C++을 공부하다 보면 다음과 같은 매크로를 계속 만나게 된다.</p>
<pre><code class="language-cpp">UCLASS()
USTRUCT()
UPROPERTY()
UFUNCTION()</code></pre>
<p>이 코드는 단순히 C++ 컴파일러에게 넘겨서 처리하는 것으로 끝나지 않는다.</p>
<p>언리얼에는 <strong>UHT(Unreal Header Tool)</strong>와 <strong>UBT(Unreal Build Tool)</strong>가 있기 때문이다.</p>
<h2 id="uht와-ubt">UHT와 UBT</h2>
<p>대략적인 흐름을 단순화하면 다음과 같이 이해할 수 있다.</p>
<pre><code class="language-text">내가 작성한 Unreal C++ 코드
        ↓
UBT가 빌드 과정 관리
        ↓
UHT 실행
        ↓
UCLASS / UPROPERTY / UFUNCTION 등 분석
        ↓
UObject 시스템에 필요한 코드 생성
        ↓
C++ 컴파일
        ↓
실행 가능한 코드 생성</code></pre>
<p>즉,</p>
<blockquote>
<p><strong>언리얼 C++은 일반적인 C++ 컴파일뿐 아니라 언리얼의 UObject 및 리플렉션 시스템을 지원하기 위한 코드 생성 과정이 추가된다.</strong></p>
</blockquote>
<p>이 점이 일반적인 C++ 프로젝트와 Unreal C++ 프로젝트를 이해할 때 중요한 차이 중 하나다.</p>
<hr>
<h1 id="3-블루프린트도-컴파일한다">3. 블루프린트도 컴파일한다</h1>
<p>블루프린트는 노드를 연결하면 곧바로 실행되는 것처럼 보이지만 블루프린트 역시 <strong>컴파일 과정</strong>이 존재한다.</p>
<p>블루프린트 에디터에서 <code>Compile</code> 버튼을 누르면 엔진은 작성된 블루프린트의 변수, 함수, 이벤트 그래프 등의 내용을 검사하고 실행 가능한 형태로 준비한다.</p>
<p>다만 여기서 주의할 점이 있다.</p>
<blockquote>
<p><strong>&quot;블루프린트는 컴파일하기 때문에 C++보다 느리다&quot;라고 단순하게 결론 내리는 것은 적절하지 않다.</strong></p>
</blockquote>
<p>블루프린트와 C++의 런타임 성능 차이는 실행 방식과 구현 내용에 따라 달라질 수 있기 때문에, 실제 최적화에서는 어떤 로직을 얼마나 자주 실행하는지를 함께 확인해야 한다.</p>
<hr>
<h1 id="4-material과-shader도-별도의-컴파일-과정이-있다">4. Material과 Shader도 별도의 컴파일 과정이 있다</h1>
<p>언리얼에서 &quot;컴파일&quot;이라고 하면 C++만 생각하기 쉽지만 Material과 Shader에도 별도의 컴파일 과정이 존재한다.</p>
<p>예를 들어 필요한 Shader Map이 캐시에 존재하지 않는 머티리얼이 필요해지면 엔진에서 셰이더 컴파일 작업이 발생할 수 있다.</p>
<p>이러한 작업은 Shader Compile Worker를 통해 병렬로 처리될 수 있다.</p>
<p>중요한 것은 이것이 <strong>메시나 텍스처 자체를 로드하는 작업과는 다르다</strong>는 점이다.</p>
<p>셰이더 컴파일은 GPU가 사용할 <strong>셰이더 코드를 준비하는 과정</strong>으로 이해하면 된다.</p>
<p>TA를 공부하는 입장에서는 C++ 컴파일뿐만 아니라 셰이더 컴파일 역시 구별해서 알아둘 필요가 있다.</p>
<hr>
<h1 id="5-리플렉션reflection이란">5. 리플렉션(Reflection)이란?</h1>
<p>언리얼 C++을 공부하면서 상당히 중요한 개념이 <strong>리플렉션 시스템</strong>이다.</p>
<p>리플렉션을 간단하게 표현하면,</p>
<blockquote>
<p><strong>프로그램이 자신의 클래스, 변수, 함수 등에 관한 정보를 다룰 수 있도록 만드는 체계</strong></p>
</blockquote>
<p>라고 이해할 수 있다.</p>
<p>예를 들어 일반적인 C++ 클래스가 있다고 하자.</p>
<pre><code class="language-cpp">class PlayerData
{
public:
    int HP;
    float MoveSpeed;
};</code></pre>
<p>사람이 이 코드를 읽으면 <code>PlayerData</code> 안에 <code>HP</code>와 <code>MoveSpeed</code>가 있다는 것을 쉽게 알 수 있다.</p>
<p>하지만 <strong>언리얼 엔진과 에디터가 이 정보를 자신들의 시스템에서 활용하려면 별도의 메타데이터가 필요하다.</strong></p>
<p>예를 들어 엔진은 다음과 같은 작업을 해야 한다.</p>
<ul>
<li>특정 클래스가 어떤 클래스인지 파악</li>
<li>프로퍼티 정보를 에디터에서 활용</li>
<li>C++ 기능을 블루프린트에 노출</li>
<li>C++ 클래스를 블루프린트와 연결</li>
<li>직렬화 및 저장에 필요한 정보 활용</li>
<li>네트워크 복제 시스템에서 프로퍼티 정보 활용</li>
<li>런타임에서 타입 정보 확인</li>
</ul>
<p>이러한 여러 엔진 기능의 기반이 되는 것이 언리얼의 <strong>리플렉션 시스템</strong>이다.</p>
<p>따라서 리플렉션을 단순히</p>
<blockquote>
<p>&quot;디테일 패널에 변수를 표시하는 기능&quot;</p>
</blockquote>
<p>이라고 이해하면 범위가 너무 좁다.</p>
<p>디테일 패널 노출은 리플렉션을 이용해서 구현되는 <strong>여러 기능 중 하나</strong>라고 보는 것이 더 정확하다.</p>
<hr>
<h1 id="6-pragma-once는-매크로가-아니다">6. <code>#pragma once</code>는 매크로가 아니다</h1>
<p>언리얼 C++ 헤더를 열면 거의 항상 위쪽에 다음 코드가 있다.</p>
<pre><code class="language-cpp">#pragma once</code></pre>
<p>처음 보면 <code>UCLASS()</code> 같은 언리얼 매크로와 비슷해 보일 수 있지만 <code>#pragma once</code>는 언리얼 리플렉션 매크로가 아니다.</p>
<p>이는 <strong>전처리 지시문</strong>이다.</p>
<p>역할은 간단하다.</p>
<blockquote>
<p><strong>하나의 헤더 파일이 같은 컴파일 단위에서 중복으로 포함되는 것을 방지한다.</strong></p>
</blockquote>
<p>따라서 다음은 구별해야 한다.</p>
<pre><code class="language-text">#pragma once
→ 전처리와 관련된 지시문

UCLASS()
UPROPERTY()
UFUNCTION()
→ Unreal Header Tool이 처리하는 언리얼 리플렉션 관련 매크로</code></pre>
<hr>
<h1 id="7-generatedh는-왜-존재할까">7. <code>*.generated.h</code>는 왜 존재할까?</h1>
<p>언리얼 C++ 헤더에서는 다음과 같은 코드를 볼 수 있다.</p>
<pre><code class="language-cpp">#include &quot;CoreMinimal.h&quot;
#include &quot;GameFramework/Actor.h&quot;
#include &quot;TestActor.generated.h&quot;</code></pre>
<p>여기에서 특히 중요한 것이</p>
<pre><code class="language-cpp">#include &quot;TestActor.generated.h&quot;</code></pre>
<p>이다.</p>
<p>이 파일은 내가 직접 작성하는 파일이 아니다.</p>
<p>UHT가 헤더를 분석하면서 언리얼의 리플렉션 및 UObject 시스템을 지원하기 위해 필요한 코드를 생성하며, 이 과정에서 만들어지는 코드와 연결되는 헤더다.</p>
<p>그리고 중요한 규칙이 하나 있다.</p>
<blockquote>
<p><strong><code>*.generated.h</code>는 해당 헤더 파일의 include 목록에서 마지막에 위치해야 한다.</strong></p>
</blockquote>
<p>따라서 다음처럼 작성해야 한다.</p>
<pre><code class="language-cpp">#include &quot;CoreMinimal.h&quot;
#include &quot;GameFramework/Actor.h&quot;
#include &quot;TestActor.generated.h&quot;</code></pre>
<p>그 아래에 다시 다른 헤더를 <code>#include</code>하는 방식은 피해야 한다.</p>
<hr>
<h1 id="8-uclass는-무엇인가">8. <code>UCLASS()</code>는 무엇인가?</h1>
<p>다음과 같은 Unreal C++ 코드를 살펴보자.</p>
<pre><code class="language-cpp">UCLASS()
class FRONTLINEFPS_API ATestActor : public AActor
{
    GENERATED_BODY()
};</code></pre>
<p>여기서</p>
<pre><code class="language-cpp">UCLASS()</code></pre>
<p>는 언리얼의 리플렉션과 관련된 매크로다.</p>
<p>UHT가 이 선언을 분석하고 해당 클래스가 언리얼의 UObject 시스템에서 필요한 처리를 받을 수 있도록 한다.</p>
<p><code>AActor</code> 역시 <code>UObject</code> 계열의 클래스이므로 Unreal Object System과 연결되어 있다.</p>
<p>따라서</p>
<pre><code class="language-cpp">class ATestActor : public AActor</code></pre>
<p>는</p>
<blockquote>
<p><code>ATestActor</code>가 <code>AActor</code>를 상속받는다.</p>
</blockquote>
<p>라는 일반 C++의 상속 관계를 나타내고,</p>
<pre><code class="language-cpp">UCLASS()</code></pre>
<p>는 이 클래스가 언리얼의 UObject 및 리플렉션 체계 안에서 처리될 수 있도록 필요한 정보를 제공하는 역할을 한다.</p>
<p>두 코드는 서로 다른 역할을 담당한다.</p>
<hr>
<h1 id="9-uclass-안에는-옵션을-넣을-수-있다">9. <code>UCLASS()</code> 안에는 옵션을 넣을 수 있다</h1>
<p><code>UCLASS()</code>의 괄호 안에는 <strong>클래스 지정자(Class Specifier)</strong>를 작성할 수 있다.</p>
<p>예를 들어,</p>
<pre><code class="language-cpp">UCLASS(Blueprintable)</code></pre>
<p>처럼 사용할 수 있다.</p>
<h3 id="blueprintable"><code>Blueprintable</code></h3>
<p>해당 클래스를 기반으로 블루프린트 클래스를 만들 수 있도록 지정하는 데 사용한다.</p>
<pre><code class="language-cpp">UCLASS(Blueprintable)
class AMyActor : public AActor
{
    GENERATED_BODY()
};</code></pre>
<h3 id="blueprinttype"><code>BlueprintType</code></h3>
<p>해당 타입을 블루프린트에서 변수 타입 등으로 사용할 수 있도록 노출하는 데 관련된 지정자다.</p>
<pre><code class="language-cpp">UCLASS(BlueprintType)</code></pre>
<p><code>Blueprintable</code>과 <code>BlueprintType</code>은 이름이 비슷하지만 <strong>동일한 역할을 하는 옵션은 아니다.</strong></p>
<p>따라서 실제 사용 시에는 해당 클래스의 부모 클래스와 지정자의 상속 여부까지 확인하는 것이 좋다.</p>
<hr>
<h1 id="10-myproject_api는-무엇인가">10. <code>MYPROJECT_API</code>는 무엇인가?</h1>
<p>Unreal C++ 클래스에서는 다음과 같은 형태도 볼 수 있다.</p>
<pre><code class="language-cpp">class MYPROJECT_API ATestActor : public AActor</code></pre>
<p>여기서</p>
<pre><code class="language-cpp">MYPROJECT_API</code></pre>
<p>는 <strong>모듈 API 지정자(Module API Specifier)</strong>다.</p>
<p>프로젝트 이름에 따라 이름이 달라질 수 있다.</p>
<p>언리얼 프로젝트는 하나의 거대한 코드 덩어리로만 구성되는 것이 아니라 여러 모듈로 나뉘어 빌드될 수 있다.</p>
<p>예를 들면 엔진에는 다음과 같은 모듈들이 있다.</p>
<pre><code class="language-text">Core
CoreUObject
Engine
UMG
GameplayAbilities
...</code></pre>
<p>게임 프로젝트에도 자체 게임 모듈이 존재한다.</p>
<p>모듈 API 지정자는 클래스나 함수 등의 심볼을 <strong>다른 모듈에서 사용할 수 있도록 노출하는 것과 관련된 표시</strong>다.</p>
<p>따라서 이것을 <code>public</code>, <code>private</code> 같은 C++ 접근 지정자와 완전히 같은 것으로 보면 안 된다.</p>
<p><code>public/private</code>가 <strong>클래스 내부에서 누가 멤버에 접근할 수 있는가</strong>를 제어한다면,</p>
<p><code>MYPROJECT_API</code>와 같은 모듈 API 지정자는 <strong>모듈 또는 DLL 경계를 넘어 해당 심볼을 사용할 수 있도록 노출하는 것</strong>과 관련된다.</p>
<hr>
<h1 id="11-dll이란">11. DLL이란?</h1>
<p>DLL은 <strong>Dynamic Link Library</strong>의 약자다.</p>
<p>쉽게 표현하면 프로그램에서 사용할 코드와 기능을 별도의 라이브러리 파일로 만들어 둔 것이다.</p>
<p>언리얼의 모듈 시스템을 공부하다 보면 DLL과 모듈 API 지정자 개념이 함께 등장할 수 있다.</p>
<pre><code class="language-text">Unreal Project
│
├─ Module A
│   └─ Class A
│
├─ Module B
│   └─ Class B
│
└─ Module C</code></pre>
<p>모듈이 분리되어 있다면 어떤 클래스나 함수를 다른 모듈에서 사용할 수 있도록 내보내야 하는 상황이 생길 수 있다.</p>
<p>이때 모듈 API 지정자가 중요한 역할을 한다.</p>
<hr>
<h1 id="전체-내용-정리">전체 내용 정리</h1>
<p>이번 학습에서 가장 중요했던 내용을 연결하면 다음과 같다.</p>
<pre><code class="language-text">[Unreal C++ 코드 작성]
        │
        ├── UCLASS
        ├── UPROPERTY
        ├── UFUNCTION
        │
        ↓
[UHT가 헤더 분석]
        ↓
[리플렉션/UObject용 코드 생성]
        ↓
[UBT가 빌드 과정 관리]
        ↓
[C++ 컴파일]
        ↓
[게임 코드]</code></pre>
<p>그리고 게임 전체를 배포하는 관점에서는 다시,</p>
<pre><code class="language-text">코드 작성
   ↓
Build / Compile
   ↓
Cook
   ↓
Stage
   ↓
Package
   ↓
Deploy / Run</code></pre>
<p>와 같은 더 큰 흐름으로 이어진다.</p>
<p>결국 <code>UCLASS()</code>, <code>UPROPERTY()</code>, <code>generated.h</code> 등을 각각 외우는 것보다 중요한 것은</p>
<blockquote>
<p><strong>&quot;일반 C++ 코드를 언리얼 엔진이 이해하고 활용할 수 있도록 연결해 주는 별도의 시스템이 존재한다.&quot;</strong></p>
</blockquote>
<p>는 구조를 이해하는 것이다.</p>
<p>특히 리플렉션은 단순히 디테일 패널에 버튼이나 변수를 보여주기 위한 기능이 아니다.</p>
<p><strong>C++로 작성한 클래스와 프로퍼티, 함수 등의 정보를 Unreal Engine의 에디터와 런타임 시스템에서 활용할 수 있도록 만드는 기반 체계</strong>라고 이해하는 편이 정확하다.</p>
<hr>
<h2 id="개인-학습-메모">개인 학습 메모</h2>
<p>이번 내용을 공부하면서 <code>UCLASS</code>, <code>UPROPERTY</code>, <code>UFUNCTION</code> 등을 단순히 &quot;언리얼에서 반드시 붙여야 하는 매크로&quot;로 외우기보다 <strong>왜 필요한지를 먼저 이해하는 것이 중요하다고 느꼈다.</strong></p>
<p>앞으로 Unreal C++ 코드를 읽을 때는 다음과 같이 구분해서 살펴볼 생각이다.</p>
<ol>
<li><strong>일반 C++ 문법인가?</strong></li>
<li><strong>언리얼 리플렉션 시스템과 관련된 코드인가?</strong></li>
<li><strong>빌드 시스템과 관련된 코드인가?</strong></li>
<li><strong>모듈 경계를 처리하기 위한 코드인가?</strong></li>
</ol>
<p>이 네 가지를 구별할 수 있다면 처음 보는 Unreal C++ 헤더도 이전보다 구조적으로 읽을 수 있을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[언리얼 C++  VOD 듣기]]></title>
            <link>https://velog.io/@jupiter_0609/%EC%96%B8%EB%A6%AC%EC%96%BC-C-VOD-%EB%93%A3%EA%B8%B0</link>
            <guid>https://velog.io/@jupiter_0609/%EC%96%B8%EB%A6%AC%EC%96%BC-C-VOD-%EB%93%A3%EA%B8%B0</guid>
            <pubDate>Tue, 11 Aug 2026 14:50:44 GMT</pubDate>
            <description><![CDATA[<p>이번은 강의 주간이라 해당 강의를 읽고 작성한 각주 및 코드를 AI에 넣은 뒤, 퀴즈를 산출해 내 퀴즈를 풀어 복습했습니다.</p>
<h2 id="1단계--코드-읽기">1단계 — 코드 읽기</h2>
<h3 id="q1-생성자-찾기">Q1. 생성자 찾기</h3>
<p>다음 중 <code>AItem</code> 클래스의 <strong>생성자</strong>는 무엇입니까?</p>
<p>A. <code>BeginPlay()</code>
B. <code>AItem::AItem()</code>
C. <code>Tick()</code>
D. <code>PostInitializeComponents()</code></p>
<hr>
<h3 id="q2-루트-컴포넌트">Q2. 루트 컴포넌트</h3>
<pre><code class="language-cpp">SceneRoot = CreateDefaultSubobject&lt;USceneComponent&gt;(TEXT(&quot;SceneRoot&quot;));
SetRootComponent(SceneRoot);</code></pre>
<p>위 코드에서 <code>SceneRoot</code>가 하는 역할을 가장 잘 설명한 것은?</p>
<p>A. 액터의 부모 클래스를 지정한다.
B. 액터에 붙는 컴포넌트들의 기준점이 된다.
C. 메시를 화면에 그린다.
D. 매 프레임 위치를 계산한다.</p>
<hr>
<h3 id="q3-createdefaultsubobject">Q3. <code>CreateDefaultSubobject</code></h3>
<pre><code class="language-cpp">StaticMeshComp =
    CreateDefaultSubobject&lt;UStaticMeshComponent&gt;(TEXT(&quot;StaticMesh&quot;));</code></pre>
<p>이 코드를 일상적인 말로 가장 자연스럽게 바꿔보십시오.</p>
<blockquote>
<p><code>UStaticMeshComponent</code> 타입의 <strong>____</strong>을 만들고, 그것을 <strong>____</strong>에 저장한다.</p>
</blockquote>
<hr>
<h3 id="q4-컴포넌트-연결">Q4. 컴포넌트 연결</h3>
<pre><code class="language-cpp">StaticMeshComp-&gt;SetupAttachment(SceneRoot);</code></pre>
<p>이 코드의 의미는 무엇입니까?</p>
<p>A. StaticMeshComp를 삭제한다.
B. SceneRoot를 StaticMeshComp에 붙인다.
C. StaticMeshComp를 SceneRoot 아래에 붙인다.
D. StaticMeshComp와 SceneRoot를 같은 객체로 만든다.</p>
<hr>
<h2 id="2단계--에셋-불러오기">2단계 — 에셋 불러오기</h2>
<h3 id="q5-메시를-찾는-코드">Q5. 메시를 찾는 코드</h3>
<pre><code class="language-cpp">static ConstructorHelpers::FObjectFinder&lt;UStaticMesh&gt; MeshAsset(
    TEXT(&quot;/Game/Resources/Props/SM_Chair.SM_Chair&quot;));</code></pre>
<p>이 코드에서 실제로 찾으려는 것은 무엇입니까?</p>
<p>A. 액터
B. 머티리얼
C. 스태틱 메시 에셋
D. SceneComponent</p>
<hr>
<h3 id="q6-왜-succeeded를-확인할까요">Q6. 왜 <code>Succeeded()</code>를 확인할까요?</h3>
<pre><code class="language-cpp">if (MeshAsset.Succeeded())
{
    StaticMeshComp-&gt;SetStaticMesh(MeshAsset.Object);
}</code></pre>
<p>만약 <code>Succeeded()</code> 확인 없이 바로 <code>MeshAsset.Object</code>를 사용한다면 어떤 문제가 생길 수 있을까요?</p>
<p>짧게 한 문장으로 답해보십시오.</p>
<hr>
<h3 id="q7-머티리얼의-0">Q7. 머티리얼의 <code>0</code></h3>
<pre><code class="language-cpp">StaticMeshComp-&gt;SetMaterial(0, MaterialAsset.Object);</code></pre>
<p>여기서 <code>0</code>은 무엇을 의미할까요?</p>
<p>A. 투명도
B. 머티리얼 슬롯 번호
C. 메시 번호
D. 액터 ID</p>
<hr>
<h2 id="3단계--로그">3단계 — 로그</h2>
<h3 id="q8-로그-카테고리">Q8. 로그 카테고리</h3>
<pre><code class="language-cpp">DEFINE_LOG_CATEGORY(LogJun);</code></pre>
<p>그리고</p>
<pre><code class="language-cpp">UE_LOG(LogJun, Warning, TEXT(&quot;Hello&quot;));</code></pre>
<p>가 있다면 <code>LogJun</code>은 무엇입니까?</p>
<p>A. 출력할 문자열
B. 로그의 분류용 카테고리
C. 함수 이름
D. 액터 이름</p>
<hr>
<h3 id="q9-로그-등급-연결">Q9. 로그 등급 연결</h3>
<p>다음을 연결하십시오.</p>
<pre><code class="language-text">Display
Warning
Error</code></pre>
<p>① 일반적인 정보 출력
② 경고
③ 오류</p>
<hr>
<h3 id="q10-s와-getname">Q10. <code>%s</code>와 <code>GetName()</code></h3>
<pre><code class="language-cpp">UE_LOG(LogJun, Warning, TEXT(&quot;%s BeginPlay&quot;), *GetName());</code></pre>
<p>여기서 <code>%s</code> 자리에는 무엇이 들어갑니까?</p>
<p>A. RotationSpeed
B. 현재 액터의 이름
C. 현재 프레임
D. 메시 파일 경로</p>
<hr>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/8998b276-9077-4702-b648-ce03ca3e97de/image.png" alt=""></p>
<p>제가 쓴 답입니다.
주관식을 처참히 틀렸군요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[VOD 학습 - 언리얼 C++]]></title>
            <link>https://velog.io/@jupiter_0609/VOD-%ED%95%99%EC%8A%B5-%EC%96%B8%EB%A6%AC%EC%96%BC-C</link>
            <guid>https://velog.io/@jupiter_0609/VOD-%ED%95%99%EC%8A%B5-%EC%96%B8%EB%A6%AC%EC%96%BC-C</guid>
            <pubDate>Mon, 10 Aug 2026 14:22:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/ec95f0aa-6559-47d2-bb0a-c44a73bba279/image.png" alt="">
오늘 이걸 다 들어버리겠습니다</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/257eec49-66a4-4e74-9208-20ea1c5e965a/image.png" alt="">
소스 코드를 확실히 2022버전으로 바꾸어 줍시다. 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/949a6386-d6ea-4ed2-82b4-abe59f06f19a/image.png" alt="">
예전에 졸업작품 할 때는 건들어 보지 않은 기능입니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/267006a2-3742-4c35-8613-2488f485d601/image.png" alt=""></p>
<p>빈 프로젝트임에도 이렇게 폴더가 많은 이유는 엔진의 전체 소스도 같이 열리는 까닭이라고 하더군요. 그렇기에 추후에는 엔진 커스터마이징도 가능하다고 합니다. 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/bdb98910-d686-4a86-ae12-9343e757edf5/image.png" alt="">
이런 느낌의 코드들이군요.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/8e487731-9db5-494e-9bfa-04e6d1976194/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀 프로젝트 KPT 회고]]></title>
            <link>https://velog.io/@jupiter_0609/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@jupiter_0609/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-KPT-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Wed, 05 Aug 2026 12:48:12 GMT</pubDate>
            <description><![CDATA[<h2 id="1-팀-전체-회고">1. 팀 전체 회고</h2>
<h3 id="keep--계속-유지할-점">Keep — 계속 유지할 점</h3>
<h4 id="원활하고-적극적인-의사소통">원활하고 적극적인 의사소통</h4>
<p>팀원들이 문제와 진행 상황을 솔직하게 공유했으며, 이슈가 발생했을 때 즉시 보고하고 함께 해결했다. 요청 사항이 있을 경우 필요한 내용을 빠르게 전달했고, 요청을 받은 팀원도 신속하게 대응했다.</p>
<p>자신의 작업이 끝났을 때도 다른 팀원에게 도움이 필요한 부분이 있는지 먼저 확인하는 등 적극적으로 협업했다. 이러한 소통 방식은 프로젝트 진행 속도를 높이고, 문제를 장기간 방치하지 않도록 하는 데 도움이 되었다.</p>
<h4 id="긍정적인-팀-분위기">긍정적인 팀 분위기</h4>
<p>팀원들이 서로의 의견을 존중하고 편안하게 의견을 제시할 수 있는 분위기가 유지되었다. 단순히 맡은 기능을 구현하는 것에 그치지 않고, 팀 전체의 완성도를 높이기 위해 함께 고민하는 태도가 돋보였다.</p>
<h4 id="체계적인-기획과-문서화">체계적인 기획과 문서화</h4>
<p>기능을 바로 구현하기보다 필요한 로직을 먼저 체계적으로 정리한 후 코딩하려고 노력했다. 또한 FigJam을 활용해 기획 내용과 진행 상황을 문서화했으며, 변경 사항을 가능한 한 바로 기록했다.</p>
<p>이러한 문서화 방식은 팀원들이 프로젝트의 방향과 작업 내용을 공유하는 데 도움이 되었으므로 앞으로도 유지할 필요가 있다.</p>
<h4 id="지속적으로-다음-목표를-설정하는-태도">지속적으로 다음 목표를 설정하는 태도</h4>
<p>정해진 목표를 달성한 뒤 작업을 바로 끝내기보다, 다음 개선 목표를 설정하고 새로운 내용을 배우려는 태도를 유지했다. 단순히 기능을 완성하는 것보다 프로젝트를 통해 더 많은 것을 학습하는 데 의미를 두었다.</p>
<hr>
<h3 id="problem--개선이-필요한-점">Problem — 개선이 필요한 점</h3>
<h4 id="지나치게-촉박한-일정">지나치게 촉박한 일정</h4>
<p>일부 작업 일정이 야간 작업과 철야를 전제로 구성되었다. 그 결과 작업 후반부에 피로가 누적되었으며, 충분한 검토 없이 기능을 통합하거나 수정하는 상황이 발생했다.</p>
<p>앞으로는 예상치 못한 오류와 수정 시간을 고려해 일정을 더욱 보수적으로 설정할 필요가 있다.</p>
<h4 id="불명확한-우선순위">불명확한 우선순위</h4>
<p>필수 기능과 추가 기능의 우선순위가 명확하게 구분되지 않아, 세부 기능에 집중하는 동안 전체 구조나 핵심 기능의 안정성을 충분히 확인하지 못한 경우가 있었다.</p>
<p>프로젝트 초기에 다음과 같이 기능을 구분할 필요가 있다.</p>
<ul>
<li>반드시 구현해야 하는 핵심 기능</li>
<li>시간이 남을 경우 구현할 추가 기능</li>
<li>이후 프로젝트에서 시도할 확장 기능</li>
</ul>
<h4 id="역할과-일정-분담의-세분화-부족">역할과 일정 분담의 세분화 부족</h4>
<p>팀원별 담당 영역은 정해져 있었지만, 기능별 작업 범위와 완료 기준이 충분히 세분화되지 않았다. 이로 인해 기능 간 경계가 겹치거나 동일한 함수가 중복으로 작성되는 문제가 발생했다.</p>
<h4 id="서로의-코드에-대한-이해-부족">서로의 코드에 대한 이해 부족</h4>
<p>각자가 자신의 담당 기능을 중심으로 작업하면서 다른 팀원의 코드 구조와 로직을 충분히 이해하지 못했다. 함수의 선언, 정의, 호출부가 서로 다르거나 매개변수가 일치하지 않는 문제가 발생했으며, Merge 이후에야 오류를 발견하는 경우도 있었다.</p>
<h4 id="ai-활용-과정에서-발생한-코드-충돌">AI 활용 과정에서 발생한 코드 충돌</h4>
<p>생성형 AI를 활용해 코드를 작성하면서 기존 프로젝트의 변수명, 함수 구조, 설계 방식과 맞지 않는 코드가 추가되기도 했다. 동일한 기능이 중복 구현되거나 서로 다른 코드 작성 방식이 혼재하면서 충돌 가능성이 높아졌다.</p>
<p>문제는 AI 사용 자체보다 생성된 코드를 프로젝트 구조에 맞게 검토하고 수정하는 과정이 부족했다는 점이다.</p>
<h4 id="빌드-성공-이후의-실행-검증-부족">빌드 성공 이후의 실행 검증 부족</h4>
<p>코드가 정상적으로 빌드되더라도 실제 실행 과정에서 기능이 누락되거나, 콘솔 출력이 밀리거나, 글자와 이미지가 사라지는 문제가 발생했다.</p>
<p>빌드 성공만으로 작업이 완료되었다고 판단하지 않고, 실제 게임 흐름에 따라 기능과 UI를 끝까지 테스트할 필요가 있다.</p>
<h4 id="ui의-일관성-부족">UI의 일관성 부족</h4>
<p>초기 화면과 이후 화면 사이에 UI 디자인과 출력 방식의 통일성이 부족했다. 각 기능에서 개별적으로 출력 코드를 작성하면서 화면 구성과 간격이 서로 달라졌다.</p>
<h4 id="건강-관리-부족">건강 관리 부족</h4>
<p>프로젝트 완성도를 높이기 위해 장시간 작업했지만, 철야와 과로로 인해 작업 효율이 오히려 낮아지는 문제가 있었다. 프로젝트 일정에는 작업 시간뿐만 아니라 휴식과 컨디션 관리도 포함되어야 한다.</p>
<hr>
<h3 id="try--다음-프로젝트에서-시도할-점">Try — 다음 프로젝트에서 시도할 점</h3>
<h4 id="작업-일정을-세부적으로-작성하기">작업 일정을 세부적으로 작성하기</h4>
<p>기능 단위뿐만 아니라 설계, 구현, Merge, 테스트, 수정 단계를 나누어 일정을 작성한다. 예상하지 못한 오류에 대응할 수 있도록 별도의 여유 시간도 확보한다.</p>
<h4 id="기능의-우선순위를-명확하게-설정하기">기능의 우선순위를 명확하게 설정하기</h4>
<p>프로젝트 초기에 핵심 기능과 추가 기능을 구분한다. 핵심 기능의 구현과 안정화를 완료한 이후에 추가 기능을 개발한다.</p>
<h4 id="역할과-책임-범위를-구체적으로-정하기">역할과 책임 범위를 구체적으로 정하기</h4>
<p>담당 기능뿐만 아니라 다음 항목까지 사전에 결정한다.</p>
<ul>
<li>기능의 입력값과 반환값</li>
<li>다른 클래스와의 연결 방식</li>
<li>함수와 변수의 명명 규칙</li>
<li>담당자가 수정할 수 있는 파일 범위</li>
<li>기능의 완료 기준</li>
<li>테스트 방법</li>
</ul>
<h4 id="정기적인-스크럼-진행하기">정기적인 스크럼 진행하기</h4>
<p>팀 분위기나 개인적인 친분에 의존하지 않고, 정해진 시간에 스크럼을 진행한다.</p>
<p>스크럼에서는 다음 내용을 공유한다.</p>
<ol>
<li>어제 완료한 작업</li>
<li>오늘 진행할 작업</li>
<li>현재 발생한 문제</li>
<li>다른 팀원의 확인이나 도움이 필요한 사항</li>
<li>Merge 예정 파일과 변경된 인터페이스</li>
</ol>
<h4 id="코드-의도를-명확하게-표현하기">코드 의도를 명확하게 표현하기</h4>
<p>다른 팀원이 코드를 이해할 수 있도록 함수명과 변수명을 명확하게 작성하고, 복잡한 로직에는 필요한 주석을 추가한다.</p>
<p>단순히 코드의 동작을 설명하는 주석보다 해당 구조를 선택한 이유와 주의해야 할 사항을 기록한다.</p>
<h4 id="ai-코드를-프로젝트-규칙에-맞게-검토하기">AI 코드를 프로젝트 규칙에 맞게 검토하기</h4>
<p>AI 사용을 무조건 줄이기보다 생성된 코드를 다음 기준으로 검토한다.</p>
<ul>
<li>기존 클래스 구조와 일치하는가?</li>
<li>동일한 기능이 이미 존재하지 않는가?</li>
<li>함수명과 변수명 규칙을 따르는가?</li>
<li>불필요한 매개변수나 include가 추가되지 않았는가?</li>
<li>다른 팀원의 코드와 충돌할 가능성이 없는가?</li>
<li>코드의 동작 원리를 직접 설명할 수 있는가?</li>
</ul>
<h4 id="브랜치와-merge-절차-개선하기">브랜치와 Merge 절차 개선하기</h4>
<p>기능별 브랜치를 적극적으로 활용하고, 정상적으로 작동하는 시점마다 커밋을 남겨 복구 지점을 만든다.</p>
<p>메인 브랜치에서 직접 문제를 해결하기보다 각 팀원이 자신의 브랜치에서 최신 메인 브랜치를 반영하고 충돌을 먼저 해결한 후, 테스트가 완료된 코드를 통합하는 방식을 사용한다.</p>
<h4 id="공통-기능을-초기에-제작하기">공통 기능을 초기에 제작하기</h4>
<p>UI 출력, 커서 이동, 아스키아트 출력 등 여러 기능에서 반복적으로 사용하는 코드는 프로젝트 초기에 공통 클래스로 제작한다.</p>
<p>공통 함수를 배포할 때는 함수의 역할, 매개변수, 반환값, 사용 예시를 함께 문서화한다.</p>
<h4 id="실행-테스트-체크리스트-만들기">실행 테스트 체크리스트 만들기</h4>
<p>Merge 이후에는 다음 내용을 확인한다.</p>
<ul>
<li>전체 프로젝트가 정상적으로 빌드되는가?</li>
<li>게임 시작부터 종료까지 진행할 수 있는가?</li>
<li>경험치와 레벨업이 정상적으로 적용되는가?</li>
<li>전투 결과와 보상이 정상적으로 반영되는가?</li>
<li>콘솔 화면이 밀리거나 잘리는 부분은 없는가?</li>
<li>한글과 특수문자가 깨지지 않는가?</li>
<li>아스키아트가 정상적인 위치에 출력되는가?</li>
<li>입력 대기와 화면 전환이 정상적으로 작동하는가?</li>
</ul>
<h4 id="철야를-전제로-하지-않는-일정-운영">철야를 전제로 하지 않는 일정 운영</h4>
<p>주어진 시간 안에서 구현할 수 있는 최적의 완성도를 목표로 한다. 일정이 부족할 경우 핵심 기능을 우선 완성하고, 추가 기능의 범위를 조정한다.</p>
<hr>
<h3 id="feel--프로젝트를-마친-소감">Feel — 프로젝트를 마친 소감</h3>
<p>과로한 부분은 있었지만 프로젝트 자체는 매우 즐거웠다. 팀원들과 함께 오류를 해결하고 하나의 게임을 끝까지 완성하는 과정에서 협업과 통합의 중요성을 배울 수 있었다.</p>
<p>특히 기능이 개별적으로 작동하는 것과 여러 기능이 결합된 하나의 완성된 게임으로 보이는 것은 다르다는 점을 체감했다. Merge와 구조 설계 과정에서 어려움이 많았지만, 직접 문제를 발견하고 해결한 경험이 이후 프로젝트를 진행할 수 있는 자신감으로 남았다.</p>
<hr>
<h1 id="2-개인별-회고">2. 개인별 회고</h1>
<h2 id="주소리본인--ui-및-프로젝트-통합">주소리(본인) — UI 및 프로젝트 통합</h2>
<h3 id="keep">Keep</h3>
<p>FigJam을 활용해 기획 내용과 변경 사항을 바로 문서화한 점은 효과적이었다. 팀원 간 의사소통도 원활했으며, 문제 발생 시 즉시 공유하고 함께 해결할 수 있었다.</p>
<h3 id="problem">Problem</h3>
<p>UI는 초기 기획 없이 구현을 시작할 경우 화면별 출력 방식이 달라지고 수정 범위가 커질 수 있다는 점을 확인했다. 또한 아스키아트와 한글 출력을 사용하면서 유니코드와 문자 인코딩 관련 문제가 발생했다.</p>
<p>의사소통이 원활한 팀원들과 함께했기 때문에 별도의 고정 스크럼 없이도 프로젝트를 진행할 수 있었지만, 모든 팀에서 동일한 방식이 가능하지는 않다. 따라서 개인적인 소통 능력이나 팀 분위기에 의존하지 않는 공식적인 소통 체계가 필요하다.</p>
<p>파일 충돌을 메인 브랜치에서 직접 해결하는 과정에서도 불필요한 수정과 재작업이 발생했다. 또한 작업 우선순위를 충분히 협의하지 않아 후반부에 작업이 집중되었고, 철야가 발생했다.</p>
<h3 id="try">Try</h3>
<p>다음 프로젝트에서는 전체 UI 레이아웃과 화면 전환 방식을 초기에 설계한다. 유니코드, 콘솔 문자 집합, 파일 인코딩 방식도 프로젝트 시작 단계에서 통일한다.</p>
<p>아스키아트 출력, 박스 그리기, 커서 이동과 같이 반복적으로 사용하는 함수는 초기에 제작한 후 사용법과 함께 팀원들에게 배포한다.</p>
<p>정해진 시간에 스크럼을 진행하고, 작업 시작 전에 기능의 우선순위를 팀원들과 협의한다. Merge 전에는 각 팀원이 자신의 브랜치에서 최신 메인 브랜치를 반영하고 충돌을 해결한 뒤, 정상 작동을 확인한 코드를 통합한다.</p>
<p>철야를 줄이고 주어진 시간 안에서 구현할 수 있는 최적의 완성도를 만드는 것을 목표로 한다.</p>
<hr>
<h2 id="이영빈--플레이어-시스템-구조">이영빈 — 플레이어 시스템 구조</h2>
<h3 id="keep-1">Keep</h3>
<p>플레이어와 관련된 기능을 하나의 관리 구조로 통합하려고 고민한 점은 향후 유지보수성과 확장성을 높일 수 있는 방향이었다.</p>
<h3 id="problem-1">Problem</h3>
<p>플레이어 클래스에 상태 데이터와 관련 기능이 과도하게 집중될 경우 클래스의 책임이 지나치게 커질 수 있다. 여러 기능이 플레이어 객체에 직접 접근하면서 코드가 여러 방향으로 뻗는 구조가 만들어질 가능성도 있었다.</p>
<p>브랜치를 충분히 활용하지 않을 경우 기능 수정 과정에서 기존 코드가 손상되거나 복구가 어려워질 수 있다.</p>
<h3 id="try-1">Try</h3>
<p>플레이어 클래스는 플레이어 객체가 직접 보유해야 하는 데이터와 기본 동작을 담당하도록 구성한다. 플레이어의 상태 출력, 경험치 처리, 장비 적용과 같은 관리 기능은 별도의 <code>PlayerManager</code>에서 담당하는 구조를 검토한다.</p>
<p>각 시스템이 서로 직접 참조하는 구조를 줄이고, 시스템 사이의 요청을 중계하는 매니저나 인터페이스를 설계한다.</p>
<p>기능별 브랜치를 적극적으로 활용하고, 하나의 함수에 여러 책임이 집중되는 문어발식 코드를 줄인다.</p>
<p>아트, 음악 등 제작 비용이 큰 비프로그래밍 리소스에는 생성형 AI를 적극적으로 활용하되, 저작권과 사용 조건을 확인하고 프로젝트의 전체 콘셉트에 맞게 수정한다.</p>
<hr>
<h2 id="정윤재--인벤토리·상점·강화·제작-시스템-및-게임-기획">정윤재 — 인벤토리·상점·강화·제작 시스템 및 게임 기획</h2>
<h3 id="keep-2">Keep</h3>
<p>인벤토리, 상점, 강화소, 제작소 등 여러 시스템을 연결하면서 기능 간 데이터 전달과 클래스 의존 관계를 직접 경험했다.</p>
<p>게임의 핵심 메시지를 ‘코더여, 성장하라’로 설정하고, 스토리와 시스템이 동일한 메시지를 전달하도록 구성하려고 한 점도 의미가 있었다. 이는 광고, 홍보, 게임 콘텐츠 등 여러 요소가 일관된 메시지를 전달하는 IMC 관점과도 연결할 수 있다.</p>
<h3 id="problem-2">Problem</h3>
<p>함수의 매개변수가 <code>Player* player</code>, <code>Inventory&amp; inventory</code>와 같이 여러 개로 늘어나면서 다른 클래스와 파일을 참조해야 하는 상황이 많아졌다. 이 과정에서 include 누락, 전방 선언, 타입 불일치 등 여러 오류가 발생했다.</p>
<p>기존 템플릿 인벤토리를 설계할 때 장착 아이템까지 충분히 고려하지 않아 이후 장비창과 전용 장비 인벤토리를 별도로 구현해야 했다. 초기 설계가 부족해 기능이 추가될수록 구조가 복잡해졌다.</p>
<p>현재 게임의 주요 소재가 내일배움캠프 언리얼 10기 구성원에게 맞춰져 있어, 다른 사용자가 게임을 플레이할 경우 상황과 유머에 공감하기 어렵다는 한계가 있다.</p>
<h3 id="try-2">Try</h3>
<p>함수를 작성하기 전에 반드시 필요한 매개변수인지 검토하고, 여러 객체를 반복적으로 전달해야 한다면 해당 객체를 관리하는 상위 매니저나 컨텍스트 구조를 도입하는 방안을 고려한다.</p>
<p>인벤토리를 설계할 때 일반 아이템, 소비 아이템, 장착 아이템, 장착 슬롯, 강화와 제작 기능까지 확장될 가능성을 먼저 검토한다. 세부 기능을 구현하기 전에 전체 클래스 구조와 데이터 흐름을 먼저 설계한다.</p>
<p>게임의 소비층을 특정 교육 과정 참여자뿐만 아니라 코딩을 처음 배우는 사용자 전체로 확장한다. 이를 위해 특정 기수나 내부 상황에만 의존하는 표현을 줄이고, 초보 개발자라면 공감할 수 있는 오류, 과제, 디버깅, 성장 경험을 중심으로 스토리를 개편한다.</p>
<hr>
<h2 id="박성빈--전투-시스템-및-브랜치-관리">박성빈 — 전투 시스템 및 브랜치 관리</h2>
<h3 id="keep-3">Keep</h3>
<p>전투 시스템에 새로운 기능을 추가할 때 GitHub Desktop에서 별도의 브랜치를 생성하고, 정상적으로 작동하는 시점마다 커밋을 남겨 복구 지점을 만든 점이 효과적이었다.</p>
<p>오류가 발생하더라도 이전의 정상 상태로 쉽게 돌아갈 수 있어 수정에 대한 부담을 줄일 수 있었다.</p>
<h3 id="try-3">Try</h3>
<p>앞으로도 기능별 브랜치를 사용하고, 하나의 기능이 정상적으로 작동하는 시점마다 의미 있는 커밋을 남긴다.</p>
<p>커밋 메시지에는 변경한 기능과 확인해야 할 사항을 구체적으로 작성하고, Merge 전에는 해당 브랜치에서 빌드와 실행 테스트를 완료한다.</p>
<hr>
<h2 id="장우혁--경험치-및-레벨업-시스템">장우혁 — 경험치 및 레벨업 시스템</h2>
<h3 id="keep-4">Keep</h3>
<p><code>GainExp</code> 함수에서 외부로부터 전달받은 경험치를 현재 경험치에 누적하고, <code>while</code>문을 사용해 한 번에 여러 레벨이 상승하는 경우에도 레벨업이 누락되지 않도록 구현했다.</p>
<p>또한 클래스 간 순환 참조를 방지하기 위해 전방 선언을 활용했다.</p>
<p>콘솔 출력이 너무 빠르게 넘어가 확인하기 어려운 경우 입력 대기나 일시정지 기능을 사용해 출력 내용을 확인할 수 있도록 한 시도도 효과적이었다.</p>
<h3 id="problem-3">Problem</h3>
<p>Merge 과정에서 경험치와 레벨업 관련 코드가 일부 누락되거나 연결이 변경되면서 프로젝트는 빌드되지만 실제 게임에서 레벨업 메시지나 기능이 나타나지 않는 문제가 발생했다.</p>
<p>오류를 수정한 뒤 빌드 성공 여부만 확인하고 실제 게임 흐름을 충분히 테스트하지 않아 문제를 늦게 발견했다. 이후 게임을 처음부터 반복 실행하며 경험치 획득과 레벨업 과정을 하나씩 확인해야 했다.</p>
<h3 id="try-4">Try</h3>
<p>Merge 전후에 경험치와 레벨업 관련 함수의 선언, 정의, 호출부가 모두 정상적으로 연결되어 있는지 확인한다.</p>
<p>오류를 수정한 뒤에는 반드시 다시 빌드하고, 게임을 직접 실행해 다음 사항을 검증한다.</p>
<ul>
<li>경험치가 정상적으로 누적되는가?</li>
<li>한 번에 여러 레벨이 상승하는가?</li>
<li>레벨업 보상이 정상적으로 적용되는가?</li>
<li>레벨업 메시지가 화면에 출력되는가?</li>
<li>콘솔 출력이 밀리거나 사라지지 않는가?</li>
<li>한글과 아스키아트가 깨지지 않는가?</li>
</ul>
<p>빌드 성공은 문법과 연결 오류가 없다는 의미일 뿐, 기능이 정상적으로 작동한다는 의미는 아니라는 점을 항상 인식한다.</p>
<h3 id="feel">Feel</h3>
<p>이번 프로젝트를 통해 Merge 과정에서 예상보다 많은 충돌과 기능 누락이 발생할 수 있다는 점을 체감했다. 오류를 모두 수정한 것처럼 보여도 실제 실행 과정에서는 추가 문제가 나타날 수 있으므로, 빌드 이후의 플레이 테스트가 반드시 필요하다는 것을 배웠다.</p>
<hr>
<h2 id="이미르--몬스터-및-전투-시스템">이미르 — 몬스터 및 전투 시스템</h2>
<h3 id="keep-5">Keep</h3>
<p>몬스터 시스템을 처음부터 끝까지 구현하고 지속적으로 수정했다. 정예 몬스터가 각 챕터에서 한 번씩만 등장하도록 조건을 구성했으며, 최종 보스전을 두 명의 매니저와 보스가 대결하는 2대1 전투 구조로 구현했다.</p>
<p>팀원들과 적극적으로 소통하며 오류를 함께 해결했고, 발표를 통해 프로젝트와 자신이 구현한 기능에 대한 자신감을 얻었다.</p>
<h3 id="problem-4">Problem</h3>
<p>Merge 과정에서 충돌과 오류가 많이 발생했다. 함수의 선언, 정의, 호출부에서 매개변수가 서로 일치하지 않는 문제가 있었으며, 동일한 함수가 중복으로 작성되어 오류가 발생하기도 했다.</p>
<p>시작 화면과 이후 화면의 UI가 통일되지 않았으며, 세부 기능 구현에 집중한 나머지 전체 구조를 충분히 기획하지 못했다.</p>
<h3 id="try-5">Try</h3>
<p>다음 프로젝트에서는 세부 기능을 구현하기 전에 전체 클래스 구조와 데이터 흐름을 먼저 정리한다.</p>
<p>함수의 매개변수를 최소화하고, 반복적으로 전달되는 데이터는 매니저나 별도의 데이터 구조를 통해 관리한다.</p>
<p>UI 출력 기능을 별도의 클래스로 분리하고, 모든 화면이 동일한 규칙으로 출력되도록 구성한다.</p>
<p>변수명과 함수명 규칙을 사전에 정하는 것에 그치지 않고, 코드 리뷰 과정에서 실제로 규칙이 지켜지고 있는지 확인한다.</p>
<p>전투 시스템을 확장할 경우 다음과 같은 기능도 시도한다.</p>
<ul>
<li>파티 시스템</li>
<li>공격 빗나감과 명중률</li>
<li>상태 이상</li>
<li>캐릭터별 역할 구분</li>
<li>행동 순서와 우선순위</li>
<li>자원 관리가 필요한 전략적 전투</li>
</ul>
<h3 id="feel-1">Feel</h3>
<p>이번 프로젝트를 통해 기능이 정상적으로 작동하는 것과 게임이 완성도 있게 보이는 것은 서로 다른 문제라는 점을 느꼈다.</p>
<p>초보자로서 Merge와 전체 구조 설계가 어려웠지만, 실제로 오류를 경험하고 직접 해결하면서 많은 것을 배울 수 있었다. 팀원들과 소통하며 프로젝트를 끝까지 완성한 경험이 다음 프로젝트에 도전할 수 있는 자신감으로 남았다.</p>
<hr>
<h1 id="3-종합-결론">3. 종합 결론</h1>
<p>이번 프로젝트의 가장 큰 강점은 적극적인 의사소통과 문제 해결 태도였다. 팀원들은 자신의 담당 기능에만 머무르지 않고, 문제가 발생하면 함께 원인을 찾고 해결했다.</p>
<p>반면 가장 큰 개선 과제는 초기 구조 설계, 일정 관리, 코드 통합 절차였다. 개별 기능은 정상적으로 구현되었지만, 여러 기능을 하나의 프로젝트로 통합하는 과정에서 클래스 의존성, 함수 인터페이스, UI 일관성, 실행 테스트 문제가 나타났다.</p>
<p>다음 프로젝트에서는 다음 원칙을 우선적으로 적용한다.</p>
<ol>
<li>구현 전에 전체 구조와 데이터 흐름을 먼저 설계한다.</li>
<li>핵심 기능과 추가 기능의 우선순위를 구분한다.</li>
<li>일정에 Merge, 테스트, 수정 시간을 별도로 포함한다.</li>
<li>정기적인 스크럼을 통해 변경 사항과 문제를 공유한다.</li>
<li>공통 함수와 코딩 규칙을 초기에 확정한다.</li>
<li>AI가 작성한 코드도 팀의 구조와 규칙에 맞게 직접 검토한다.</li>
<li>Merge 전후에 빌드뿐만 아니라 실제 플레이 테스트를 진행한다.</li>
<li>철야를 전제로 하지 않고 지속 가능한 작업 범위를 설정한다.</li>
</ol>
<p>이번 프로젝트는 단순히 게임 기능을 구현한 경험을 넘어, 협업 개발에서 설계와 소통, 버전 관리, 통합 테스트가 얼마나 중요한지를 학습한 프로젝트였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[충돌연구소]]></title>
            <link>https://velog.io/@jupiter_0609/%EC%B6%A9%EB%8F%8C%EC%97%B0%EA%B5%AC%EC%86%8C</link>
            <guid>https://velog.io/@jupiter_0609/%EC%B6%A9%EB%8F%8C%EC%97%B0%EA%B5%AC%EC%86%8C</guid>
            <pubDate>Tue, 04 Aug 2026 11:58:48 GMT</pubDate>
            <description><![CDATA[<p>사실 그렇게 대단한 건 아니지만 필요한 일입니다.
제 능력에 가당치 않게 멋진 임무를 하게 되었는데요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/88b0a512-6bc8-44b3-a865-d2f53c0b325f/image.png" alt="">
그건 바로 최종 파일을 머지하며 취사선택하는 일이지요.
어떻게 하는 거야, 이거. 그래도 옳아 보이는 걸 선택하며 작업한 팀원분들께 물어보며 이모저모 나아가봅니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/bb6ec2d0-7013-4157-bd29-69ad0f02443a/image.png" alt="">
난리도 아닙니다.
그래도 어떻게 머지 완료.</p>
<p>유니코드 관련  오류도 종종 보이네요. 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/e83e63ad-dbbf-4e62-9bf2-652f8e0b0b1b/image.png" alt="">
어떤 멋진 분께서 한번에 인코딩 설정을 바꾸는 법을 알려주셨습니다. 전 왜 하나 하나 바꾸었을까요? 이제라도 알게되어 다행인 일입니다.</p>
<p>그래도 오류가 몇 개 뜹니다마는
<img src="https://velog.velcdn.com/images/jupiter_0609/post/56f021ca-a1f2-4f90-8bac-046cf8a0e225/image.png" alt="">
141개 보다가 24개 보니 천사같군요.</p>
<p>근데 뭐 보통 하나 해결되면 주르륵 해결되는 경우도 있던데 그걸 바라봅니다.
라고 생각하던 찰나 구원투수가 커밋을 해줍니다.</p>
<p>대부분의 오류가 해결되었고
<img src="https://velog.velcdn.com/images/jupiter_0609/post/be6bfb0e-7e40-4c05-bcbd-aacba024df91/image.png" alt="">
고난과 역경란에 한 획을 그었습니다.</p>
<p>빌드가 됩니다!
아주 잘된 일입니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/8ff2ebd6-a21d-4b33-8fe2-f2ff39a2a47e/image.png" alt="">
짐만 실으면 끝입니다. 정말로.
UI 작업 레츠고.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[유니코드 오류]]></title>
            <link>https://velog.io/@jupiter_0609/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C-%EC%98%A4%EB%A5%98</link>
            <guid>https://velog.io/@jupiter_0609/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C-%EC%98%A4%EB%A5%98</guid>
            <pubDate>Mon, 03 Aug 2026 13:42:34 GMT</pubDate>
            <description><![CDATA[<p>아스키아트를 위한 매니저 만들기</p>
<p>아스키아트는 줄바꿈이나 간격으로 인해 발생하는 오류가 잦습니다.
따라서 유지보수를 위해 아트를 따로 txt파일로 분리하고, 이걸 읽게 하기 위한 매니저를 만드는 게 좋겠습니다. 이렇게 할 경우 아트 변경 시 메모장만 변경하면 됩니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/6c425dde-5944-48d6-9a70-b6042b0391c3/image.png" alt="">
일단 공유문서를 먼저 배포해버립시다.
이제 만들기만 하면 됩니다. 할 수 있겠지?</p>
<p>물론 아직 코드 창조를 하기엔 제게 인풋된 정보가 없으므로 AI에게 얼개를 요청하고 채워나갔습니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/5c6391b5-180b-4cee-9860-568a6a8aa6f7/image.png" alt="">
우선 헤더부터.
텍스트 파일을 여는 함수를 멋있게 선언해 줍니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/a9489d54-6921-41d8-9d53-4675d279f1d4/image.png" alt="">
구차할 정도로 각주를 달아줍시다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/7d915f9c-d3eb-45e2-8885-0dc6b8f67014/image.png" alt="">
그리고 메인에 넣어 파일 경로가 잘 되어 있는지 확인합시다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/983af3d1-84a2-483a-972c-303f56234883/image.png" alt="">
경로는 문제가 없지만 제가 선택하지 않은 멋진 글씨가 나열됩니다.</p>
<p>그래요, 소스 파일 저장 인코딩(UTF-8 or CP949)과 콘솔의 코드페이지가 달라 생기는 문제라는군요.
아직 나약한 저로서는 매일 매일이 새롭고 놀라운 오류들입니다. 각 잡고 관련 설정 공부 해야겠습니다만,
일단 급한 마감부터 하죠.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/78ec3682-bbe0-4c5b-9409-50c99acba8bc/image.png" alt="">
유니코드는 네게 아직 이르다는 말을 AI에게 듣고 할수 있는 일을 하기로 합니다.
비주얼 스튜디오 2022에는 고급 저장 옵션이 기본세팅되어 있지 않으므로 사용자 지정에서 고급 설정을 불러옵니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/aa945c33-9cb2-4e53-ad77-ab692acd6c60/image.png" alt="">
경로 찾기가 어렵네요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/42976f4c-d1e3-43b3-9d88-8cea08409acb/image.png" alt="">
찾았다.</p>
<p>모든 cpp파일을 이렇게 저장해주면 된다고 하는군요. 하하.
모든... 파일을...
비합리적이다. 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/2b7fbc8c-3c29-4625-8f7d-3af9753e119f/image.png" alt="">
?
과거로 돌아가야 할까요.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/338c7085-8585-4164-a515-44e7d977d7a7/image.png" alt=""></p>
<p>해치웠나?
라고 생각했는데 경고가 2천개 뜨더군요. 
위의 구성과 플랫폼을 &#39;모든&#39;으로 바꾸면
<img src="https://velog.velcdn.com/images/jupiter_0609/post/bec8f115-5b91-476f-8a45-195ff4789ca0/image.png" alt="">
2개만 남습니다.
전 짱입니다.</p>
<p>저 두개도 그냥 제가 확인 못한 파일을 UTF-8로 바꿔주지 않아 생긴 문제였습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/d7955ea6-2896-4b60-bbb8-05e02f6260ed/image.png" alt="">
해결해결!!</p>
<p>다음엔 그냥 유니코드도 도전해보지요.
일단 나오긴 하지만 약간의 틀어짐이 보입니다.
<a href="https://github.com/naver/d2-coding-font">https://github.com/naver/d2-coding-font</a>
여기서 폰트를 설치해보지요.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[파일 정리 및 로그 속도 관리]]></title>
            <link>https://velog.io/@jupiter_0609/%EC%9E%98%EB%AA%BB%EB%90%9C-%EB%A7%8C%EB%82%A8merge</link>
            <guid>https://velog.io/@jupiter_0609/%EC%9E%98%EB%AA%BB%EB%90%9C-%EB%A7%8C%EB%82%A8merge</guid>
            <pubDate>Fri, 31 Jul 2026 13:35:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/5e3e60bc-1504-41da-a69c-aa68963fe537/image.png" alt="">
이제 저 비어있는 함수를 채워야 합니다.
위에 멤버변수가 있으므로 활용을 해야 합니다만...</p>
<p>사용해야하는 system() 은 int를 직접 해석하지 못한다고 합니다. 고로 숫자 120을 문자열로 변경해야 합니다.
문자열 명령어가 들어가야 하는 부분입니다.</p>
<p>근데 이거 뭔가 아마추어인 제가 보기에도 못생기고 투박하지 않나, 하는 생각이 듭니다. <strong>좀 더 깔끔한 방법</strong>이 없나...</p>
<p>일단은 추후에 수정해보기로 하고
급한 일부터 해봅시다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/830786c3-cf55-4215-844e-6e8e9a3d243b/image.png" alt="">
우선 매인을 시원하게 밀었습니다. </p>
<p>이것만 테스트를 해보고 싶은데 현재 다른 파일이 꼬여 있어 실행 오류가 뜨는군요.</p>
<p>테스트 파일을 팝시다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/5a0cc903-70ca-4d3c-bd64-3527a0db4d83/image.png" alt=""></p>
<p>좋아요. 잘 작동 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/25052f2d-2161-4015-9ecb-c579124c23bd/image.png" alt="">
좌표 변경도 잘 되는 걸 볼 수 있습니다. 뿌듯한 일입니다.</p>
<p>그리고 텍스트가 너무 빠르게 나오는 듯 해서 Slow_Print 함수를 추가했습니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/31242fbc-6649-468c-9baf-2f926ec2fa85/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/241e6c91-f441-4211-8be1-26603a1a0d9d/image.png" alt="">
각주도 잘 달아준 뒤 팀원분들께 안내도 드렸습니다. 야호.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/7f3b865e-31f6-47d9-a797-f976b435e5e3/image.png" alt="">
이런식으로 나오는 걸 볼 수 있군요.
상태창 같은 걸 제외하고 스토리라인은 이렇게 연출하는 게 좋아보입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[첫만남은 너무 어려워(feat, Branch merge하기)]]></title>
            <link>https://velog.io/@jupiter_0609/%EC%B2%AB%EB%A7%8C%EB%82%A8%EC%9D%80-%EB%84%88%EB%AC%B4-%EC%96%B4%EB%A0%A4%EC%9B%8Cfeat-Branch-merge%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jupiter_0609/%EC%B2%AB%EB%A7%8C%EB%82%A8%EC%9D%80-%EB%84%88%EB%AC%B4-%EC%96%B4%EB%A0%A4%EC%9B%8Cfeat-Branch-merge%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 30 Jul 2026 14:52:52 GMT</pubDate>
            <description><![CDATA[<h3 id="유지보수가-쉽게-아스키-아트를-넣는-법을-연구해보기"><strong>유지보수가 쉽게 아스키 아트를 넣는 법을 연구해보기</strong></h3>
<p>텍스트로만 이루어진 아트의 유지보수가 쉽기 위해선 어떻게 해야 할까요?
우선 <strong>초기 콘솔 세팅</strong>이 중요할 겁니다.</p>
<blockquote>
<p>콘솔 크기 고정
화면 지우기
커서 이동
아스키 출력
입력 대기</p>
</blockquote>
<p>고로 이것부터 한 뒤 커밋하고 아트를 넣는 게 좋은 방향일 것으로 판단됩니다. 레츠고.</p>
<p>콘솔 크기를 고정하기 위해서는 필요한 헤더 파일이 있습니다.</p>
<blockquote>
<p>#include &lt;windows.h&gt;</p>
</blockquote>
<ul>
<li>Windows.h는 콘솔 API를 사용하기 위한 헤더로, 콘솔 화면 제어를 위해 필요합니다.</li>
</ul>
<p>우선 임시로 크기를 설정해줍시다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/3eac0517-229c-4a8d-8323-7bbb03cd7b17/image.png" alt="">
여기서
cols : 가로 문자 개수
lines : 세로 줄 개수
입니다.</p>
<p>보통 이 사이즈면 아트가 들어가기에 적합하다고 합니다만
추후에 생성할 아트에 맞추어 수정하기로 합니다.
다만 진짜 API를 이용하는 게 아니라 흉내만 내는 거라고 하더군요.
필요하면 나중에 Windows API로 실제 화면 크기 대응 개선하는 게 맞다고 합니다만...</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/bae6b25e-3fcc-4a35-b9b8-a95a34e7fb78/image.png" alt=""></p>
<p>라는 의문이 생기지 않을 수가 없군요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/13d3f139-d813-4183-add4-5a6d4b609855/image.png" alt="">
지금으로선 필요 없지만 추후에는 필요한 듯 하네요.
우선 의문은 일단락 하고 마저 진행하겠습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/a09d75f8-36dc-4ebc-949e-8a1ae701bb70/image.png" alt="">
잊지 말고 커밋.</p>
<p>그렇지만 생각해보니 클래스로 관리하는 게 협업 특성상 더 적합지 않나 하는 생각이 들어 급히 콘솔 매니저를 만들었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/a2473df5-6ae5-4cdb-af65-c7cf652c8517/image.png" alt="">
공유 페이지에 추가도 하고
<img src="https://velog.velcdn.com/images/jupiter_0609/post/51385f89-a8bc-4c3f-9787-8c87edf25451/image.png" alt="">
클래스 목록에도 추가 했습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/fb1e6327-1b7d-4269-8332-4567a8ee142a/image.png" alt="">
처음 만들어보는 애다 보니 각주를 열심히 달아주었습니다. 나중에 팀원분들께도 보시기 편하시면 좋겠군요.
혹은 저의 아마추어미에 놀라실지도. 하하.</p>
<p>아무튼, 이후에 작업한 branch를 merge하게 되었습니다. 만...
고난과 역경이 끊이지 않아 피그잼에 고난과 역경란을 만들었습니다. 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/1239e9b1-ca44-481d-861a-54e1f347a200/image.png" alt="">
더불어 일 하다가 팀원에게 무언가를 요청할 수 있는 페이지도 팠습니다. 피그잼은 정말 좋은 툴입니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/3b410798-a092-4ebb-a1ee-dc4be7e2dded/image.png" alt=""></p>
<p>아무튼, 이후 폭탄을 해제했다고 믿을 찰나, 파일을 열어보니
<img src="https://velog.velcdn.com/images/jupiter_0609/post/32dd9f64-ba97-4bec-975b-971695dc0268/image.png" alt="">
작업한 파일이 보이지 않습니다. 하하.</p>
<p>어제 나름대로 <strong>Git-boom</strong>이란 레포지터리를 파가며 연습했습니다만
실전에서 생기는 문제는 역시 있군요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/3db03026-6116-41fa-bb83-14270c429a4e/image.png" alt=""></p>
<p>그렇지만 완성하고야 말겠습니다.
저를 죽이지 못한 오류는 절 더 강하게 해줄 뿐입니다.</p>
<p>...그러길 바랍니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[협업 셋업하기]]></title>
            <link>https://velog.io/@jupiter_0609/%ED%98%91%EC%97%85-%EC%85%8B%EC%97%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jupiter_0609/%ED%98%91%EC%97%85-%EC%85%8B%EC%97%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 29 Jul 2026 14:27:56 GMT</pubDate>
            <description><![CDATA[<p>협업에 있어 가장 중요한 건?
원활한 소통입니다.
손발이 맞아야 개인의 능력이 빛날 수 있습니다. 
<br> 운 좋게도 일정을 조율하는 데 효과적인 툴을 알고 있어 
이번 기회에 적극적으로 사용해봤습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/0b51c8a3-3bd4-4c62-9751-f15c94dc321f/image.png" alt=""></p>
<p>필수 과제를 개인의 역량 및 선호에 맞춰 역할을 분배하고
비교적 난이도가 낮은 경우 디벨롭 방향을 논의 했습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/31b59409-ee43-4d0d-b18d-ad030d088a0b/image.png" alt="">
이후 코드 규약을 논의 및 정돈해 확정 짓고
<img src="https://velog.velcdn.com/images/jupiter_0609/post/6360448e-c9c4-421e-baa6-973b5c0558eb/image.png" alt="">
<img src="https://velog.velcdn.com/images/jupiter_0609/post/474f9774-9721-42d6-a3e1-4e9a75212c3b/image.png" alt="">
공통 일정의 얼개를 공유했으며 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/3913dc9f-ed99-45ec-b74e-5d85cda3abb6/image.png" alt="">
<img src="https://velog.velcdn.com/images/jupiter_0609/post/c155bbe3-c6e5-41f3-b399-d069ce62eb44/image.png" alt="">
간단하게 로직을 작성했습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/66839869-7237-4f83-b5b4-3f20c98a2029/image.png" alt="">
코멘트 또한 자유롭게 달며 기획과 규약을 정돈할 수 있었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Text RPG 제작하기(9)]]></title>
            <link>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B09-daf7jrim</link>
            <guid>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B09-daf7jrim</guid>
            <pubDate>Tue, 28 Jul 2026 06:45:38 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/d8100bda-7efd-4462-a64a-078faaa9823e/image.png" alt="">
처음에 입력한 닉네임과 
<img src="https://velog.velcdn.com/images/jupiter_0609/post/f9b37cbb-9178-48a2-8d74-c821a3312aaa/image.png" alt=""></p>
<p>추후에 불러오는 닉네임이 다릅니다.
당연합니다.</p>
<p>다른 객체를 넣었으니 말이지요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/2c293ef2-d534-4d0b-9f89-6113ee0091d2/image.png" alt="">
이렇게 넣으면 뜰 듯 하네요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/5425284d-b79a-4dab-a782-aa5419890d59/image.png" alt="">
해결.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/1a201092-9226-4642-9b8a-002c977d9d3c/image.png" alt="">
이제 가상함수를 쓸 생각에 가슴이 떨립니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/e7b52013-b2c9-48a7-b884-7db8210e359e/image.png" alt=""></p>
<p>정말 바로 에러가 뜨는군요. 하긴 그냥 해도 나는 에러이니, 확정적으로 난다고 했으면 날만 합니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/0aec4ce3-67bf-43cd-93dc-573cf97138e6/image.png" alt="">
그래요. 일단 Player는 버츄얼이 되어버렸습니다. 이제 이걸 상속받을 전사를 만들어 줍시다.</p>
<p>그 이전에 아주 중요한 걸 알았습니다. 항상 헤더 파일을 생상할 때마다 마주하던 친구입니다.</p>
<blockquote>
<p><strong>#pragma once</strong> : 중복 포함 방지 장치</p>
</blockquote>
<ul>
<li>헤더 파일이 여러 번 include되어도 문제가 생기지 않게 막는 장치가 필요</li>
<li>같은 설계도를 여러 번 책상 위에 올려도, 실제로는 한 번만 참고하게 하는 표시</li>
</ul>
<p>라고 합니다. <strong>Include Guard</strong>라는 전통적인 방식도 있군요.</p>
<blockquote>
<p>#ifndef WARRIOR_H
#define WARRIOR_H
// Warrior 클래스 선언
#endif</p>
</blockquote>
<p>이런식으로 진행한다고 합니다. 의미는</p>
<blockquote>
<p>WARRIOR_H가 아직 정의되지 않았다면
지금 이 파일 내용을 읽고
WARRIOR_H를 정의해서
다음부터는 다시 읽지 않게 함</p>
</blockquote>
<p>이런식인가 보군요. 약간 std::과 using namespace std;를 보는 기분입니다. 저는 야비하게 편한 걸 써보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/ed14abee-be87-4a17-b7c9-f16b27d5ee86/image.png" alt=""></p>
<p>나는 Player를 기반으로 만들어진 클래스라는 선언도 해주었습니다.
여기에 attack함수를 넣어주면 수정이 가능해지겠죠?
<img src="https://velog.velcdn.com/images/jupiter_0609/post/d4bf735d-7e9e-4401-96ba-76f83d7ee40f/image.png" alt=""></p>
<p>cpp를 작성하러 가봅시다. 이제 초록 스파게티 면은 덜 두려운 기분이 듭니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/56020807-b479-477d-a1a7-b26069c85682/image.png" alt="">
무언가 많이 잘못되었습니다.</p>
<p>목표 흐름은 이렇습니다.</p>
<blockquote>
<p>Warrior 생성자 → Player 생성자 호출 → Warrior 전용 직업 값 설정 → attack() 구현</p>
</blockquote>
<p>Player 생성자 호출을 어떻게 하는지 모르겠군요.</p>
<blockquote>
<p>: Player(name, hp, mp, power, defence)</p>
</blockquote>
<p>이걸 현재 함수 뒤에 덧붙이는 형식으로 호출을 한다고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/79bf5e3b-bd25-458b-a573-dd197d5c4612/image.png" alt="">
= 이건 그냥 뼈란다. 응당 뼈와 피와 살이 있어야 마땅한데 피와 살은 어디있니. 로 들리는 건 기분 탓일 겁니다.</p>
<p>새롭게 알게된 지점 중 하나는 <strong>using namespace std;</strong> 이 편리하긴 하지만 <strong>std::string</strong> 가 실무에서 더 많이 쓰인다는 겁니다. <strong>이 이름이 어디에서 온 것인지 명확하게 보여주기 때문</strong>이라고 하는군요.</p>
<p>근데 이렇게 하고 보니 확실히 가상함수 부분이 와닿습니다. 언리얼이랑 연결해서 생각해보니 더 연상이 편하기도 하군요. </p>
<p>이제 그럼 가상소멸자 파트입니다.</p>
<blockquote>
<p><strong>가상소멸자?</strong>
부모 포인터로 자식 객체를 삭제할 때 자식의 정리 과정까지 제대로 실행되게 하려고 필요</p>
</blockquote>
<p>Player가 “공통 캐릭터 계약서”이며 Warrior가 실제 캐릭터라고 생각합시다. 게임이 끝나서 캐릭터를 정리할 때, 겉으로는 Player로만 보고 있더라도 실제 안에는 Warrior일 수 있습니다. 이때 소멸자가 가상이 아니면:
“Player 부분만 정리하고 끝”
처럼 동작할 수 있지만
가상 소멸자가 있으면:
“실제 객체가 Warrior였네? 그럼 Warrior 정리부터 하고 Player도 정리하자”
라는 흐름이 된다고 합니다.</p>
<blockquote>
<p><strong>핵심 개념</strong>
Player 타입으로 Warrior를 가리킴
나중에 그 객체를 삭제함
이때 실제 객체인 Warrior의 소멸자도 호출되어야 함
그래서 부모 클래스인 Player의 소멸자에 virtual을 붙임</p>
</blockquote>
<blockquote>
<p><strong>정리</strong>
Player처럼 상속의 부모로 쓰이는 클래스라면 가상 소멸자를 두는 것이 안전.</p>
</blockquote>
<ul>
<li>자식 객체의 소멸자가 제대로 호출됨</li>
<li>동적 메모리 정리가 안전해짐</li>
<li>다형성을 사용하는 클래스에서 기본적으로 권장됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/2c243748-b9d5-44f4-84af-29249ef8b0ec/image.png" alt=""></p>
<p>형식에 맞게 Player.h에 잘 추가해 둡니다.</p>
<p>직업을 선택할 수 있게끔 함수 작업을 해봅시다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/60276995-534a-4374-b8b4-bbdd1361c47b/image.png" alt="">
이전 Upgrade 함수를 응용해서 만들어 봅니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/f3e23681-5b32-46de-bf30-22bc3c9f36bd/image.png" alt="">
실행은 되지만 오류가 사라지지 않습니다. E0308코드...
알고보니 헤더 오류였습니다. 하하. include를 상시 확인합시다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/c3d97179-c83c-4e9e-a7e8-5a384e966f13/image.png" alt="">
그리고 이렇게 몬스트 초기화를 해두면 몬스터 추가가 어려워지므로
<img src="https://velog.velcdn.com/images/jupiter_0609/post/817b9c3d-ccb4-40ed-a44c-6df0cc760451/image.png" alt="">
이렇게 값을 비워두는 게 범용성에서 낫겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/c938579e-bf73-4196-9f40-ff44573c0573/image.png" alt="">
그리고 main에서 이렇게 불러주면 슬라임이 생성됩니다.
이제 객체화에 조금은 익숙해졌습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Text RPG 제작하기(8)]]></title>
            <link>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B08</link>
            <guid>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B08</guid>
            <pubDate>Fri, 24 Jul 2026 14:50:53 GMT</pubDate>
            <description><![CDATA[<p>여태 몰랐던 대단하진 않지만 엄청난 기능이 있습니다.
여기서 클래스를 선택하면 cpp와 h파일이 같이 생기는군요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/a4cf8f85-cc78-4357-b698-445c5c1ad0f3/image.png" alt="">
따로 만드는 게 그렇게 힘든 일은 아니지만? 편리하니 애용해야겠습니다. 그도 그럴게 클래스를 한두번 만들 게 아니니 말입니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/17fd1da7-b0ac-413d-b541-80dd5db94cda/image.png" alt="">
이렇게 필요한 사항이 알아서 기입되어 있으니 좋기도 합니다.
나중에 &lt;비주얼 스튜디오, 몰랐지만 편리한 기능들&gt; 모음을 만들어도 좋겠단 생각이 듭니다. 툴은 좀 자기 몸처럼 사용할 줄 알아야 응용이 가능하다고 믿기 때문입니다.</p>
<p>각설하고 작업을 개시합시다.
이번엔 직업(자식)들이 상속받을 Player(부모) 클래스를 만들어야 합니다.
상속 + 다형성 + 생성자 초기화를 연습할 수 있는 절호의 기회!</p>
<p>앞서 작성했던 인벤토리 클래스와 다르게 여기선 <strong>protected</strong>를 써줍시다. 그래야 추후에 자식 클래스에서 수정이 가능합니다.</p>
<blockquote>
<p>private   → Player 내부에서만 접근 가능
protected → Player와 자식 클래스에서 접근 가능</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/148d95e0-b666-4fc6-8c6a-1d82a4b0d5fc/image.png" alt="">
일단 클래스 선언 1차가 완성되었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/0310cd6b-3419-4ee1-85a9-86cc276f664e/image.png" alt="">
그리고 이렇게 하면 안되는 것 같죠?
매개변수로 받은 hp, mp, power, defence를 멤버 변수에 넣는 방식으로 가야 하는데, 여기서 문제는 매개변수와 멤버 변수 이름이 같다는 지점입니다.
이를 구분하기 위해 <strong>this-&gt;</strong>를 사용해줍니다.</p>
<blockquote>
<p>this-&gt;hp = hp;
왼쪽은 멤버 변수, 오른쪽은 매개변수</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/9b3dd987-49a3-4e58-ae7c-6fdf01e87f97/image.png" alt="">
스파게티가 말끔히 없어졌습니다.</p>
<p>다만 this 개념이 햇갈리니 다시 정리하겠습니다.</p>
<blockquote>
<p>this-&gt;name = name;
= 이 객체가 가진 name 멤버 변수에
생성자로 들어온 name 값을 넣어라</p>
</blockquote>
<p>먼 옛날에 언리얼을 처음 만지작 거릴 무렵, get과 set의 개념 이해가 제법 어려웠습니다. 보기에 get은 그냥 어디에 붙이는 것 같았고, set은 중간에 실행핀에 기워져 기능할 때가 많았다 정도만 직관적으로 알았지만, 이게 본질적으로 어떻게 다른가에 대해 설명하라면 설명할 수 없었습니다. 깊이 있게 이해하기엔 당자 마감이 급해 미뤘지만 이번 기회에 이 불쾌한 인지오류를 해결하고 확정지을 수 있을 듯 합니다.</p>
<blockquote>
<p><strong>getter</strong></p>
</blockquote>
<ul>
<li>값을 읽어오는 함수</li>
<li><em>setter*</em></li>
<li>필요한 값들을 바꾸는 함수
특히 자식 클래스나 외부에서 스탯을 조정할 때 사용 가능</li>
</ul>
<p>이러니 언리얼에서 왜 그렇게 쓰였는지 이해가 갑니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/d8e14b02-ba38-49e3-868e-4b507a867185/image.png" alt="">
선언을 해줍니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/535f2bec-fd80-4bed-a6a7-ec38ddceb0a4/image.png" alt="">
틀려서 고쳤습니다. 이렇게 쓰는게 맞군요.
get은 앞에 자료형을, 
set은 받을 변수를 적어야 합니다. 이제 좀 get/set이 와닿습니다.</p>
<p>근데 그러고 보니 생성자에 레벨이나 직업은 지금 안 넣어도 되나 헷갈립니다.</p>
<p>레벨은 그냥 처음에 1로 초기화 하고, 직업은 자식 클래스에서 변경하는 방향이 합리적일 듯 합니다. 나중에 자식 클래스를 만들면서 보완하도록 하고 넘어가죠.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/78bc0f4d-85cf-43a9-a796-5f2792d73c69/image.png" alt="">
이렇게 선언을 마쳤으나 printPlayerStatus는 매개변수가 없어도 된다고 합니다. <strong>멤버 함수는 같은 클래스 안의 멤버 변수에 직접 접근할 수 있기 때문에, 상태 출력 함수가 굳이 job, level을 매개변수로 받을 필요가 없기 때문</strong>이라고 하는군요.</p>
<p>그러면 유사했던 Inventory.h를 같이 살펴보면 좋을 것 같습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/20b8cb84-bfa7-42c5-b301-53ade1520078/image.png" alt=""></p>
<p>여기선 int ItemType이 새로은 매개변수 선언이기 때문에 적었었군요.</p>
<p>이어서 Play.cpp에서 함수를 정의해보겠습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/1bf0209d-a589-486b-85e9-6da08ac4e49f/image.png" alt="">
왜 스파게티?</p>
<blockquote>
<p>setName은 선언처럼 세미콜론을 붙이면 안 되고, 함수 몸통을 바로 써야 해요.</p>
</blockquote>
<p>아.
바로 바꾸어 주었습니다.</p>
<p>근데 작업하다보니 레벨은... set하면 안되지 않나 싶었습니다. 그러니까 public에 얘가 있으면 사용자가 접근 가능한 거 아닌가?</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/345d2f8b-5cad-48f2-a904-6d891d5f1a4e/image.png" alt="">
좋은 방향을 알게 되었습니다. 7세 아동이 되어 무수한 질문으로 부모님을 귀찮게 구는 기분입니다. 하지만 AI는 감당해주시겠죠?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Text RPG 제작하기(7)]]></title>
            <link>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B07</link>
            <guid>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B07</guid>
            <pubDate>Thu, 23 Jul 2026 12:09:35 GMT</pubDate>
            <description><![CDATA[<p>헤더 작업을 하며 추가로 알게된 점이 생겼습니다.</p>
<blockquote>
<p><strong>1. 헤더의 using namespace std;는 빼는 게 좋다</strong></p>
</blockquote>
<ul>
<li>헤더는 여러 .cpp에서 포함될 수 있어서 std 전체를 강제로 열어버리면 이름 충돌 가능성이 높아집니다. 따라서 직접 써주는 방향이 맞다고 합니다.</li>
<li><em>2. 클래스 정의도 헤더 파일에 할 것*</em></li>
<li>헤더 파일에 클래스 정의를 두고 cpp에는 함수 정의를 두는 게 맞습니다.</li>
</ul>
<p>파일을 분리하는 게 생각보다 오래 걸리는군요. 뭘 어디에 두어야 하는지 햇갈리는 것 같습니다.</p>
<p>일단 제가 정리한 방향은</p>
<blockquote>
<ul>
<li>Item.h에 Item구조체 정의</li>
</ul>
</blockquote>
<ul>
<li>Inventory.h에서 Item.h를 include</li>
</ul>
<p>하는 방향입니다.
지금 당장은 안 쓰지만 우선은 해둡시다.</p>
<blockquote>
<p><strong>추가로 알게 된 사항</strong></p>
</blockquote>
<ul>
<li>함수 이름은 동사형으로</li>
<li>생성자는 딱 한번 호출되는 함수로 초기값을 세팅할 수 있음
Inventory.h
→ “생성자가 있다”라고 선언
<img src="https://velog.velcdn.com/images/jupiter_0609/post/dc9126e4-e251-4fb7-88c4-f256d616e446/image.png" alt="">
Inventory.cpp
→ “처음 만들 때 포션을 5개로 세팅한다”라고 구현
<img src="https://velog.velcdn.com/images/jupiter_0609/post/9a66646a-bb96-4c34-8676-f97b765da785/image.png" alt=""></li>
</ul>
<p>여기서 고민되는 지점은
case와 인벤토리에서 아이템 선택하는 걸 어떻게 연결할 것인가인데요...
<img src="https://velog.velcdn.com/images/jupiter_0609/post/6fc6264b-d827-4de6-93ba-e1616ba93dd5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/2afa104b-d525-41e4-a481-2d56d007ac8c/image.png" alt="">
Inventory.cpp에서 이렇게 조건을 세우고</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/2fe87b33-5355-44ff-a6a2-c3da8855d1a6/image.png" alt="">
호출해주었습니다.</p>
<p>작동을 잘 하는지 보려는데
<img src="https://velog.velcdn.com/images/jupiter_0609/post/b29681f7-31f9-4a56-895a-5cd4b8399847/image.png" alt="">
컴파일 오류가 발견됩니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/0c0374d6-2e4e-4255-b157-4a0ee1c6e5c1/image.png" alt="">
메인 헤더에 인벤토리를 적지 않았군요
인벤토리 헤더도 include 해주고 선언도 추가해 줍시다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/eb2d5fe1-07f1-435a-bc9e-5b0e875afa08/image.png" alt=""></p>
<p>빌드 성공이라는 문구는 왜이리 가슴 설렐까요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/dc746c4f-2954-4560-ad36-e8bdec25da06/image.png" alt=""></p>
<p>콘솔에서도 문제가 없습니다!
<img src="https://velog.velcdn.com/images/jupiter_0609/post/bc95aa6a-117c-498f-b7cd-bb3de5a89c09/image.png" alt=""></p>
<p>아주 만족스럽습니다.
7할 정도는 AI의 힘을 빌렸을지언정 2할 정도는 제가 했다고 믿습니다.
나머지 1할은 기능 구현 가이드에게 드립니다.</p>
<p>그렇지만 자랑스러워하고 있으면 버그가 찾아오기 마련입니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/823c0b0b-e936-4fbb-bd68-7eedba5c22ab/image.png" alt=""></p>
<p>HP 포션이 하나 남은 듯 한데, MP 포션이 0개라 사용이 불가하군요.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/f704cfee-4b2c-4e5e-a7f0-7d16a44fddef/image.png" alt="">
이 조건을 수정해야할 듯 합니다. 
저는 HP 포션이 0이어도, MP 포션이 0이어도 포션 부족을 출력하길 바랐는데
다시 생각해보니 둘 중 하나만 0이면 무조건 포션 부족이 뜨는 조건이군요.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/d9afa89d-07cb-47c0-9ddf-fa0d995078e8/image.png" alt="">
이렇게 바꿔주었습니다.</p>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/93a22afc-b774-4d93-9201-b70064b07707/image.png" alt="">
문제 해결!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TIL] Text RPG 제작하기(6)]]></title>
            <link>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B06</link>
            <guid>https://velog.io/@jupiter_0609/TIL-Text-RPG-%EC%A0%9C%EC%9E%91%ED%95%98%EA%B8%B06</guid>
            <pubDate>Wed, 22 Jul 2026 12:30:08 GMT</pubDate>
            <description><![CDATA[<h2 id="1-2-포션-차감하기">1-2 포션 차감하기</h2>
<p>코드를 작성하기에 앞서 생각해보면 좋을 지점이 있습니다. 지금 구현만 보았을 때는 강화 함수 내에서 포션 갯수 변수를 만들 수는 있습니다. 다만 전체 흐름을 생각하면 나중에 포션을 따로 제작하기도 하고 인벤토리도 구현해야 하므로 포션 갯수를 item.cpp에서 인벤토리와 함께 관리하는 방향이 낫지 않을까 합니다.</p>
<p>이 방향성을 ai와 논의해보자 좀 더 나은 방향이 제시되었습니다.</p>
<blockquote>
<p><strong>Inventory</strong>
어떤 아이템을 몇 개 가지고 있는지 관리
아이템이 있는지 확인
사용하면 개수 감소</p>
</blockquote>
<blockquote>
<p><strong>Item</strong>
아이템의 이름, 종류, 효과 같은 정보 관리</p>
</blockquote>
<p>이렇게 구분하는 게 나중에 추가될 아이템을 고려해보았을 때 나을 듯 합니다. 그럼 <strong>포션이 필요한 강화라면 인벤토리에 사용을 요청하는 방향</strong>으로 가보겠습니다.</p>
<p>인벤토리 설계는 나중 스탭에 있으니 일단 초기 작업만 해두겠습니다. </p>
<p>주어진 구현항목은 다음과 같습니다만, 앞선 단계이니 살짝만 건들여보겠습니다.</p>
<blockquote>
<p><strong>구현 항목</strong></p>
</blockquote>
<ul>
<li><input disabled="" type="checkbox"> <code>Item</code> 구조체 정의하기: <code>name</code>, <code>price</code>, <code>void PrintInfo() const</code></li>
<li><input disabled="" type="checkbox"> <code>#include &lt;vector&gt;</code> 추가하기</li>
<li><input disabled="" type="checkbox"> <code>vector&lt;Item&gt; inventory</code> 선언하기</li>
<li><input disabled="" type="checkbox"> 전투 승리 시 <code>inventory.push_back(droppedItem)</code> 연결하기</li>
<li><input disabled="" type="checkbox"> 메인 메뉴 추가하기: 1. 던전 / 2. 인벤토리 / 0. 종료</li>
<li><input disabled="" type="checkbox"> 인벤토리 메뉴에서 range-for로 전체 아이템 출력하기</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jupiter_0609/post/63a8b989-dac0-4956-80ed-0b73b6e1b54a/image.png" alt="">
우선 구조체를 선언했습니다. </p>
<blockquote>
<p>*<em>구조체 선언? *</em></p>
</blockquote>
<ul>
<li>새로운 자료형을 만드는 행위</li>
<li>main 함수 전에 작성</li>
<li>양식 
struct 구조체 이름
{
  자료형 변수이름;
  자료형 변수이름;
  반환형 함수이름() const
  {<pre><code>  // 함수 내용</code></pre>  }
};</li>
</ul>
<p>이라기는 합니다. 다만 main에 넣으면 길어질 것 같군요.
Item.cpp나 inventory.cpp에 선언해서 호출할 수 있으면 좋겠습니다.
혹은 다른 방법이 있는지 알아보겠습니다.</p>
<p>드디어 class를 한번 써보지요. class를 선택한 이유는 아래와 같습니다.</p>
<blockquote>
<p><strong>클래스로 묶기 좋은 경우</strong>
다음 중 2개 이상 해당하면 클래스를 고려</p>
</blockquote>
<ul>
<li>관련된 데이터가 여러 개 있다
예: 체력, 공격력, 위치, 이름</li>
<li>그 데이터를 사용하는 함수가 여러 개 있다
예: 이동하기, 공격하기, 데미지 받기</li>
<li>같은 구조의 대상을 여러 개 만들고 싶다
예: 몬스터 여러 마리, 학생 여러 명, 아이템 여러 개</li>
<li>데이터를 함부로 바꾸면 위험하다
예: 체력이 음수가 되면 안 됨</li>
</ul>
<p>앞으로도 인벤토리나 포션은 계속 호출될 것이기 때문에 초장에 class로 묶어주겠습니다.
<img src="https://velog.velcdn.com/images/jupiter_0609/post/8a77e98f-9807-43eb-96c4-df76a2316f4c/image.png" alt="">
class를 처음 써봐서 설레는군요. 마주할 오류에 가슴이 뜁니다.</p>
]]></description>
        </item>
    </channel>
</rss>