<?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>Tue, 20 Jan 2026 19:45:01 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%ED%83%9C%ED%95%9C-%ED%95%98%EB%A3%A8</link>
            <guid>https://velog.io/@wanna_make_game/%EB%82%98%ED%83%9C%ED%95%9C-%ED%95%98%EB%A3%A8</guid>
            <pubDate>Tue, 20 Jan 2026 19:45:01 GMT</pubDate>
            <description><![CDATA[<p>사람이 어찌 이리 나태할 수가 있을까.
나는 분명 나태의 신이 가호를 내린 나태나태 인간일 것이다.
인간이 어찌 이리 나태나태 열매를 먹은 것마냥 목적 없이 흘러가는 대로 살 수가 있을까.
내가 이리 나태한 데에는 이유가 있을 것이다.
규칙적인 생활습관을 가지지 않고 불규칙적인 수면습관을 가진 것도 이유가 될 것이다.
키에 비해 많이 나가는 몸무게도 한 몫을 하고 있을 것이다.
게임에 빠져서 시간가는 줄도 모르고, 시간 제한 없이 하는 것도 영향이 있을 것이다.
누가 부르면 그 시간에 계획해둔 일이 있더라도 상관없이 약속에 가는 것도 필히 상관이 있을 것이다.
지금 나에겐, 목적의식이 없다.
내가 간절히 바라고 이루고 싶은 목표, 해내야만 하는 목표, 이걸 꼭 해내야겠다 싶은 목표가 없다.
삶의 이정표가 될 목표가 없다. 마치, 네비게이션 없이 달리는 차처럼.</p>
<p>물론, 나도 하고싶은 것은 많다. 1인 인디게임 개발도 해보고 싶고, 노래도 작곡하고 불러보고 싶고, 소설도 써보고 싶고, 영화도 만들어보고 싶다. 누가 들으면 터무니없는 목표라고 말할지도 모를 해보고 싶은 것들, 아무리 고개를 쳐들어도 목적지가 보이지 않는 목표들을 보고 있자면, 어디로 가야할 지 무엇을 해야할 지 감이 쉽사리 잡히지 않는다.
그저 저 멀리 어딘가에 있을 것들을 상상만 하며 제자리에서 우왕좌왕하고 있을 뿐이다.</p>
<p>오늘은 스키장에 다녀왔다. 일주일 전부터, 설레는 마음에 언제 오늘이 올까 기다리며 하루하루를 보냈고, 오늘 새벽 4시 반에 알람 소리에 맞춰 일어나 준비를 하고 버스를 타러 갔다.
스키를 타는 것은 허벅지도 아프고, 리프트를 타기 위해 이동하는 것도 힘들고, 기다리고 올라가는 것도 지루했지만, 재미있었다. 버스를 3시간을 타고, 전철도 타며 피곤한 몸을 이끌고 집에 왔음에도 나는 오늘 스키장을 잘 다녀왔다고, 좋은 하루였다고 말할 수 있었다. 피곤하고 힘들었지만 즐겁고 보람찬 하루였다.</p>
<p>나는 목욕을 오래한다. 따뜻한 물에 몸을 담그고 있으면 이루말할 수 없는 포근함과 편안함, 온몽의 피로가 풀어해쳐지며 긴장이 풀리는 감각 등이 몰려온다. 그러고 있으면, 이 따뜻한 물에서 도저히 나가고 싶은 마음이 들지 않는다. 어쩌면, 나갈 이유를 못 느낀다고 해도 좋을 것이다. 아무튼, 따뜻한 탕에서 빠져나오기란 쉽지 않았다.</p>
<p>오늘은 목욕을 했다. 역시나 행복한 감각이 나를 덮쳐오며 내 몸의 피로를 풀어주었고, 나는 오랫동안 목욕을 했다. 물이 차가워진다 싶으면 뜨거운 물을 조금씩 틀어서 다시 따뜻한 탕을 유지해주기를 반복, 장장 2시간이 넘게 탕안에 있었다.</p>
<p>그러다 문득 그런 생각이 들었다. 평소에는 아침에 원하는 시간에 일어나는 것도 버거워하면서, 오늘은 어떻게 그렇게 반짝반짝한 정신으로 일어나자마자 옷을 입고, 짐을 챙겨 버스를 타러 갔을까? 그리고, 지금은 왜 이 탕에 오래있는 게 좋지 않고, 나가야 함을 알고 있음에도 나가지 못하고 있는 걸까?</p>
<p>곰곰이 생각해보았다. 결과는, 목적의식이 있느냐 없느냐의 차이였다.
스키장에 가기 위해서는 4시 반에 일어나서, 내복과 긴 양말, 따뜻한 옷을 입고, 전날 챙겨놓은 짐이 든 가방을 들고 4시 50분에는 나가야 한다는 목표를 가지고 움직였다. 미리 계획한 목표와 준비들은, 나를 빠르고 정확하게 움직일 수 있게 했다.</p>
<p>반면, 목욕탕 속에 있는 나는 아무런 목적의식이 없었다. 아무 생각과 목표가 없으니, 주구장창 편안한 환경에서 쉬면서 일어날 의지조차 상실한 것이다. 아니, 사실 목표가 있긴 했다. &#39;따뜻한 물에 몸을 담그고 피로를 풀어야지&#39; 라는 목표가 있었다. 하지만, 시작과 끝이 없고, 어느정도를 풀 것인지에 대한 기준이 없고, 정해놓은 시간이 없는 흐지부지한 목표는 나를 뜨끈뜨끈한 지상낙원 같은 탕에서 빠져나오게 할 수 없었다.
그런 차이인 것이다.</p>
<p>물론 결국 나오긴 했지만, 나올 수 있었던 이유는 충격적이다. 욕탕에서 자면 체온이 떨어져 죽을 수도 있다는 것을 알기에, 자고 싶은데 욕탕에서 자면 안되니까 살기 위해 나온 것이다. 이렇게 분명한 목표가 생기니 바로 나올 수 있었다.</p>
<p>그래서 오늘 나는 결국 인생도 다를 게 없다고 느꼈다.
하고 싶은 것은 있지만, 아무 생각 없이 그냥 흘러가는대로 살면서 언젠가는 저걸 해보고 싶다고 생각하기만 하고 있으면, 나는 아무것도 제대로 하지 못할 게 분명하다.
분명한 내 인생의 목표를 정하고, 그것을 위한 내 행동의 우선순위를 명확히 해야만 내가 해보고 싶은 것들을 할 수 있게 될 거라고 생각했다.</p>
<p>그래서 곰곰이 또 생각해 보았다.
내 인생에서 내가 추구하는 것이 무엇인지.</p>
<p>우선 궁극적인 목표는 행복이다.
내가 어떻게 살든 행복하면 그만 아니겠는가? 인생은 그런 것이다.</p>
<p>그런 행복을 얻기 위해서는 여러가지 필요한 조건들이 있겠지만, 3가지 커다란 범위로 간추려 보았다.
즐거움, 만족, 평화 이 세가지가 나의 행복을 위한 조건들이다.</p>
<p>일을 하는 즐거움, 게임을 할 때의 즐거움, 친구와 농담을 할 때의 즐거움, 밥을 먹을 때의 즐거움, 잠을 잘 때의 즐거움, 무엇을 하든 즐겁다면 그건 곧 행복이다. 세상에 즐거움이란 감정이 없어진다면, 그만큼 삭막한 세상이 있을 수 있을까?</p>
<p>그 다음은 만족이다. 만족에는 굉장히 여러가지가 담겨있다. 성장에서 오는 만족감인 성취감, 내가 무언가를 해내고 얻는 만족인 뿌듯함, 가족과의 즐거운 저녁식사 후의 만족, 편안함, 이외의 여러 충만한 감각들이 모두 만족이라고 할 수 있겠다.</p>
<p>마지막으로 평화다. 평화롭기 위해서는 무엇이 필요할까? 다툼, 스트레스 같은 외부의 압력에 대항하기 위한 힘이 필요할 것이다. 그리고, 건강, 사랑, 우정, 감사 같은 마음이 필요하다. 눈앞의 위협으로부터 나를 지킬 육체와 스트레스로부터 나를 지킬 건강한 마음이 필요할 것이다. 무엇을 하든 쉽게 지치지 않는 육체, 건강한 육체가 필요할 것이고, 남을 사랑하고 긍정적으로 생각하는 마음이 필요할 것이다. 결국, 평화에는 강인한 육체와 강인하면서도 따뜻한 마음이 필요하다.</p>
<p>나는 행복을 얻기위한 3가지의 큰 가치들을 정했다.
그리고 다음으로 필요한 것은, 나의 행동 우선순위였다.</p>
<p>행동 우선순위를 정하는 데에는 많은 생각이 필요했다.
우선, 가족, 가족은 우선순위에서 높을 수도 있고, 낮을 수도 있는 애매한 순위였다. 부모님의 바람만을 위한 인생을 산다면, 과연 그것이 나의 인생이라고 할 수 있을까? 그것은 내 인생이 아니라, 부모님의 인생을 대신 산 것이리라. 효도를 했다는 것에 대한 보람은 있을 지라도, 결국, 그 인생은 분명 후회로 점철될 것이다.
그렇다면, 가족을 아예 우선순위에서 빼버린다? 그건 있을 수 없는 일이다. 나를 사랑으로 키워주신 부모님을 후순위로 계속 미루다보면, 분명 해주지 못한 것들이 두고두고 후회될 것이다. &#39;계실 때 더 잘할 걸...&#39; 하고 말이다. 효도는 결국 계실 때 해주지 못하면 의미가 없다.</p>
<p>그래서 나는 행동 우선순위 1위를 &#39;가족의 안녕&#39; 으로 정했다.
내 인생을 살되, 우리 가족의 건강, 행복 등을 우선시 해야 한다.
아무리 내가 돈을 많이 벌어도, 우리 가족의 불행해진다면 나도 불행함을 피할 수 없을 것이다.</p>
<p>그 다음으로는, 돈이다. 정말 생각을 많이 했지만, 돈이 없다면 행복해질 수 없다는 것은 분명한 사실이다. 혹자는 말한다, 돈으로는 행복을 살 수 없다고. 근데 나는 동의하지 않는다. 돈이 있으면 분명히 행복해질 수 있다. 좋은 집에서 살고, 맛있는 밥을 먹고, 해보고 싶은 것을 하고, 가보고 싶은 곳을 가보고 이런 것들에는 모두 돈이 들어간다. 돈이 없으면, 겨울에 춥고 여름에 더움 집에서 살고, 매일 라면으로 밥을 때우고, 무언가를 하기도 어렵다. 돈이 있다고 무조건 행복한 것은 아니겠지만은, 돈이 없다면 불행이 아주 가까울 것이다.</p>
<p>그러므로 행동 우선순위 2위는 &#39;돈&#39; 이다. 돈을 벌기 위한 궁리를 끊임없이 하고, 수행하고, 돈을 쓰기 전에 꼭 필요한 것인지 한 번 더 생각해야 한다.</p>
<p>세 번째로는, &#39;자기 개발&#39; 이다.
그것을 아는가? 세상은 인성이 좋고 성격 좋은 사람보다는 목소리 큰 사람의 말을 더 잘 들어준다. 상대방을 불편하게 하는 사람의 요구조건을 더 잘 들어준다.
하지만, 한 가지 더 입김이 센 사람이 있는데, 바로 강한 사람이다. 여기서의 강함이란, 육체적 강함도 있겠지만, 나는 그 사람이 가지고 있는 어떤 분야에서의 전문성, 지식을 말하고 싶다.
예를 들어, 대학교에서는 교수가 굉장히 강한 사람이겠다. 대학교에서는 교수의 목소리가 크지 않더라도, 모두가 귀 귀울여 교수의 목소리를 듣는다. 교수는 해당 분야에서의 지식과 전문성으로는 이미 사회적으로 인정을 받았고, 현재도 끊임없이 연구하며 그 분야를 개척해나가는 사람이기 때문에, 교수의 목소리는 그 자체로 진실이자 배워야 할 교본으로 간주될 것이다.</p>
<p>이렇듯, 자신만의 힘을 갈고 닦아서 자신의 강함을 채우는 것, 그것이 자기 개발이다.
나의 강함, 나의 가치를 쌓으면, 내 목소리에 힘이 실리고, 어디에서든 인정을 받고, 일자리를 구할 때도 많은 돈과 좋은 대우를 받을 수 있을 것이다.</p>
<p>네 번째로는.......사실 정하지 못했다.
세상에 있는 모든 일을 어떻게 정형화해서 우선순위를 정핧 수 있을까.
AI라면 근시일 내에 가능할지도 모르겠다만, 나는 사람이다.
현재, 새벽 1시쯤부터 글을 쓰기 시작해서 벌써 4시 22분이 넘어간다. 아이 피곤해.</p>
<p>그래서, 몇 가지만 더 얘기하고 마무리하자면, 이 세가지의 우선순위 아래에 확실히 있는 것들이 있다.</p>
<p>바로 생각나는 것은 &#39;교우관계&#39; 이다.
내가 말하고자 하는 것은 &#39;내 자신만 중요하니 주변 친구들이나 동료들을 거들떠도 보지 말아라&#39; 같은 게 아니다. 우선순위를 명확히 하라는 것이다.
내가 오늘 해야할 자기개발 계획이 있는데 덜컥 친구가 게임하자고 전화왔다고 게임을 하러가고, 이번주에 지출을 이만큼만 써야 하는데 친구와 밥먹는다던가 놀러간다던가 하면서 무리한 지출을 하고, 하는 것들을 자제하자는 것이다.
자신만의 우선순위를 정해서, &#39;자기개발 &gt; 친구와 놀기&#39;, &#39;지출 계획 &gt; 친구와 놀기&#39; 등으로 실행하라는 것이다. &quot;오늘은 해야만 하는 계획이 있으니 다음주 언제 놀자.&quot; 라고 말할 줄 알아야 한다는 것이다. 당장은 친구와 친하게 지내는 것이 중요한 것 같지만, 친구가 돈 벌어주고 밥 먹여주지 않는다. 너가 무엇을 해야한다고 진지하게 말한다면, 그 친구도 이해할 것이다. 만약, 너의 계획과 목표를 중요하게 여기지 않는 친구라면 진정한 친구인지 다시한 번 생각해보자. 정말 함꼐하고 싶어서 징얼거리는 어린아이같은 친구인지, 너를 별로 존중하지 않는 친구인지 잘 판단해야 할 것이다.</p>
<p>이렇게 길게 말한 것은 자신의 시간을 온전히 자신의 시간으로 컨트롤 할 수 있게 하는 것이 중요하기 때문이다. 내가 해야할 것이 많은데, 내 시간이 없다면 언제 그것을 하겠는가?
내일? 내일도 내일로 미루지 않을 거라는 보장이 있는가? 당장 오늘의 내 시간을 사용하는 방법부터 익혀야 할 것이다.</p>
<p>물론, 그렇다고 돈이 친구보다 중요하다? 라고 한다면, 그렇지는 않다. 하지만, 돈이 없으면, 친구도 없다. 매번 돈이 없어서 얻어먹는 사람과 만나고 싶은 친구가 있을까? 그저 매번 자신의 상황에 맞게 잘 저울질을 하길 권할 뿐이다.</p>
<p>그 다음으로 생각나는 것은........당장은 없다.
이외의 모든 상황이 아마 해당되지 않을까 싶다. 뭐 살아가다 떠오르면 또 적어놓으면 되겠지 싶다.</p>
<p>나는 사실 기억력이 좋지 않다. 그래서 이것도 그냥 일기처럼 간단하게 쓰려고 킨 것이다.
어느세 5시가 다 되가지만, 일기 쓰는 것도 생각보다 재밌다.
굿 밤이다. shiit</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/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80</link>
            <guid>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80</guid>
            <pubDate>Thu, 26 Dec 2024 23:59:40 GMT</pubDate>
            <description><![CDATA[<p>2024.12.26(목)</p>
<p>부대정보의 기사 데이터를 변경하는 것과 데이터를 일치시켰다.</p>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/53dd81ec-3cf8-4153-a69c-658514ee0ede/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[팀 프로젝트 일일 개발일지: UI 연결하기]]></title>
            <link>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9D%BC%EC%9D%BC-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-UI-%EC%97%B0%EA%B2%B0%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-%EC%9D%BC%EC%9D%BC-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-UI-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 24 Dec 2024 12:58:52 GMT</pubDate>
            <description><![CDATA[<p>2024.12.24(화)</p>
<p>내가 만든 UI와 팀원이 만든 UI를 연결해 보았다. 아직 임무부분과 병력부분 데이터를 연결하는 중이다.</p>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/86659e17-da66-4684-923c-5ac9e1f44ba4/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[최종 프로젝트: 중간 발표 대본]]></title>
            <link>https://velog.io/@wanna_make_game/%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A4%91%EA%B0%84-%EB%B0%9C%ED%91%9C-%EB%8C%80%EB%B3%B8</link>
            <guid>https://velog.io/@wanna_make_game/%EC%B5%9C%EC%A2%85-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A4%91%EA%B0%84-%EB%B0%9C%ED%91%9C-%EB%8C%80%EB%B3%B8</guid>
            <pubDate>Mon, 23 Dec 2024 13:12:42 GMT</pubDate>
            <description><![CDATA[<p>2024.12.23(월)</p>
<p>오늘은 8주 프로젝트 중간 발표를 하였다. 매우 떨렸지만, 13분의 시간에 맞게 발표하였다. 다음은, 팀원들이 준 발표자 노트의 내용을 나의 언어로 풀어서 바꾼 대본이다.</p>
<h1 id="프로젝트-중간-발표-대본">프로젝트 중간 발표 대본</h1>
<p><a href="https://docs.google.com/presentation/d/1lWX56Qtu3mdbNvUr2IVs6po8_GhjIrZ87cu_r8215y0/edit?usp=sharing">Docs.Presentation: 13조 중간 발표 PPT</a></p>
<p>[시작]
안녕하세요 13조 사철남의 중간발표를 맡게 된 이영근입니다. 발표 시작하겠습니다.</p>
<p>[목차]
다음은 이번 발표의 목차입니다.
프로젝트 소개
시연영상
기술적 의사 결정
트러블 슈팅
중간지점에서의 소회 와 같은 순서로 진행될 예정입니다.</p>
<h1 id="프로젝트-소개">프로젝트 소개</h1>
<p>프로젝트 소개입니다.</p>
<p>[농담 씬]
저희 프로젝트는 플레이어가 직접 지휘관이 되서 전쟁을 지휘한다면 어떤 느낌일까? 라는 생각에서 출발하였습니다. 중세 시대의 전쟁에서는 지휘관 회의에서 정립한 여러 전략들을 북이나 깃발, 뿔피리 같은 여러 신호 전달 수단을 통해 전달하여 전쟁을 수행합니다. 이런 실제 전쟁 상황을 고려해, 플레이어가 전쟁에 나가기 전에 직접 다양한 전략을 수립하고 전쟁에서 준비한 전략들을 이용해 전쟁을 펼치게 하여 플레이어에게 지휘관으로서 전쟁을 수행하는 경험을 제공하고자 하였습니다.</p>
<p>[레퍼런스 씬]
프로젝트에 들어서며 팀 회의를 통해 정해진 저희의 게임은 영지 경영과 실시간 전투 시뮬레이션을 결합한 턴제 경영 시뮬레이션 장르입니다.
플레이어는 턴을 진행하며 영지를 경영하고, 여러 이벤트들을 통해 다른 영지와의 전쟁을 경험할 수 있습니다.</p>
<h1 id="시연영상">시연영상</h1>
<p>다음은 시연영상입니다. 시연영상 보시고 가겠습니다.</p>
<h1 id="기술적-의사결정">기술적 의사결정</h1>
<p>다음은 기술적 의사결정입니다.</p>
<p>[데이터 관리]</p>
<p>첫 번재로, 게임 데이터를 어떻게 관리할 것인가? 라는 고민을 하였는데, 저희는 JSON 형식으로 데이터를 저장해서 사용하기로 결정했습니다.
[JSON을 사용하는 정확한 이유?]</p>
<p>저희 데이터의 간단한 ERD입니다. 영지, 즉, Domain ID를 기준으로 그 영지에 소속되어있는 기사, 병사들과 영지의 전략들을 가져올 수 있게 하였습니다.</p>
<p>저희는 입력하거나 수정된 데이터를 바로 공유할 수 있고 함께 작업하기 좋은 구글 스프레드 시트를 이용해서 데이터 베이스를 작성하였고, 작성된 시트를 이용해 JSON으로 변경해서 사용하였습니다.</p>
<p>각 데이터 테이블에 맞게 클래스를 만들었고, 다양한 데이터 타입에 대응하여 JSON파일을 불러올 수 있게 제네릭 프로그래밍을 통해 여러 클래스들에 대응할 수 있는 시스템을 구축하였습니다.</p>
<p>[시간의 흐름과 턴을 어떻게 구현해야 하는가] 
다음은 기술적 의사 결정의 2번째입니다. 저희 게임인 영지경영과 실시간 전쟁 시뮬레이션의 시간의 흐름과 턴을 어떻게 구현해야 하는가에 대한 파트입니다.</p>
<p>[시간의 흐름 구상]
막상 처음에 전략 게임을 만드려고 하니 막막했습니다. Time.deltaTime으로 시간 흐름을 잡아야 하나?
그런 고민들을 하다가, 전투과 영지경영의 시간 흐름을 분리시켜서 전투만 실시간으로 진행하고, 영지경영을 턴제로 진행하기로 하였습니다.</p>
<p>보통의 턴제 전략 게임의 경우, 시간이 정량적으로 흐릅니다. 턴 한번을 누르면 진행되는 시간의 길이가 일정 길이로 정해져 있습니다.
예를 들어, 문명의 한턴의 길이는 bc4000년부터 시작해 2150년까지를, 정해진 턴 총량만큼을 나눈 값입니다.
삼국지14와 같은 경우는 한 턴의 길이가 10일로 정해져 있습니다.</p>
<p>그러나, 풋볼매니저에서는, 한 턴은 하루일 때도 있지만, 어쩔 땐 이틀, 어떤 경우엔 며칠을 훅 건너 뛰기도 합니다.
이렇게 진행되는 이유는, 풋볼매니저는 한 턴 진행이 이벤트 중심으로 설계가 되어 있기 때문입니다.
저희 프로젝트의 기획도 이와 비슷합니다.</p>
<p>저희 시간의 흐름 아이디어는 이렇습니다. 각 의사결정 주체들, 즉, 이벤트 발생 주체들이 이벤트를 발생 시키면, 해당 이벤트를 대기열 컬렉션에 집어넣습니다.
각 주체들이 넣은 이벤트들은 대기열에 쌓여있을 것입니다. 그 중에 가장 종료예정일이 짧은 이벤트를 기준으로 종료예정일이 가장 가까운 날짜로 시간을 이동시킵니다.
그래서 한 턴의 길이는 이벤트들 간의 종료일 간격이 됩니다.</p>
<p>[턴 진행 구현]
지금까지는 시간의 흐름을 어떻게 표현할 지에 대해 정했습니다. 그렇다면, 턴 진행을 어떻게 구현해야 할까요?</p>
<p>사실 시간의 흐름과 턴은 관련이 있지만서도 별개의 구분되는 개념입니다. 플레이어는 시간이 연속적으로 흐른다고 느끼지만, 내부 설계에서는 시간이 턴 진행에 따라 워프하는 형식으로 진행됩니다.
또한, 플레이어가 체감하는 한 턴은, 사실 자신의 턴 뿐만 아니라, AI의 턴과 월드 턴을 포함하고 있습니다. 다음과 같이 내턴, 행동결정, 결정행동 대기열 등록, ai턴, 침공여부결정, 대기열 등록, 월드턴, 이벤트 결정, 결정행동 대기열등록, 다시 내 턴, 이런 식으로 진행됩니다.</p>
<p>따라서 실제로 턴의 흐름은 그림과 같습니다. 어디선가 본 구조같지 않나요? 뭔가 FSM같지 않나요? 맞습니다. 각 주체들의 턴을 State로 간주할 수 있겠다라는 생각에 상태패턴을 활용해 턴을 구현했습니다.</p>
<p>그래서 결과적으로 턴매니저는 턴진행흐름을 관리하고, 타임매니저는 시간의흐름을 계산하거나 이벤트 대기열을 관리하는 역할을 맡음으로서 시간과 턴을 관리할 수 있었습니다.</p>
<h1 id="트러블-슈팅">트러블 슈팅</h1>
<p>다음은 트러블 슈팅입니다.</p>
<p>[이벤트 결과 출력메서드에 문제가 있다!]
트러블 슈팅 첫 번째 문제입니다. 이벤트 결과 출력메서드에 문제가 있었습니다.</p>
<p>이벤트가 종료되면 그 이벤트들의 결과를 업데이트하는 메서드자체는 문제가 없지만 유지보수 측면에서 문제가 있다고 봤습니다.
나중에 게임 볼륨을 늘릴때 이벤트를 마구마구 늘리게 될텐데, 그럼 이벤트 하나가 추가될 때 마다 else if를 계~속 추가하게 되어 유지보수와 확장성 측면에서 문제가 생깁니다.</p>
<p>매일 저녁 자기 업무진행을 보고하는 스크럼에서 이영근팀장이 과거 자기 작업을 경험으로 코드 개선점을 제시해줬고, 충분히 좋은 개선방향이라고 느꼈습니다.</p>
<p>이영근팀장의 개선방향에 더불어 전략패턴까지 잘 어우러질 수 있겠다는 생각이 들었습니다.</p>
<p>결과적으로 이제 이벤트들이 추가될 때마다 else if를 추가하면서 해당 메서드를 매번수정하지 않게 되었습니다.
이벤트가 추가될 때 마다, 생성자 한줄 추가와</p>
<p>IEventHandler를 상속받는 새로운 클래스만 추가해주면 됩니다.</p>
<p>[Drag이벤트 간에 문제가 있다!]
다음 트러블 슈팅은 드래그 이벤트 간에 생긴 문제입니다.</p>
<p>UI에서 드래그 앤 드롭을 구현할 때, Unity 지원하는 인터페이스들을 이용해서 구현할 수 있습니다.
DraggableUI라는 드래그 앤 드롭을 할 대상을 만들어, 스크립트에 해당 인터페이스들을 상속받게 되면, EventSystem가 자동으로 OnBeginDrag, OnDrag, OnEndDrag 메서드들을 호출해서 드래그 동작을 관리합니다.
그런데, 여기서 문제가 생기는 점은, ScrollView도 똑같은 인터페이스들을 사용한다는 것입니다.</p>
<p>결과적으로 DraggableUI와 ScrollView의 같은 인터페이스와 메서드를 사용하기 때문에, EventSystem이 가장 먼저 호출된 메서드만 처리하여, 다음과 같이 드래그를 하였을 때, 드래그 앤 드롭 시스템만 작동을 하고, 스크롤 뷰가 작동을 하지 않는 현상이 발생합니다.</p>
<p>이 문제의 수정 방향성은 이렇습니다.
보유기사 탭의 ScrollView 영역에서는 ScrollView의 유니티 Drag 이벤트 동작을 수행하고, ScrollView가 아닌 곳에서는 DraggableUI의 유니티 드래그 이벤트 동작을 수행하도록 수정하였습니다.</p>
<p>[폭력적인 script 파트]
기존 DraggbleUI의 OnBeginDrag가 호출될 때, ScrollView의 영역에 있는지 확인하고, ScrollView의 영역이면, ScrollView의 OnBeginDrag를 실행합니다.
마찬가지로, OnDrag 중일 때, ScrollView 영역인지 확인하고, 바깥이면, OnEndDrop으로 ScrollView의 드래그를 끝내고, 드래그 앤 드랍의 드래그를 시작하고, ScrollView의 바깥에서 안으로 들어온 상황이면, 드래그 앤 드롭을 끝내고, ScrollView의 OnBeginDrag를 시작합니다.
마지막으로 OnEndDrag에서 현재 상태가 ScrollView였으면, ScrollView의 OnEndDrag로 드래그를 종료합니다.</p>
<p>수정후엔 다음과 같이 스크롤 뷰 내부에선 스크롤 동작을, 바깥에선 드래그 앤 드롭 동작을 하는 것을 볼 수 있습니다.</p>
<p>[JsonUtility에 문제가 있다!]
다음 트러블 슈팅은 JsonUtility에 문제가 있던 것입니다.</p>
<p>Json 데이터가 배열로 저장되어있을 때, 배열을 수정하면, Json데이터가 싹 날라가버리는 문제가 있었습니다.</p>
<p>JsonUtility를 사용하여 Json데이터를 변경시에 배열값을 변환을 재대로 해주지 못하는 경우가 있어서, 그것을 해결하기 위해서 뉴톤소프트 패키지를 통해 Json을 변환하는것으로 문제를 해결하였습니다.</p>
<p>[프리펨 설정에 문제가 있다!]
마지막 트러블 슈팅입니다. 프리펩 설정에 문제가 있었습니다.</p>
<p>UI를 동적으로 생성하는 과정에서 뒤쪽의 큰 창과 앞쪽의 작은 창이 각각 다른 UI 프리팹으로 구성되어 있었습니다.
이로 인해 동일한 버튼 프리팹을 사용하려 할 때, Inspector(인스펙터)에서 위치를 수동으로 설정하는 방식이 제대로 적용되지 않는 문제가 발생했습니다.
이를 해결하기 위해 새로운 버튼 프리팹을 만들어 사용할 수도 있었지만,
이 방식은 생성된 버튼의 정보를 다른 버튼에 계속 전달해야 하는 번거로움을 가져올 수 있었습니다.
따라서 같은 버튼 프리팹을 재사용하는 것이 더 효율적이고, 유지보수 측면에서도 적합한 방법이라고 판단했습니다.</p>
<p>FindGameObject로 특정 오브젝트의 Transform에 접근해서 버튼을 넣어줍니다.
동일한 데이터를 활용해 다른 UI에 두 번째 버튼도 생성합니다.
부모 오브젝트가 없을 경우, GameObject.Find를 통해 동적으로 탐색하도록 설계하였습니다.
이 버튼은 클릭 시 클릭하는 위치에 따라서 주어진 기능이 달라집니다.
위에 박스는 버튼 클릭시에 LoadCustomUI를 보이게 하는 용도, 아래 박스는 버튼 클릭시에 임무를 기사에게 할당하는 기능을 합니다.</p>
<p>결과적으로 이와 같이 버튼이 UI에 동시에 생성되고 두 버튼 다 같은 정보를 가지고 있지만, 각각 다른 역할을 할 수 있게 되었습니다.</p>
<h1 id="중간-지점에서의-각자의-소회">중간 지점에서의 각자의 소회</h1>
<p>마지막으로, 중간 지점에서의 각자의 소회입니다.</p>
<p>저희 팀원들이 중간 지점까지 진행하면서 각각 느낀 점들을 적어보았습니다.</p>
<p>이상으로 13조 사철남의 중간 발표를 마치겠습니다.
감사합니다.</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[팀 프로젝트 개발 일지:]]></title>
            <link>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80</link>
            <guid>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80</guid>
            <pubDate>Fri, 20 Dec 2024 00:14:48 GMT</pubDate>
            <description><![CDATA[<p>2024.12.19(목)</p>
<ul>
<li>UnitFormationManager.cs</li>
</ul>
<pre><code class="language-cs">using System.Collections.Generic;
using UnityEngine;

public class UnitFormationManager
{
    // 병력을 생성하고 배치하는 메서드
    public void CreateAndArrangeUnits(GameObject soldierPrefab, Transform parentTransform, int soldierCount, Vector2Int gridDimensions, string formationType = &quot;Rectangle&quot;)
    {
        if (soldierPrefab == null)
        {
            Debug.LogError(&quot;Soldier prefab is null!&quot;);
            return;
        }

        if (parentTransform == null)
        {
            Debug.LogError(&quot;Parent transform is null!&quot;);
            return;
        }

        if (gridDimensions.x &lt;= 0 || gridDimensions.y &lt;= 0)
        {
            Debug.LogError(&quot;Invalid grid dimensions! Both rows and columns must be greater than 0.&quot;);
            return;
        }

        ClearExistingUnits(parentTransform);

        switch (formationType.ToLower())
        {
            case &quot;rectangle&quot;:
                ArrangeRectangleFormation(soldierPrefab, parentTransform, soldierCount, gridDimensions);
                break;

            case &quot;triangle&quot;:
                ArrangeTriangleFormation(soldierPrefab, parentTransform, soldierCount);
                break;

            default:
                Debug.LogError(&quot;Unsupported formation type: &quot; + formationType);
                break;
        }
    }

    // 직사각형 형태로 배치
    private void ArrangeRectangleFormation(GameObject soldierPrefab, Transform parentTransform, int soldierCount, Vector2Int initialGridDimensions)
    {
        Collider soldierCollider = soldierPrefab.GetComponent&lt;CapsuleCollider&gt;();
        if (soldierCollider == null)
        {
            Debug.LogError(&quot;Soldier prefab does not have a CapsuleCollider component!&quot;);
            return;
        }

        float spacingBuffer = 0.5f; // 최소 간격 버퍼
        float xSpacing = 2 * ((CapsuleCollider)soldierCollider).radius + spacingBuffer; // 가로 간격
        float zSpacing = ((CapsuleCollider)soldierCollider).height + spacingBuffer;    // 세로 간격

        // 비율에 따라 행과 열 계산
        float aspectRatio = (float)initialGridDimensions.x / initialGridDimensions.y;
        int targetColumns = Mathf.CeilToInt(Mathf.Sqrt(soldierCount * aspectRatio));
        int targetRows = Mathf.CeilToInt((float)soldierCount / targetColumns);

        int createdSoldiers = 0;
        for (int i = 0; i &lt; targetRows; i++)
        {
            for (int j = 0; j &lt; targetColumns; j++)
            {
                if (createdSoldiers &gt;= soldierCount)
                {
                    return;
                }

                Vector3 position = parentTransform.position + new Vector3(j * xSpacing, 0, i * zSpacing);
                position = CalculateNonOverlappingPosition(position, soldierPrefab, parentTransform);
                GameObject soldier = Object.Instantiate(soldierPrefab, position, Quaternion.identity, parentTransform);
                soldier.name = $&quot;Soldier_{createdSoldiers + 1}&quot;;
                createdSoldiers++;
            }
        }
    }

    // 삼각형 형태로 배치
    private void ArrangeTriangleFormation(GameObject soldierPrefab, Transform parentTransform, int soldierCount)
    {
        float spacingBuffer = 0.5f; // 최소 간격 버퍼

        Collider soldierCollider = soldierPrefab.GetComponent&lt;Collider&gt;();
        if (soldierCollider == null)
        {
            Debug.LogError(&quot;Soldier prefab does not have a Collider component!&quot;);
            return;
        }

        float xSpacing = soldierCollider.bounds.size.x + spacingBuffer;
        float zSpacing = soldierCollider.bounds.size.z + spacingBuffer;

        int createdSoldiers = 0;
        int rowCount = 0;

        while (createdSoldiers &lt; soldierCount)
        {
            rowCount++;
            int soldiersInRow = rowCount;

            // Ensure at least one soldier in the last row
            if (createdSoldiers + soldiersInRow &gt; soldierCount)
            {
                soldiersInRow = soldierCount - createdSoldiers;
            }

            for (int j = 0; j &lt; soldiersInRow; j++)
            {
                float offsetX = -(soldiersInRow - 1) * xSpacing / 2; // 중심 정렬
                Vector3 position = CalculateNonOverlappingPosition(parentTransform.position + new Vector3(j * xSpacing + offsetX, 0, rowCount * zSpacing), soldierPrefab, parentTransform);
                GameObject soldier = Object.Instantiate(soldierPrefab, position, Quaternion.identity, parentTransform);
                soldier.name = $&quot;Soldier_{createdSoldiers + 1}&quot;;
                createdSoldiers++;

                if (createdSoldiers &gt;= soldierCount)
                {
                    return;
                }
            }
        }
    }

    // 콜라이더를 고려한 위치 계산
    private Vector3 CalculateNonOverlappingPosition(Vector3 proposedPosition, GameObject soldierPrefab, Transform parentTransform)
    {
        Collider prefabCollider = soldierPrefab.GetComponent&lt;CapsuleCollider&gt;();
        if (prefabCollider == null)
        {
            return proposedPosition;
        }

        Vector3 offset = Vector3.zero;
        int maxAttempts = 10;
        float minimumSpacing = ((CapsuleCollider)prefabCollider).radius * 2 + 0.5f; // 최소 간격

        for (int attempt = 0; attempt &lt; maxAttempts; attempt++)
        {
            bool isOverlapping = false;

            foreach (Transform child in parentTransform)
            {
                Collider childCollider = child.GetComponent&lt;CapsuleCollider&gt;();
                if (childCollider != null)
                {
                    float distance = Vector3.Distance(
                        childCollider.ClosestPoint(proposedPosition + offset),
                        proposedPosition + offset
                    );

                    if (distance &lt; minimumSpacing)
                    {
                        isOverlapping = true;
                        offset += new Vector3(0.1f, 0, 0.1f);
                        break;
                    }
                }
            }

            if (!isOverlapping)
            {
                return proposedPosition + offset;
            }
        }

        Debug.LogWarning(&quot;Could not find a non-overlapping position after max attempts.&quot;);
        return proposedPosition + offset;
    }

    // 기존 병사들을 제거하는 메서드
    private void ClearExistingUnits(Transform parentTransform)
    {
        foreach (Transform child in parentTransform)
        {
            Object.Destroy(child.gameObject);
        }
    }

    // 병력 프리셋 클래스
    public class FormationPreset
    {
        public string Name { get; private set; }
        public Vector2Int GridDimensions { get; private set; }

        public FormationPreset(string name, Vector2Int gridDimensions)
        {
            Name = name;
            GridDimensions = gridDimensions;
        }
    }

    // 프리셋 리스트를 제공하는 메서드
    public List&lt;FormationPreset&gt; GetAvailablePresets()
    {
        return new List&lt;FormationPreset&gt;
        {
            new FormationPreset(&quot;Square Formation&quot;, new Vector2Int(5, 5)),
            new FormationPreset(&quot;Line Formation&quot;, new Vector2Int(10, 1)),
            new FormationPreset(&quot;Rectangle Formation&quot;, new Vector2Int(8, 3)),
            new FormationPreset(&quot;Triangle Formation&quot;, new Vector2Int(0, 0)) // Triangle doesn&#39;t need grid dimensions
        };
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024.12.18 작업내용]]></title>
            <link>https://velog.io/@wanna_make_game/2024.12.18-%EC%9E%91%EC%97%85%EB%82%B4%EC%9A%A9</link>
            <guid>https://velog.io/@wanna_make_game/2024.12.18-%EC%9E%91%EC%97%85%EB%82%B4%EC%9A%A9</guid>
            <pubDate>Wed, 18 Dec 2024 17:15:37 GMT</pubDate>
            <description><![CDATA[<p>2024.12.18(수)</p>
<ul>
<li>DomainArmy.cs<pre><code class="language-cs">using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
</code></pre>
</li>
</ul>
<p>// 군 전체 정보
public class DomainArmy : MonoBehaviour
{
    Domain domainData;</p>
<pre><code>// 전략 세팅 시스템
StrategySettingSystem strategySettingSystem;

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


// 데이터 관리와 실제 군 관리는 동시에 이루어진다.(그렇게 할 예정)

// 영주 데이터
public Lord LordData { get; private set; }
// 전체 기사 데이터 리스트
public List&lt;UserKnight&gt; WholeKnightDataList { get; private set; } = new List&lt;UserKnight&gt;();
// 전체 병종 데이터 리스트
public List&lt;UserUnitType&gt; WholeUnitTypeDataList { get; private set; } = new List&lt;UserUnitType&gt;();

// 지휘관
public ArmyCommander ArmyCommander { get; private set; }
// 지휘관 부대 데이터
public UnitDivision ArmyCommanderUnitDivisionData { get; private set; }
// 지휘관 부대
public ArmyUnitDivision ArmyCommanderUnitDivision { get; private set; }
// 지휘관 프리펩
ArmyCommander armyCommanderPrefabs;

// 보유 기사 데이터 리스트(출전x)
public List&lt;UserKnight&gt; OwnedKnightDataList { get; private set; } = new List&lt;UserKnight&gt;();
// 보유 병종 데이터 리스트(출전x)
public List&lt;UserUnitType&gt; OwnedUnitTypeDataList { get; private set; } = new List&lt;UserUnitType&gt;();

// 출전 기사 데이터 리스트
public List&lt;UserKnight&gt; DeployedKnightDataList { get; private set; } = new List&lt;UserKnight&gt;();
// 출전 병종 데이터 리스트
public List&lt;UserUnitType&gt; DeployedUnitTypeDataList { get; private set; } = new List&lt;UserUnitType&gt;();




// 전체 전략과 부대, 부대배치, 부대임무는 출전 병력의 데이터만 사용할 것이다.
// 기본 전략
public Strategy StartStrategy { get; private set; }
// 전체 전략 데이터 리스트
public List&lt;Strategy&gt; StrategyDataList { get; private set; } = new List&lt;Strategy&gt;();
// 전체 부대 데이터 리스트
public List&lt;UnitDivision&gt; UnitDivisionDataList { get; private set; } = new List&lt;UnitDivision&gt;();
// 전체 부대배치 데이터 리스트
public List&lt;UnitDivisionPosition&gt; UnitDivisionPositionDataList { get; private set; } = new List&lt;UnitDivisionPosition&gt;();
// 전체 부대임무 데이터 리스트
public List&lt;UnitDivisionRole&gt; UnitDivisionRoleDataList { get; private set; } = new List&lt;UnitDivisionRole&gt;();


/// &lt;summary&gt;
/// 전략을 관리하기 위해, 하나의 전략에 있는 데이터들을 가져와서 묶어 놓는다.
/// 전략 리스트 (ArmyStategy)
///     전략1
///         부대 리스트
///             부대
///             부대배치
///             부대임무
///             ...
///     전략2
///         부대 리스트
///             부대
///             부대배치
///             부대임무
///             ...
///     ...
/// &lt;/summary&gt;
// 전략 리스트 (ArmyStategy)
public List&lt;ArmyStrategy&gt; ArmyStrategyList { get; private set; } = new List&lt;ArmyStrategy&gt;();
// 기본 전략 (실제)
public ArmyStrategy StartArmyStrategy { get; private set; }
// ArmyStrategy 부모 위치
[SerializeField] private Transform strategyParent;
// ArmyStrategy 프리팹 참조
[SerializeField] private ArmyStrategy armyStrategyPrefab;


// 영주 출전 여부
public bool IsLordDeployed { get; private set; }

/// &lt;summary&gt;
/// 실제로 군대가 출전했을 때의 동작을 담당하게 될 군대 데이터가 있는 리스트
/// 출전 부대 리스트 (실제)
///     출전 부대
///         출전 기사
///         출전 병종
///         부대 임무 - 기본 전략의 임무
///     ...
/// &lt;/summary&gt;
// 출전 부대 리스트 (실제)
public List&lt;ArmyUnitDivision&gt; AllArmyUnitDivisionList { get; private set; } = new List&lt;ArmyUnitDivision&gt;();

//ArmyLord domainArmyLord;
//List&lt;ArmyKnight&gt; knightList = new List&lt;ArmyKnight&gt;();
//List&lt;ArmyUnitType&gt; unitTypeList = new List&lt;ArmyUnitType&gt;();



private void Awake()
{
    strategySettingSystem = new StrategySettingSystem(this);
    StrategyManager.Instance.strategySettingSystem = strategySettingSystem;

    armyCommanderPrefabs = Resources.Load&lt;ArmyCommander&gt;(&quot;Prefabs/TestUnits/ArmyCommander&quot;);
    ArmyCommander = Instantiate(armyCommanderPrefabs, transform);
    LoadDomainData();
    LoadDomainArmy(domainData);
    strategySettingSystem.SetStrategySettingSystemData();

    strategySettingSystem.SetUIStrategySettingData();
}
private void Start()
{

}


public void LoadDomainData(string domainID = &quot;do007&quot;)
{
    List&lt;Domain&gt; domainList = LocatorManager.Instance.dataManager.domainInfo.Data.Domain;
    for (int i = 0; i &lt; domainList.Count; i++)
    {
        if (domainList[i].ID == domainID)
        {
            domainData = domainList[i];
        }
    }
}

public void LoadDomainArmy(Domain inputDomainData)
{
    string doID = inputDomainData.ID;
    List&lt;Lord&gt; lordList = LocatorManager.Instance.dataManager.lordInfo.Data.Lord;
    for (int i = 0; i &lt; lordList.Count; i++)
    {
        if (lordList[i].DomainID == doID)
        {
            LordData = lordList[i];
            break;
        }
    }
    List&lt;UserKnight&gt; userKnightList = LocatorManager.Instance.dataManager.userKnightInfo.Data.UserKnight;
    for (int i = 0;i &lt; userKnightList.Count; i++)
    {
        if (userKnightList[i].DomainID == doID)
        {
            WholeKnightDataList.Add(userKnightList[i]);
        }
    }
    if (WholeKnightDataList.Count == 0)
    {
        Debug.LogWarning(&quot;LoadDomainArmy: WholeKnightDataList is empty after loading!&quot;);
    }
    List&lt;UserUnitType&gt; userUnitTypeList = LocatorManager.Instance.dataManager.userUnitTypeInfo.Data.UserUnitType;
    for (int i = 0; i &lt; userUnitTypeList.Count; i++)
    {
        if (userUnitTypeList[i].DomainID == doID)
        {
            WholeUnitTypeDataList.Add(userUnitTypeList[i]);
        }
    }
    List&lt;Strategy&gt; strategyList = LocatorManager.Instance.dataManager.strategyInfo.Data.Strategy;
    for (int i = 0; i &lt; strategyList.Count; i++)
    {
        if (strategyList[i].DomainID == doID)
        {
            StrategyDataList.Add(strategyList[i]);
        }
    }
    List&lt;UnitDivision&gt; unitDivisionList = LocatorManager.Instance.dataManager.unitDivisionInfo.Data.UnitDivision;
    for (int i = 0; i &lt; unitDivisionList.Count; i++)
    {
        if (unitDivisionList[i].DomainID == doID)
        {
            UnitDivisionDataList.Add(unitDivisionList[i]);
        }
    }

    // 기본 전략은 첫 번째 전략
    if (StrategyDataList.Count &gt; 0)
    {
        StartStrategy = StrategyDataList[0];
    }
    else
    {
        Debug.LogError(&quot;StrategyDataList is empty!&quot;);
        // TODO : 새로운 Strategy를 자동 생성하는 코드
    }

    // 전략 리스트 (ArmyStategy)
    for (int i = 0; i &lt; StrategyDataList.Count; i++)
    {
        ArmyStrategy armyStrategy = Instantiate(armyStrategyPrefab, strategyParent);
        ArmyStrategyList.Add(armyStrategy);
        armyStrategy.SetStrategyData(StrategyDataList[i], unitDivisionList);
    }

    // 출전 부대 리스트 (실제)
    for (int i = 0; i &lt; ArmyStrategyList.Count; i++)
    {
        List&lt;ArmyUnitDivision&gt; strategyUnitDivisionList = ArmyStrategyList[i].StrategyUnitDivisionList;
        for (int j = 0; j &lt; strategyUnitDivisionList.Count; j++)
        {
            AllArmyUnitDivisionList.Add(strategyUnitDivisionList[j]);
        }
    }

    // 기본 전략 (ArmyStrategy) 설정
    for (int i = 0; i &lt; ArmyStrategyList.Count; i++)
    {
        if (StartStrategy.ID == ArmyStrategyList[i].StrategyData.ID)
        {
            StartArmyStrategy = ArmyStrategyList[i];
        }
    }

    // 전략에 로드가 있는 부대가 있으면 true
    IsLordDeployed = StartArmyStrategy.IsLordDeployed;


    // 부대 기준으로 세팅(ArmyUnitDivision에서 세팅)
    // 중복 방지하면서 세팅
    for (int i = 0; i &lt; AllArmyUnitDivisionList.Count; i++)
    {
        AddListUnique&lt;UnitDivisionPosition&gt;(UnitDivisionPositionDataList, AllArmyUnitDivisionList[i].UnitDivisionPositionData);
        AddListUnique&lt;UnitDivisionRole&gt;(UnitDivisionRoleDataList, AllArmyUnitDivisionList[i].UnitDivisionRoleData);
    }


    // 보유 병종과 출전 병종 데이터 Init(보유 = 전체, 출전 = 0)
    for (int i = 0; i &lt; WholeUnitTypeDataList.Count; i++)
    {
        AddListUnique(OwnedUnitTypeDataList, (UserUnitType)WholeUnitTypeDataList[i].Clone());
        AddListUnique(DeployedUnitTypeDataList, (UserUnitType)WholeUnitTypeDataList[i].Clone());
        DeployedUnitTypeDataList[i].UnitTypePersonnal = 0;
    }


    // 기본 전략 기준으로 보유 병력(기사, 병종), 출전 병력 세팅
    List&lt;ArmyUnitDivision&gt; startStrategyUnitDivisionList = StartArmyStrategy.StrategyUnitDivisionList;
    for (int i = 0; i &lt; startStrategyUnitDivisionList.Count; i++)
    {
        // 출전 기사 데이터 삽입
        AddListUnique(DeployedKnightDataList, startStrategyUnitDivisionList[i].KnightData);
        // 보유 병종과 출전 병종의 인원 설정
        for (int j = 0; j &lt; OwnedUnitTypeDataList.Count; j++)
        {
            if (startStrategyUnitDivisionList[i].UnitDivisionData.UserUnitTypeID == OwnedUnitTypeDataList[j].ID)
            {
                OwnedUnitTypeDataList[j].UnitTypePersonnal -= startStrategyUnitDivisionList[i].UnitDivisionData.UnitDivisionPersonnel;
                DeployedUnitTypeDataList[j].UnitTypePersonnal += startStrategyUnitDivisionList[i].UnitDivisionData.UnitDivisionPersonnel;
            }
        }
        // 지휘관 설정
        if (startStrategyUnitDivisionList[i].UnitDivisionData.IsCommander == true)
        {
            ArmyCommanderUnitDivision = startStrategyUnitDivisionList[i];
            ArmyCommanderUnitDivisionData = ArmyCommanderUnitDivision.UnitDivisionData;
            if (ArmyCommanderUnitDivision.IsKnightLord)
            {
                ArmyCommander.SetArmyCommander(ArmyCommanderUnitDivision.LordData);
            }
            else
            {
                ArmyCommander.SetArmyCommander(ArmyCommanderUnitDivision.KnightData);
            }
        }
    }
    // 전체 기사에서 출전 기사를 제외한 나머지를 보유 기사에 편입

    // 보유/출전 기사 분리
    for (int i = 0; i &lt; WholeKnightDataList.Count; i++)
    {
        bool isDeployed = false;

        for (int j = 0; j &lt; DeployedKnightDataList.Count; j++)
        {
            if (WholeKnightDataList[i].ID == DeployedKnightDataList[j].ID)
            {
                isDeployed = true;
                break;
            }
        }

        if (!isDeployed)
        {
            AddListUnique(OwnedKnightDataList, WholeKnightDataList[i]);
        }
    }

}

// 리스트에 추가할 때, 중복되는 데이터 방지
public void AddListUnique&lt;T&gt;(List&lt;T&gt; list, T item)
{
    if (!list.Contains(item))
    {
        list.Add(item);
    }
}
// OwnedKnightDataList DeployedKnightDataList
public void DeployKnight(UserKnight deployedKnight)
{
    for (int i = 0; i &lt; OwnedKnightDataList.Count; i++)
    {
        if (OwnedKnightDataList[i].ID == deployedKnight.ID)
        {
            DeployedKnightDataList.Add(deployedKnight);
            OwnedKnightDataList.RemoveAt(i);
            i--;
        }
    }
}

public void UnDelpoyKnight(UserKnight unDeployedKnight)
{
    for (int i = 0; i &lt; DeployedKnightDataList.Count; i++)
    {
        if (DeployedKnightDataList[i].ID == unDeployedKnight.ID)
        {
            OwnedKnightDataList.Add(unDeployedKnight);
            DeployedKnightDataList.RemoveAt(i);
            i--;
        }
    }
}</code></pre><p>}</p>
<pre><code>
- StrategySettingSystem.cs
```cs
using System.Collections.Generic;
using System.Text;
using UnityEngine;
// 전략 설정 시스템
public class StrategySettingSystem
{
    public DomainArmy DomainArmy { get; private set; }

    // ui와 연결
    UI_StrategySetting ui_StrategySetting;

    // 영주 데이터
    public Lord LordData { get; private set; }
    // 전체 기사 데이터 리스트
    public List&lt;UserKnight&gt; WholeKnightDataList { get; private set; }
    // 전체 병종 데이터 리스트
    public List&lt;UserUnitType&gt; WholeUnitTypeDataList { get; private set; }

    // 지휘관
    public ArmyCommander ArmyCommander { get; private set; }
    // 지휘관 부대 데이터
    public UnitDivision ArmyCommanderUnitDivisionData { get; private set; }
    // 지휘관 부대
    public ArmyUnitDivision ArmyCommanderUnitDivision { get; private set; }

    // 보유 기사 데이터 리스트(출전x)
    public List&lt;UserKnight&gt; OwnedKnightDataList { get; private set; }
    // 보유 병종 데이터 리스트(출전x)
    public List&lt;UserUnitType&gt; OwnedUnitTypeDataList { get; private set; }

    // 출전 기사 데이터 리스트
    public List&lt;UserKnight&gt; DeployedKnightDataList { get; private set; }
    // 출전 병종 데이터 리스트
    public List&lt;UserUnitType&gt; DeployedUnitTypeDataList { get; private set; }

    // 전체 전략과 부대, 부대배치, 부대임무는 출전 병력의 데이터만 사용할 것이다.
    // 기본 전략
    public Strategy StartStrategy { get; private set; }
    // 전체 전략 데이터 리스트
    public List&lt;Strategy&gt; StrategyDataList { get; private set; }
    // 전체 부대 데이터 리스트
    public List&lt;UnitDivision&gt; UnitDivisionDataList { get; private set; }
    // 전체 부대배치 데이터 리스트
    public List&lt;UnitDivisionPosition&gt; UnitDivisionPositionDataList { get; private set; }
    // 전체 부대임무 데이터 리스트
    public List&lt;UnitDivisionRole&gt; UnitDivisionRoleDataList { get; private set; }

    // 전략 리스트 (ArmyStategy)
    public List&lt;ArmyStrategy&gt; ArmyStrategyList { get; private set; }
    // 기본 전략 (실제)
    public ArmyStrategy StartArmyStrategy { get; private set; }
    // 영주 출전 여부
    public bool IsLordDeployed { get; private set; }
    // 출전 부대 리스트 (실제)
    public List&lt;ArmyUnitDivision&gt; AllArmyUnitDivisionList { get; private set; }


    // 현재 (세팅중인) 전략
    public Strategy CurrentStrategy { get; private set; }

    public ArmyStrategy CurrentArmyStrategy { get; private set; }


    public StrategySettingSystem(DomainArmy domainArmy)
    {
        DomainArmy = domainArmy;
        SetStrategySettingSystemData();
    }


    // DomainArmy의 데이터를 참조해서 설정
    public void SetStrategySettingSystemData()
    {
        ui_StrategySetting = DomainArmy.ui_StrategySetting;
        LordData = DomainArmy.LordData;
        WholeKnightDataList = DomainArmy.WholeKnightDataList;
        WholeUnitTypeDataList = DomainArmy.WholeUnitTypeDataList;
        OwnedKnightDataList = DomainArmy.OwnedKnightDataList;
        OwnedUnitTypeDataList = DomainArmy.OwnedUnitTypeDataList;
        DeployedKnightDataList = DomainArmy.DeployedKnightDataList;
        DeployedUnitTypeDataList = DomainArmy.DeployedUnitTypeDataList;
        StartStrategy = DomainArmy.StartStrategy;
        StrategyDataList = DomainArmy.StrategyDataList;
        UnitDivisionDataList = DomainArmy.UnitDivisionDataList;
        UnitDivisionPositionDataList = DomainArmy.UnitDivisionPositionDataList;
        UnitDivisionRoleDataList = DomainArmy.UnitDivisionRoleDataList;
        ArmyStrategyList = DomainArmy.ArmyStrategyList;
        StartArmyStrategy = DomainArmy.StartArmyStrategy;
        IsLordDeployed = DomainArmy.IsLordDeployed;
        AllArmyUnitDivisionList = DomainArmy.AllArmyUnitDivisionList;
        if (StartStrategy != null)
        {
            CurrentStrategy = StartStrategy;
            CurrentArmyStrategy = StartArmyStrategy;
        }
    }
    // 데이터가 잘 들어갔는지 확인
    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);
    }


    // UI의 반응과 연계하여 전략 설정 및 저장


    public void SetUIStrategySettingData()
    {
        ui_StrategySetting.SetOwnedKnightData(OwnedKnightDataList);
        ui_StrategySetting.SetDeployedKnightData(DeployedKnightDataList);
        ui_StrategySetting.SetDeployedUnitTypeData(DeployedUnitTypeDataList);
    }
}</code></pre><ul>
<li>ArmyCommander.cs<pre><code class="language-cs">using UnityEngine;
</code></pre>
</li>
</ul>
<p>public class ArmyCommander : MonoBehaviour
{
    // 영주 출전 여부
    public bool IsLordDeployed { get; private set; }</p>
<pre><code>public Lord LordData { get; private set; }
public UserKnight KnightData { get; private set; }

public void SetArmyCommander(Lord lordData)
{
    IsLordDeployed = true;
    LordData = lordData;
}
public void SetArmyCommander(UserKnight knightData)
{
    IsLordDeployed = false;
    KnightData = knightData;
}
public Lord GetArmyLordCommander()
{
    return LordData;
}
public UserKnight GetArmyKnightCommaner()
{
    return KnightData;
}</code></pre><p>}</p>
<pre><code>
- ArmyStrategy.cs
```cs
using System;
using System.Collections.Generic;
using UnityEngine;

// 전략
public class ArmyStrategy : MonoBehaviour
{
    // 영주 출전 여부
    public bool IsLordDeployed { get; private set; }
    // 전략 정보
    public Strategy StrategyData { get; private set; }
    // 부대 리스트
    public List&lt;ArmyUnitDivision&gt; StrategyUnitDivisionList { get; private set; } = new List&lt;ArmyUnitDivision&gt;();
    public List&lt;UserKnight&gt; WholeKnightDataList { get; private set; }
    public List&lt;UserUnitType&gt; WholeUnitTypeDataList { get; private set; }

    private ArmyUnitDivision armyUnitDivisionPrefab;

    private void Awake()
    {
        armyUnitDivisionPrefab = Resources.Load&lt;ArmyUnitDivision&gt;(&quot;Prefabs/ArmyUnitDivisionControll/ArmyUnitDivision&quot;);
    }
    private void Start()
    {
        WholeKnightDataList = StrategyManager.Instance.strategySettingSystem.WholeKnightDataList;
        WholeUnitTypeDataList = StrategyManager.Instance.strategySettingSystem.WholeUnitTypeDataList;
    }
    public void SetStrategyData(Strategy inputStrategyData, List&lt;UnitDivision&gt; unitDivisionList)
    {
        if (armyUnitDivisionPrefab == null)
        {
            Debug.LogError(&quot;SetStrategyData: ArmyUnitDivision prefab is not loaded!&quot;);
            return;
        }

        if (inputStrategyData == null)
        {
            Debug.LogError(&quot;SetStrategyData: inputStrategyData is null!&quot;);
            return;
        }

        if (unitDivisionList == null)
        {
            Debug.LogError(&quot;SetStrategyData: unitDivisionList is null!&quot;);
            return;
        }

        StrategyData = inputStrategyData;
        for (int i = 0; i &lt; unitDivisionList.Count; i++)
        {
            if (unitDivisionList[i].StrategyID == StrategyData.ID)
            {
                // 부대 생성 및 초기화
                ArmyUnitDivision armyUnitDivision = Instantiate(armyUnitDivisionPrefab);
                if (armyUnitDivision == null)
                {
                    Debug.LogError(&quot;SetStrategyData: Failed to instantiate ArmyUnitDivision prefab!&quot;);
                    continue;
                }


                // 부대 이끄는 자가 로드인지
                if (unitDivisionList[i].KnightID[0] == &#39;l&#39;)
                {
                    Lord lord = StrategyManager.Instance.strategySettingSystem.LordData;

                    armyUnitDivision.SetUnitDivisionData(unitDivisionList[i], lord);
                }
                else
                {
                    UserKnight knight = FindKnight(unitDivisionList[i].KnightID);
                    if (knight == null)
                    {
                        Debug.LogWarning($&quot;SetStrategyData: Knight with ID {unitDivisionList[i].KnightID} not found.&quot;);
                    }


                    armyUnitDivision.SetUnitDivisionData(unitDivisionList[i], knight);
                }
                StrategyUnitDivisionList.Add(armyUnitDivision);
            }
        }
    }

    public UserKnight FindKnight(string userKnightID)
    {
        if (WholeKnightDataList == null)
        {
            Debug.Log(&quot;FindKnight: WholeKnightDataList is null!&quot;);
            WholeKnightDataList = StrategyManager.Instance.strategySettingSystem.DomainArmy.WholeKnightDataList;
        }

        for (int i = 0; i &lt; WholeKnightDataList.Count; i++)
        {
            if (WholeKnightDataList[i].ID == userKnightID)
            {
                return WholeKnightDataList[i];
            }
        }
        Debug.LogWarning($&quot;FindKnight: Knight with ID {userKnightID} not found in WholeKnightDataList.&quot;);
        return null;
    }
}</code></pre><ul>
<li><p>ArmyUnitDivision.cs</p>
<pre><code class="language-cs">using DG.Tweening;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;
// 부대
public class ArmyUnitDivision : MonoBehaviour
{
  // 부대 정보
  public UnitDivision UnitDivisionData { get; private set; }
  public UnitDivisionPosition UnitDivisionPositionData { get; private set; }
  public UnitDivisionRole UnitDivisionRoleData { get; private set; }
  Vector3 position;
  Vector3 rotaion;
  public bool IsKnightLord { get; private set; } = false;
  public Lord LordData { get; private set; }
  public UserKnight KnightData { get; private set; }
  public ArmyKnight ArmyKnight { get; private set; }
  public UserUnitType UnitTypeData { get; private set; }
  public List&lt;ArmySoldier&gt; ArmySoldierList { get; private set; }

  ArmySoldier soldierPrefab;

</code></pre>
</li>
</ul>
<pre><code>private void Awake()
{
}

// Set ArmyUnitDivision - 기사일 때
public void SetUnitDivisionData(UnitDivision inputUnitDivisionData, UserKnight knightData)
{
    if (inputUnitDivisionData == null)
    {
        Debug.LogError(&quot;SetUnitDivisionData: inputUnitDivisionData is null!&quot;);
        return;
    }

    if (knightData == null)
    {
        Debug.LogError($&quot;SetUnitDivisionData: Knight with ID {inputUnitDivisionData.KnightID} is null!&quot;);
    }

    UnitDivisionData = inputUnitDivisionData;

    // 기사인 부분
    KnightData = knightData;
    IsKnightLord = false;

    List&lt;UnitDivisionPosition&gt; unitDivisionPositionList = LocatorManager.Instance.dataManager.unitDivisionPositionInfo.Data.UnitDivisionPosition;
    for (int i = 0; i &lt; unitDivisionPositionList.Count; i++)
    {
        if (unitDivisionPositionList[i].UnitDivisionID == UnitDivisionData.ID)
        {
            UnitDivisionPositionData = unitDivisionPositionList[i];
            break;
        }
    }
    if (UnitDivisionPositionData == null)
    {
        Debug.LogError($&quot;SetUnitDivisionData: Position data for UnitDivisionID {UnitDivisionData.ID} is missing!&quot;);
    }

    List&lt;UnitDivisionRole&gt; unitDivisionRoleList = LocatorManager.Instance.dataManager.unitDivisionRoleInfo.Data.UnitDivisionRole;
    for (int i = 0; i &lt; unitDivisionRoleList.Count; i++)
    {
        if (unitDivisionRoleList[i].UnitDivisionID == UnitDivisionData.ID)
        {
            UnitDivisionRoleData = unitDivisionRoleList[i];
            break;
        }
    }
    if (UnitDivisionRoleData == null)
    {
        Debug.LogError($&quot;SetUnitDivisionData: Role data for UnitDivisionID {UnitDivisionData.ID} is missing!&quot;);
    }

    // Position &amp; Rotation 설정
    if (UnitDivisionPositionData != null)
    {
        position = new Vector3(UnitDivisionPositionData.PositionX, UnitDivisionPositionData.RotationZ, UnitDivisionPositionData.PositionY);
        rotaion = new Vector3(UnitDivisionPositionData.RotationX, UnitDivisionPositionData.RotationZ, UnitDivisionPositionData.RotationY);
    }

    soldierPrefab = Resources.Load&lt;ArmySoldier&gt;(&quot;Prefabs/TestUnits/ArmySoldier&quot;);

    if (soldierPrefab == null)
    {
        Debug.LogError(&quot;Soldier prefab not found in Resources!&quot;);
    }

    // 월드 좌표 설정
    SetUnitDivisionToWorld();
}

// Set ArmyUnitDivision - 로드일 때
public void SetUnitDivisionData(UnitDivision inputUnitDivisionData, Lord lordData)
{
    if (inputUnitDivisionData == null)
    {
        Debug.LogError(&quot;SetUnitDivisionData: inputUnitDivisionData is null!&quot;);
        return;
    }

    if (lordData == null)
    {
        Debug.LogError($&quot;SetUnitDivisionData: Knight with ID {inputUnitDivisionData.KnightID} is null!&quot;);
    }

    UnitDivisionData = inputUnitDivisionData;

    // 영주인 부분
    LordData = lordData;
    IsKnightLord = true;

    List&lt;UnitDivisionPosition&gt; unitDivisionPositionList = LocatorManager.Instance.dataManager.unitDivisionPositionInfo.Data.UnitDivisionPosition;
    for (int i = 0; i &lt; unitDivisionPositionList.Count; i++)
    {
        if (unitDivisionPositionList[i].UnitDivisionID == UnitDivisionData.ID)
        {
            UnitDivisionPositionData = unitDivisionPositionList[i];
            break;
        }
    }
    if (UnitDivisionPositionData == null)
    {
        Debug.LogError($&quot;SetUnitDivisionData: Position data for UnitDivisionID {UnitDivisionData.ID} is missing!&quot;);
    }

    List&lt;UnitDivisionRole&gt; unitDivisionRoleList = LocatorManager.Instance.dataManager.unitDivisionRoleInfo.Data.UnitDivisionRole;
    for (int i = 0; i &lt; unitDivisionRoleList.Count; i++)
    {
        if (unitDivisionRoleList[i].UnitDivisionID == UnitDivisionData.ID)
        {
            UnitDivisionRoleData = unitDivisionRoleList[i];
            break;
        }
    }
    if (UnitDivisionRoleData == null)
    {
        Debug.LogError($&quot;SetUnitDivisionData: Role data for UnitDivisionID {UnitDivisionData.ID} is missing!&quot;);
    }

    // Position &amp; Rotation 설정
    if (UnitDivisionPositionData != null)
    {
        position = new Vector3(UnitDivisionPositionData.PositionX, UnitDivisionPositionData.RotationZ, UnitDivisionPositionData.PositionY);
        rotaion = new Vector3(UnitDivisionPositionData.RotationX, UnitDivisionPositionData.RotationZ, UnitDivisionPositionData.RotationY);
    }

    soldierPrefab = Resources.Load&lt;ArmySoldier&gt;(&quot;Prefabs/TestUnits/ArmySoldier&quot;);

    if (soldierPrefab == null)
    {
        Debug.LogError(&quot;Soldier prefab not found in Resources!&quot;);
    }

    // 월드 좌표 설정
    SetUnitDivisionToWorld();
}


public void SetUnitDivisionToWorld()
{
    transform.position = position;
    transform.rotation = Quaternion.Euler(rotaion);
    //armyKnight = Instantiate(armyKnight);
    //boxCubeFieldGenerator.GenerateField(soldierPrefab, 10, 10);
}

public void SetArmyKnight(ArmyKnight inputArmyKnight)
{
    ArmyKnight = inputArmyKnight;
}
public void SetArmyLord(Lord inputLordData)
{
    LordData = inputLordData;
}

public void LoadSoldierPrefab&lt;T&gt;()
{
    //T prefab = Resources.Load&lt;T&gt;($&quot;Prefabs/TestUnits/{typeof(T)}&quot;);
}

// 유닛 배치 변경
public void MoveUnitDivisionPosition()
{

}</code></pre><p>}</p>
<pre><code>

- StrategyManager.cs
```cs
public class StrategyManager : MonoSingleton&lt;StrategyManager&gt;
{
    public StrategySettingSystem strategySettingSystem;
    public ArmyPrefabsSO armyPrefabs;
}</code></pre><ul>
<li>ArmyPrefabsSO.cs<pre><code class="language-cs">using UnityEngine;
</code></pre>
</li>
</ul>
<p>[CreateAssetMenu(fileName = &quot;Army Prefabs&quot;, menuName = &quot;Army Prefabs&quot;)]
public class ArmyPrefabsSO : ScriptableObject
{
    public ArmyLord armyCommander;
    public ArmyKnight armyKnight;
    public ArmySoldier armySoldier;
}</p>
<pre><code>












</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[2024.12.17 TIL]]></title>
            <link>https://velog.io/@wanna_make_game/2024.12.17-TIL</link>
            <guid>https://velog.io/@wanna_make_game/2024.12.17-TIL</guid>
            <pubDate>Wed, 18 Dec 2024 00:01:18 GMT</pubDate>
            <description><![CDATA[<p>2024.12.17(화)</p>
<p>DomainArmy.cs의 데이터 변경점이다.</p>
<ul>
<li>DomainArmy.cs</li>
</ul>
<pre><code class="language-cs">    // 데이터 관리와 실제 군 관리는 동시에 이루어진다.(그렇게 할 예정)

    // 영주 데이터
    public Lord LordData { get; private set; }
    // 전체 기사 데이터 리스트
    public List&lt;UserKnight&gt; WholeKnightDataList { get; private set; } = new List&lt;UserKnight&gt;();
    // 전체 병종 데이터 리스트
    public List&lt;UserUnitType&gt; WholeUnitTypeDataList { get; private set; } = new List&lt;UserUnitType&gt;();



    // 보유 기사 데이터 리스트(출전x)
    public List&lt;UserKnight&gt; OwnedKnightDataList { get; private set; }
    // 보유 병종 데이터 리스트(출전x)
    public List&lt;UserUnitType&gt; OwnedUnitTypeDataList { get; private set; }

    // 출전 기사 데이터 리스트
    public List&lt;UserKnight&gt; DeployedKnightDataList { get; private set; }
    // 출전 병종 데이터 리스트
    public List&lt;UserUnitType&gt; DeployedUnitTypeDataList { get; private set; }




    // 전체 전략과 부대, 부대배치, 부대임무는 출전 병력의 데이터만 사용할 것이다.
    // 기본 전략
    public Strategy StartStrategy { get; private set; }
    // 전체 전략 데이터 리스트
    public List&lt;Strategy&gt; StrategyDataList { get; private set; } = new List&lt;Strategy&gt;();
    // 전체 부대 데이터 리스트
    public List&lt;UnitDivision&gt; UnitDivisionDataList { get; private set; } = new List&lt;UnitDivision&gt;();
    // 전체 부대배치 데이터 리스트
    public List&lt;UnitDivisionPosition&gt; UnitDivisionPositionDataList { get; private set; } = new List&lt;UnitDivisionPosition&gt;();
    // 전체 부대임무 데이터 리스트
    public List&lt;UnitDivisionRole&gt; UnitDivisionRoleDataList { get; private set; } = new List&lt;UnitDivisionRole&gt;();


    /// &lt;summary&gt;
    /// 전략을 관리하기 위해, 하나의 전략에 있는 데이터들을 가져와서 묶어 놓는다.
    /// 전략 리스트 (ArmyStategy)
    ///     전략1
    ///         부대 리스트
    ///             부대
    ///             부대배치
    ///             부대임무
    ///             ...
    ///     전략2
    ///         부대 리스트
    ///             부대
    ///             부대배치
    ///             부대임무
    ///             ...
    ///     ...
    /// &lt;/summary&gt;
    // 전략 리스트 (ArmyStategy)
    public List&lt;ArmyStrategy&gt; ArmyStrategyList { get; private set; } = new List&lt;ArmyStrategy&gt;();
    // 기본 전략 (실제)
    public ArmyStrategy StartArmyStrategy { get; private set; }
    // ArmyStrategy 부모 위치
    [SerializeField] private Transform strategyParent;
    // ArmyStrategy 프리팹 참조
    [SerializeField] private ArmyStrategy armyStrategyPrefab;


    // 영주 출전 여부
    public bool IsLordDeployed { get; private set; }

    /// &lt;summary&gt;
    /// 실제로 군대가 출전했을 때의 동작을 담당하게 될 군대 데이터가 있는 리스트
    /// 출전 부대 리스트 (실제)
    ///     출전 부대
    ///         출전 기사
    ///         출전 병종
    ///         부대 임무 - 기본 전략의 임무
    ///     ...
    /// &lt;/summary&gt;
    // 출전 부대 리스트 (실제)
    public List&lt;ArmyUnitDivision&gt; AllArmyUnitDivisionList { get; private set; } = new List&lt;ArmyUnitDivision&gt;();</code></pre>
]]></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[대화]]></title>
            <link>https://velog.io/@wanna_make_game/%EB%8C%80%ED%99%94</link>
            <guid>https://velog.io/@wanna_make_game/%EB%8C%80%ED%99%94</guid>
            <pubDate>Fri, 13 Dec 2024 13:53:48 GMT</pubDate>
            <description><![CDATA[<p>2024.12.13(금)</p>
<p>오늘은 마음에 쌓아두고 있던 내 마음을 팀원에게 전달했다.</p>
<p>사실, 한 팀원분의 말에 좀 힘들어서 조그음 일에 집중도 안되고 스트레스도 자꾸 생기고 그랬는데, 오늘 팀원분에게 그동안 내가 마음속으로 힘들었던 점들을 전달했다.</p>
<p>팀원분도 조금 마음에 담아두고 있던 부분이라 나의 이 고민을 흔쾌히 받아들여주시고 공감해주셨다.</p>
<p>오전까지 너무 힘들어서 집중이 안되었었는데, 팀원분과의 대화를 통해 서로의 오해를 알게 되고 화합을 다질 수 있었다.</p>
<p>그러고 나니까 마음이 좀 가벼워지고 힘이 났다.</p>
<p>역시 사람과의 일은 대화를 통해 해결할 수 있는 것이다.</p>
<p>잘 해결되서 행복하다.</p>
<p>오늘의 TIL 끝.</p>
]]></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[TIL]]></title>
            <link>https://velog.io/@wanna_make_game/TIL</link>
            <guid>https://velog.io/@wanna_make_game/TIL</guid>
            <pubDate>Tue, 10 Dec 2024 13:07:19 GMT</pubDate>
            <description><![CDATA[<p>2024.12.10(화)</p>
<p>오늘은 오전에 면접준비를 하고, 오후엔 팀원분이 주말과 월요일 동안 수정하신 데이터 테이블에 맞게 데이터들을 수정했다.</p>
<p>데이터를 어차피 써서 리스트에 저장하고 작업을 하게 될 거 같아서, 데이터를 다시 가져오는 작업을 했는데, 뭔가..... 되게 뭔가 한 게 없어 보인다.</p>
<p>팀에서 팀장을 맡고 있는데, 뭔가 내가 진행을 많이 한 게 없는 것 같아서 좀 팀장으로서 잘 하고 있나..? 이런 생각도 들고 조금 힘든 것 같다.</p>
<p>제일 힘든 건, 팀원들이 짠 코드에 바로바로 이 부분은 어떤건지, 어떻게 사용해야 되는지, 내가 이걸 이용해 이렇게 하려면 여기에 있는 코드의 어딜 이용해 뭘 해야 하는지, 내가 원하는 기능이 이건데 내가 찾아봤을 때 잘 모르겠는데 이미 있는 기능인지, 이런이런 부분들을 이렇게 바꾸고 싶은데 바꿀 수 있는지, 이런이런 부분들을 이렇게 바꾸는 게 좋아보이는데 바꿀 수 있는지, 아니면 더 좋은 해결책이 있는지 등등 바로바로 소통해서 해결하고 싶었고, 이런 부분에서 자주 얘기해서 같이 만들어 나가야 한다고 생각했었는데, 사람들의 생각이 다 똑같을 순 없나 보다.</p>
<p>조금 힘들긴 하지만, 최종 프로젝트 마무리 까지 힘내야 겠다.</p>
<p>오늘의 TIL 끝.</p>
<p>PS: 때로는 완벽한 리더가 시키는 것만 그대로 머리 비우고 가이드 라인 따라서 하거나, 혼자서 자유롭게 프로젝트를 진행하고 싶다는 생각이 든다.
하지만, 나는 내가 내 생각을 사람들에게 전달하는 방법과 소통하는 방법을 익히고, 여러 사람들을 만나면서 사람들이 대화할 때 내 말을 어떻게 받아들이는 지에 대해 배우는 과정을 거치면서, 여러 사람들과 협업할 때 소통하고 대화하는 법을 배우고 있다.
조금 힘들지라도, 이러한 과정들이 나를 성장시켜 나의 머릿속의 그림을 남과 더욱 쉽게 공유하며 소통할 수 있게 만들 것이다.
조금 힘들지만, 난 잘하고 있다.
감정적이지 않고, 이성적으로 잘 대화하고 있다..
나, 잘하고 있겠지?
음.
잘하고 있다ㅡ.
......
때론, 아무것도 안하고 싶을 때도 있고, 격렬하게 아무것도 안하고 싶을 때도 있지만, 어느정도의 스트레스를 받는 것은 나를 성장시키는 중요한 원동력이 된다고 생각한다.
스트레스를 받지 않으면 사람은 잘 바뀌지 않으니깐...
이 스트레스를 잘 이용해서 매일 성장하는 괴물이 될 것이다.</p>
<p>너무 태우다가 멈추지만 말자.
멈췄다가 다시 움직이는 것은 너무 힘들다.</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[팀 프로젝트 일일 개발 일지: JSON 데이터를 이용해 군 데이터에 연결하기]]></title>
            <link>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9D%BC%EC%9D%BC-%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80-JSON-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EA%B5%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%97%90-%EC%97%B0%EA%B2%B0%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-%EC%9D%BC%EC%9D%BC-%EA%B0%9C%EB%B0%9C-%EC%9D%BC%EC%A7%80-JSON-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%B4-%EA%B5%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0%EC%97%90-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 06 Dec 2024 17:36:02 GMT</pubDate>
            <description><![CDATA[<p>(금)2024.12.06 자 끄적끄적...
오랜만에 TIL 처음 쓰던 때처럼 TIL을 끄적끄적 작성해보겠다.</p>
<p>오늘도 팀 프로젝트 개발일지다..!</p>
<p>사실, 오늘 하루의 절반 가까이를 삽질을 한 것 같다.
영지 경영 + 전쟁 전략 시뮬레이션 게임을 만들기 위해, 여러개의 ai영지들의 정보와 플레이어 영지의 정보들을 저장하는 곳이 필요한데, 이를 만들기 위해, WorldDB라는 데이터 베이스를 만드려고 했었다.
여기에는 모든 영지의 정보와 기사들의 정보, 병종들의 정보 등 여러가지의 인 게임 중에 변하면서 저장해야 할 필요성이 있는 정보들을 JSON 형태로 저장할 계획이었다.
이렇게 한다면, 하나의 World를 Load할 때, 이 DB에 있는 정보만 빼서 사용하면 되겠다는 생각이었다.</p>
<p>그런데, 데이터 구조 전체를 한 번 리펙토링 하시면서 각 영지의 정보를 stage형태처럼 하나로 묶어서 정보를 저장하게 만드신다고 하였다.
사실, 어제도 얘기한 내용이긴 했지만, 그렇게 할 수도 있고, &#39;이렇게 하겠다!&#39; 라고 완벽하게 정해진 느낌은 아니였어서, 그렇게 아예 바꾸는 형식으로 가고 있는지를 알 지 못하였다. (그냥 이런 방향도 있는데 이건 어떠냐? 하면서 서로 의견 나누다가 흐지부지 끝난 느낌이었다. 내가 느끼기로는)</p>
<p>아무튼, 그래서 진행중이던 작업을 중단하고, 월드에 상관없이 그냥 임시로 데이터를 받아서 플레이어의 군대 데이터에 넣어주고, 이것을 토대로 로직을 구현하는 방향으로 가기로 결정했다. (실제 사용 데이터 구조와 연결하는 것은 나중으로 하고, 현재 만들어 놓은 데이터 테이블 구조를 가지고 전략 설정 및 전투 구현 로직 작성이 가능하도록 하는 것이 목표이다.)</p>
<h2 id="json-데이터-가져오기">JSON 데이터 가져오기</h2>
<p>그래서, 다음과 같이 TempDomainData를 만들어서, 임시적으로 영지의 정보가 담긴 데이터들을 JSON에서 가져왔다. (정말 그냥 사용만 하기 위해)</p>
<ul>
<li>TempDomainData.cs<pre><code class="language-cs">using Newtonsoft.Json;
using System.IO;
using UnityEditor.PackageManager;
using UnityEngine;
</code></pre>
</li>
</ul>
<p>// 임시 영지 데이터
public class TempDomainData : MonoBehaviour
{
    public DomainArray domainArray;
    public KnightDataArray knightDataArray;
    public UnitTypeDataArray unitTypeDataArray;
    public StrategyDataArray strategyDataArray;
    public UnitDivisionPositionDataaArray unitDivisionPositionDataArray;
    public UnitDivisionDataArray unitDivisionDataArray;
    public UnitDivisionRoleDataArray unitDivisionRoleDataArray;</p>
<pre><code>string dataPath = &quot;JsonData/&quot;;
private void Awake()
{
    SetTempData();
}
public void SetTempData()
{
    domainArray = JsonConvert.DeserializeObject&lt;DomainArray&gt;(LoadData(&quot;DomainDataTable&quot;));
    knightDataArray = JsonConvert.DeserializeObject&lt;KnightDataArray&gt;(LoadData(&quot;KnightDataTable&quot;));
    unitTypeDataArray = JsonConvert.DeserializeObject&lt;UnitTypeDataArray&gt;(LoadData(&quot;UnitTypeDataTable&quot;));
    strategyDataArray = JsonConvert.DeserializeObject&lt;StrategyDataArray&gt;(LoadData(&quot;StrategyDataTable&quot;));
    unitDivisionPositionDataArray = JsonConvert.DeserializeObject&lt;UnitDivisionPositionDataaArray&gt;(LoadData(&quot;UnitDivisionPositionDataTable&quot;));
    unitDivisionDataArray = JsonConvert.DeserializeObject&lt;UnitDivisionDataArray&gt;(LoadData(&quot;UnitDivisionDataTable&quot;));
    unitDivisionRoleDataArray = JsonConvert.DeserializeObject&lt;UnitDivisionRoleDataArray&gt;(LoadData(&quot;UnitDivisionRoleDataTable&quot;));
}
public string LoadData(string fileName)
{
    string loadDataPath = Path.Combine(dataPath, fileName);
    Debug.Log($&quot;LoadDataPath: {loadDataPath}&quot;);

    TextAsset jsonTextAsset = Resources.Load&lt;TextAsset&gt;(loadDataPath);
    if (jsonTextAsset != null )
    {
        Debug.Log($&quot;Load 성공!&quot;);
        return jsonTextAsset.text;
    }

    Debug.LogError($&quot;{fileName} 데이터 없음.&quot;);
    return &quot;&quot;;
}</code></pre><p>}</p>
<pre><code>## 실제 영지의 군에 데이터 삽입하기
TempSetData를 만들어서, 이전에 DomainArmy에 만들어 놓은 기사, 병종, 전략, 부대임무 리스트에 데이터를 넣는 작업을 진행했다.
그리고, LogDomainArmyData메서드를 만들어서 데이터가 잘 들어갔는지 확인하는 작업도 진행했다.

- DomainArmy.cs (&amp; DomainArmy에 연결된 클래스들. 나중에 다른 cs폴더로 옮겨줄 것이다.)
```cs
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

// 군 전체 정보
public class DomainArmy : MonoBehaviour
{
    // 전략 세팅 시스템
    StrategySettingSystem strategySettingSystem = new StrategySettingSystem();

    TempDomainData tempDomainData;

    // 군 정보 리스트
    // 기사리스트
    List&lt;Knight&gt; knightList = new List&lt;Knight&gt;();
    // 병종리스트
    List&lt;UnitType&gt; unitTypeList = new List&lt;UnitType&gt;();
    // 전략리스트
    List&lt;Strategy&gt; strategyList = new List&lt;Strategy&gt;();
    // 부대임무리스트
    List&lt;UnitDivisionRoleDataTable&gt; unitDivisionRoleDataList = new List&lt;UnitDivisionRoleDataTable&gt;();

    public List&lt;Knight&gt; KnightList
    {
        get { return knightList; }
        private set { knightList = value; }
    }
    public List&lt;UnitType&gt; UnitTypeList
    {
        get { return unitTypeList; }
        private set { unitTypeList = value; }
    }
    public List&lt;Strategy&gt; StrategyList {
        get { return strategyList; } 
        set { strategyList = value; }
    }
    public List&lt;UnitDivisionRoleDataTable&gt; UnitDivisionRoleDataList
    {
        get { return unitDivisionRoleDataList; }
        set {  unitDivisionRoleDataList = value; }
    }
    private void Awake()
    {

    }
    private void Start()
    {
        if (TryGetComponent&lt;TempDomainData&gt;(out tempDomainData))
        {
            Debug.Log($&quot;{typeof(TempDomainData)} 로드 성공!&quot;);
            TempSetData();
        }
        else
        {
            Debug.LogError(&quot;데이터 로드 실패!&quot;);
        }
        LogDomainArmyData();
    }

    public void TempSetData()
    {
        TempSetKnightList();
        TempSetUnitTypeList();
        TempSetStrategyList();
        TempSetUnitDivisionRoleDataList();
    }
    public void TempSetKnightList()
    {
        if (tempDomainData.knightDataArray == null || tempDomainData.knightDataArray.KnightDataTable == null)
        {
            Debug.LogError(&quot;knightDataArray or KnightDataTable is null!&quot;);
            return;
        }
        List&lt;KnightDataTable&gt; KnightDataTable = tempDomainData.knightDataArray.KnightDataTable;
        for (int i = 0; i &lt; KnightDataTable.Count; i++)
        {
            //GameObject knightObj = new GameObject($&quot;Knight_{i}&quot;);
            //Knight knight = knightObj.AddComponent&lt;Knight&gt;();
            //knight.SetKnightData(KnightDataTable[i]);
            //knightList.Add(knight);
            knightList.Add(new Knight(KnightDataTable[i]));
        }

    }
    public void TempSetUnitTypeList()
    {
        List&lt;UnitTypeDataTable&gt; unitTypeDataTable = tempDomainData.unitTypeDataArray.UnitTypeDataTable;
        for (int i = 0; i &lt; unitTypeDataTable.Count; i++)
        {
            unitTypeList.Add(new UnitType(unitTypeDataTable[i]));
        }
    }
    public void TempSetStrategyList()
    {
        List&lt;StrategyDataTable&gt; strategyDataTable = tempDomainData.strategyDataArray.StrategyDataTable;
        for (int i = 0; i &lt; strategyDataTable.Count; i++)
        {
            strategyList.Add(new Strategy(strategyDataTable[i]));
        }
    }
    public void TempSetUnitDivisionRoleDataList()
    {
        List&lt;UnitDivisionRoleDataTable&gt; unitDivisionRoleDataTable = tempDomainData.unitDivisionRoleDataArray.UnitDivisionRoleDataTable;
        for (int i = 0; i &lt; unitDivisionRoleDataTable.Count; i++)
        {
            unitDivisionRoleDataList.Add(unitDivisionRoleDataTable[i]);
        }
    }

    public void LogDomainArmyData()
    {
        LogKnightData();
        LogUnitTypeData();
        LogStrategyData();
        LogUnitDivisionRoleData();
    }

    public void LogKnightData()
    {
        StringBuilder sb = new StringBuilder();
        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;);
        Debug.Log(sb);
    }
    public void LogUnitTypeData()
    {
        StringBuilder sb = new StringBuilder();
        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);
    }
    public void LogStrategyData()
    {
        StringBuilder sb = new StringBuilder();
        sb.Append(&quot;전략 목록: &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;);
        Debug.Log(sb);
    }
    public void LogUnitDivisionRoleData()
    {
        StringBuilder sb = new StringBuilder();
        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;);
        Debug.Log(sb);
    }

}
// 전략 설정 시스템
public class StrategySettingSystem : MonoBehaviour
{
    DomainArmy domainArmy;
    // 전략리스트 
    List&lt;Strategy&gt; strategyList;
    // 부대임무리스트
    List&lt;UnitDivisionRoleDataTable&gt; unitDivisionRoleDataList;

    Strategy currentStrategy;

    private void Start()
    {
        strategyList = domainArmy.StrategyList;
        unitDivisionRoleDataList = domainArmy.UnitDivisionRoleDataList;
    }
    // UI의 반응과 연계하여 전략 설정 및 저장
}
// 기사
public class Knight : MonoBehaviour
{
    // 기사 정보
    KnightDataTable knightData;

    public KnightDataTable KnightData
    {
        get { return knightData; }
        private set { knightData = value; }
    }

    public Knight (KnightDataTable knightData)
    {
        SetKnightData(knightData);
    }

    public void SetKnightData(KnightDataTable inputKnightData)
    {
        knightData = inputKnightData;
    }
}
// 병종
public class UnitType : MonoBehaviour
{
    // 병종 정보
    UnitTypeDataTable unitTypeData;

    public UnitTypeDataTable UnitTypeData
    {
        get { return unitTypeData; }
        private set { unitTypeData = value; }
    }

    public UnitType (UnitTypeDataTable unitTypeData)
    {
        SetUnitTypeData(unitTypeData);
    }
    public void SetUnitTypeData(UnitTypeDataTable inputUnitTypeData)
    {
        unitTypeData = inputUnitTypeData;
    }
}
// 전략
public class Strategy : MonoBehaviour
{
    // 전략 정보
    StrategyDataTable strategyData;

    public StrategyDataTable StrategyData
    {
        get { return strategyData; }
        private set { strategyData = value; }
    }

    public Strategy (StrategyDataTable strategyData)
    {
        SetStrategyData(strategyData);
    }

    public void SetStrategyData(StrategyDataTable inputStrategyData)
    {
        strategyData = inputStrategyData;
    }
}
// 부대
public class UnitDivision : MonoBehaviour
{
    // 부대 정보
    UnitDivisionDataTable unitDivisionData;

    public UnitDivisionDataTable UnitDivisionData
    {
        get { return unitDivisionData; }
        private set { unitDivisionData = value; }
    }

    public UnitDivision (UnitDivisionDataTable inputUnitDivisionData)
    {
        SetUnitDivisionData(unitDivisionData);
    }

    public void SetUnitDivisionData(UnitDivisionDataTable inputUnitDivisionData)
    {
        unitDivisionData = inputUnitDivisionData;
    }
}
// 부대 배치
public class UnitDivisionPosition : MonoBehaviour
{
    // 부대배치정보 - 부대(UnitDivision), 위치(Position), 회전(Rotation)
    UnitDivisionPositionDataTable unitDivisionPositionData;

    public UnitDivisionPositionDataTable UnitDivisionPositionData
    {
        get { return unitDivisionPositionData; }
        private set { unitDivisionPositionData = value; }
    }

    public UnitDivisionPosition (UnitDivisionPositionDataTable inputUnitDivisionPositionData)
    {
        SetUnitDivisionPositionData(unitDivisionPositionData);
    }

    public void SetUnitDivisionPositionData(UnitDivisionPositionDataTable inputUnitDivisionPositionData)
    {
        unitDivisionPositionData = inputUnitDivisionPositionData;
    }

    //UnitDivision unitDivision;
    //Vector3 position;
    //Vector3 rotation;
    // 부대의 병종과 수, 위치, 방향에 맞게 병사와 기사를 필드에 생성해주는 로직
}

// 병사
public class Soldier : MonoBehaviour
{
    // 부대의 UnitType의 데이터를 참조해서, 병사들이 전투중에 행동하는 로직에 사용
    UnitDivisionDataTable unitDivisionData;

    public UnitDivisionDataTable UnitDivisionData
    {
        get { return unitDivisionData; }
        private set { unitDivisionData = value; }
    }

    public Soldier (UnitDivisionDataTable inputUnitDivisionData)
    {
        SetSoldierData(unitDivisionData);
    }

    public void SetSoldierData(UnitDivisionDataTable inputUnitDivisionData)
    {
        unitDivisionData = inputUnitDivisionData;
    }
}</code></pre><h2 id="데이터-로드-및-확인-로그">데이터 로드 및 확인 로그</h2>
<p>이렇게 진행한다면, 다음과 같이 데이터 로드가 성공하고, 데이터 들어간 것을 확인할 수 있다.</p>
<ul>
<li>데이터 로드 로그
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e14f45d4-86a9-4a76-9b1c-fa1f16efce52/image.PNG" alt=""></li>
</ul>
<ul>
<li>데이터 확인 로그
<img src="https://velog.velcdn.com/images/wanna_make_game/post/3d9ff1e0-9eb4-412c-bd6e-7e27791663ef/image.PNG" alt=""></li>
</ul>
<h2 id="추가로-작업해야-할-사항">+추가로 작업해야 할 사항</h2>
<p>현재 기사, 병종, 전략, 부대 임무 리스트들을 new로 생성하고 있는데, MonoBehaviour는 new로 생성하면 MonoBehaviour의 기능을 사용하지 못하기 때문에, 쓰면 안된다고 한다.
그래서 GameObject를 만들어서 거기에 Component를 달아서 사용해야 할 듯하다.</p>
<p>다음과 같이 바꾸는 작업을 해야할 것이다.</p>
<ul>
<li>기존 코드<pre><code class="language-cs">knightList.Add(new Knight(KnightDataTable[i]));</code></pre>
</li>
<li>바뀌게 될 코드<pre><code class="language-cs">GameObject knightObj = new GameObject($&quot;Knight_{i}&quot;);
Knight knight = knightObj.AddComponent&lt;Knight&gt;();
knight.SetKnightData(KnightDataTable[i]);
knightList.Add(knight);</code></pre>
</li>
</ul>
<p>오늘은 데이터를 관리하는 것을 고민하고, 직접 시스템에 부여하는 작업까지 해보았다. 물론, 처음에 한 고민이 이번 프로젝트에 안 쓰일지도 모르지만, 이러한 고민을 하며 구조를 짜보는 행위를 한 것 자체만으로 데이터 쪽을 보는 능력을 조금은 성장시킬 수 있었다,</p>
<p>조금만 더 속도를 높여서 작업할 수 있으면 좋겠지만, 열심히 한다고 한 것이 아직 이 정도이다. 아직 데이터 구조, 디자인 패턴, 서버와의 통신 등의 부분에서 자세히 알지 못하는 부분이 많아 속도가 느리지만, 해당 부분을 하나씩 공부하여 알게 되었을 땐, 굉장히 빠른 속도로 작업할 수 있을 것 같다.</p>
<p>이상이다.</p>
<p>오늘의 TIL 끝!</p>
]]></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%9D%BC%EC%9D%BC-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%EA%B3%A0%EB%AF%BC-%EA%B5%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%97%B0%EA%B2%B0-%EB%B0%A9%EC%8B%9D-%EB%B0%8F-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EC%8B%9D%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B5%AC%EC%A1%B0</link>
            <guid>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%9D%BC%EC%9D%BC-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%EA%B3%A0%EB%AF%BC-%EA%B5%B0-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%97%B0%EA%B2%B0-%EB%B0%A9%EC%8B%9D-%EB%B0%8F-%EC%B2%98%EB%A6%AC-%EB%B0%A9%EC%8B%9D%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B5%AC%EC%A1%B0</guid>
            <pubDate>Thu, 05 Dec 2024 12:21:56 GMT</pubDate>
            <description><![CDATA[<p>2024.12.05(목)</p>
<p>오늘도 특별한 기술을 공부한 것이 없어서 개발일지를 작성해보도록 하겠다. (사실 이전까지 배운 것 중에 정리 안한 것도 꽤 있어서 정리하긴 해야 되는데...)</p>
<p>자! 그럼 오늘의 개발일지 Let&#39;s go~</p>
<h2 id="1-군-데이터-연결-방식-및-처리-방식-구조-작성">1. 군 데이터 연결 방식 및 처리 방식 구조 작성</h2>
<p>다음과 같이 영지의 군의 데이터를 묶어서 관리하는 방식을 작성해 보았다. 그런데, UI와 연결해서 사용하기 이전에, 일단 이 데이터들을 JSON으로 저장하고 관리해 줄 필요성을 느꼈다.
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e952fe25-b580-4f9e-95c3-6300a6df20df/image.PNG" alt=""></p>
<p>그래서, 오른쪽 글과 같은 고민들을 해봤는데, World별로 각 영지의 데이터가 들어있는 데이터 베이스를 만들어서, 데이터를 관리해주면 좋겠다는 생각이 들었다.</p>
<h2 id="2-현재-생각중인-worlddb">2. 현재 생각중인 WorldDB</h2>
<p>다음은 내가 고민한 방식대로 진행했을 때의 WorldDB가 어떻게 될 지를 생각하며 만든 데이터 베이스이다.</p>
<p>각 Info 클래스들은 DataManager를 만들어주신 분이 만들어 놓은 DataTable들이 들어있는 List가 담겨있는 클래스다.
<img src="https://velog.velcdn.com/images/wanna_make_game/post/01b0afe6-82c8-4b06-813e-e4fcfd7c7fde/image.PNG" alt=""></p>
<h2 id="3-데이터-연결-구조">3. 데이터 연결 구조</h2>
<p>아래는 아까 작성한 구조에 기반하여 작성해본 각 객체의 클래스들이다.</p>
<ul>
<li>영지 군(DomainArmy)
<img src="https://velog.velcdn.com/images/wanna_make_game/post/a9072ef7-485e-4b64-8dbe-1cdd1f2af0bc/image.PNG" alt=""></li>
<li>기사(Knight), 병종(UnitType), Strategy(전략)
<img src="https://velog.velcdn.com/images/wanna_make_game/post/e29a00ca-160e-4826-9a30-625681ab741c/image.PNG" alt=""></li>
<li>전략 설정(StrategySettingSystem), 부대(UnitDivision)
<img src="https://velog.velcdn.com/images/wanna_make_game/post/7800d567-4bda-4782-84a8-3676f3a5fb70/image.PNG" alt=""></li>
<li>부대 배치(UnitDivisionPosition), 병사(Soldier)
<img src="https://velog.velcdn.com/images/wanna_make_game/post/4be6719f-7639-4d02-b379-9958a932e48f/image.PNG" alt=""></li>
</ul>
<hr>
<p>아직 완벽하게 모든 구조가 완성되진 않았는데, 어떻게 진행할 지에 대한 방향이 잡힌 것 같다. 내일 모든 구조를 완벽히 작성하고, UI와 연결을 해볼려고 한다.</p>
<p>오늘의 TIL 끝!</p>
]]></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%A4%91%EA%B0%84-%EC%9E%91%EC%97%85-%EB%82%B4%EC%9A%A9-%EC%A0%84%ED%88%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%8F-%EB%A1%9C%EC%A7%81-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@wanna_make_game/%ED%8C%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%A4%91%EA%B0%84-%EC%9E%91%EC%97%85-%EB%82%B4%EC%9A%A9-%EC%A0%84%ED%88%AC-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B0%8F-%EB%A1%9C%EC%A7%81-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Wed, 04 Dec 2024 15:17:11 GMT</pubDate>
            <description><![CDATA[<p>2024.12.04(수)</p>
<p>오늘은 피로한 관계로 정말 간단하게 작업내용을 작성해 보겠다.</p>
<h1 id="전투-데이터-및-로직-관리-관리">전투 데이터 및 로직 관리 관리</h1>
<p>우선 지금까지 작업한 UI들의 작업과 연결이 모두 완료가 되었고, 이제, 전투 관련 데이터를 어떻게 관리하고, 전투 시작 시 어떻게 전달하는지를 고민하고 구상하는 단계를 거쳤다.</p>
<h2 id="구조">구조</h2>
<p>아래는 가볍게 영지의 군(DomainArmy)에서 기사, 병종, 전략, 부대임무의 데이터를 관리하고, 전략 설정 시스템(StrategySettingSystem)을 통해 군에 저장되어있는 전략들과 부대임무(UnitDivision)들을 관리해준다.</p>
<p>(정말 간단하게 써서 부족한 부분이 많은 구조입니다.)</p>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/903b9093-1072-436d-8195-cd5e3611a2d1/image.PNG" alt=""></p>
<h2 id="스크립트">스크립트</h2>
<p>아래는 전투의 데이터들과 로직들을 관리하기 위해 작성한 스크립트들이다.</p>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/5fa2b21f-8e89-4bd5-8472-135fd7a59d4b/image.PNG" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wanna_make_game/post/929d6c36-683d-45ee-afca-fce3a2c9abe5/image.PNG" alt=""></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>
    </channel>
</rss>