<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ggm-_-</title>
        <link>https://velog.io/</link>
        <description>나태 마스터</description>
        <lastBuildDate>Fri, 18 Sep 2026 03:14:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ggm-_-</title>
            <url>https://velog.velcdn.com/images/wanna_make_game/profile/e3227ce8-7e04-4a3a-8033-144bc908d004/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ggm-_-. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wanna_make_game" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[나에게 하루는]]></title>
            <link>https://velog.io/@wanna_make_game/%EB%82%98%EC%97%90%EA%B2%8C-%ED%95%98%EB%A3%A8%EB%8A%94</link>
            <guid>https://velog.io/@wanna_make_game/%EB%82%98%EC%97%90%EA%B2%8C-%ED%95%98%EB%A3%A8%EB%8A%94</guid>
            <pubDate>Fri, 18 Sep 2026 03:14:41 GMT</pubDate>
            <description><![CDATA[<p>2026.09.18(금) 12:14</p>
<p>나에게 하루는 이틀과 같다.</p>
<p>음.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[음]]></title>
            <link>https://velog.io/@wanna_make_game/%EC%9D%8C</link>
            <guid>https://velog.io/@wanna_make_game/%EC%9D%8C</guid>
            <pubDate>Tue, 15 Sep 2026 20:51:23 GMT</pubDate>
            <description><![CDATA[<p>2026.09.16 05:51
오늘부터 그냥 아무거나 하나씩 글을 올려볼까 한다.
그렇게 의미는 없지만 의미가 있을지도...?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 정리: 옵저버 패턴, 이벤트 버스 패턴, 명령 패턴]]></title>
            <link>https://velog.io/@wanna_make_game/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%95%EB%A6%AC-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EC%8A%A4-%ED%8C%A8%ED%84%B4-%EB%AA%85%EB%A0%B9-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@wanna_make_game/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%95%EB%A6%AC-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EC%8A%A4-%ED%8C%A8%ED%84%B4-%EB%AA%85%EB%A0%B9-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Sun, 16 Feb 2025 09:24:07 GMT</pubDate>
            <description><![CDATA[<p>2025.02.16(일)
스탠다드 반에서 진행한 디자인 패턴 2번째 강의를 정리합니다.</p>
<p>_<span style="color: gray">Unity 6기 스탠다드 반에서 송지원 튜터님이 강의하신 디자인 패턴 강의를 바탕으로 작성한 글입니다.</span>_</p>
<p>이전 글: <a href="https://velog.io/@wanna_make_game/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8B%91-%ED%92%80-%ED%8C%A8%ED%84%B4-%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4-%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4">디자인 패턴 정리: 싱글톤 패턴, 오브젝틑 풀 패턴, 전략 패턴, 상태 패턴</a></p>
<h2 id="이-글에서-다룰-패턴들">이 글에서 다룰 패턴들</h2>
<ul>
<li>옵저버 패턴</li>
<li>이벤트 버스 패턴</li>
<li>명령 패턴</li>
</ul>
<hr>
<h1 id="이벤트-기반-프로그래밍">이벤트 기반 프로그래밍</h1>
<ul>
<li>이벤트 기반 프로그래밍은 효율적으로 프로그램이 실행되도록 설계할 수 있다.</li>
<li>이를 통해 코드 실행의 흐름을 효율화할 수 있다.</li>
<li>Pub-Sub 패턴이라고도 할 수 있다.
(publish발행 subscribe구독 패턴)</li>
</ul>
<table>
<thead>
<tr>
<th>발행자</th>
<th>구독자</th>
<th>대응 방식</th>
</tr>
</thead>
<tbody><tr>
<td>1</td>
<td>1</td>
<td>함수 호출</td>
</tr>
<tr>
<td>1</td>
<td>can be N</td>
<td>옵저버 패턴</td>
</tr>
<tr>
<td>can be N</td>
<td>can be N</td>
<td>이벤트 버스 패턴</td>
</tr>
</tbody></table>
<hr>
<h1 id="옵저버-패턴">옵저버 패턴</h1>
<p>1 발행자, N 구독자인 Pub-Sub 패턴</p>
<ul>
<li>옵저버 패턴은 구독자들이 발행자를 관찰하며 발행자가 이벤트를 발생시켰을 때 반응한다. </li>
<li>옵저버 패턴을 구현하고 있지 않으면 매 프레임마다 발행자를 관찰해야된다.</li>
</ul>
<hr>
<h1 id="이벤트-버스-패턴">이벤트 버스 패턴</h1>
<p>N 발행자, N 구독자인 Pub-Sub 패턴</p>
<h3 id="왜-쓸까">왜 쓸까?</h3>
<ul>
<li>이벤트 구현 시 발행자와 구독자 사이에서 의존성을 만들지 않고 싶어서</li>
<li>퀘스트 시스템을 구현할 때 퀘스트의 종류가 많지 않아, 간단하고 이해가 슉슉되는 이벤트 시스템을 만들고 싶을 수 있다.</li>
<li>그리고 이벤트의 발행자와 구독자 간의 의존성을 만들지 않은 상태에서요.</li>
</ul>
<h3 id="구현">구현</h3>
<ul>
<li>EventBus에는 액션을 등록하고, Publisher가 이벤트를 Invoke하도록 한다.
이때, Publisher가 한 개의 클래스라는 가정은 없고, 모두가 공용으로 쓰는 EventManager가 탄생한다.</li>
</ul>
<h2 id="이벤트-버스-패턴-고려사항">이벤트 버스 패턴 고려사항</h2>
<ul>
<li>이벤트 버스에 대한 의존성이 너무 높아질 가능성이 있다.</li>
<li>복잡해질 경우 이벤트 관리가 안될 가능성이 있다. (&lt;- 근데 튜터님은 써보니까 너무 편했다고 한다)</li>
</ul>
<hr>
<h1 id="명령-패턴">명령 패턴</h1>
<p>행동을 객체화해서 복잡한 작업을 순차적으로 처리하거나 되돌릴 수 있게 만드는 패턴</p>
<h3 id="왜-쓸까-1">왜 쓸까?</h3>
<ul>
<li>행동을 객체로 만들어 재사용, 기록, 취소, 병렬 처리를 쉽게 하기 위해</li>
</ul>
<blockquote>
<h4 id="❓-왜-명령을-객체로-묶을까">❓ 왜 명령을 객체로 묶을까?</h4>
<p>할 일들을 To-Do리스트로 만든다고 생각해보자. 이때, 우리는 자연스럽게 할 일들을 객체화할 생각을 하게 된다. 언제? 무엇을? 어디에서? 누구랑? 언제까지?와 같은 내용들을 묶으려고 하다보면, 행동과 관련된 정보를 객체로 묶고 이를 체계적으로 관리하는 것은 매우 좋은 해결책이 될 것이다.</p>
</blockquote>
<h3 id="구현-1">구현</h3>
<p>명령 패턴의 주요 구성 요소는 다음과 같다.</p>
<h4 id="1-command-명령-인터페이스">1. Command (명령 인터페이스)</h4>
<ul>
<li>실행될 명령을 정의하는 인터페이스 또는 추상 클래스
Execute(), Undo() 등의 메서드를 보유</li>
</ul>
<h4 id="2-concretecommand-구체적인-명령-클래스">2. ConcreteCommand (구체적인 명령 클래스)</h4>
<ul>
<li>Command 인터페이스를 구현하여 특정 동작을 수행
명령을 실행할 실제 객체(Receiver)를 참조하여 요청을 전달</li>
</ul>
<h4 id="3-receiver-실제-동작을-수행하는-객체">3. Receiver (실제 동작을 수행하는 객체)</h4>
<ul>
<li>ConcreteCommand가 실행할 기능을 가진 객체</li>
</ul>
<h4 id="4-invoker-명령을-요청하는-객체">4. Invoker (명령을 요청하는 객체)</h4>
<ul>
<li>Command 객체를 보관하고 있다가 특정 시점에 실행</li>
</ul>
<h4 id="5-client-클라이언트">5. Client (클라이언트)</h4>
<ul>
<li>ConcreteCommand를 생성하고 Invoker에 전달</li>
</ul>
<h3 id="구현-예제-코드">구현 예제 코드</h3>
<ul>
<li>예제 코드<pre><code class="language-cs">// 1. Command 인터페이스 정의
public interface ICommand
{
  void Execute();
  void Undo();
}
</code></pre>
</li>
</ul>
<p>// 2. Receiver (명령을 실행하는 실제 객체)
public class Player
{
    public void Jump()
    {
        Debug.Log(&quot;Player Jumped!&quot;);
    }</p>
<pre><code>public void UndoJump()
{
    Debug.Log(&quot;Jump Undone!&quot;);
}</code></pre><p>}</p>
<p>// 3. ConcreteCommand (구체적인 명령)
public class JumpCommand : ICommand
{
    private Player _player;</p>
<pre><code>public JumpCommand(Player player)
{
    _player = player;
}

public void Execute()
{
    _player.Jump();
}

public void Undo()
{
    _player.UndoJump();
}</code></pre><p>}</p>
<p>// 4. Invoker (명령을 저장하고 실행)
public class CommandInvoker
{
    private ICommand _command;</p>
<pre><code>public void SetCommand(ICommand command)
{
    _command = command;
}

public void ExecuteCommand()
{
    _command?.Execute();
}

public void UndoCommand()
{
    _command?.Undo();
}</code></pre><p>}</p>
<p>// 5. Client (사용 예)
public class GameManager : MonoBehaviour
{
    private CommandInvoker _invoker = new CommandInvoker();
    private Player _player = new Player();</p>
<pre><code>void Start()
{
    ICommand jumpCommand = new JumpCommand(_player);
    _invoker.SetCommand(jumpCommand);

    // 버튼 클릭 시 실행
    _invoker.ExecuteCommand(); // &quot;Player Jumped!&quot; 출력
    _invoker.UndoCommand();    // &quot;Jump Undone!&quot; 출력
}</code></pre><p>}</p>
<p>```</p>
<hr>
<h3 id="다음에-공부할-패턴">다음에 공부할 패턴</h3>
<ul>
<li>메멘토 패턴</li>
<li>파사드 패턴</li>
<li>etc...</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[디자인 패턴 정리: 싱글톤 패턴, 오브젝틑 풀 패턴, 전략 패턴, 상태 패턴]]></title>
            <link>https://velog.io/@wanna_make_game/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8B%91-%ED%92%80-%ED%8C%A8%ED%84%B4-%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4-%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@wanna_make_game/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0-%EC%8B%B1%EA%B8%80%ED%86%A4-%ED%8C%A8%ED%84%B4-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8B%91-%ED%92%80-%ED%8C%A8%ED%84%B4-%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4-%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Thu, 30 Jan 2025 21:08:26 GMT</pubDate>
            <description><![CDATA[<p>2025.01.24(금)
TMI: 내일배움캠프 Unity 6기의 스탠다드반 특강에서 디자인 패턴 강의를 진행하였었다. 반이 달라 듣지 못하였었어도 &#39;무조건 들어야겠다<del>&#39; 싶을 정도로 중요한 내용이기에 시간나면 들어야지라는 생각은 했었는데, 시간이 촉?박하고 &#39;이거 들으면 프로젝트 진행이 더뎌지지 않나?&#39; 하는 생각에 자꾸 멀리하게 되었었는데 드디어 듣게 되었다.
아무튼 여차저차 저번주에 강의를 들으며 메모한 내용을 바탕으로 정리한다.
본격, 내가 보려고 정리한 디자인 패턴 Let&#39;s go</del></p>
<p>_<span style="color: gray">Unity 6기 스탠다드 반에서 송지원 튜터님이 강의하신 디자인 패턴 강의를 바탕으로 작성한 글입니다.</span>_</p>
<p>_<span style="color: gray">이 글은 99.9%의 강의 내용과 0.1%의 사족으로 이루어져 있습니다. (이것도 사족인가?)</span>_</p>
<h2 id="코딩이란-무엇인가">코딩이란 무엇인가?</h2>
<p>코딩은 무공이다</p>
<ul>
<li>갑작스럽게 깨달음이 찾아오는가?</li>
<li>갑자기 그 깨달음이 사라지기도 하는가?</li>
<li>윗대가 남긴 구결이 있는가?</li>
</ul>
<p>이 모든 조건을 만족하는 코딩은 무공! 갈고 닦아서 자신의 코딩 기술을 연마해서 무림고수가 되자!</p>
<hr>
<h1 id="디자인-패턴">디자인 패턴</h1>
<h2 id="디자인-패턴이란-무엇인가">디자인 패턴이란 무엇인가?</h2>
<p>디자인 패턴은 기출문제집이다.</p>
<ul>
<li>천재 개발자들(GoF)이 OOP의 주요 설계 어려움들을 푸는 기출문제집을 제공</li>
<li>이후 언어별로 디자인 패턴들이 증식</li>
</ul>
<p>_<span style="color: gray">Gang of Four(GoF) : 네 명의 천재 개발자
네 명의 천재 개발자들이 소프트웨어에서 공통적으로 나타나는 주요 구조 및 설계들을 모아서 디자인 패턴이라고 하였다.</span>_</p>
<blockquote>
<p>💡 디자인 패턴을 얼마나 아는지가 중요한 게 아니라, 디자인 패턴을 가지고 어떻게 해결하는지가 중요하다!</p>
</blockquote>
<h2 id="디자인-패턴을-쓰면-좋은-이유">디자인 패턴을 쓰면 좋은 이유?</h2>
<ul>
<li>복잡하고 난해한 문제를 패턴화해서 풀 수 있다.</li>
<li>다른 개발자와 소통할 때 간단하게 설명할 수 있다.</li>
</ul>
<h2 id="디자인-패턴을-사용할-때-주의해야-할-점">디자인 패턴을 사용할 때 주의해야 할 점</h2>
<p>디자인 패턴은 <span style="color: red">문제를 푸는 데</span> 사용해야 한다!</p>
<ul>
<li>디자인 패턴을 쓰기 위해 문제를 만드는 경우를 조심할 것</li>
<li>디자인 패턴을 무지성으로 활용하는 것은 매우 지양할 것</li>
</ul>
<blockquote>
<p>☝ 디자인 패턴을 사용할 때, 항상 이 패턴이 정말 필요한 지 점검해보고 써야하는 이유를 생각해보자!</p>
</blockquote>
<p>_<span style="color: gray">포트폴리오에서는 이 디자인 패턴을 왜 썼는지를 어필하는 것이 매우 중요한 포인트이다!</span>_</p>
<hr>
<h2 id="이-글에서-다룰-패턴들">이 글에서 다룰 패턴들</h2>
<ul>
<li>싱글톤 패턴</li>
<li>오브젝트 풀 패턴</li>
<li>전략 패턴</li>
<li>상태 패턴</li>
</ul>
<hr>
<h1 id="싱글톤-패턴">싱글톤 패턴</h1>
<p>접근이 잦은 핵심 기능에 대한 전역적 접근을 허용하는 패턴</p>
<h3 id="왜-쓸까">왜 쓸까?</h3>
<ul>
<li>중요하고 유일하게 존재하는 대상을 쉽게 접근하고 싶어서</li>
</ul>
<h3 id="구현">구현</h3>
<ul>
<li>유일하다 - 추가적으로 생기는 것을 제한하는 방법이 필요</li>
<li>전역적으로 접근이 가능하다 - public static을 통해 구현</li>
</ul>
<p>_<span style="color: gray">&gt;static은 항상 메모리 상에 존재하게 된다.</span>_</p>
<h2 id="싱글톤-확장---generic과-연계">싱글톤 확장 - Generic과 연계</h2>
<h3 id="왜-쓸까-1">왜 쓸까?</h3>
<ul>
<li>싱글톤 객체들 간의 유사성이 매우 높음</li>
<li>그런데 단순하게 상속으로 상위클래스로 단순하게 접근하긴 싫음
(각 싱글톤의 고유 기능 캐스팅 없이 바로 쓰고 싶다는 것)</li>
</ul>
<blockquote>
<p>👉 Generic -&gt; &#39;일반화 프로그래밍&#39; 이라고 생각한다</p>
</blockquote>
<h2 id="제네릭-싱글톤-작은-팁">제네릭 싱글톤 작은 팁</h2>
<ol>
<li><p>라이플 사이클 주의 (<code>Awake</code> / <code>Start</code>)</p>
</li>
<li><p>모든 싱글톤을 <code>DontDestoryOnLoad</code>할 필요가 없다.</p>
</li>
</ol>
<p>-&gt; 개인적으로 <code>Singleton&lt;T&gt;</code> / <code>SingletonDontDestory&lt;T&gt;</code> 로 네이밍 구분 추천
-&gt; <code>DontDestory</code> 했는데, 참조하는 객체들이 <code>null</code>되어 있는 것이 더 피곤할 수 있음</p>
<ol start="3">
<li>Init 이라는 메서드를 만들어서 초기화 순서에 맞게 관리할 수 있다.</li>
</ol>
<h2 id="매니저-특징">매니저 특징</h2>
<ul>
<li>유일성이 있어야 한다.</li>
<li>참조가 많이 발생한다.</li>
</ul>
<hr>
<h1 id="오브젝트-풀-패턴">오브젝트 풀 패턴</h1>
<p>생성/파괴가 반복되는 오브젝트를 재활용하는 패턴</p>
<h3 id="왜-쓸까-2">왜 쓸까?</h3>
<ul>
<li>할당/해제에 걸리는 성능 낭비와 메모리 낭비를 줄이고 싶어서
(if문을 줄이는 것보다, 메모리를 할당하고 해제하는 것이 더 많은 낭비를 야기한다)</li>
<li>풀(Pool)은 뽑힐 수 있는 대상을 묶는 단위라고 생각하면 된다.</li>
</ul>
<blockquote>
<p>🔥 로딩할 때 0.1초 기다리는 것은 상관없지만, 6프레임 이상이 지나가는 게임 플레이 시간에서의 0.1초는 끊기면 안된다.
(60fps의 게임이라고 했을 때, 0.1초에 6개의 프레임이 지나간다.)</p>
</blockquote>
<h3 id="구현-1">구현</h3>
<h4 id="필수-구현-사항">필수 구현 사항</h4>
<ul>
<li>파괴하는 대신 오브젝트를 비활성화한다.<h4 id="선택-구현-사항">선택 구현 사항</h4>
</li>
<li>필요한 만큼을 미리 생성해두고, 비활성화해둔다.</li>
<li>생성 대신 비활성화된 오브젝트를 찾아 활성화한다.</li>
<li>미리 생성해둔 양 이상을 요구한다면 추가로 생성한다.</li>
</ul>
<blockquote>
<p>🍳 포트폴리오에서 자주쓰는 오브젝트 풀 예시:
스크롤 뷰에서 스크롤을 내릴 때, 보여지는 만큼의 content 정도의 오브젝트를 만들어놓고 위, 아래로 내릴 때, 재활용해서 사용한다.</p>
</blockquote>
<h2 id="오브젝트-풀-고려사항">오브젝트 풀 고려사항</h2>
<ul>
<li>씬 로드 시 오브젝트 풀의 모든 오브젝트를 전부 다 생성해 두어야 함</li>
<li><blockquote>
<p>씬 로드 시간을 매우 길게 만들 수 있음</p>
</blockquote>
</li>
<li><blockquote>
<p>필요한만큼만 생성할 것</p>
</blockquote>
</li>
</ul>
<h2 id="유니티에서-제공하는-오브젝트-풀">유니티에서 제공하는 오브젝트 풀</h2>
<h3 id="unityenginepool">UnityEngine.Pool</h3>
<ul>
<li>2021버전 이후부터 제공됨</li>
<li>간단하게 델리게이트를 걸어 커스터마이징 가능</li>
</ul>
<p>_<span style="color: gray">activeSelf: 자기가 active되어있는지
activeInHierarchy: Hierarchy 창에서 active되어있는지(부모 오브젝트가 꺼져있으면 Hierarchy에서 꺼짐)</span>_
_<span style="color: gray"></span>_</p>
<hr>
<h1 id="전략-패턴">전략 패턴</h1>
<p>마치 골프에서 상황에 맞게 채를 바꾸는 것처럼, 상황에 맞게 로직군을 바꿔주는 방식의 패턴</p>
<h3 id="왜-쓸까-3">왜 쓸까?</h3>
<ul>
<li>원본 클래스를 건드리지 않고 다양하게 추가되는 방식을 대응하고 싶어서</li>
</ul>
<blockquote>
<p>❓ <strong>전략이 뭐길래</strong> ❓
전략이란? 로직들의 군(묶음)을 정의하고, 로직군들간에서 교체가능하게 해준다. 
예를 들어, 일반적인 기능들을 포함하고 있는 로봇이 있다고 하자. 이 로봇이 상황에 따라 청소 소프트웨어를 구동하게 되면 청소 로봇으로, 경비 소프트웨어를 구동하게 되면 경비 로봇으로서 동작할 것이다.
이처럼, _<span style="color: red"><strong>같은 역할군</strong></span>_이지만 전략에 따라 _<span style="color: red"><strong>다른 기능</strong></span>_을 대입해주는 것이 전략 패턴이라고 할 수 있다.</p>
</blockquote>
<h3 id="구현-2">구현</h3>
<ul>
<li>전략 패턴을 구현하기 위해서 전략 인터페이스를 정의한다.</li>
<li>전략 인터페이스에서는 세부 동작을 구현
(로봇 전체를 상속 받는 것이 아닌, 로봇의 행동 하나를 상속받는다고 생각)</li>
</ul>
<p>_<span style="color: gray">전략패턴은 보통 Strategy를 컨벤션으로 붙여서 사용(ex: CleanStartegy)</span>_</p>
<h2 id="전략-패턴의-활용---ui-연출">전략 패턴의 활용 - UI 연출</h2>
<p>연출에 대한 구현을 UIView클래스에서 직접 하지 않고, UI를 보여주거나 숨기는 방법을 각 전략 인터페이스에서 가능하도록 구현</p>
<ul>
<li>인터페이스로 전략패턴을 구현해서 UI를 보여주는 연출과, 숨기는 연출을 인터페이스로 실행하게 만든다.</li>
<li><blockquote>
<p>그러면, 수정 없이 인터페이스를 상속받는 코드만 추가해서 보여주는 연출과 숨기는 연출을 추가할 수 있다.</p>
</blockquote>
</li>
</ul>
<hr>
<h1 id="상태-패턴">상태 패턴</h1>
<p>idle, angry, dead와 같이 여러 상태를 두고, 해당 상태일 때 그 상태에 맞는 행동을 하게 만드는 패턴</p>
<h3 id="왜-쓸까-4">왜 쓸까?</h3>
<ul>
<li>여러 상태들이 있고, 실행되는 로직이 달라짐을 체계적으로 관리하고 싶어서</li>
</ul>
<p>_<span style="color: gray">대표적인 사용 사례: 애니메이터</span>_
_<span style="color: gray"> 👉 코드로 구현하는 방법도 있지만, 사실상 Animator가 대표적인 사례이다.</span>_</p>
<h2 id="fsm">FSM</h2>
<p><strong>Finite State Machine</strong>의 약자, 유한 상태 기계</p>
<ul>
<li>N개의 <code>가능한 상태</code>, <code>현재 상태</code>, <code>상태 간의 전환</code>으로 구성되어 있다.
(애니메이터가 대표적인 FSM)</li>
</ul>
<h3 id="fsm-구현">FSM 구현</h3>
<p>숙련 주차의 적 구현처럼, N개의 상태를 두고, 그 안에서 상태의 전이가 일어나는 형태의 구현에서 활용</p>
<h4 id="구성요소">구성요소</h4>
<ul>
<li>상태들을 전환하는 역할을 하는 StateMachine</li>
<li>실행되어야 할 로직을 정의하는 State들로 구성</li>
</ul>
<h2 id="hfsm">HFSM</h2>
<p><strong>Hierarchial Finite State Machine</strong>의 약자, 계층형 유한 상태 기계
-큰 개념의 상태와, 실제 행동을 결정하는 세부 상태를 구분하여 구현</p>
<ul>
<li>예시
Idle / Jump / Walk / Run 이 아니라
Idle / Move (Jump, Walk, Run)으로 나누는 것</li>
</ul>
<hr>
<h2 id="디자인-패턴을-공부하며-느낀-점">디자인 패턴을 공부하며 느낀 점</h2>
<p>디자인 패턴을 공부하고 정리하면서 느낀 점은 알아놓았을 때, 다양한 상황에 활용하여 적용하여 보다 객체지향적인 프로그래밍을 할 수 있겠다는 것이다.</p>
<p>주의사항에서도 말했듯이, 디자인 패턴에 매몰되어 패턴을 위한 코딩을 하지 않도록 주의해야 겠지만, 잘 활용한다면 유지보수와 확장성이 좋은 코드를 작성하여 일의 능률을 늘릴 수 있을 것이다.</p>
<p>여기까지의 패턴들의 개념에 대한 부분은 이제 이해하였는데, 이것을 직접 활용해보면서 패턴들을 언제든지 상황에 맞게 쓸 수 있는 나의 무기로 만들어야 겠다.
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e4bd2a98-9171-48fd-a601-d62ad7e63722/image.webp" alt="">
(전략 패턴처럼 패턴을 꺼내쓰는 나)</p>
<hr>
<h2 id="다음-글에서-다룰-패턴들">다음 글에서 다룰 패턴들</h2>
<ul>
<li>옵저버 패턴</li>
<li>이벤트 버스 패턴</li>
<li>명령 패턴</li>
</ul>
<p>다음글: <a href="https://velog.io/@wanna_make_game/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%A0%95%EB%A6%AC-%EC%98%B5%EC%A0%80%EB%B2%84-%ED%8C%A8%ED%84%B4-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%B2%84%EC%8A%A4-%ED%8C%A8%ED%84%B4-%EB%AA%85%EB%A0%B9-%ED%8C%A8%ED%84%B4">디자인 패턴 정리: 옵저버 패턴, 이벤트 버스 패턴, 명령 패턴</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[역 기획서: 리그 오브 레전드]]></title>
            <link>https://velog.io/@wanna_make_game/%EC%97%AD-%EA%B8%B0%ED%9A%8D%EC%84%9C-%EB%A6%AC%EA%B7%B8-%EC%98%A4%EB%B8%8C-%EB%A0%88%EC%A0%84%EB%93%9C</link>
            <guid>https://velog.io/@wanna_make_game/%EC%97%AD-%EA%B8%B0%ED%9A%8D%EC%84%9C-%EB%A6%AC%EA%B7%B8-%EC%98%A4%EB%B8%8C-%EB%A0%88%EC%A0%84%EB%93%9C</guid>
            <pubDate>Mon, 23 Dec 2024 00:12:32 GMT</pubDate>
            <description><![CDATA[<p>2024.12.20(금)</p>
<p>리그 오브 레전드의 유저 매칭 시스템의 역 기획을 해보겠다.</p>
<h1 id="목차">목차</h1>
<ul>
<li>유저 매칭 시스템이란?</li>
<li>유저 매칭 시스템의 종류<ul>
<li>PVP</li>
<li>AI 상대 대전</li>
</ul>
</li>
<li>종류에 따른 게임 가능 조건</li>
<li>특징에 통한 유저 매칭 시스템 유추</li>
</ul>
<hr>
<h1 id="유저-매칭-시스템이란">유저 매칭 시스템이란?</h1>
<p>플레이어가 어떤 유저들과 매칭될 지를 정해주는 시스템.</p>
<p>크게 <strong>PVP</strong>와 <strong>AI 상대 대전</strong>으로 나뉜다. (전략적 팀 전투는 일단 제외) </p>
<hr>
<h1 id="유저-매칭-시스템의-종류">유저 매칭 시스템의 종류</h1>
<h1 id="pvp">PVP</h1>
<h2 id="pvp-종류">PVP 종류</h2>
<h3 id="소환사의-협곡">소환사의 협곡</h3>
<ul>
<li>빠른 대전</li>
<li>개인/2인 랭크 게임</li>
<li>자유 랭크 게임</li>
</ul>
<h3 id="무작위-총력전">무작위 총력전</h3>
<ul>
<li>무작위 총력전</li>
</ul>
<h1 id="ai-상대-대전">AI 상대 대전</h1>
<h2 id="ai-상대-대전-종류">AI 상대 대전 종류</h2>
<h3 id="소환사의-협곡-1">소환사의 협곡</h3>
<ul>
<li>입문</li>
<li>초보</li>
<li>중급</li>
</ul>
<hr>
<h1 id="종류에-따른-게임-가능-조건">종류에 따른 게임 가능 조건</h1>
<p>빠른 대전 : 3레벨</p>
<p>개인/2인 랭크 게임 : 30레벨, 보유 챔피언 20개 이상</p>
<p>자유 랭크 게임 : 30레벨, 보유 챔피언 20개 이상</p>
<p>무작위 총력전 : 3레벨</p>
<blockquote>
<p>보유 챔피언이 20개 이상이어야 하는 이유?
나 포함 우리 팀 5명 적 팀 5명이 모두 다른 챔피언을 벤하면 10개,
우리 팀 4명, 적 팀 5명이 픽을 하면 챔피언 9개
이렇게 최악의 경우 19개의 챔피언을 못 쓰게 되서 플레이를 못 할 수 있으므로,
최소 20개의 챔피언을 보유해야 랭크 게임을 할 수 있다.</p>
</blockquote>
<hr>
<h1 id="특징을-통한-유저-매칭-시스템-유추">특징을 통한 유저 매칭 시스템 유추</h1>
<h3 id="특징-1-유저끼리-매칭되는-pvp는-레벨3까지는-잠겨-있다">특징 1: 유저끼리 매칭되는 PVP는 레벨3까지는 잠겨 있다.</h3>
<h3 id="특징-2-랭크-게임은-30레벨-이상부터-가능하다">특징 2: 랭크 게임은 30레벨 이상부터 가능하다.</h3>
<ul>
<li><p>특징으로 유추할 수 있는 유저 매칭 시스템</p>
<p>  → 유저는 3레벨 이전까지는 튜토리얼과 AI 상대 대전을 하게 되는데, 튜토리얼과 AI 상대 대전의 KDA, 승리/패배 여부, 딜량 등 PVP 대전 이전까지의 여러 지표들을 통해 플레이어의 MMR(Matchmaking Rating)을 결정한다.</p>
<p>  이렇게 결정된 MMR을 통해, PVP에서 최대한 비슷한 MMR을 가진 상대방을 만날 수 있도록 설정한다.</p>
<p>  3레벨 이후, 일반 게임에서의 게임 결과를 통해 MMR이 결정되게 변경하고, 일반게임에서 수행한 MMR을 바탕으로 첫 랭크 게임에서의 MMR을 결정한다. (실제로 예전에 배치를 모두 같은 티어에서 시작하도록 하였다가, 나중에 일반게임의 MMR을 통해 랭크의 시작점이 어느정도 변경되도록 수정하였었다.)</p>
<p>  결정된 랭크 게임 MMR을 통한 5번의 배치고사 동안, 랭크 게임에서 플레이어가 보여준 지표들을 통해, MMR을 크게 조정하여 알맞은 실력대의 티어로 빠르게 진입할 수 있도록 한다.</p>
<p>  마지막으로, 일반 게임과 무작위 총력전의 MMR을 자신의 타입의 게임 뿐만 아니라, 랭크 게임의 수행 지표에 따라 변화하도록 한다.</p>
</li>
<li><p>예상되는 매칭 로직</p>
<p>  매칭 시작 시, 자신의 MMR에 맞는 대기실이 없다면, 자신의 MMR을 기준으로 대기방을 만들고, 매치가 이루어지는 MMR의 위 아래 최대 범위(ex. MMR -100 ~ MMR + 100)에 해당하는 유저를 받아들일 수 있도록 설정한다.</p>
<p>  자신의 MMR에 맞는 대기실에 있다면 해당 대기실로 입장하게 된다.</p>
<p>  이 때, 블루 팀과 레드 팀의 MMR 평균의 오차를 일정 수치 이상(ex. 50이상) 차이 나지 않도록 하게 플레이어들을 입장시킨다.</p>
<p>  지금까지 접속한 해당 MMR에 있는 인원수에 따른, 해당 MMR에서의 평균 유저매칭시간을 플레이어에게 제공하고, 평균 유저매칭시간을 넘길 시, 동 MMR의 다른 포지션의 플레이어를 검색하여 게임 대기열에 추가하여 유저매칭시간이 너무 길어지지 않도록 조절한다.</p>
<p>  유저가 10명이 모두 대기실에 입장하였다면 게임 수락 버튼이 10명에게 모두 전달된다.</p>
<p>  모두 수락하였다면, 게임의 벤/픽 단계를 진행 후, 게임을 시작한다.</p>
</li>
</ul>
<hr>
<h1 id="매칭-플로우">매칭 플로우</h1>
<h3 id="원하는-종류의-대기열-선택">원하는 종류의 대기열 선택</h3>
<ul>
<li>PVP 소환사의 협곡
<img src="https://velog.velcdn.com/images/wanna_make_game/post/bc7b4459-5c62-42fc-bc84-88e65319b6b0/image.PNG" alt=""></li>
</ul>
<ul>
<li>PVP 무작위 총력전
<img src="https://velog.velcdn.com/images/wanna_make_game/post/7d28bd93-ac71-4ed7-8d6f-53673a0b5f8f/image.PNG" alt=""></li>
</ul>
<ul>
<li>AI 상대 대전
<img src="https://velog.velcdn.com/images/wanna_make_game/post/23142031-220e-4959-8ac9-2909c03e8651/image.PNG" alt=""></li>
</ul>
<h3 id="게임-시작을-통해-대기열-참가">게임 시작을 통해 대기열 참가</h3>
<ul>
<li>자신의 MMR에 맞는 대기실 검색 후 입장
<img src="https://velog.velcdn.com/images/wanna_make_game/post/32b0e420-088f-43e9-9377-664ef9c46474/image.PNG" alt=""></li>
</ul>
<h3 id="수락을-통한-게임-입장">수락을 통한 게임 입장</h3>
<ul>
<li>수락 버튼 클릭 후 게임 입장</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 드래그 앤 드롭 시스템 만들기: ScrollView를 반영한 드래그 앤 드롭 시스템]]></title>
            <link>https://velog.io/@wanna_make_game/Unity-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-ScrollView%EB%A5%BC-%EB%B0%98%EC%98%81%ED%95%9C-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@wanna_make_game/Unity-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-ScrollView%EB%A5%BC-%EB%B0%98%EC%98%81%ED%95%9C-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Mon, 16 Dec 2024 16:11:33 GMT</pubDate>
            <description><![CDATA[<p>2024.12.16(월)</p>
<p>이전 글: <a href="https://velog.io/@wanna_make_game/Unity-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-UI-to-World-World-to-World">[Unity] 드래그 앤 드롭 시스템 만들기 : UI to World &amp; World to World</a></p>
<p>이전에 만들었던 드래그 앤 드롭 시스템에 사소한 문제가 있었다.
바로, 드래그 앤 드롭 시스템이 ScrollView의 Scroll 시스템을 가린다는 것이다.
찾아보니까 ScrollView의 시스템도 IBeginDragHandler, IDragHandler, IEndDragHandler에 의해 조절이 된다는 것을 알게 됐다.</p>
<p>그래서, 이것을 적절히 이용해서 드래그 앤 드롭 시스템을 ScrollView의 영역을 나갔을 때만 발동되게 만들어 주었다.</p>
<h1 id="scrollview를-반영한-드래그-앤-드롭-시스템">ScrollView를 반영한 드래그 앤 드롭 시스템</h1>
<p>UI에서만 일어나는 일이기 때문에, DraggableUI만 손봐주면 된다.</p>
<ul>
<li>DraggableUI.cs<pre><code class="language-cs">using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
</code></pre>
</li>
</ul>
<p>public class DraggableUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public DraggableContent draggableContent; // 연결된 콘텐츠 설정
    [SerializeField] private ScrollRect scrollView; // ScrollView 참조 (Inspector에서 설정)</p>
<pre><code>private bool isDragging = false; // 드래그 상태 확인
private bool isInScrollView = false; // 현재 스크롤 영역 내부인지 확인
private PointerEventData cachedEventData; // 스크롤 이벤트 캐싱용

public void OnBeginDrag(PointerEventData eventData)
{
    // ScrollView 영역인지 확인
    if (scrollView != null)
    {
        isInScrollView = RectTransformUtility.RectangleContainsScreenPoint(
            scrollView.GetComponent&lt;RectTransform&gt;(),
            eventData.position,
            eventData.pressEventCamera
        );

        if (isInScrollView)
        {
            // ScrollView 내부라면 스크롤 이벤트를 캐싱하고 ScrollView의 OnBeginDrag 호출
            cachedEventData = eventData;
            scrollView.OnBeginDrag(eventData);
            return;
        }
    }

    // ScrollView 외부라면 드래그 시작
    isDragging = true;
    DragAndDropManager.Instance.BeginDrag(draggableContent, eventData);
}

public void OnDrag(PointerEventData eventData)
{
    // 상태 전환 로직: 마우스 위치를 기반으로 상태 변경
    if (scrollView != null)
    {
        bool currentlyInScrollView = RectTransformUtility.RectangleContainsScreenPoint(
            scrollView.GetComponent&lt;RectTransform&gt;(),
            eventData.position,
            eventData.pressEventCamera
        );

        // ScrollView에서 Drag로 전환
        if (isInScrollView &amp;&amp; !currentlyInScrollView)
        {
            isInScrollView = false;
            isDragging = true;

            // ScrollView 동작 종료
            scrollView.OnEndDrag(cachedEventData);

            // Drag 동작 시작
            DragAndDropManager.Instance.BeginDrag(draggableContent, eventData);
            return;
        }

        // Drag에서 ScrollView로 전환
        if (!isInScrollView &amp;&amp; currentlyInScrollView)
        {
            isInScrollView = true;
            isDragging = false;

            // Drag 동작 종료
            DragAndDropManager.Instance.EndDrag(eventData);

            // ScrollView 동작 시작
            scrollView.OnBeginDrag(eventData);
            return;
        }
    }

    // 현재 상태에 따라 적절한 동작 수행
    if (isInScrollView)
    {
        scrollView.OnDrag(eventData); // ScrollView의 스크롤 처리
    }
    else if (isDragging)
    {
        DragAndDropManager.Instance.Drag(eventData); // Drag 처리
    }
}

public void OnEndDrag(PointerEventData eventData)
{
    // 현재 상태에 따라 동작 종료
    if (isInScrollView)
    {
        scrollView.OnEndDrag(eventData);
    }
    else if (isDragging)
    {
        DragAndDropManager.Instance.EndDrag(eventData);
    }

    // 상태 초기화
    isDragging = false;
    isInScrollView = false;
    cachedEventData = null;
}</code></pre><p>}</p>
<pre><code>
다음과 같이 작성하면 ScrollView 내에서는 드래그로 ScrollView가 작동하고, ScrollView를 벗어나면 드래그 앤 드롭 시스템이 작동, 그리고 다시 ScrollView 내부로 마우스가 들어오면 ScrollView가 작동하게 된다.
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] ScrollView와 Content의 자동화 시스템]]></title>
            <link>https://velog.io/@wanna_make_game/Unity-ScrollView%EC%99%80-Content%EC%9D%98-%EC%9E%90%EB%8F%99%ED%99%94-%EC%8B%9C%EC%8A%A4%ED%85%9C</link>
            <guid>https://velog.io/@wanna_make_game/Unity-ScrollView%EC%99%80-Content%EC%9D%98-%EC%9E%90%EB%8F%99%ED%99%94-%EC%8B%9C%EC%8A%A4%ED%85%9C</guid>
            <pubDate>Thu, 12 Dec 2024 13:57:39 GMT</pubDate>
            <description><![CDATA[<p>2024.12.12(목)</p>
<p>오늘 오전에 팀원분들과 데이터 테이블 변경점에 대한 회의를 진행하고, 오후에 작업을 진행했다.
오늘 작업한 내용은 ScrollView와 Content의 자동화...라고 할 수 있겠다.</p>
<h1 id="scrollview와-content의-자동화-시스템">ScrollView와 Content의 자동화 시스템</h1>
<h3 id="사용할-scrollview의-구조">사용할 ScrollView의 구조</h3>
<p>이번 글에서는 scrollView_OwnedKnights라는 ScrollView를 이용해 예제를 진행하겠다.
<img src="https://velog.velcdn.com/images/wanna_make_game/post/a8e30f86-39b4-4f90-8072-e03d9c5f3904/image.PNG" alt=""></p>
<h2 id="uicontent">UIContent</h2>
<h3 id="content-틀">Content 틀</h3>
<p>먼저 UIContent.cs라는  코드를 작성하였다.
이 코드는 ScrollView의 Content로 활용되는 Prefab에 부착될 스크립트의 부모이다.</p>
<p>자체적으로 바로 쓸 수 있는 클래스는 아니기 때문에, 추상클래스로 작성하였다.</p>
<p>UIContent의 제네릭으로 IData 타입의 데이터를 가져오는 <code>SetData</code> 메서드를 만들었다. 이 메서드를 다른 곳에서 호출하면, 이 Content에 해당 데이터가 들어오게 된다.</p>
<p>KnightDefault는 현재 IData를 상속받아 Knight의 데이터를 저장하는 데이터 형식이다.
데이터의 양은 많지만, 참조 형식의 복사를 통해 데이터를 가져오게 되서 깊은 복사가 일어나지 않기 때문에, 메모리적으로 큰 문제는 없을 듯하다.</p>
<ul>
<li>UIContent.cs<pre><code class="language-cs">using UnityEngine;
</code></pre>
</li>
</ul>
<p>public abstract class UIContent<T> : MonoBehaviour where T : IData
{
    protected string dataID;</p>
<pre><code>protected T data;

// 상속받는 content에서 override해서
// content의 ui들에 데이터 삽입해서 사용
public virtual void SetData(T inputData)
{
    data = inputData;
    dataID = data.ID;
}</code></pre><p>}</p>
<pre><code>- IData
```cs
public interface IData
{
    string ID { get; }
}</code></pre><ul>
<li>KnightDefault<pre><code class="language-cs">[Serializable]
public class KnightDefault : IData
{
  public string ID { get; set; }
  public string NameEn { get; set; }
  public string NameKr { get; set; }
  public string Description { get; set; }
  public int Rank { get; set; }
  public string Prefeb { get; set; }
  public string Thumbnail { get; set; }
  public float AttackPower { get; set; }
  public float AttackSpeed { get; set; }
  public float Health { get; set; }
  public float Defense { get; set; }
  public float MoveSpeed { get; set; }
  public float AttackRange { get; set; }
  public int Leadership { get; set; }
  public int Intelligence { get; set; } 
  public int Loyalty { get; set; }
  public int DownPayment { get; set; }
  public int Salary { get; set; }
  public int TrainingLevel { get; set; }
  public int Experience { get; set; }
  public float TrainingWeighted { get; set; }
  public float ExperienceWeighted { get; set; }
}</code></pre>
<h3 id="content-구현">Content 구현</h3>
틀로 만들어 놓은 Content의 구현 부분이다.</li>
</ul>
<p>UIContent를 상속받아 제네릭으로 Content의 데이터 형식에 맞게 클래스를 보내준다.
그리고, <code>[SerializeField]</code>로 선언된 변수들을 인스펙터 창에서 연결을 해 주고, <code>SetData</code> 메서드를 <code>override</code> 및 <code>base.Setdata</code>를 실행하여, Content의 데이터들을 넣어준다.</p>
<p>이렇게 하면, ScrollView에서 각 Content를 생성하고, Content 별로 <code>SetData</code>만 실행해주면 알아서 해당 데이터가 적용된 Content를 만들 수 있게 된다.</p>
<p>Content 구현 클래스의 이름은, 현재, UI의 스크립트와 Hirerachy창에 있는 오브젝트의 이름이 통일되게 작성해주고 있기 때문에, 앞 부분이 소문자가 되었다.</p>
<ul>
<li>btn_OwnedKnight의 Inspector 창
<img src="https://velog.velcdn.com/images/wanna_make_game/post/12094ba0-a519-40f9-b10d-6852c2f589ef/image.PNG" alt=""></li>
</ul>
<ul>
<li>btn_OwnedKnight.cs<pre><code class="language-cs">using TMPro;
using UnityEngine;
using UnityEngine.UI;
</code></pre>
</li>
</ul>
<p>public class btn_OwnedKnight : UIContent<KnightDefault>
{
    [SerializeField] private Image thumbnailKnight;
    [SerializeField] private TMP_Text txtKnightName;</p>
<pre><code>public void OnClickBtn()
{
    UIManager.Instance.Show&lt;UI_PopupKnightInfo&gt;();
}

public override void SetData(KnightDefault inputData)
{
    base.SetData(inputData);
    //썸네일은 아직 파일이 없기도 하고,
    //경로로 저장된 파일을 로드해오는 과정이 필요하기 때문에, 아직 구체적으로 적지 않았다.
    //thumbnailKnight = data.Thumbnail; 
    txtKnightName.text = data.NameKr;
}</code></pre><p>}</p>
<pre><code>

## UIScrollView
### ScrollView 틀
다음은 ScrollView 스크립트의 틀이다.
UIContent와 마찬가지로 추상클래스로 작성하였다.

`SetScrollView` 메서드를 통해, ScrollView를 통해 보여줄 dataList를 가져오고, 이 데이터를 이용해서, 지정된 uiContentParent 오브젝트에 uiContentPrefab의 형태의 Content들을 생성하고 데이터를 세팅해준다.

이 때, uiContentPool 이라는 리스트를 만들어서 생성한 UIContent 인스턴스들과 연결하고 관리할 수 있게 했다.
`SetActive` true, false를 통해 자체 풀링 시스템을 만들어서 dataList의 데이터가 삭제되거나 추가되었을 때, 넘치는 인스턴스들을 `SetActive` true로 만들어서 꺼내 쓸 수 있다.

`ClearScrollView` 메서드를 이용해, 모든 UIContent를 비활성화하고, dataList를 초기화할 수 있다. 그런데, 이 메서드를 쓸 일이 있을까...? 싶다.

- UIScrollView.cs
```cs
using System.Collections.Generic;
using UnityEngine;

public abstract class UIScrollView&lt;T&gt; : MonoBehaviour where T : IData
{
    protected List&lt;T&gt; dataList;

    [SerializeField] protected Transform uiContentParent;
    [SerializeField] protected UIContent&lt;T&gt; uiContentPrefab;

    // 생성된 UIContent 인스턴스 목록 (Pooling): +생성된 UIContent제어
    private List&lt;UIContent&lt;T&gt;&gt; uiContentPool = new List&lt;UIContent&lt;T&gt;&gt;();

    /// &lt;summary&gt;
    /// ScrollView를 설정합니다.
    /// &lt;/summary&gt;
    public void SetScrollView(List&lt;T&gt; newDataList)
    {
        // 데이터 리스트 업데이트
        dataList = newDataList;

        // 기존 UIContent 재사용 또는 초기화
        for (int i = 0; i &lt; dataList.Count; i++)
        {
            UIContent&lt;T&gt; uiContent;

            // Pool에 이미 생성된 UIContent가 있으면 재사용
            if (i &lt; uiContentPool.Count)
            {
                uiContent = uiContentPool[i];
                uiContent.gameObject.SetActive(true); // 활성화
            }
            else
            {
                // Pool에 없는 경우 새로 생성
                uiContent = Instantiate(uiContentPrefab, uiContentParent);
                uiContentPool.Add(uiContent);
            }

            // 데이터 설정
            uiContent.SetData(dataList[i]);
        }

        // 사용되지 않는 Pool 객체 비활성화
        for (int i = dataList.Count; i &lt; uiContentPool.Count; i++)
        {
            uiContentPool[i].gameObject.SetActive(false); // 비활성화
        }
    }

    /// &lt;summary&gt;
    /// ScrollView를 초기화하여 모든 데이터를 제거합니다.
    /// &lt;/summary&gt;
    public void ClearScrollView()
    {
        // Pool에 있는 모든 UIContent 비활성화
        foreach (var uiContent in uiContentPool)
        {
            uiContent.gameObject.SetActive(false);
        }

        // 데이터 리스트 초기화
        dataList.Clear();
    }

}</code></pre><h3 id="scrollveiw-구현">ScrollVeiw 구현</h3>
<p>아직은 특별히 ScrollView만의 로직이 따로 필요 없다.
StarteySettingSystem과 UI_StrategySetting 스크립트를 이용해, 데이터를 리스트에 빼고 넣는 로직이 작성될 것이고, 여기 ScrollView에서는 <code>SetScrollView</code> 메서드를 이용해 데이터를 넣는 것만 하면 된다.</p>
<p>ScrollView 클래스의 이름은 Content 구현 클래스와 마찬가지의 이유로 다음과 같이 작성되었다.</p>
<ul>
<li>scrollView_OwnedKnights의 Inspector 창
<img src="https://velog.velcdn.com/images/wanna_make_game/post/107ced9e-7694-4fe8-becb-2abca4f04d00/image.PNG" alt=""></li>
</ul>
<ul>
<li>scrollView_OwnedKnights.cs<pre><code class="language-cs">public class scrollView_OwnedKnights : UIScrollView&lt;KnightDefault&gt;
{
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
## ScrollView에 데이터 집어넣기
UI_StrategySetting 이라는 UI에 scrollView_OwnedKnights 가 존재하기 때문에 UI_StrategySetting 에서 ScrollView에 데이터를 집어넣는 작업을 수행할 것이다.

여기선, 다음 작업 이외의 것들은 신경쓰지 않아도 된다.

앞서 만든 scrollView_OwnedKnights라는 ScrollView 구현 클래스를 [SerializeField]를 이용해 인스펙터 창으로 연결한다.
그리고, `SetKnightData`라는 메서드를 만들어서 해당 ScrollView에 데이터를 넣어준다. 이 메서드에선 아까 만든 `SetScrollVeiw`라는 메서드만 호출해주면 된다.

이제, 마지막으로 해당 메서드를 실제 데이터가 있는 곳인 StrategySettingSystem 에서 호출해주면 된다.

- UI_StrategySetting.cs
```cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_StrategySetting : MonoBehaviour
{
    [SerializeField] private GameObject mapInspectionCamera;
    [SerializeField] private GameObject strategySettingCamera;
    [SerializeField] private GameObject uiMapInspection;
    [SerializeField] private GameObject uiStrategySetting;

    [SerializeField] private GameObject uiPopupBattleStart;

    Camera cameraMapInspectionCamera;

    [SerializeField] scrollView_OwnedKnights scrollViewOwnedKnights;


    private void Start()
    {
        cameraMapInspectionCamera = mapInspectionCamera.GetComponent&lt;Camera&gt;();
    }

    public void OnClickBattleStart()
    {
        uiPopupBattleStart.SetActive(true);
        //UIManager.Instance.Show&lt;UI_PopupBattleStart&gt;();
    }

    public void OnClickReturnToMapInspection()
    {
        mapInspectionCamera.SetActive(true);
        strategySettingCamera.SetActive(false);
        uiMapInspection.SetActive(true);
        uiStrategySetting.SetActive(false);
        DragAndDropManager.Instance.SetWorldDragCamera(cameraMapInspectionCamera);
    }

    public void SetKnightData(List&lt;KnightDefault&gt; knightDataLists)
    {
        scrollViewOwnedKnights.SetScrollView(knightDataLists);
    }
}</code></pre><p>다음은 StrategySettingSystem이다. 여기서, 전략 설정에 대한 메서드가 진행될 것이다.</p>
<p>일단은, ScrollView가 잘 작동하는 지 확인하기 위해, UI_StrategySetting 오브젝트를 public으로 선언해 인스펙터 창으로 연결해 주었다. (좀 더 괜찮은 방법이 있을 것 같은데 찾아볼 예정이다.)</p>
<p>그리고, 맨 아래의 <code>Start</code> 부분에서 UI_StrategySetting 스크립트에서 작성한 SetKnightData를 호출해서 데이터를 넘겨주었다.</p>
<ul>
<li><p>StrategySettingSystem.cs</p>
<pre><code class="language-cs">using System.Collections.Generic;
using System.Text;
using UnityEngine;
// 전략 설정 시스템
public class StrategySettingSystem : MonoBehaviour
{
  DomainArmy domainArmy;
  // 전략리스트 
  List&lt;ArmyStrategy&gt; strategyList;
  // 부대임무리스트
  List&lt;UnitDivisionRole&gt; unitDivisionRoleDataList;
  // 영주
  ArmyLord domainArmyLord;
  // 기사리스트
  List&lt;ArmyKnight&gt; knightList;
  // 병종리스트
  List&lt;ArmyUnitType&gt; unitTypeList;

  Strategy currentStrategy;

  // 임시로 ui와 연결
  public UI_StrategySetting uI_StrategySetting;

  public ArmyLord DomainArmyLord
  {
      get { return domainArmyLord; }
      private set { domainArmyLord = value; }
  }
  public List&lt;ArmyKnight&gt; KnightList
  {
      get { return knightList; }
      private set { knightList = value; }
  }
  public List&lt;ArmyUnitType&gt; UnitTypeList
  {
      get { return unitTypeList; }
      private set { unitTypeList = value; }
  }
  public List&lt;ArmyStrategy&gt; StrategyList
  {
      get { return strategyList; }
      private set { strategyList = value; }
  }

  public Strategy CurrentStrategy
  {
      get { return currentStrategy; }
      private set { currentStrategy = value; }
  }

  private void Awake()
  {
      if (TryGetComponent&lt;DomainArmy&gt;(out domainArmy))
      {
          Debug.Log($&quot;영지 -&gt; 전략설정 {typeof(DomainArmy).Name} 로드 성공!&quot;);
      }
      else
      {
          Debug.LogError($&quot;영지 -&gt; 전략설정 {typeof(DomainArmy).Name} 로드 실패!&quot;);
      }
  }

  // DomainArmy의 데이터를 참조해서 설정
  public void SetStrategySettingSystemData()
  {
      domainArmyLord = domainArmy.DomainArmyLord;
      strategyList = domainArmy.StrategyList;
      unitDivisionRoleDataList = domainArmy.UnitDivisionRoleDataList;
      knightList = domainArmy.KnightList;
      unitTypeList = domainArmy.UnitTypeList;
  }
  // 데이터가 잘 들어갔는지 확인
  public void LogStrategySettingSystemData()
  {
      StringBuilder sb = new StringBuilder();
      sb.Append(&quot;로드: &quot;);
      sb.Append(domainArmyLord.LordData.Name);
      sb.Append(&quot;\n&quot;);
      sb.Append(&quot;전략 시스템&#39;s \n전략 목록: &quot;);
      for (int i = 0; i &lt; strategyList.Count; i++)
      {
          sb.Append(strategyList[i].StrategyData.Name);
          sb.Append(&quot;, &quot;);
      }
      sb.Append(&quot;\n&quot;);
      sb.Append(&quot;부대 임무 목록: &quot;);
      for (int i = 0; i &lt; unitDivisionRoleDataList.Count; i++)
      {
          sb.Append(unitDivisionRoleDataList[i].Name);
          sb.Append(&quot;, &quot;);
      }
      sb.Append(&quot;\n&quot;);
      sb.Append(&quot;기사 목록: &quot;);
      for (int i = 0; i &lt; knightList.Count; i++)
      {
          sb.Append(knightList[i].KnightData.NameKr);
          sb.Append(&quot;, &quot;);
      }
      sb.Append(&quot;\n&quot;);
      sb.Append(&quot;병종 목록: &quot;);
      for (int i = 0; i &lt; unitTypeList.Count; i++)
      {
          sb.Append(unitTypeList[i].UnitTypeData.NameKr);
          sb.Append(&quot;, &quot;);
      }
      sb.Append(&quot;\n&quot;);
      Debug.Log(sb);
  }

</code></pre>
</li>
</ul>
<pre><code>// UI의 반응과 연계하여 전략 설정 및 저장

private void Start()
{
    List&lt;KnightDefault&gt; knightDataList = new List&lt;KnightDefault&gt;();
    for (int i = 0; i &lt; knightList.Count; i++)
    {
        knightDataList.Add(knightList[i].KnightData);
    }
    uI_StrategySetting.SetKnightData(knightDataList);
}</code></pre><p>}</p>
<pre><code>
## ScrollView 시연
위의 과정을 거치면 다음과 같이 ScrollView에 데이터가 들어간 Content들이 들어가는 것을 볼 수 있다.
![](https://velog.velcdn.com/images/wanna_make_game/post/74000a17-0bba-40a2-83e7-803970984b3d/image.gif)



오늘은 ScrollView와 Content의 틀을 만들어서 데이터 리스트를 넣어주면, 자동으로 ScrollView가 완성되게 만들어 보았다.
앞으로도 자주 쓰이게 될 ScrollView를 이제 어느정도 쉽게 쓸 수 있을 것 같다.

이상, 오늘의 TIL 끝..!


</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[건강 관리: 박용우 원장님의 체질 개선 한 달 프로젝트]]></title>
            <link>https://velog.io/@wanna_make_game/%EA%B1%B4%EA%B0%95-%EA%B4%80%EB%A6%AC-%EB%B0%95%EC%9A%A9%EC%9A%B0-%EC%9B%90%EC%9E%A5%EB%8B%98%EC%9D%98-%EC%B2%B4%EC%A7%88-%EA%B0%9C%EC%84%A0-%ED%95%9C-%EB%8B%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</link>
            <guid>https://velog.io/@wanna_make_game/%EA%B1%B4%EA%B0%95-%EA%B4%80%EB%A6%AC-%EB%B0%95%EC%9A%A9%EC%9A%B0-%EC%9B%90%EC%9E%A5%EB%8B%98%EC%9D%98-%EC%B2%B4%EC%A7%88-%EA%B0%9C%EC%84%A0-%ED%95%9C-%EB%8B%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8</guid>
            <pubDate>Wed, 11 Dec 2024 07:40:11 GMT</pubDate>
            <description><![CDATA[<p>2024.12.11(수)</p>
<p>오늘은 컨디션이 좋지 않아 병원도 다녀오고 일찍 조퇴를 했다.
점점 느끼는 거지만, 날이 갈수록 건강이 좋지 않아지는 게 느껴지는데, 총체적 난국이라 어디가 어떻게 문제인지 잘 파악이 안된다.</p>
<p>아무래도 식습관 개선과 운동을 통해 체질 개선을 해야될 듯 싶은데, 팀원분이 알려주신 한 달(4주) 체질 개선 프로젝트가 좋아 보여, 실행을 해 보려고 한다.</p>
<p>오늘은 건강관리에 관한 TIL이다...!</p>
<h2 id="박용우-원장님의-체질-개선-한-달-프로젝트">박용우 원장님의 체질 개선 한 달 프로젝트</h2>
<h3 id="박용우-박사님-소개">박용우 박사님 소개</h3>
<ul>
<li>강북삼성병원 건강의학본부 교수</li>
<li>가정의학과 전문의</li>
<li>&#39;내 몸 혁명&#39; 저자</li>
</ul>
<p>이 프로젝트의 목적은 기간을 정해두지 않고, 한 없이 다이어트를 하게 되면 지쳐서 포기하게 되기 때문에, 한 달이라는 기간을 정해두고 프로젝트를 진행하는 것이 관건이다.
(물론, 본인은 다이어트 보다는 장 건강을 위한 체질 개선이 목적이다. 다이어트도 되면 1석 2조!)</p>
<p>참고 링크: <a href="https://www.youtube.com/watch?v=z4dU3b_-l8I&amp;list=LL&amp;index=2">지식한상 Youtube: &quot;가만히 있어도 살이 빠진다&quot; 살 안찌는 체질이 되는 구체적인 방법 &#39;1가지&#39;</a></p>
<p>1주일 단위로 스케줄을 전달해주셔서 그에 맞춰서 정리해 보았다.</p>
<hr>
<h1 id="4주-건강-프로그램">4주 건강 프로그램</h1>
<p>프로젝트를 하면서 지켜야 할 것</p>
<ul>
<li>숙면을 잘 취해야 한다. (커피X - 커피를 마시게 된다면 오전에만 블랙으로)</li>
<li>운동을 같이 해주면 좋다. (효과가 배로 된다.)</li>
<li>금기음식 먹지 말기. (설탕, 밀가루, 과일)</li>
</ul>
<hr>
<h2 id="1주차">1주차</h2>
<p>3일 단백질 쉐이크 4회식사, 4일차부터 점심 일반식, 나머지 단백질 쉐이크</p>
<ul>
<li><p>일반식: 큰 볼의 1/2 - 채소, 1/4 - 계란2개나 두부 한모, 1/4 - 잡곡밥 으로 구성하여 섭취</p>
</li>
<li><p>커피 섭취 X (기왕이면 4주동안 안 섭취하는 것을 권하긴 하지만, 어쩔 수 없이 필요하기 때문에 첫 주만 금한다. 먹어야 한다면, 블랙커피로 오전에 마시는 것을 권장)</p>
</li>
</ul>
<h3 id="3일차까지">3일차까지</h3>
<p>단백질  쉐이크로 하루 4회 식사 섭취. (채소, 두부 허용)</p>
<ul>
<li>아침, 점심, 오후 간식, 저녁</li>
</ul>
<p>-&gt; 탄수화물에 제약을 줘서, 지방을 끌어쓰는 몸으로 만든다.
두부로 단백질 섭취도 좋다.</p>
<p>운동도 해주면 좋다. 근육에서 단백질을 끌어쓰는 체질도, 좀 더 빠르게 지방을 끌어쓰는 체질로 변화시킨다.</p>
<h3 id="4일차부터">4일차부터</h3>
<p>점심 일반식 나머지 단백질 쉐이크</p>
<hr>
<h2 id="2주차">2주차</h2>
<h3 id="식사">식사</h3>
<ul>
<li>아침, 오후 간식 : 단백질 쉐이크</li>
<li>점심, 저녁 : 일반식</li>
</ul>
<h3 id="규칙">규칙</h3>
<p>탄수화물은 밥의 형태로 점심 한 끼 섭취 (잡곡밥)</p>
<ul>
<li><p>간헐적 단식 1회 실시 : 24시간 간헐적 단식</p>
</li>
<li><p>금기음식: 설탕, 밀가루, 과일 - 중독성이 강함</p>
</li>
<li><p>좋아하는 음식이 댕기는 시기인데, 플레인 요거트나, 양배추로 달래기.
콩류나 견과류는 허용.</p>
</li>
</ul>
<hr>
<h2 id="3주차">3주차</h2>
<p>2주차의 반복. (근육이 빠졌을 경우 2주차 반복 - 상태봐서 결정하면 될 듯하다.)</p>
<h3 id="추가-규칙">추가 규칙</h3>
<p>간헐적 단식 2회 실시 - 24시간 간헐적 단식
(이어서 하면 안되고, 중간에 잘 챙겨먹는 과정이 필요)</p>
<p>탄수화물 허용량 증가
단호박, 토마토, 블루베리나 베리류 허용
고구마도 하루 한 개 정도 허용</p>
<ul>
<li>토마토는 좋은 음식이지만, 달달해서 많이 섭취할 우려가 있기 때문에 2주차까지는 금했다.</li>
</ul>
<hr>
<h2 id="4주차">4주차</h2>
<p>3주차의 반복. (약간 상속 느낌이 나서 재밌다.)</p>
<h3 id="추가-규칙-1">추가 규칙</h3>
<p>3주차까지 잘 수행해서 근육량이 회복이 되었다면, 4주차에서 간헐적 단식 3회를 실시해도 좋다.</p>
<p>그렇지만, 근육량이 감소하거나 그대로라면 3주차의 반복을 시행한다.</p>
<hr>
<h2 id="4주차-이후">4주차 이후...</h2>
<p>더 하고 싶다면, 더 해도 된다.
하지만, 더 시행을 하는데도 근육량 개선이 없다면, 유지기로 넘어가는 것이 좋다.</p>
<p>그리고, 1년에 한 번만(1달) 이 프로젝트를 진행하면 된다. (사람에 따라 다르겠지만, 그 정도만 해도 충분할 것이다.)</p>
<hr>
<p>이 프로젝트를 진행해서 체질 개선을 시도해 보려고 한다.
설명해주신 내용 중에는 그동안 알게 모르게 알고 있던 건강지식도 있었지만, 스케줄을 제공해주시면서 그 과정에서 지켜야 할 사항과 그 이유에 대해 설명해주셔서 지식들을 정리하는 데 도움이 되었다.</p>
<p>이제부터 이 체질 개선 프로젝트의 경과를 1주일마다 기록해 두고자 한다.
건강하고, 멋진 몸을 가지고 싶다.</p>
<p>오늘의 TIL 끝.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Unity] 드래그 앤 드롭 시스템 만들기 : UI to World & World to World]]></title>
            <link>https://velog.io/@wanna_make_game/Unity-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-UI-to-World-World-to-World</link>
            <guid>https://velog.io/@wanna_make_game/Unity-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-UI-to-World-World-to-World</guid>
            <pubDate>Mon, 09 Dec 2024 14:44:08 GMT</pubDate>
            <description><![CDATA[<meta name="google-site-verification" content="B-rJWG9SirilfVai5vJy1Kkhw91OTgRbKqdrksfG4iY" />
2024.12.09(월)
월월...

<p>오늘은 드래그 앤 드롭 시스템을 만들어 보았다.</p>
<h1 id="드래그-앤-드롭-시스템">드래그 앤 드롭 시스템</h1>
<p>먼저 Unity에서 UI에서의 드래그 앤 드롭을 구현할 때, Unity에서 지원하는 <code>IBeginDragHandler</code>, <code>IDragHandler</code>, <code>IEndDragHandler</code>와 같은 인터페이스를 사용해서 구현할 수 있다. </p>
<p><a href="https://docs.unity3d.com/kr/2022.1/Manual/SupportedEvents.html">Unity Documentation: Unity UI 지원되는 이벤트 메뉴얼</a></p>
<p>나는 UI -&gt; World, World -&gt; World 에 해당하는 드래그 앤 드롭 시스템을 만들었다.</p>
<hr>
<h2 id="draggableui">DraggableUI</h2>
<p>Unity에서 Drag and Drop을 위해 지원하는 3개의 인터페이스는 각각 다음과 같은 역할을 한다.</p>
<ul>
<li><code>IBeginDragHandler</code>
OnBeginDrag - 드래그가 시작되는 시점에 드래그 대상 오브젝트에서 호출</li>
<li><code>IDragHandler</code>
OnDrag - 드래그 오브젝트가 드래그되는 동안 호출</li>
<li><code>IEndDragHandler</code>
OnEndDrag - 드래그가 종료됐을 때 드래그 오브젝트에서 호출</li>
</ul>
<p>다음은 위 3개의 인터페이스를 이용해 만든 DraggableUI script이다. 해당 script를 드래그 가능하게 하고 싶은 UI object에 추가하면, 해당 UI object에서 프리펩이 World로 나오게 된다.</p>
<ul>
<li>DraggableUI.cs<pre><code class="language-cs">using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
</code></pre>
</li>
</ul>
<p>public class DraggableUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public DraggableContent draggableContent; // 연결된 콘텐츠 설정</p>
<pre><code>public void OnBeginDrag(PointerEventData eventData)
{
    DragAndDropManager.Instance.BeginDrag(draggableContent, eventData);
}

public void OnDrag(PointerEventData eventData)
{
    DragAndDropManager.Instance.Drag(eventData);
}

public void OnEndDrag(PointerEventData eventData)
{
    DragAndDropManager.Instance.EndDrag(eventData);
}</code></pre><p>}</p>
<pre><code>그리고, DraggableContent라는 scriptableObject를 DraggableUI에 달아주면, 이 scriptableObject에 달린 prefab이 Drag할 때, 생성되게 된다.
생성되는 과정은 DragAndDropManager에 작성이 되어 있다.
- DraggableContent.cs
```cs
using UnityEngine;

[CreateAssetMenu(menuName = &quot;Draggable Content&quot;)]
public class DraggableContent : ScriptableObject
{
    public GameObject prefab; // 드래그 후 생성할 프리팹
    public string contentName; // 콘텐츠 이름
    public Sprite icon; // UI 아이콘
    public string additionalData; // 추가 데이터 (설명 등)
}</code></pre><p>여기서 <code>public GameObject prefab;</code>을 제외한 코드는 딱히 아직까지 의미가 있진 않다.</p>
<hr>
<h2 id="draganddropmanager">DragAndDropManager</h2>
<p>아래는 DragAndDropManager다. 팀원분이 만들어주신 제네릭을 사용한 싱글톤 베이스 클래스를 상속받아 싱글톤 패턴을 적용했다. (제네릭을 모르는 분들은, 그냥, static instance 있는 매니저 하나 만들었다고 생각하면 된다.)</p>
<p><code>BeginDrag</code>, <code>Drag</code>, <code>EndDrag</code> 는 각각 드래그 앤 드롭의 드래그 시작, 드래그 중, 드래그 끝일 때의 기능에 대한 메서드다. draggableContent가 null인지 아닌지로 UI to World인지, World to World인지 구분해서 작성할 수 있으며, <code>BeginWorldDrag</code>는 후술하겠지만, World to World 드래그 앤 드랍에 사용되는 드래스 시작 메서드이다. (World to World 드래그 앤 드랍은 <code>DraggableWorldObject.cs</code> 를 통해 동작한다)</p>
<p><code>SetWorldDragCamera</code> 메서드는 드래그가 실행될 월드를 비추는 카메라를 지정해줄 수 있는 메서드다. 아무래도, 카메라를 기준으로 마우스와 월드가 소통을 하게 되는데, 이 메서드를 통해, Scene 전환이나 UI 전환 시 필요한 카메라가 바뀔 때, 이 메서드를 호출해서 카메라를 유동적으로 바꿀 수 있다.</p>
<p><code>GetWorldPosition</code>를 이용해서 UI 상의 좌표를 World 좌표로 변환하여 맵핑한다.</p>
<p><code>UpdateObjectPositionWithGround</code>를 이용해, Ground Layer의 y값을 기준으로 프리펩을 생성한다. 이렇게 하지 않으면, 프리펩의 y값이 들쭉날쭉하게 변하는 문제가 있다.</p>
<p>그리고, DropZone Tag를 만들어서, 드래그하는 동안 DropZone에 들어오면 프리펩을 초록색으로, 나가면 빨간색으로 만들어 주는 기능을 추가했다. <code>UpdateObjectColor</code>를 통해 프리팹의 색이 초록색과 빨간색으로 바뀌고, <code>ResetObjectColor</code>를 통해 EndDrag 시 원래의 색상으로 돌아온다.</p>
<p>또한, DropZone 이외의 곳에 배치하게 되면 해당 오브젝트는 파괴된다. (TODO : 파괴가 아닌 오브젝트 풀링으로 전환할 수도 있다. and 파괴 시 원래 UI의 컨텐트에 다시 복구될 수 있도록 하는 로직이 필요하다.)</p>
<ul>
<li>DragAndDropManager.cs<pre><code class="language-cs">using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
</code></pre>
</li>
</ul>
<p>public class DragAndDropManager : MonoSingleton<DragAndDropManager>
{</p>
<pre><code>private GameObject draggedObject; // 현재 드래그 중인 오브젝트
private DraggableContent draggableContent; // UI에서 드래그된 콘텐츠
private bool isDragging = false; // 드래그 상태
private Canvas currentCanvas; // UI 캔버스
private Camera mainCamera; // 월드 드래그를 위한 카메라
public LayerMask groundLayer; // 바닥 지형에 사용할 LayerMask
private Renderer draggedRenderer; // 드래그된 오브젝트의 Renderer

private Color defaultColor = Color.white; // 기본 색상
private Color validColor = Color.green;   // DropZone 안의 색상
private Color invalidColor = Color.red;   // DropZone 밖의 색상

private void Start()
{
    mainCamera = Camera.main;
}

// 월드 드래그를 위한 카메라 설정
public void SetWorldDragCamera(Camera worldDragCamera)
{
    mainCamera = worldDragCamera;
}

// 드래그 시작 (UI 콘텐츠)
public void BeginDrag(DraggableContent content, PointerEventData eventData)
{
    draggableContent = content;

    if (content.prefab == null)
    {
        Debug.LogError(&quot;드래그할 Prefab이 설정되지 않았습니다!&quot;);
        return;
    }

    currentCanvas = eventData.pointerPress?.GetComponentInParent&lt;Canvas&gt;();
    if (currentCanvas == null)
    {
        Debug.LogError(&quot;Canvas를 식별할 수 없습니다!&quot;);
        return;
    }

    // 드래그 미리보기 프리팹 생성
    draggedObject = Instantiate(content.prefab, GetWorldPosition(eventData), Quaternion.identity);
    draggedRenderer = draggedObject.GetComponent&lt;Renderer&gt;();

    if (draggedRenderer != null)
    {
        defaultColor = draggedRenderer.material.color; // 원래 색상 저장
        draggedRenderer.material.color = invalidColor; // 드래그 시작 시 빨간색
    }

    draggedObject.SetActive(true);
    isDragging = true;
}

// 드래그 시작 (월드 오브젝트)
public void BeginWorldDrag(GameObject worldObject)
{
    draggedObject = worldObject;
    draggedRenderer = draggedObject.GetComponent&lt;Renderer&gt;();
    defaultColor = draggedRenderer.material.color;
    isDragging = true;
}

// 드래그 중
public void Drag(PointerEventData eventData)
{
    if (!isDragging || draggedObject == null)
        return;

    if (draggableContent != null)
    {
        // UI → 월드 드래그
        // Drag동안 추가할 작업 있으면 추가
    }
    else
    {
        // 월드 → 월드 드래그
        // Drag동안 추가할 작업 있으면 추가
    }
    // drag동안 위치에 따라 DragZone감지 후 색 변화
    Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
    if (Physics.Raycast(ray, out RaycastHit hitInfo, Mathf.Infinity, groundLayer))
    {
        draggedObject.transform.position = hitInfo.point;
        UpdateObjectColor(); // 드래그 중 색상 업데이트
    }
}

// 드래그 종료
public void EndDrag(PointerEventData eventData)
{
    if (!isDragging || draggedObject == null)
        return;

    if (draggableContent != null)
    {
        // UI → 월드 드래그 종료
        if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out RaycastHit hitInfo, Mathf.Infinity, groundLayer))
        {
            if (hitInfo.collider.CompareTag(&quot;DropZone&quot;))
            {
                Debug.Log($&quot;Dropped {draggedObject.name} in {hitInfo.collider.name}&quot;);
                draggedObject.transform.position = hitInfo.point;
                ResetObjectColor();
            }
            else
            {
                Destroy(draggedObject); // 드롭 실패 시 제거
                // TODO : 없어진 content가 원래 있던 ui로 돌아감
            }
        }
    }
    else
    {
        // 월드 → 월드 드래그 종료
        if (Physics.Raycast(mainCamera.ScreenPointToRay(Input.mousePosition), out RaycastHit hitInfo, Mathf.Infinity, groundLayer))
        {
            if (hitInfo.collider.CompareTag(&quot;DropZone&quot;))
            {
                Debug.Log($&quot;Dropped {draggedObject.name} at {hitInfo.point}&quot;);
                ResetObjectColor();
            }
            else
            {
                Destroy(draggedObject); // 드롭 실패 시 제거
                // TODO : 없어진 content가 원래 있던 ui로 돌아감
            }
        }
    }

    draggedObject = null;
    draggableContent = null;
    isDragging = false;
}

// UI 좌표 → 월드 좌표 변환
private Vector3 GetWorldPosition(PointerEventData eventData)
{
    RectTransformUtility.ScreenPointToWorldPointInRectangle(
        currentCanvas.GetComponent&lt;RectTransform&gt;(),
        eventData.position,
        mainCamera,
        out Vector3 worldPosition
    );
    return worldPosition;
}

// 지형에 따라 Y값 업데이트
private void UpdateObjectPositionWithGround(Vector3 position)
{
    Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
    if (Physics.Raycast(ray, out RaycastHit hitInfo, Mathf.Infinity, groundLayer))
    {
        position.y = hitInfo.point.y; // 지형 충돌 위치의 Y값으로 업데이트
    }
    draggedObject.transform.position = position;
}
// 드래그 중 색상 업데이트
private void UpdateObjectColor()
{
    if (draggedRenderer == null)
        return;

    Ray ray = mainCamera.ScreenPointToRay(Input.mousePosition);
    if (Physics.Raycast(ray, out RaycastHit hitInfo, Mathf.Infinity, groundLayer))
    {
        if (hitInfo.collider.CompareTag(&quot;DropZone&quot;))
        {
            draggedRenderer.material.color = validColor; // 초록색
        }
        else
        {
            draggedRenderer.material.color = invalidColor; // 빨간색
        }
    }
}

// 드래그 종료 시 색상 초기화
private void ResetObjectColor()
{
    if (draggedRenderer != null)
    {
        draggedRenderer.material.color = defaultColor; // 원래 색상으로 복구
    }
}</code></pre><p>}</p>
<pre><code>
---

## DraggableWorldObject
World to Wold로의 드래그 앤 드랍을 할 때, 대상이 되는 오브젝트에 DraggableWorldObject script를 붙이면 드래그 대상으로 사용할 수 있다.

UI 드래그 앤 드롭과 달리 인터페이스는 따로 필요하지 않다.

- DraggableWorldObject.cs
```cs
using UnityEngine;

public class DraggableWorldObject : MonoBehaviour
{
    private void OnMouseDown()
    {
        DragAndDropManager.Instance.BeginWorldDrag(gameObject);
    }

    private void OnMouseDrag()
    {
        DragAndDropManager.Instance.Drag(null); // 이벤트 데이터가 필요 없음
    }

    private void OnMouseUp()
    {
        DragAndDropManager.Instance.EndDrag(null); // 이벤트 데이터가 필요 없음
    }
}</code></pre><hr>
<h2 id="inspector-설정">Inspector 설정</h2>
<p>다음 코드를 이용해 설정할 오브젝트들의 Inspector 창 설정에 대한 설명이다.</p>
<ul>
<li><p>DropZone Tag 및 Layer
<img src="https://velog.velcdn.com/images/wanna_make_game/post/1f020132-3aec-4150-96f0-68be60d20cf2/image.PNG" alt="">
DropZone 생성 후, Tag와 Layer 설정을 다음과 같이 해준다. (본인은 Plane으로 DropZone오브젝트를 만들었다. 나중에 지형이 생겼을 때는 어떻게 할지는 고려해야 할 듯하다.)</p>
</li>
<li><p>Content인 UI의 Tag 및 부착 script
<img src="https://velog.velcdn.com/images/wanna_make_game/post/9fa4f194-e213-4eed-a4f1-c618a5b64e25/image.PNG" alt="">
ScrollView의 Content로 쓰이는 btnOwnedKnight라는 UI의 Inspector창이다. 드래그할 UI에는 DraggableUI script를 달아주면 된다. Tag는 Draggable로 설정.</p>
</li>
<li><p>Content UI에 연결할 ScriptableObject (프리팹 is here)
<img src="https://velog.velcdn.com/images/wanna_make_game/post/08e56921-b435-4bce-ab56-ec360a12c98b/image.PNG" alt="">
Content인 UI에 연결된 ScriptableObject이다. 여기서, Prefab을 연결해준다.</p>
</li>
<li><p>드래그 앤 드롭으로 꺼낼 프리팹의 Tag 및 부착 script
<img src="https://velog.velcdn.com/images/wanna_make_game/post/b35c4626-bf1f-4f36-b888-671107339de9/image.PNG" alt="">
드래그 앤 드롭으로 꺼낼 프리팹이다. 나는 해당 프리팹도 Drag 가능하게 해 줄 예정이기 때문에, World 용 드래그 script인 DraggableWorldObject를 달아준다. Tag도 Draggable로 설정.</p>
</li>
</ul>
<p>이상으로 드래그 앤 드롭 시스템 만들기를 마친다.
뭔가 조금 더 보완할 수 있을 것 같지만, 아직까진 딱히 사용하는데 문제는 없어 보인다.</p>
<p>내가 나중에 다시 보기 쉽게 정리한 거지만, 다들 이 글을 보고 드래그 앤 드롭을 조금 더 쉽게 쓸 수 있으면 뿌듯할 듯 싶다.</p>
<p>이상 오늘의 TIL 끝!</p>
<hr>
<h1 id="문제점">문제점</h1>
<h2 id="scrollview와-함께-쓸-때의-문제점">ScrollView와 함께 쓸 때의 문제점</h2>
<p>위의 코드를 사용했을 때, ScrollView와 함께 사용하는 Content가 드래그 앤 드롭 시스템과만 반응하고, ScrollView의 Content로서 클릭이 안된다는 문제가 있었다.</p>
<p>다음글에서 다음 문제를 해결한다.</p>
<p>다음글: <a href="https://velog.io/@wanna_make_game/Unity-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-ScrollView%EB%A5%BC-%EB%B0%98%EC%98%81%ED%95%9C-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD-%EC%8B%9C%EC%8A%A4%ED%85%9C">[Unity] 드래그 앤 드롭 시스템 만들기: ScrollView를 반영한 드래그 앤 드롭 시스템</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[맵 시찰 UI 및 전략 설정 UI 제작]]></title>
            <link>https://velog.io/@wanna_make_game/%EB%A7%B5-%EC%8B%9C%EC%B0%B0-UI-%EB%B0%8F-%EC%A0%84%EB%9E%B5-%EC%84%A4%EC%A0%95-UI-%EC%A0%9C%EC%9E%91</link>
            <guid>https://velog.io/@wanna_make_game/%EB%A7%B5-%EC%8B%9C%EC%B0%B0-UI-%EB%B0%8F-%EC%A0%84%EB%9E%B5-%EC%84%A4%EC%A0%95-UI-%EC%A0%9C%EC%9E%91</guid>
            <pubDate>Tue, 03 Dec 2024 13:27:48 GMT</pubDate>
            <description><![CDATA[<p>2024.12.03(화)</p>
<p>오늘의 TIL은 저번주 금요일에 작업분담을 시작해서, 오늘까지 작업한 내용에 대해 간단하게 작성해 보겠다.</p>
<p>나는 LHW님과 함께 1사이클에서 전투 부분을 맡게 되었는데, 오늘까지의 작업에선 전투 흐름에 따른 UI를 완성하였다.</p>
<h1 id="전투-ui">전투 UI</h1>
<ul>
<li>맵 시찰 UI - (Me)</li>
<li>전투 설정 UI - (Me)</li>
<li>부대 정보 UI - (LHW)</li>
<li>부대 임무 설정 UI - (LHW)</li>
<li>전투 시작 UI - (LHW)</li>
</ul>
<p>ScrollView를 적절히 이용해서 작업해 주었다.</p>
<h2 id="hirerachy-구조">Hirerachy 구조</h2>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/89c6ef57-f734-4564-b468-c045badcb0dd/image.PNG" alt=""></p>
<h2 id="맵-시찰-ui">맵 시찰 UI</h2>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/91f27ed8-72a3-446c-91a0-1a00d6634a62/image.PNG" alt=""></p>
<h2 id="전투-설정-ui">전투 설정 UI</h2>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/32977bc8-5a4b-4dab-b7a9-fe48011f48e8/image.PNG" alt=""></p>
<h2 id="전투-시작-popup">전투 시작 Popup</h2>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/cf60c809-8d1c-4110-9620-e32d2fee438a/image.PNG" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[카메라와 UI의 순서 설정하기]]></title>
            <link>https://velog.io/@wanna_make_game/%EC%B9%B4%EB%A9%94%EB%9D%BC%EC%99%80-UI%EC%9D%98-%EC%88%9C%EC%84%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wanna_make_game/%EC%B9%B4%EB%A9%94%EB%9D%BC%EC%99%80-UI%EC%9D%98-%EC%88%9C%EC%84%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 02 Dec 2024 05:40:45 GMT</pubDate>
            <description><![CDATA[<p>2024.12.02(월)</p>
<h1 id="카메라와-ui의-순서-설정하기">카메라와 UI의 순서 설정하기</h1>
<p>그림0과 같은 맵에서 아군의 군대를 맵에 배치하고 전략을 설정하게 하기 위해서, 아군의 배치가능지역을 그림1과 같은 전략 설정 탭에서 위에서 내려다 보는 식으로 보이게 할 예정이다.</p>
<ul>
<li><p>그림0
<img src="https://velog.velcdn.com/images/wanna_make_game/post/b69f46ca-44f6-4d55-ac64-e7c8d386bb95/image.PNG" alt=""></p>
</li>
<li><p>그림1
<img src="https://velog.velcdn.com/images/wanna_make_game/post/d8bef4f4-dbec-472a-afb0-84843fbbd9d8/image.PNG" alt=""></p>
</li>
</ul>
<p>일단, 내가 해볼 방식은, 카메라가 화면에 보이는 크기를 조절하고, 그 뒤에 UI를 배치하는 형식이다.
내가 사용한 방식은 다음과 같다.</p>
<h3 id="1-그림1에-배치해줄-카메라의-크기를-조절해주자-카메라-오브젝트의-이름은-strategysetting-camera로-설정했다">1. 그림1에 배치해줄 카메라의 크기를 조절해주자. 카메라 오브젝트의 이름은 StrategySetting Camera로 설정했다.</h3>
<p>여기에선, Camera컴포넌트의 Output토글에 있는 Viewport Rect를 설정해주면 된다.
나는 그림1처럼 UI를 구성하기 위해 적절히 그림2와 같이 설정해줬다.</p>
<ul>
<li>그림2
<img src="https://velog.velcdn.com/images/wanna_make_game/post/dc2c06a8-b71c-4f77-a0d6-6d9839b7eba5/image.PNG" alt=""></li>
</ul>
<p>그림2와 같이 설정해줬다면, 그림3과 같은 화면이 나오게 된다.</p>
<ul>
<li>그림3
<img src="https://velog.velcdn.com/images/wanna_make_game/post/6386ae64-0446-4b61-a1de-01c409723650/image.PNG" alt=""></li>
</ul>
<h3 id="2-이제-그림4와-같이-panel을-만들고-canvas의-이름을-ui_strategysetting으로-만들어-주었다-이렇게-하면-기본적으로-그림5처럼-rendermode가-screen-space---overlay-형식으로-나올-것이다">2. 이제 그림4와 같이 Panel을 만들고, Canvas의 이름을 UI_StrategySetting으로 만들어 주었다. 이렇게 하면, 기본적으로 그림5처럼 RenderMode가 Screen Space - Overlay 형식으로 나올 것이다.</h3>
<ul>
<li><p>그림4
<img src="https://velog.velcdn.com/images/wanna_make_game/post/f4d5acbe-db14-486e-acb1-9fc26bebd84e/image.PNG" alt=""></p>
</li>
<li><p>그림5
<img src="https://velog.velcdn.com/images/wanna_make_game/post/3f0d1999-8c70-4f07-88d1-f181c51e424b/image.PNG" alt=""></p>
</li>
</ul>
<p>이때의 화면을 보면 그림6 처럼, 내가 만든 카메라까지 덮이는 것을 볼 수 있다.(여기서 추가로 Panel Color의 Alpha값을 255로 변경해 놓았다.)</p>
<ul>
<li>그림6
<img src="https://velog.velcdn.com/images/wanna_make_game/post/7e29dd3d-85b1-4506-9da8-76bccb89017c/image.PNG" alt=""></li>
</ul>
<p>하지만, 내가 원하는 화면은, 카메라가 Panel이 있는 Canvas의 앞에 위치하는 것이다.
이것을 위해 다음과 같은 절차를 진행하였다.</p>
<h3 id="3-그림7처럼-canvas의-render-mode를-screen-space---camera로-변경해-주었다">3. 그림7처럼 Canvas의 Render Mode를 Screen Space - Camera로 변경해 주었다.</h3>
<ul>
<li>그림7
<img src="https://velog.velcdn.com/images/wanna_make_game/post/7c981a24-6e33-4bdf-a6d5-b35da58e500e/image.PNG" alt=""></li>
</ul>
<h3 id="4-그리고-그림4를-보고-눈치채신-분들도-있겠지만-ui-camera를-하나-만들어-주었다">4. 그리고 그림4를 보고 눈치채신 분들도 있겠지만, UI Camera를 하나 만들어 주었다.</h3>
<p>그리고, 여기서 그림8 처럼 Priority를 설정해서 UI Camera가 뒤로 가게 만들어 주면 된다.</p>
<ul>
<li>그림8
<img src="https://velog.velcdn.com/images/wanna_make_game/post/d2853925-f149-4876-9d2b-fb57094544a9/image.PNG" alt=""></li>
</ul>
<p>또한, 여기서 UI의 Y값을 1000으로 해두고, Culling Mask를 UI로만 설정해서, 혹시 다른 카메라에 해당 Panel이 잡히거나(Screen Space - Camera로 설정을 하면, Canvas가 카메라를 쫒아다닌다), 다른 오브젝트가 잡히는 문제를 미연에 방지할 수 있다. (물론 이것을 안해도 문제가 안생기는 지는 잘 모르겠다.)</p>
<h3 id="5-그-다음-그림7에서-screen-space---camera로-설정해서-생긴-render-camera에-그림8에서-생성한-ui-camera를-달아주면-된다">5. 그 다음, 그림7에서 Screen Space - Camera로 설정해서 생긴 Render Camera에 그림8에서 생성한 UI Camera를 달아주면 된다.</h3>
<ul>
<li>그림9
<img src="https://velog.velcdn.com/images/wanna_make_game/post/0cd1ae33-a8d0-49fd-ab11-36738b24241d/image.PNG" alt=""></li>
</ul>
<p>그러면, 그림10처럼 UI의 Panel이 Camera의 뒤에 생성되는 것을 볼 수 있다.</p>
<ul>
<li>그림10
<img src="https://velog.velcdn.com/images/wanna_make_game/post/5a9184f4-a363-4152-995a-f50adcd42d26/image.PNG" alt=""></li>
</ul>
<p>처음엔, UI의 Order in Layer와 카메라의 Priority를 이용해서 UI와 카메라의 순서를 정해줄 수 있을 것 같았는데, 생각대로 되지 않았다.
그래서, 카메라의 Priority끼리의 순서를 이용해서, UI만 비추는 카메라를 만들어 순서를 정해주었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[엑셀시트를 이용해 Unity에 데이터를 넣는 방식 - JSON과 ScriptableObject]]></title>
            <link>https://velog.io/@wanna_make_game/%EC%97%91%EC%85%80%EC%8B%9C%ED%8A%B8%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-Unity%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EB%84%A3%EB%8A%94-%EB%B0%A9%EC%8B%9D-JSON%EA%B3%BC-ScriptableObject</link>
            <guid>https://velog.io/@wanna_make_game/%EC%97%91%EC%85%80%EC%8B%9C%ED%8A%B8%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-Unity%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EB%84%A3%EB%8A%94-%EB%B0%A9%EC%8B%9D-JSON%EA%B3%BC-ScriptableObject</guid>
            <pubDate>Wed, 27 Nov 2024 10:22:42 GMT</pubDate>
            <description><![CDATA[<p>2024.11.27(수)</p>
<h1 id="json을-이용한-방식">JSON을 이용한 방식</h1>
<p>사용 패키지 GitHub 주소: <a href="https://github.com/npckero/ExcelToJsonWizard">GitHub: ExcelToJsonWizard</a></p>
<h3 id="1-key와-inttype-iddescription를-그림1과-같이-작성하여-데이터를-넣을-수-있다">1. key와 int(type), ID(description)를 그림1과 같이 작성하여 데이터를 넣을 수 있다.</h3>
<ul>
<li>그림1
<img src="https://velog.velcdn.com/images/wanna_make_game/post/b53a859c-0cc4-493d-978d-2fac0ef1df00/image.PNG" alt=""></li>
</ul>
<h3 id="2-그림2와-같이-제공된-클래스와-메서드들을-이용해-사용할-수-있다">2. 그림2와 같이 제공된 클래스와 메서드들을 이용해 사용할 수 있다.</h3>
<ul>
<li>그림2
<img src="https://velog.velcdn.com/images/wanna_make_game/post/2dd31db5-627d-4614-a2ea-a958ebbe8a0a/image.PNG" alt=""></li>
</ul>
<h3 id="3-그림3은-enum타입을-정의하고-사용하는-방법이다">3. 그림3은 enum타입을 정의하고 사용하는 방법이다</h3>
<ul>
<li>그림3
<img src="https://velog.velcdn.com/images/wanna_make_game/post/29834e3d-40ee-46e3-8110-8645ddef191b/image.PNG" alt=""><h3 id="4-그림4와-같이-designenums에-엑셀시트에서-작성한-enum이-모이게-된다">4. 그림4와 같이 DesignEnums에 엑셀시트에서 작성한 enum이 모이게 된다.</h3>
</li>
<li>그림4
<img src="https://velog.velcdn.com/images/wanna_make_game/post/015615a1-55ee-476f-bf7c-36b205f869e5/image.PNG" alt=""></li>
</ul>
<h3 id="5-designenums를-활용한-데이터의-모습은-그림5와-같다">5. DesignEnums를 활용한 데이터의 모습은 그림5와 같다.</h3>
<ul>
<li>그림5
<img src="https://velog.velcdn.com/images/wanna_make_game/post/d22ea90c-e614-491a-a840-b24ad080fb55/image.PNG" alt=""></li>
</ul>
<h3 id="6-그림6은-엑셀-파일을-어떻게-다룰-지-옵션을-보여준다당장은-건드릴-필요가-없다">6. 그림6은 엑셀 파일을 어떻게 다룰 지 옵션을 보여준다.(당장은 건드릴 필요가 없다.)</h3>
<ul>
<li>그림6
<img src="https://velog.velcdn.com/images/wanna_make_game/post/72ec6804-3747-418c-ac48-0e983df984f2/image.PNG" alt=""></li>
</ul>
<h1 id="scriptableobject를-이용한-방식">ScriptableObject를 이용한 방식</h1>
<p>사용 패키지 GitHub 주소: <a href="https://github.com/mikito/unity-excel-importer?tab=readme-ov-file">GitHub: Unity Excel Importer</a>
Package 주소: <a href="https://github.com/mikito/unity-excel-importer.git?path=Assets/ExcelImporter#v0.1.1/upm">https://github.com/mikito/unity-excel-importer.git?path=Assets/ExcelImporter#v0.1.1/upm</a></p>
<h3 id="1-그림7과-같이-데이터를-저장한다">1. 그림7과 같이 데이터를 저장한다</h3>
<ul>
<li>그림7
<img src="https://velog.velcdn.com/images/wanna_make_game/post/815a3118-f2f3-4fa5-9887-60a1a01ff0ac/image.PNG" alt=""></li>
</ul>
<h3 id="2-그림8과-같은-형식으로-데이터를-받는다">2. 그림8과 같은 형식으로 데이터를 받는다.</h3>
<ul>
<li>그림8
<img src="https://velog.velcdn.com/images/wanna_make_game/post/b5f0c9a9-eff4-4b3b-b053-16dc3d3fe02f/image.PNG" alt=""></li>
</ul>
<h3 id="3-그림9와-같은-형식으로-데이터를-받는다-2900">3. 그림9와 같은 형식으로 데이터를 받는다. 29:00</h3>
<ul>
<li>그림9
<img src="https://velog.velcdn.com/images/wanna_make_game/post/cb839759-f0e1-46dc-98b3-02d96c6a341f/image.PNG" alt=""></li>
</ul>
<h3 id="4-그림10과-같은-형식으로-아이템db를-만들고-스크립트에서-꺼내쓰는-형식으로-사용-가능하다">4. 그림10과 같은 형식으로 아이템DB를 만들고, 스크립트에서 꺼내쓰는 형식으로 사용 가능하다.</h3>
<ul>
<li>그림10
<img src="https://velog.velcdn.com/images/wanna_make_game/post/a08be0d5-76f6-4846-a177-552f6787bfa0/image.PNG" alt=""></li>
</ul>
<h3 id="5-그림10과-같은-방법처럼-인덱스를-통해-접근하는-것보단">5. 그림10과 같은 방법처럼 인덱스를 통해 접근하는 것보단,</h3>
<p>그림11처럼 전체 순회가 가능한 Enumerator를 가져와서 접근하는 방식이 좋다.(고 생각한다-by YYC튜터님)
(캡슐화를 강조하기 때문에 직접적으로 사용하는 방식은 권장하지 않는다.)</p>
<ul>
<li>그림11
<img src="https://velog.velcdn.com/images/wanna_make_game/post/ce11207c-0488-4fc2-a64b-fdbdeea8fe84/image.PNG" alt=""></li>
</ul>
<h3 id="6-그림12과-같이-모든-데이터가-저장되는-database에-itemdb들을-저장해서우리-입장에서는-꼭-item일-필요는-없음">6. 그림12과 같이 모든 데이터가 저장되는 Database에 ItemDB들을 저장해서(우리 입장에서는 꼭 Item일 필요는 없음)</h3>
<p>Database에 있는 DB들을 꺼내서 쓸 수 있음.</p>
<ul>
<li>그림12
<img src="https://velog.velcdn.com/images/wanna_make_game/post/a89abc0c-4bf1-4b3b-a1db-21cf80d21543/image.PNG" alt=""></li>
</ul>
<h3 id="7-그림13-14-15처럼-id를-이용해-메인-카테고리-서브-카테고리-개별-인덱스-형식으로-나눌-수도-있다">7. 그림13, 14, 15처럼 id를 이용해, 메인 카테고리, 서브 카테고리, 개별 인덱스 형식으로 나눌 수도 있다.</h3>
<ul>
<li><p>그림13
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e235de92-fa90-4180-938c-fd31b54120a2/image.PNG" alt=""></p>
</li>
<li><p>그림14
<img src="https://velog.velcdn.com/images/wanna_make_game/post/8997ded0-ab7c-4d28-b292-93d457ae8241/image.PNG" alt=""></p>
</li>
<li><p>그림15
<img src="https://velog.velcdn.com/images/wanna_make_game/post/692dc314-1585-45c5-b3a4-a1fac5051741/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="8-그림16처럼-강화방법에-따른-데이터-저장법이-나뉠-수-있다">8. 그림16처럼 강화방법에 따른 데이터 저장법이 나뉠 수 있다.</h3>
<p>(하지만, 우리는 아마 대부분의 상황에서 강화방법 1을 사용할 것이다.)</p>
<ul>
<li>그림16
<img src="https://velog.velcdn.com/images/wanna_make_game/post/c6c1a9ce-ab22-49fe-bcd6-30e675e996b6/image.PNG" alt=""></li>
</ul>
<h3 id="9-개인적인-의문그림-17에서-정의한-타입이-그림18에서-자동으로-매칭이-되는가">9. (개인적인 의문)그림 17에서 정의한 타입이 그림18에서 자동으로 매칭이 되는가?</h3>
<ul>
<li>그림17
<img src="https://velog.velcdn.com/images/wanna_make_game/post/2f348a1d-83cd-40d1-b8ca-979a5ae475bc/image.PNG" alt=""></li>
<li>그림18
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e08859f0-f647-4191-b4d0-770427fd6465/image.PNG" alt=""></li>
</ul>
<h3 id="10-그림19는-던전의-강화된버전들을-정리한-방법의-예시다그림16의-강화방법-2-예시">10. 그림19는 던전의 강화된버전들을 정리한 방법의 예시다(그림16의 강화방법 2 예시)</h3>
<p>-&gt; 그림 20처럼 추가했을 때의 인덱스를 고려해서 엑셀시트를 작성한다.</p>
<ul>
<li><p>그림19
<img src="https://velog.velcdn.com/images/wanna_make_game/post/76d727d2-99e6-4de3-85bd-683affdfc5c5/image.PNG" alt=""></p>
</li>
<li><p>그림20
<img src="https://velog.velcdn.com/images/wanna_make_game/post/75a2a5c9-9929-42be-a922-873c6dc73d16/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="11-그림21처럼-id를-테이블-번호와-카테고리-인덱스를-나눠서-작성한다그림131415내용">11. 그림21처럼 id를 테이블 번호와 카테고리 인덱스를 나눠서 작성한다.(그림13,14,15내용)</h3>
<ul>
<li>그림21
<img src="https://velog.velcdn.com/images/wanna_make_game/post/89ef3dd5-9f92-4589-b838-52bee80cb21b/image.PNG" alt=""></li>
</ul>
<h3 id="12-그림22처럼-하위-데이터를-테이블-별로-데이터를-관리를-하고">12. 그림22처럼 하위 데이터를 테이블 별로 데이터를 관리를 하고,</h3>
<p>그림23처럼 상위 데이터에 하위 데이터 테이블을 연결해서 사용한다.</p>
<ul>
<li><p>그림22
<img src="https://velog.velcdn.com/images/wanna_make_game/post/acc71af0-7de0-47eb-a88e-1f533ac3a964/image.PNG" alt=""></p>
</li>
<li><p>그림23
<img src="https://velog.velcdn.com/images/wanna_make_game/post/63905501-c3fb-461d-b55d-6d25bd52e468/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="13-그림24처럼-행동과-타겟-횟수로-퀘스트를-구분할-수-있다">13. 그림24처럼 행동과 타겟, 횟수로 퀘스트를 구분할 수 있다.</h3>
<p>그림25처럼 행동과 타겟을 연결하고, 횟수를 부여하면 해당 조건이 되는 식으로 만들 수 있다.
완료되면, 그림26과 같은 형식으로 사용할 수 있다.
(우리의 프로젝트에서는 &#39;게이트 종류에 상관없이 6회 클리어&#39;라는 조건을 보여주지
않을 수 있지만, 기획자로서 데이터 테이블을 넣을 때, 어떤 조건인지를 보는 용도로도
사용 가능할 것이다.)</p>
<ul>
<li><p>그림24
<img src="https://velog.velcdn.com/images/wanna_make_game/post/dc3ce584-6d55-42a5-b2f0-57c4b36d00f1/image.PNG" alt=""></p>
</li>
<li><p>그림25
<img src="https://velog.velcdn.com/images/wanna_make_game/post/0e324450-22b8-42ff-b636-9fc001bde530/image.PNG" alt=""></p>
</li>
<li><p>그림26
<img src="https://velog.velcdn.com/images/wanna_make_game/post/8086a4b7-2224-473f-a6a2-588299bc57d3/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="14-그림27-형식으로-테이블을-정리해서-사용하면-좋을-것이다">14. 그림27 형식으로 테이블을 정리해서 사용하면 좋을 것이다.</h3>
<ul>
<li>그림27
<img src="https://velog.velcdn.com/images/wanna_make_game/post/c9cba99c-0d38-42f9-b9a6-021c0359928b/image.PNG" alt=""></li>
</ul>
<h3 id="15-계산의-정확도를-위해-그림28을-고려하거나-글로벌-런칭을-고려하기-위해-그림29를-생각할-수도-있을-것이다">15. 계산의 정확도를 위해 그림28을 고려하거나, 글로벌 런칭을 고려하기 위해 그림29를 생각할 수도 있을 것이다.</h3>
<ul>
<li><p>그림28
<img src="https://velog.velcdn.com/images/wanna_make_game/post/01949bd6-5b32-416e-abfa-10a8be10f327/image.PNG" alt=""></p>
</li>
<li><p>그림29
<img src="https://velog.velcdn.com/images/wanna_make_game/post/c1de2625-3d67-4311-a84d-84624826f403/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="16-그림30처럼-버프수치-옆에--표시를-적어-단위에-대한-명확성을-보여주는-게-좋다">16. 그림30처럼 버프수치 옆에 (%) 표시를 적어 단위에 대한 명확성을 보여주는 게 좋다.</h3>
<ul>
<li>그림30
<img src="https://velog.velcdn.com/images/wanna_make_game/post/adfba62e-9e09-47b6-84e6-42242ce1ff69/image.PNG" alt=""></li>
</ul>
<h3 id="17-그림31처럼-확률처럼-부동소수점-형식으로-처리가-될-때">17. 그림31처럼 확률처럼 부동소수점 형식으로 처리가 될 때,</h3>
<p>그림28에서의 얘기처럼 float보단 int를 쓰는 게 정확도에 도움이 될 것이다.</p>
<ul>
<li><p>그림31
<img src="https://velog.velcdn.com/images/wanna_make_game/post/da6612e3-bbab-4791-b0c8-220e60a13c2a/image.PNG" alt=""></p>
</li>
<li><p>그림28
<img src="https://velog.velcdn.com/images/wanna_make_game/post/38ef9954-f047-4264-b92b-56bcfaf1a06d/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="18-부동소수점은-그림32처럼-이루어져-있으며">18. 부동소수점은 그림32처럼 이루어져 있으며,</h3>
<p>그림33과 같은 표준 형식을 사용해서 사용할 수 있을 것이다.
그림34처럼 int의 표현범위를 고려해 9자리 수로 나타낼 수도 있을 것이다.</p>
<ul>
<li><p>그림32
<img src="https://velog.velcdn.com/images/wanna_make_game/post/68524f49-c6a5-4497-bf18-3ebfea3abd55/image.PNG" alt=""></p>
</li>
<li><p>그림33
<img src="https://velog.velcdn.com/images/wanna_make_game/post/3d029472-feb7-43a3-9579-a6b361dfb3e0/image.PNG" alt=""></p>
</li>
<li><p>그림34
<img src="https://velog.velcdn.com/images/wanna_make_game/post/bcbee272-5941-4eec-ba83-d3141f54d7bf/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="19-그림35-그리고-스트링은-모두-글자기-때문에-id를-초기에는-너무-크게-잡지-않는-게-좋다">19. 그림35: 그리고, 스트링은 모두 글자기 때문에, ID를 초기에는 너무 크게 잡지 않는 게 좋다.</h3>
<p>하지만, 필요하다면 길어도 신경쓰지 말자.</p>
<ul>
<li>그림35
<img src="https://velog.velcdn.com/images/wanna_make_game/post/2fb4722b-0858-4fe3-91ef-0eeadd2bf61f/image.PNG" alt=""></li>
</ul>
<ol start="20">
<li>그림36처럼 float값을 가질 데이터와 int값을 가질 데이터를 고려하여 상황에 맞게 작성하자.</li>
</ol>
<ul>
<li>그림36
<img src="https://velog.velcdn.com/images/wanna_make_game/post/0b5774b8-d3d6-4446-8f24-f61c1e8c178d/image.PNG" alt=""></li>
</ul>
<h3 id="21-그림-37처럼-배율을-1001로-적용할-건지-정해서-엑셀시트에-입력할-수-있다">21. 그림 37처럼 배율을 (100=1%)로 적용할 건지 정해서 엑셀시트에 입력할 수 있다.</h3>
<ul>
<li>그림37
<img src="https://velog.velcdn.com/images/wanna_make_game/post/a5cf4048-fc1b-437f-be24-a83875a84157/image.PNG" alt=""></li>
</ul>
<h3 id="22-그림38에-있는-isclear처럼-유저가-클리어-했는지에-대한-데이터는">22. 그림38에 있는 isClear처럼 유저가 클리어 했는지에 대한 데이터는</h3>
<p>유저의 행동에 의해 바꿀 수 있게 만든다.</p>
<ul>
<li>그림38
<img src="https://velog.velcdn.com/images/wanna_make_game/post/fc25cd1a-8a0e-45fd-9ee9-163a18d8fbdf/image.PNG" alt=""></li>
</ul>
<h3 id="23-그림39처럼-speed나-itemtext라고-적기보단-movespeed와-attack스피드처럼-어떤-스피드인지">23. 그림39처럼 speed나 itemText라고 적기보단, moveSpeed와 attack스피드처럼 어떤 스피드인지,</h3>
<p>itemName과 itemDescription처럼 어떤 Text인지 명확하게 해주는 것이 좋다.</p>
<ul>
<li>그림39
<img src="https://velog.velcdn.com/images/wanna_make_game/post/bd4ad8cd-e0cd-44c9-910d-1494b9260698/image.PNG" alt=""></li>
</ul>
<h3 id="24-그림40같은-동일한-내용은-그림41처럼-통일하여-표현하는-것이-좋다">24. 그림40같은 동일한 내용은, 그림41처럼 통일하여 표현하는 것이 좋다.</h3>
<p>(나쁜 사례: 그림42, 좋은 사례: 그림43)</p>
<ul>
<li><p>그림40
<img src="https://velog.velcdn.com/images/wanna_make_game/post/f12199ae-a106-48c7-a1b2-93276494a6bc/image.PNG" alt=""></p>
</li>
<li><p>그림41
<img src="https://velog.velcdn.com/images/wanna_make_game/post/a4e9493d-2372-4e84-8d67-d0b0423d4dca/image.PNG" alt=""></p>
</li>
<li><p>그림42
<img src="https://velog.velcdn.com/images/wanna_make_game/post/af66a21e-32c8-49f1-989b-7f673ae4b933/image.PNG" alt=""></p>
</li>
<li><p>그림43
<img src="https://velog.velcdn.com/images/wanna_make_game/post/f9a97d41-d19d-48ab-9a41-2e58bd829085/image.PNG" alt=""></p>
</li>
</ul>
<h3 id="25-여러-데이터를-처리할-때-그림44처럼-스킬을-3개-칸을-주고-비어있으면-그냥-두거나">25. 여러 데이터를 처리할 때, 그림44처럼 스킬을 3개 칸을 주고, 비어있으면 그냥 두거나,</h3>
<p>&#39;,&#39;를 지우면서 다음 데이터들을 리스트로 가져올 수 있다.(JSON에서 썼던 거랑 비슷한 듯)</p>
<ul>
<li>그림44
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e28eda5f-ddbc-49d7-aed2-819fc0b35a35/image.PNG" alt=""></li>
</ul>
<h3 id="26-그림45처럼-그룹테이블을-만들어서-적용할-수도-있을-것이다우리가-처음-고려했던-것과-비슷">26. 그림45처럼 그룹테이블을 만들어서 적용할 수도 있을 것이다.(우리가 처음 고려했던 것과 비슷)</h3>
<ul>
<li>그림45
<img src="https://velog.velcdn.com/images/wanna_make_game/post/f1022a80-2a00-4596-8848-7b190e58246d/image.PNG" alt=""></li>
</ul>
<h3 id="27-그림46처럼-던전-이름을-id값으로-다른곳에서-작성하게-만들면">27. 그림46처럼 던전 이름을 ID값으로 다른곳에서 작성하게 만들면,</h3>
<p>나중에 다국어 패치를 할 때, 해당 부분만 바꿔서 끼워주면 된다.</p>
<ul>
<li>그림46
<img src="https://velog.velcdn.com/images/wanna_make_game/post/645940a4-b8f7-4e94-ad09-48651b0b2b93/image.PNG" alt=""></li>
</ul>
<h3 id="28-그림47처럼-작성하면-문자열에-데이터를-넣어서-만들어-줄-수-있다">28. 그림47처럼 작성하면, 문자열에 데이터를 넣어서 만들어 줄 수 있다.</h3>
<ul>
<li>그림47
<img src="https://velog.velcdn.com/images/wanna_make_game/post/cda9defb-ad27-49a1-8acc-4e2144cedbeb/image.PNG" alt=""></li>
</ul>
<h3 id="29-실제-물리적인-파일은-그림48처럼-경로를-이용해서-추가할-수-있다">29. 실제 물리적인 파일은 그림48처럼 경로를 이용해서 추가할 수 있다.</h3>
<ul>
<li>그림48
<img src="https://velog.velcdn.com/images/wanna_make_game/post/417c1e5e-f692-495e-8f24-ad1e47523e9f/image.PNG" alt=""></li>
</ul>
<h3 id="30-그림49처럼-리소스와-경로를-따로-분리해서-저장도-가능">30. 그림49처럼 리소스와 경로를 따로 분리해서 저장도 가능</h3>
<ul>
<li>그림49
<img src="https://velog.velcdn.com/images/wanna_make_game/post/28f9e416-b82f-41db-ab47-7992a7c9c545/image.PNG" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀 프로젝트 자가 피드백]]></title>
            <link>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9E%90%EA%B0%80-%ED%94%BC%EB%93%9C%EB%B0%B1</link>
            <guid>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9E%90%EA%B0%80-%ED%94%BC%EB%93%9C%EB%B0%B1</guid>
            <pubDate>Thu, 07 Nov 2024 11:55:50 GMT</pubDate>
            <description><![CDATA[<p>2024.11.07(목)</p>
<h3 id="팀-프로젝트를-마치며">팀 프로젝트를 마치며...</h3>
<p>팀 프로젝트를 진행하면서, 프로젝트를 빌드하는 방향에서 고려해야 할 프로젝트 진행구조를 어떻게 빌드하고 작성해야 하는지 깨닫게 되었습니다. 
물론, 제가 깨달아서 정리한 부분이 모든 프로젝트에서 정석적으로 사용될 지는 모르겠습니다만, 개인적으로 팀 프로젝트를 어떻게 진행해야 하는지를 적립하게 된 것 같습니다.</p>
<p>다음은 이번에 진행한 팀 프로젝트의 구조입니다.</p>
<h2 id="프로젝트-구조">프로젝트 구조</h2>
<p>이번 팀 프로젝트의 프로젝트 구조 및 역할분담은 다음과 같습니다. (팀원 별로 도형으로 표시했습니다. 동그라미가 저입니다.)
<img src="https://velog.velcdn.com/images/wanna_make_game/post/52df775a-f9e9-46d2-8547-e27f2fb7b006/image.png" alt=""></p>
<p>이번 프로젝트에서는 프로젝트 구조를 각 기능들을 모듈로 나눠서 앞선 모듈들을 구현하면 다음 모듈을 구현할 수 있는 지도를 만들어서 진행했습니다.</p>
<p>화살표 이전에 있는 모듈을 구현해야만, 다음 모듈을 만들 수 있는데, 이 정보가 있는 지도를 만들고 공유하여, 프로젝트를 진행하면서 팀원분들은 자신이 구현하고 싶은 모듈이 선행 모듈(앞서 구현되어야 할 내용이 있는 부분)이 모두 구현되어있는지 확인한 후 모듈을 선택해서 구현합니다.</p>
<h3 id="이런-방식으로-진행했을-때의-문제점">이런 방식으로 진행했을 때의 문제점</h3>
<p>이 방식으로 팀 프로젝트를 진행하면서 들이닥친 문제점이 있었는데, 이제 한 팀원이 앞선 모듈을 개발하다가 문제점이 생겼을 때, 그 모듈과 의존된 뒤의 모듈들을 다른 팀원이 개발하기 어렵다는 점이 있습니다.</p>
<p>개발 가능한 다른 모듈들이 있을 땐 어느정도 상관없을 수도 있지만, 프로젝트 전반에 걸쳐서 의존성이 많이 있는 부분에서 문제가 생긴다면, 연계되는 많은 부분들을 작성하기 어렵다는 문제가 있습니다.</p>
<h2 id="팀-프로젝트-자가-피드백">팀 프로젝트 자가 피드백</h2>
<p>이번 프로젝트에서 적립한 팀 프로젝트 빌딩 과정에서 고려해야 하는 부분을 정리하겠습니다.</p>
<p>이번 프로젝트에서는 기능들을 모듈별로 나누어서 그 모듈이 만들어지기 위해 선행되어야 하는 모듈을 순서도로 작성하여, 앞선 모듈부터 각각 선택해서 차례대로 만드는 방식으로 진행했습니다.</p>
<p>이 방식의 문제점은 중간의 한 모듈을 만들 때, 문제가 발생하면 그 모듈을 이용해서 만드는 이후 기능들을 만들지 못한다는 단점이 있습니다.</p>
<p>이 방식을 해결하기 위해서, 각 팀원들이 그냥 한 모듈별로 맡아서 작업을 하면서 필요한 부분들을 interface로 요청하면 그 기능들을 만들어 주는 방식으로 작업을 하면 된다고 느꼈습니다.</p>
<p>정리하면, 각 개발자가 모듈의 내부기능들을 만들면, 다른 모듈을 담당하는 개발자가 그 모듈의 정보를 필요로 할 때, 그 인터페이스를 요청하는 방식으로 프로젝트를 진행하면 좋을 것 같다는 생각이 듭니다.</p>
<p>끝.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[인터페이스 사용 이유, Text Mesh Pro 폰트 사용하기]]></title>
            <link>https://velog.io/@wanna_make_game/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0-Text-Mesh-Pro-%ED%8F%B0%ED%8A%B8-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wanna_make_game/%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%82%AC%EC%9A%A9-%EC%9D%B4%EC%9C%A0-Text-Mesh-Pro-%ED%8F%B0%ED%8A%B8-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 27 Oct 2024 23:19:26 GMT</pubDate>
            <description><![CDATA[<p>2024.10.25(금)</p>
<h2 id="인터페이스-사용-이유">인터페이스 사용 이유</h2>
<p>코드를 작성할 때는 코드들의 _<strong>결합도</strong>_가 낮아야 한다. 코드의 _<strong>결합도</strong>_가 높으면 코드끼리의 _<strong>의존성</strong>_이 높아져서 좋은 객체를 만들지 못하기 때문이다.
따라서, 인터페이스를 활용하여 코드의 _<strong>결합도</strong>_를 낮춰 _<strong>의존성</strong>_을 떨어뜨려야 된다.</p>
<p>또한, 코드의 _<strong>응집도</strong>_를 높여서 _<strong>결합도</strong>_를 낮춰야 한다.</p>
<h3 id="클래스-보다-인터페이스-상속이-유용한-경우는">클래스 보다 인터페이스 상속이 유용한 경우는?</h3>
<p>같은 기능을 가진 코드를 작성해야 되서 상속 받을 일이 있을 때, 구체적인 코드를 가진 클래스를 상속받는 것보다는, 기능들을 쪼개서 인터페이스를 상속받는 것이 좋다.
그러면 나중에 필요없어지는 기능들은 없앨 수도 있고 관리하기 편하다!</p>
<h3 id="그-외">그 외</h3>
<p>또한, 인터페이스를 미리 정해놔서 구현되지 않은 틀을 미리 잡을 수 있다는 장점도 있다.</p>
<h2 id="text-mesh-pro-폰트-사용하기">Text Mesh Pro 폰트 사용하기</h2>
<p>Text Mesh Pro에서 폰트를 사용하려면, 사용하려는 폰트를 Asset으로 만들어줘야 한다.</p>
<p>폰트를 Asset으로 만드는 방법은 다음과 같다.</p>
<ol>
<li>Unity 창에서 다음을 실행한다.</li>
</ol>
<pre><code>Windows&gt;Text Mesh Pro&gt; Font Asset Creator</code></pre><p>실행했다면 다음과 같은 창이 뜰 것이다.
<img src="https://velog.velcdn.com/images/wanna_make_game/post/43ae8dae-0cc1-4db9-98f3-a72dbea632e0/image.PNG" alt=""></p>
<ol start="2">
<li>Character Sequence 설정
폰트 Asset을 만들 때, 문자가 할당되는 범위를 지정해준다. (해당 문자가 저장된 범위)<pre><code>32-126         &gt; 영어 범위. 문자의 번호 넣어줌
44032-55203    &gt; 한글 범위.
12593-12643    &gt; 한글에서 모음이나 자음 하나만 작성했을 때
8200-9900     &gt; 특수문자</code></pre>하지만, 해당 폰트에 저장된 문자가 없을 경우 범위 지정을 해주더라도 적용이 되지 않는다.</li>
</ol>
<p>그러니까, 폰트 자체가 지원을 안하면 아무리 범위를 Import해줘도 안되는 것.
폰트가 일본어, 중국어를 지원 안하면, 그 언어의 범위를 넣어줘도 지원을 안한다.</p>
<p><code>Render Mode</code>를 <code>SDFAA</code>로 설정하고 <code>Generate Font Atlas</code>를 클릭하면 에셋을 만든다. (오래걸린다.)
과정이 완료되면 Save 눌러서 Asset을 사용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TryGetComponent, 카메라 절두체, 코루틴, 라이트]]></title>
            <link>https://velog.io/@wanna_make_game/TryGetComponent-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EC%A0%88%EB%91%90%EC%B2%B4-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EB%9D%BC%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@wanna_make_game/TryGetComponent-%EC%B9%B4%EB%A9%94%EB%9D%BC-%EC%A0%88%EB%91%90%EC%B2%B4-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EB%9D%BC%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Thu, 24 Oct 2024 19:13:09 GMT</pubDate>
            <description><![CDATA[<p>2024.10.24(목)</p>
<h1 id="trygetcomponent">TryGetComponent</h1>
<p>지금까지 GetComponent를 이용해 Component들을 가져왔었는데 TryGetComponent를 이용해 가져오는 법을 알아보겠다.
<code>TryGetComponent&lt;T&gt;(out component)</code>는 T타입의 컴포넌트가 있으면 true를 반환하고 컴포넌트를 out으로 가져온다.
T타입의 컴포넌트가 없으면 false를 반환한다.</p>
<ul>
<li><p>사용하는 이유: 컴포넌트가 없어도 예외를 발생시키지 않고 안전하게 처리한다.</p>
</li>
<li><p>예제 코드</p>
<pre><code class="language-cs">using UnityEngine;
</code></pre>
</li>
</ul>
<p>public class ExampleScript : MonoBehaviour
{
    private void Start()
    {
        // 게임 오브젝트에 Rigidbody 컴포넌트가 있는지 확인하고 가져옵니다.
        Rigidbody rb;
        if (TryGetComponent<Rigidbody>(out rb))
        {
            // Rigidbody 컴포넌트가 있다면 해당 컴포넌트로 원하는 동작을 수행합니다.
            rb.AddForce(Vector3.up * 100f);
        }
        else
        {
            // Rigidbody 컴포넌트가 없다면 다른 처리를 수행합니다.
            Debug.Log(&quot;Rigidbody component not found.&quot;);
        }
    }
}</p>
<pre><code>
---
# 카메라 절두체(Camera Frustum)
&gt; ❓ 절두체란? 기하학에서 절두체(frustum)는 입체(보통 원뿔이나 각뿔)를 절단하는 하나나 두 평행면 사이의 부분이다. (from 위키백과)



- 절두체 예시 (from 위키백과)
![](https://velog.velcdn.com/images/wanna_make_game/post/d3923fe7-5b7b-4c31-82a0-76ffd588580f/image.png)

카메라 절두체는 카메라의 시야 영역을 정의하는 3차원 기하학적 모양이다.
카메라가 화면에 표시할 수 있는 공간을 설명한다.

시야 범위(FOV)와 Far clipping plane, Near clipping plane을 조정해 절두체의 모양을 바꿔서 렌더링할 범위를 조절할 수 있다.
- Far clipping plane: 카메라와 가까운 평면으로, 이 평면보다 카메라에 가까운 객체는 렌더링되지 않는다.
- Near clipping plane: 카메라와 멀리 있는 평면으로, 이 평면보다 카메라에서 먼 객체는 렌더링되지 않는다.

* Far clipping plane 과 Near clipping plane 그림 (form Unity Documentation)
![](https://velog.velcdn.com/images/wanna_make_game/post/42548eed-b231-4bce-834b-b70cff9dc1b1/image.png)

---
# 코루틴(Coroutine)
코루틴을 한 마디로 설명하자면 &#39;비동기적 동기&#39; 라고 할 수 있다.
코루틴은 실제로는 동기적으로 작동하지만, 작동하는 방식은 비동기적 작동을 모방한다.

`IEnumerator` 를 이용해 코루틴을 사용할 수 있다.
- 코루틴 예제
```cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;

public class DamageIndicater : MonoBehaviour
{
    public Image image;
    public float flashSpeed;

    private Coroutine coroutine;
    // Start is called before the first frame update
    void Start()
    {
        CharacterManager.Instance.Player.condition.onTakeDamage += Flash;
    }


    public void Flash()
    {
        if(coroutine != null)
        {
            StopCoroutine(coroutine);//이 코루틴이 실행중이면 멈추고 다시 시작
        }

        image.enabled = true;
        image.color = new Color(1f, 76f / 255f, 76f / 255f);
        coroutine = StartCoroutine(FadeAway());
    }

    //코루틴
    private IEnumerator FadeAway()
    {
        float startAlpha = 0.3f;
        float a = startAlpha;

        while(a &gt; 0)
        {
            a -= (startAlpha / flashSpeed) * Time.deltaTime;
            image.color = new Color(1f, 76f / 255f, 76f / 255f, a);
            yield return null;
        }

        image.enabled = false;
    }
}</code></pre><hr>
<h1 id="라이트light">라이트(Light)</h1>
<ul>
<li>점 광원(Point Light): 모든 방향으로 균등하게 빛을 발산</li>
<li>방향성 라이트(Directional Light): 무한 히 멀리 위치하여 한 방향으로만 빛을 발산</li>
<li>스포트 라이트(Spot Light): 씬의 한 점에 위치하여 원뿔 모양으로 빛을 발산</li>
<li>면 광원(Area Light): 한쪽 면에서 모든 방향으로 균등하게 빛을 발산</li>
</ul>
<h2 id="공통적으로-공유하는-속성들">공통적으로 공유하는 속성들</h2>
<ul>
<li>속성: 위치, 방향, 강도(intensity), 색상(color), 범위(range), 각도(angle)</li>
<li>그림자: intensity와 마찬가지로 최적화가 중요</li>
<li>성능: 라이트는 렌더링 성능에 많은 영향을 미친다. 최적화가 중요하다.
<code>Lighting Intensity Multiplier</code>: 환경 전체의 빛. 실제 환경의 빛을 조절한다. 해가 지고 뜨고 하는거를 여기서 조절한다고 보면 된다.
<code>Reflecting Intensity Multiplier</code>: 물체에 반사되는 정도를 조절한다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[New Input System의 Invoke Unity Events, localEulerAngles와 eulerAngles]]></title>
            <link>https://velog.io/@wanna_make_game/Invoke-Unity-Events-localEulerAngles%EC%99%80-eulerAngles</link>
            <guid>https://velog.io/@wanna_make_game/Invoke-Unity-Events-localEulerAngles%EC%99%80-eulerAngles</guid>
            <pubDate>Wed, 23 Oct 2024 17:30:03 GMT</pubDate>
            <description><![CDATA[<p>2024.10.23(수)</p>
<p>TMI: 유니티 숙련 주차가 시작되었다. 이번 주차는 [ 강의수강 + 개인 프로젝트 진행 ] 과 같이 진행된다.</p>
<p>이번에는 Send Messages 방식이 아니라 Invoke Unity Events 방식을 사용하여 입력을 받아 볼 것이다.</p>
<hr>
<h1 id="invoke-unity-events">Invoke Unity Events</h1>
<h2 id="input-system의-invoke-unity-events-사용하기">Input System의 Invoke Unity Events 사용하기</h2>
<p>얼마전에 작성한 New Input System의 Input Action설정 방법과 같이 Input Action을 작성한다.</p>
<p><span style="color: gray"><em>Input Action 작성 방법:</em></span> <a href="https://velog.io/@wanna_make_game/Unity%EC%9D%98-%EC%9E%85%EB%A0%A5-%EA%B5%AC%ED%98%84-New-Input-System-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0">New Input System 사용하기</a></p>
<p>저번에는 Send Messages를 사용하였지만, 이번에는 Invoke Unity Events방식으로 사용을 해볼 것이다.</p>
<ul>
<li>Invoke Unity Events로 바꾸면 Player Input의 맨 아래에 다음과 같이 Events 탭이 생기게 된다.
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e5dbfeb6-5541-4270-ab05-0f74a445fca0/image.PNG" alt=""></li>
</ul>
<hr>
<h2 id="invoke-unity-events-1">Invoke Unity Events</h2>
<p>Invoke Unity Events 방식은 Button 사용 방식과 유사하다.
Button 사용시 Button의 Inspector 창에서 메서드를 할당해줘야 되는데, Invoke Unity Events도 마찬가지로, 생성한 Input Actions에 대한 이벤트를 Inspector 창에서 할당해주면 된다.</p>
<ul>
<li>Invoke Unity Events일 때의 Player Input의 Inspector창
<img src="https://velog.velcdn.com/images/wanna_make_game/post/f508ccb9-88a5-4f3d-9ef4-4f8a73e9e57d/image.PNG" alt="">
그림과 같이 할당해주면 각각 PlayerController의 OnMove, OnLook, OnJump가 호출되게 된다.</li>
</ul>
<p>그럼 Send Messages 방식이랑 같은 거 아니냐? 라는 의문이 들 수 있다.
하지만, Invoke Unity Events를 사용하면 script들에서 설정해야 하는 event 시스템을 작성하지 않고, 간단하게 입력이 들어왔을 때, 버튼처럼 메서드들을 할당하면 된다는 장점이 있다.</p>
<hr>
<h1 id="localeulerangles와-eulerangles">localEulerAngles와 eulerAngles</h1>
<p><code>localEulerAngles</code>와 <code>eulerAngles</code>의 차이를 알아보자.</p>
<h2 id="👉-localeulerangles">👉 localEulerAngles</h2>
<p><code>transform.localEulerAngles</code>는 오브젝트의 부모 오브젝트가 있을 때, 부모의 좌표계 [ 로컬 좌표계 ] 를 기준으로 하는 회전을 Euler 각도로 나타낸다.</p>
<h2 id="👉-eulerangles">👉 eulerAngles</h2>
<p><code>transform.eulerAngles</code>는 오브젝트의 회전을 [ 월드 좌표계 ] 에서 Euler 각도로 나타낸다.</p>
<h2 id="↔-localeulerangles와-eulerangles의-차이점">↔ localEulerAngles와 eulerAngles의 차이점</h2>
<p>정리하면 <code>localEulerAngles</code>는 부모의 좌표계를 기준으로 하는 [ 로컬 좌표계 ] 이고, <code>eulerAngles</code>는 [ 월드 좌표계 ] 를 기준으로 한다.</p>
<h2 id="☝-사용-시-유의점">☝ 사용 시 유의점</h2>
<h3 id="gimbal-lock-문제">Gimbal Lock 문제</h3>
<p>Euler 각도는 회전을 순차적으로 적용하기 때문에, 회전 순서에 따라 제한되는 Gimbal Lock문제가 발생할 수 있다. 이를 해결하기 위해, <code>Quaternion</code>을 사용해 회전을 다루는 것이 좋은 방법이다.</p>
<h3 id="360도-회전">360도 회전</h3>
<p><code>localEulerAngles</code>와 <code>eulerAngles</code>는 360도를 기준으로 값을 반환한다. 예를 들어, 오브젝트가 -90도 회전했다면, 이 값은 270도로 반환될 수도 있다. 이것은 Unity에서 회전을 0~360도로 관리하기 때문이다.</p>
<h2 id="❗-localeulerangleseuler-vs-localrotation-quaternion">❗ localEulerAngles(Euler) vs localRotation (Quaternion)</h2>
<ul>
<li><code>localEulerAngles</code>는 회전을 <code>Vector3</code>를 사용한 <code>Eulor</code> 각도로 표현하고, <code>localRotation</code>는 <code>Quaternion</code>으로 회전을 표현한다.</li>
<li><code>Quaternion</code>은 <code>Euler</code>보다 안정적으로 회전을 표현하지만, 사람에게 직관적이지 않다. </li>
<li><code>Euler</code>는 사람에게 직관적이지만, 회전 계산 시 문제가 발생할 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀 프로젝트 Trouble Shooting]]></title>
            <link>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Trouble-Shooting</link>
            <guid>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-Trouble-Shooting</guid>
            <pubDate>Tue, 22 Oct 2024 17:12:25 GMT</pubDate>
            <description><![CDATA[<p>2024.10.22(화)</p>
<p>팀 프로젝트를 마치며 그동안 발생한 문제들에 대해 해결했던 방법을 정리하는 Trouble Shooting을 작성해보도록 하겠다.</p>
<h2 id="trouble-shooting">Trouble Shooting</h2>
<h3 id="작업내용-병합-시-생기는-nullreference">작업내용 병합 시 생기는 NullReference</h3>
<p>Scene에서 작업해야 할 내용이 각자 있어서, 나의 작업 Scene을 따로 만들어서 작업을 하였는데, 코드를 병합할 때, 여러가지 컴포넌트들에서 NullReference가 일어나는 문제가 발생했다.
Scene에서 작업한 오브젝트들을 다른 Scene으로 옮길 때, 할당되어 있던 컴포넌트들의 참조가 풀리면서 script에서 해당 컴포넌트들을 찾을 수 없는 문제였고, 다시 컴포넌트를 할당해주어 해결했다.</p>
<h3 id="github-revert를-사용하는-문제점">GitHUb Revert를 사용하는 문제점</h3>
<p>Dev브런치에서 가져온 코드를 이용해 작업하려고 했는데, 여러가지 오류가 있어서 작업을 마무리하고 다시 Merge해서 가져오려고 Merge한 내용을 Revert하였는데 나중에 다른 팀원분이 작성하신 파일이 없어지는 문제가 발생했다.
나중에 살펴보니, 내가 Revert로 특정 코드 파일을 없앤다는 내용이 Dev로 머지할 때 적용되면서, 팀원분이 작성하신 파일이 없어지게 되는 구조였다. 
그래서 Revert를 다시 알아보니, Revert는 돌아간다는 뜻으로 그 시점으로 회귀하는 거라고 볼 수 있다. 
그런데, 여기서 문제는 내가 돌아간 시점에는 A라는 파일이 없는 시점이다. 그러므로 가장 최신에 Merge를 한 내가 A라는 파일이 없는 시점으로 회귀를 했으므로, 다른 사용자가 작업한 A라는 파일이 내가 Dev에 Merge를 함으로서 없어진 것이다.</p>
<p>내가 원하던 것은 Merge 취소였는데, 과거로 회귀하는 동작이 작동한 것이다.
이런 일이 일어난 이유는 Merge를 한 후 바로 Push를 눌렀기 때문이다.
GitHub에서는 Undo를 통해 작업 내용을 취소할 수 있는데, 이미 Push를 진행한 작업은 취소를 할 수 없다.
그러므로, Merge를 해와서 Push를 하기 전 먼저 프로젝트를 확인하여 이상이 없는지 체크한 후, Push를 하는 습관을 들이도록 해야 한다.
그렇지 않으면, 만약 Merge 해온 프로젝트가 망가졌을 때, 본인 브랜치는 되돌릴 수 없는 강을 건너게 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀 프로젝트: DodgeShooting게임(그런데 뱀서를 곁들인...) -아이템 부분 구현하기]]></title>
            <link>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-DodgeShooting%EA%B2%8C%EC%9E%84%EA%B7%B8%EB%9F%B0%EB%8D%B0-%EB%B1%80%EC%84%9C%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8...-%EC%95%84%EC%9D%B4%ED%85%9C-%EB%B6%80%EB%B6%84-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-DodgeShooting%EA%B2%8C%EC%9E%84%EA%B7%B8%EB%9F%B0%EB%8D%B0-%EB%B1%80%EC%84%9C%EB%A5%BC-%EA%B3%81%EB%93%A4%EC%9D%B8...-%EC%95%84%EC%9D%B4%ED%85%9C-%EB%B6%80%EB%B6%84-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 19 Oct 2024 05:27:10 GMT</pubDate>
            <description><![CDATA[<p>2024.10.18(금)</p>
<h3 id="tmi">TMI</h3>
<p>팀 프로젝트 주차가 시작되었는데 여러 고전 게임들 3개 중 하나를 선택해서 재해석하고 구현하는 것이다. 우리팀은 그 중에 Dodge게임을 선택했다. 프로젝트 기간은 1주일이다.</p>
<h1 id="프로젝트-소개">프로젝트 소개</h1>
<p>현재 팀 프로젝트에서 만들고 있는 것은 Dodge와 Shooting게임 그리고 뱀서라이크를 결합한 게임이다.
사방에서 총을 쏘는 적이 나오고, 플레이어는 적과 총알을 피하면서 적을 파괴하는 게임이다.
그리고 플레이어는 아이템을 통해 폭탄을 얻거나, 체력을 회복하거나, 총알 개수를 늘리거나, 총알 데미지를 올리거나, 스피드나 체력같은 스탯을 올릴 수 있다.</p>
<hr>
<h1 id="item-로직-구현하기">Item 로직 구현하기</h1>
<p>나는 프로젝트 내에서 아이템 부분을 맡았다.</p>
<h2 id="item-정보-관리-구조">Item 정보 관리 구조</h2>
<p>아이템을 관리하기 위해 <code>ScriptableObject</code>를 만들어서 Item들의 정보들을 관리하기로 결정했다.</p>
<ul>
<li>ItemSO : ScriptableObject<pre><code class="language-cs">using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
</code></pre>
</li>
</ul>
<p>public enum ItemType
{
    StatModifier,
    UsableItem
}</p>
<p>[CreateAssetMenu(fileName =&quot;ItemSO&quot;, menuName = &quot;DodgeController/Items/ItemSO&quot;)]
public class ItemSO : ScriptableObject
{
    [Header(&quot;Base Info&quot;)]
    public Sprite itemSprite;
    public ItemType itemType;
    public string itemName;
    public Color itemBackColor;
    public Color fontColor;
    public string itemInfoText;
    public string statInfoText;
    public float stat;</p>
<pre><code>private void OnValidate()
{
    itemName = this.name;
}</code></pre><p>}</p>
<pre><code>여기서 만든 ItemType이라는 `enum` 타입으로 StatModifier와 UsableItem을 나눠서 스탯을 변경해주는 아이템과 사용아이템으로 분류했다.

각 타입은 다음과 같은 기능을 할 것이다.
- StatModifier: itemName과 stat을 이용해 스탯을 변경
- UsableItem: 각 사용 아이템 별로 따로 로직 생성
    (이때, 사용 아이템도 다른 아이템과 마찬가지로 stat을 이용해 능력치를 정해준다.)

itemName은 `OnValidate`를 이용해 자동으로 생성된 아이템의 이름을 가지고 오게 만들었다.

---
## 뱀서라이크 처럼 아이템 선택지 만들기

그리고 나머지 필드들은 SelectPanel의 Button들에 있는 이미지와 색들에 적절히 넣어줄 것이다.
- SelectPanel 구조
![](https://velog.velcdn.com/images/wanna_make_game/post/f247858d-2f3b-42d5-88f8-1149cdf027c3/image.PNG)

이렇게 만든 ItemSO를 이용해 아이템 인스턴스들을 만들어 준다.
- 만든 아이템들
![](https://velog.velcdn.com/images/wanna_make_game/post/5d059586-0779-4889-bc3b-258002b048e4/image.PNG)

### ItemButtonHandler
그리고 이렇게 만든 아이템들은 각 Button들에 부착된 ItemButtonHandler를 통해 Button에 아이템의 정보가 들어가게 된다.
- ItemButtonHandler
```cs
using System;
using UnityEngine;
using UnityEngine.UI;

public class ItemButtonHandler : MonoBehaviour
{
    [SerializeField] private Image buttonImage;
    [SerializeField] private Image itemImage;
    [SerializeField] private Text optionText;
    [SerializeField] private Text statText;

    public void SetUpButton(ItemSO itemSO)
    {
        itemImage.sprite = itemSO.itemSprite;
        buttonImage.color = itemSO.itemBackColor;

        optionText.color = itemSO.fontColor;
        statText.color = itemSO.fontColor;

        optionText.text = itemSO.itemInfoText;

        statText.text = itemSO.statInfoText;
    }


}</code></pre><h3 id="itemselectionmanager">ItemSelectionManager</h3>
<p>다음으로 뱀서라이크의 특징인 레벨업할 시 선택지 3개를 랜덤으로 제공하여 보여주는 기능을 추가할 것이다.</p>
<p>ItemSelectionManager를 통해 각 버튼들에 아이템을 랜덤으로 보여준다.</p>
<ul>
<li>ItemSelectionManager<pre><code class="language-cs">using System;
using System.Collections;
using System.Collections.Generic;
using UnityEditorInternal.Profiling.Memory.Experimental;
using UnityEngine;
using UnityEngine.UI;
using Random = UnityEngine.Random;
</code></pre>
</li>
</ul>
<p>public class ItemSelectionManager : MonoBehaviour
{
    public static ItemSelectionManager instance;</p>
<pre><code>[SerializeField] private GameObject selectPanel;
[SerializeField] private List&lt;ItemSO&gt; allItems;
[SerializeField] private List&lt;ItemButtonHandler&gt; optionButtons;
private int selectionOptionCount = 3;

[SerializeField] private StatHandler statHandler;

private void Awake()
{
    if (instance == null)
    {
        instance = this;
    }
    else
    {
        Destroy(gameObject);
    }
}

public void DisplaySelectionOptions()
{
    //selectPanel을 활성화하고 버튼에 아이템을 랜덤으로 넣는다.
    selectPanel.SetActive(true);

    List&lt;ItemSO&gt; selectedItems = GetRandomItems(selectionOptionCount);

    for (int i = 0; i &lt; optionButtons.Count; i++)
    {
        optionButtons[i].SetUpButton(selectedItems[i]);

        int index = i;
        optionButtons[i].GetComponent&lt;Button&gt;().onClick.RemoveAllListeners();
        optionButtons[i].GetComponent&lt;Button&gt;().onClick.AddListener(() =&gt; OnItemSelected(selectedItems[index]));
    }
}


private List&lt;ItemSO&gt; GetRandomItems(int count)
{
    List&lt;ItemSO&gt; selectedItems = new List&lt;ItemSO&gt;();
    List&lt;int&gt; selectedIndexes = new List&lt;int&gt;();
    while (selectedItems.Count &lt; count)
    {
        int randomIndex = Random.Range(0, allItems.Count);

        if (selectedIndexes.Contains(randomIndex) == false)
        {
            selectedItems.Add(allItems[randomIndex]);
            selectedIndexes.Add(randomIndex);
        }
    }
    return selectedItems;
}

private void OnItemSelected(ItemSO selectedItemSO)
{
    //TODO : 아이템 선택 처리 로직
    if (selectedItemSO.itemType == ItemType.StatModifier)
    {
        statHandler.ModifiyCharacterStat(selectedItemSO);
    }
    else if (selectedItemSO.itemType == ItemType.UsableItem)
    {

    }
    Debug.Log($&quot;Button {selectedItemSO.itemName} onClick assigned&quot;);

    selectPanel.SetActive(false);
}</code></pre><p>}</p>
<pre><code>이제 플레이어의 레벨업이나 아이템을 먹을 시 DisplaySelectionOptions를 호출해주면 아이템 선택창이 다음과 같이 나오게 된다.
- 아이템 선택 창
![](https://velog.velcdn.com/images/wanna_make_game/post/3ff66653-067d-4cca-89cc-602c5cbd8f04/image.PNG)
(가운데 하얀 네모는 ItemSO의 sprite가 아직 비어있기 때문이다. 도트 찍을 줄 아시는 팀원분이 있는데 그분이 만들 예정이다.) 

---
## Item 별로 기능 부여하기
그러면 이제 각 아이템들의 동작을 관리해 줘야 하는데, 이것은 `Dictionary`를 이용해 관리했다.
StatModifer와 UsableItem으로 크게 동작을 나누고 각각의 아이템 이름에 따라 세부적으로 기능을 나눠주었다.


각 이름별로 기능을 나눠주는 동작은 StatModifer와 UsableItem 각각 ItemToStatAssigner와 GetUsableItemAssigner에서 만들어 주었다. 이렇게 해준다면 아이템을 추가할 때, 이곳에서 기능을 작성해주면 되므로 유지보수/확장 측면에서 좋다고 할 수 있다.
### ItemToStatAssigner
- ItemToStatAssigner
```cs
using System;
using System.Collections.Generic;
using UnityEngine;

public class ItemToStatAssigner : MonoBehaviour
{
    private Dictionary&lt;string, Action&lt;HealthStatSO, float&gt;&gt; statModifiers;

    private void Awake()
    {
        statModifiers = new Dictionary&lt;string, Action&lt;HealthStatSO, float&gt;&gt;
        {
            {&quot;IncreaseBulletNum&quot;, (stat, value) =&gt; stat.bulletNum += (int)value },
            {&quot;IncreaseBulletDamage&quot;, (stat, value) =&gt; stat.ATK += value },
            {&quot;IncreaseSpeed&quot;, (stat, value) =&gt; stat.speed += value },
            {&quot;IncreaseHealth&quot;, (stat, value) =&gt; stat.maxHP += value }
            //StatModifier추가시 여기에 기능 추가
        };
    }

    public void ModifyStatBasedOnItem(ItemSO itemSO, HealthStatSO currentStat)
    {
        if (itemSO.itemType == ItemType.StatModifier)
        {
            if (statModifiers.TryGetValue(itemSO.itemName, out Action&lt;HealthStatSO, float&gt; modifyAction))
            {
                modifyAction(currentStat, itemSO.stat);
                Debug.Log(&quot;스탯변경 완료.&quot;);
            }
            else
            {
                Debug.Log($&quot;{itemSO.itemName}에 대한 스탯 변경 규칙이 정의되지 않았습니다.&quot;);
            }
        }
    }
}</code></pre><h3 id="getusableitemassigner">GetUsableItemAssigner</h3>
<ul>
<li>GetUsableItemAssigner<pre><code class="language-cs">using System;
using System.Collections.Generic;
using UnityEngine;
</code></pre>
</li>
</ul>
<p>public class GetUsableItemAssigner : MonoBehaviour
{
    [SerializeField] private HealthSystem playerHealthSystem;
    private Dictionary&lt;string, Action&lt;float, HealthStatSO&gt;&gt; getUsableItemActions;
    private void Awake()
    {
        getUsableItemActions = new Dictionary&lt;string, Action&lt;float, HealthStatSO&gt;&gt;()
        {
            {&quot;RecoveryPotion&quot;, GetRecoveryPotion },
            {&quot;Bomb&quot;, GetBomb}
        };
    }</p>
<pre><code>public void GetRecoveryPotion(float itemStat, HealthStatSO currentStat)
{
    playerHealthSystem.ChangeHealth(itemStat);
    Debug.Log(&quot;Use RecoveryPotion&quot;);
}
public void GetBomb(float itemStat, HealthStatSO currentStat)
{
    // TODO : itemStat으로 폭탄의 데미지를 조정한다.
    // 플레이어의 Bomb의 개수를 관리하는 곳에 Bomb +1
    Debug.Log(&quot;Get Bomb&quot;);
}</code></pre><p>}</p>
<pre><code>### StatHandler
다음은 캐릭터의 stat을 변경해주는 StatHandler이다. 다른 팀원분이 작성해주신 틀에 스탯을 변경하는 부분을 추가하였다.
- StatHandler
```cs
using System;
using System.Collections.Generic;
using UnityEngine;

public class StatHandler : MonoBehaviour
{
    [SerializeField] private HealthStatSO baseStat;
    public HealthStatSO CurrentStat { get; private set; }

    public ItemToStatAssigner itemToStatAssigner;

    private void Awake()
    {
        UpdateCharacterStat();
    }

    private void UpdateCharacterStat()
    {
        CurrentStat = (HealthStatSO)baseStat.Clone();
    }

    //스탯 변경 추가
    public void ModifiyCharacterStat(ItemSO itemSO)
    {
        itemToStatAssigner.ModifyStatBasedOnItem(itemSO, CurrentStat);
    }
}</code></pre><hr>
<h2 id="추가로-필요한-작업">추가로 필요한 작업</h2>
<p>이렇게 어느정도 Item들을 추가하고 사용하는 방법과 아이템 구조를 확장성과 유지보수성을 고려하며 작성해 보았다.</p>
<p>이제 추가적으로 작업해야 하는 부분들에 대해 이야기해 보자면, 아직 아이템이 맵에 랜덤으로 생성되서 플레이어가 먹는 로직이나, 플레이어가 레벨업하는 로직이 없기 때문에 선택지가 활성화되는 순간이 없는데, 이것을 만들어 줘야 할 것이다.</p>
<p>그리고 아무래도 선택 시 바로 사용이 되는 아이템이 있고, Bomb처럼 유저가 이후에 자유롭게 타이밍 맞춰서 사용하는 아이템이 있으니 UseItemAssigner도 필요하다고 생각된다.</p>
<p>이상 2024.10.18 TIL마침.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[delegate와 event]]></title>
            <link>https://velog.io/@wanna_make_game/delegate%EC%99%80-event</link>
            <guid>https://velog.io/@wanna_make_game/delegate%EC%99%80-event</guid>
            <pubDate>Thu, 17 Oct 2024 12:14:13 GMT</pubDate>
            <description><![CDATA[<p>2024.10.17(목)</p>
<h1 id="delegate">delegate</h1>
<p>delegate (&#39;위임하다&#39;, &#39;대리자&#39; 라는 뜻을 가지고 있다)</p>
<h2 id="delegate는-어디에-사용되는가">delegate는 어디에 사용되는가?</h2>
<ul>
<li>팝업을 만들때,</li>
<li>API통신 만들때, (로그인 후 어떻게 처리하고 로그아웃 후 어떻게 처리하는 지)</li>
<li>이벤트 기반 시스템 만들 때,</li>
</ul>
<p>등등에 사용된다.</p>
<h2 id="delegate는-어떻게-사용하는가">delegate는 어떻게 사용하는가?</h2>
<p>delegate는 함수에 대한 참조들을 가지고 있는 타입이다.
delegate를 변수처럼 사용할 수 있다.
delegate를 매개변수로 전달 가능하다.
=&gt; 함수를 변수처럼 저장하거나, 매개변수로 전달 가능하다.</p>
<p>다음과 같이 delegate 변수를 만들고 함수를 저장할 수 있다.</p>
<pre><code class="language-cs">delegate 반환형 델리게이트이름(매개변수);

델리게이트이름 변수이름;

변수이름 = 함수이름1;
변수이름 += 함수이름2;</code></pre>
<p>delegate는 외부에서 실행가능하다.</p>
<h1 id="event">event</h1>
<p>event
기본적으로는 델리게이트의 한 종류이다.
특정 이벤트가 발생하면 연결된 함수들을 호출하는 구조이다.
옵저버 패턴과 유사하게 활용할 수 있게 도와준다.</p>
<blockquote>
<p>옵저버 패턴?
객체를 관찰하는 옵저버들이 객체의 변화를 감지하여 옵저버를 보낸 객체에게 알림을 보내는 패턴.</p>
</blockquote>
<p>다음과 같이 event 변수를 만들고 함수를 저장할 수 있다.</p>
<pre><code class="language-cs">event 델리게이트이름 변수이름;

변수이름 += 함수이름;</code></pre>
<p>event는 구독기반 로직이며 외부에서 실행불가하다.</p>
<h1 id="delegate와-event의-차이점">delegate와 event의 차이점</h1>
<p>delegate는 외부에서 직접 호출이 가능하다.
event는 외부에서 구독에 대한 작업만 가능하고, 외부에서 직접 호출이 불가능하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메모리 구조: Heap과 Stack, 구조체와 클래스의 차이점]]></title>
            <link>https://velog.io/@wanna_make_game/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0-Heap%EA%B3%BC-Stack-%EA%B5%AC%EC%A1%B0%EC%B2%B4%EC%99%80-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</link>
            <guid>https://velog.io/@wanna_make_game/%EB%A9%94%EB%AA%A8%EB%A6%AC-%EA%B5%AC%EC%A1%B0-Heap%EA%B3%BC-Stack-%EA%B5%AC%EC%A1%B0%EC%B2%B4%EC%99%80-%ED%81%B4%EB%9E%98%EC%8A%A4%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90</guid>
            <pubDate>Wed, 16 Oct 2024 12:54:45 GMT</pubDate>
            <description><![CDATA[<p>Unity의 C#을 사용한다고 가정하고 작성하였습니다. C나 C++같은 언어는 GC가 없다고 합니다.</p>
<h1 id="메모리">메모리</h1>
<hr>
<h2 id="메모리-영역">메모리 영역</h2>
<p>메모리는 Code, Data, Heap, Stack 4가지 영역으로 구분된다.</p>
<h3 id="code">Code</h3>
<p>프로그래머가 작성한 코드를 보관</p>
<h3 id="data">Data</h3>
<p>어플리케이션 전반에 필요한 데이터 저장
static const
프로그램 시작시 생성, 프로그램 종료시 삭제</p>
<h3 id="heap">Heap</h3>
<p>객체(참조 데이터) 저장
new를 통해 생성, GC로 인해 삭제
In유니티) Instantiate(), Destroy()-&gt; 참조만 끊고 결국엔 GC로 인해 삭제</p>
<h3 id="stack">Stack</h3>
<p>로컬 변수, 매개변수 등, 어플리케이션에 필요한 데이터를 보관
에플리케이션 실행순서에 필요한 데이터 보관
데이터를 변수 할당시 생성, {} 끝날 때 삭제</p>
<hr>
<h2 id="heap과-stack의-메모리-동작">Heap과 Stack의 메모리 동작</h2>
<h3 id="stack의-메모리-동작">Stack의 메모리 동작</h3>
<p>실행 1.</p>
<ul>
<li>int x = 10;
처음에 Stack 메모리 주소 맨 아래에 저장된다.</li>
</ul>
<p>실행 2.</p>
<ul>
<li>int y = x;
x에 있는 값을 y로 가져온다.
Stack 메모리의 x의 위에 y가 x의 값을 복사해와서 저장한다. </li>
</ul>
<p>실행 3.</p>
<ul>
<li><p>int x = 5;</p>
</li>
<li><p>결과</p>
<pre><code>x = 5, y = 10</code></pre><p>x와 y는 연결되어 있지 않음!</p>
</li>
</ul>
<h3 id="heap의-메모리-동작">Heap의 메모리 동작</h3>
<blockquote>
<p>Class는 참조형이다. 
값형과 참조형의 차이는 Stack에 값이 직접 저장되냐, Stack엔 주소가 저장되고 값은 Heap영역에 저장되느냐의 차이다.</p>
</blockquote>
<p>실행 1.</p>
<pre><code class="language-cs">void Update()
{
    Monster orc = new Monster();
    Debug.Log(orc.Score);
}</code></pre>
<p>Stack의 메모리 주소 맨 아래에 orc의 주소가 저장된다.
Heap영역에 orc의 데이터가 생성된다.
Update 한 번이 끝나면, Stack에 저장된 orc는 없어진다.
하지만, Heap에 저장된 orc의 데이터는 계속 남는다.
Update가 반복되면, Heap영역에 orc가 계속 생긴다.</p>
<p>그렇다면 여기서 Heap영역에 저장된 데이터는 언제 없어질까?</p>
<p>활용되지 않는 데이터 = 쓰레기 = 가비지 라고 불린다.
Heap영역에 버려진 가비지들은 가비지 컬렉터(GC)에 의해 언젠가는 사라진다.
그러나, 언제 사라질 지는 모른다.
이게 가비지 컬렉터의 단점이자, Heap메모리 영역을 사용할 때 잘 설계해야 하는 이유이다.
(참고로 Instansitate는 new를 한 거와 같다.)</p>
<hr>
<h2 id="heap영역의-gc문제에-대한-해결책">Heap영역의 GC문제에 대한 해결책?</h2>
<p>Unity의 Scene에서 생성되는 Cube 이런 것들도 다 클래스이다.
MonoBehavior를 계승한 객체들이다.</p>
<p>이런 것들을 어떻게 해결할까?
답은 재활용!
오브젝트 풀을 활용해 객체들을 파괴하지 않고 비활성화 시켜서 나중에 다시 사용할 때 Instaciate를 하지 않고 비활성화한 객체를 다시 활성화 시켜서 활용한다.</p>
<hr>
<h2 id="vector3와-string-파헤치기">Vector3와 string 파헤치기</h2>
<h3 id="vector3">Vector3</h3>
<p>Vector3는 구조체다.
구조체는 Stack에 저장되기 때문에 만들고 삭제하면 데이터가 바로바로 파괴된다.
Stack 메모리가 더 빠르다.</p>
<h3 id="string">String</h3>
<p>문자열 연산은 불변객체다.</p>
<p>실행 1.</p>
<pre><code class="language-cs">string name = &quot;GAME&quot; + &quot;game&quot;;</code></pre>
<p>Heap영역에 있는 string 데이터 &quot;GAME&quot;에 &quot;game&quot;을 더하려고 한다면 새로운 Heap영역에 &quot;GAME&quot;과 &quot;game&quot;길이만큼의 공간을 확보한 후 연산을 진행한다.
이게 반복되면 엄청난 길이의 메모리가 필요하다.
string과 같은 방식으로 동작하는 것을 &#39;파편화&#39; 라고 한다.</p>
<h4 id="문자열-연산의-해결책은">문자열 연산의 해결책은?</h4>
<ol>
<li>string은 보간 문자열($&quot;&quot;)을 사용하는 게 좋다. ( $&quot;Name: {name}&quot;)</li>
<li>StringBuilder를 사용해서 문자열을 합친다.</li>
</ol>
<hr>
<h2 id="구조체와-클래스의-차이점은">구조체와 클래스의 차이점은?</h2>
<p>값 형식과 참조 형식이라는 차이점이 있다.
이 특성 때문에, 구조체는 Stack에 할당되고 클래스는 Heap에 할당된다.
클래스는 Heap에서 메모리가 가비지 컬렉터에 의해 관리되고,
구조체는 Stack에서 바로바로 메모리가 사라진다.</p>
<p>클래스가 기본적으로 구조체보다 기능적인 측면에서 좋은 점이 많다.
하지만, 구조체는 단기적으로 빠르게 순환될 때 성능적으로 유리하다.</p>
<hr>
<h2 id="data영역에-저장되는-static">Data영역에 저장되는 static</h2>
<p>static은 Data영역에 저장된다.
프로그램 시작시 생성되기 때문에 언제든지 사용할 수 있다.
Scene이 넘어가도 데이터가 계속 유지되어 있다는 특성이 있다.</p>
<p>그러나, static으로 만들었다고 바로 사용할 수 있는 건 아니다.
데이터를 할당 하지 않으면 null이다. 사용은 할 수 있지만, 데이터는 초기화를 해줘야 된다.
static을 작성할 때, Heap과 Stack영역에 있는 데이터를 바로 넣을 순 없다.</p>
]]></description>
        </item>
    </channel>
</rss>