<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>gyul_is_yummy.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Fri, 25 Apr 2025 02:52:34 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>gyul_is_yummy.log</title>
            <url>https://velog.velcdn.com/images/gyul_is_yummy/profile/496442ad-2577-4cb9-8985-402e53eccc8b/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. gyul_is_yummy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gyul_is_yummy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2025.04.25 (금)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.25-%EA%B8%88</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.25-%EA%B8%88</guid>
            <pubDate>Fri, 25 Apr 2025 02:52:34 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="구글-로그인-파이어베이스와-연결하기">구글 로그인 파이어베이스와 연결하기</h4>
</li>
<li><h4 id="트러블-슈팅">트러블 슈팅</h4>
</li>
</ul>
<br>

<h3 id="📍학습-내용">📍학습 내용</h3>
<br>

<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<br>

<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.24 (목)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.24-%EB%AA%A9</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.24-%EB%AA%A9</guid>
            <pubDate>Thu, 24 Apr 2025 13:11:59 GMT</pubDate>
            <description><![CDATA[<h3 id="구글-로그인-구현">구글 로그인 구현</h3>
<ul>
<li>트러블 슈팅 시간 나는 대로 작성해보겠음 (너무 대장정이라 엄두가 X)</li>
<li>아래 사진은 성공 스샷!!</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/af05ad1f-02ca-4737-84f6-f15534c98d43/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.23 (화)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.23-%ED%99%94</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.23-%ED%99%94</guid>
            <pubDate>Tue, 22 Apr 2025 12:20:27 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="최종-프로젝트-13일차">최종 프로젝트 13일차</h4>
<ul>
<li>구글 계정 로그인 구현 (1)</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="구글-계정-로그인-구현">구글 계정 로그인 구현</h4>
<ul>
<li>주말에 사진 자료와 함께 찬찬히 정리해보겠음</li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><h4 id="keystore-얻는-과정에서-다음과-같은-오류-발생">keystore 얻는 과정에서 다음과 같은 오류 발생</h4>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/f9d9ea4a-9812-497a-9a36-782625ed7a0f/image.png" alt=""></li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>이메일 로그인 구현할때까지만 해도 구글 로그인에 이만큼 삽질을 할줄은 몰랐는데......</li>
<li>오늘 꼭 세팅 끝내고 내일은 코드 작업을 들어갈 수 있었으면 좋겠다.</li>
<li>오늘코딩님이 기적처럼 내일 당장 파이어베이스 구글 로그인편을 만들어서 올려주셨으면. . . .</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.17 (목)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.17-%EB%AA%A9</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.17-%EB%AA%A9</guid>
            <pubDate>Thu, 17 Apr 2025 12:47:03 GMT</pubDate>
            <description><![CDATA[<h3 id="오늘-진행한-작업">오늘 진행한 작업</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/82a385fe-b5b3-439a-9b18-91e4e1d3ebeb/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/ddc0352b-6cda-4763-9d19-75f0a8a83e03/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/89430e5d-05d9-41ea-939d-3fd1b1ee1633/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/214950d1-2af3-469c-8496-cab41ed5d81d/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/08aa7e9d-0865-44c8-ac5a-ff193ebf5c9e/image.png" alt=""></p>
</blockquote>
<h3 id="내일-진행할-작업">내일 진행할 작업</h3>
<ul>
<li>게임 재시작 기능 구현</li>
<li>버그잡기<blockquote>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/43c75f45-c944-41db-a6b4-4b8331ca15c7/image.png" alt=""></p>
</blockquote>
</li>
<li>각자 작업한 것 합치기</li>
<li>에셋 찾고 적용하기</li>
</ul>
<h3 id="내일은-아니지만-염두에-두어야-하는-작업">내일은 아니지만 염두에 두어야 하는 작업</h3>
<ul>
<li>로그인 기능 구현을 공식적으로 3주차 일정에 넣기로 함... 아자</li>
<li>어드레서블 기능도 파이어베이스에 연결하게 될 듯</li>
<li>언어 버전과 어드레서블 버전을 나눠서 관리 / 체크하도록 데이터싱크매니저 수정해주어야 함 (당장은X)</li>
</ul>
<h3 id="짧은-회고">짧은 회고</h3>
<ul>
<li>오늘은 자잘자잘한 구현을 많이 한듯</li>
<li>속성 강화 기능을 구현하는 과정에서 재민님이 구현한 코드를 소개받았는데 너무 신기했다</li>
<li><code>Enum</code>을 배열 값으로 넣는 방식은 자주 사용해서 알고 있었는데, <code>Indexer</code>는 처음 들어봐서 꼭 공부해봐야지 싶었음!!</li>
<li>공식 가이드 링크: <a href="https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/indexers/">https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/indexers/</a></li>
<li>그리고 팀 피드백에서 칭찬 받아서 기분 좋았다! 앞으로도 화이팅 화이팅~~!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.16 (수)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.16-%EC%88%98</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.16-%EC%88%98</guid>
            <pubDate>Wed, 16 Apr 2025 03:13:26 GMT</pubDate>
            <description><![CDATA[<h4 id="시작-전-반성">시작 전 반성:</h4>
<ul>
<li>아무리 정신 없다지만 너무 빈 글만 두는 것 같아서 일단 초고라도 올려보기로 했음...</li>
<li>초고인만큼 나중에 수정될 수는 있겠지만 내용은 그대로 가져갈 듯!</li>
<li>못 올린 날짜의 TIL도 업노트에 초고가 있어서 여유될 때마다 (아마 주말) 하나씩 추가될 예정...😂</li>
</ul>
<h4 id="최근-진행중인-작업">최근 진행중인 작업:</h4>
<ul>
<li>파이어베이스 기능 구현을 어느 정도 마치고 화요일부터 스테이지 작업을 시작함.</li>
<li>스테이지 작업을 간만에 만져서인지 내가 뭘 작업해야할지 잘 감이 잡히지 않아서 내가 구현해야 하는 것들을 정리하는 시간을 가졌음 (화요일 TIL이 추가된다면 이 내용이 들어갈 듯)</li>
<li>오늘 작업은 미션 기능 구현!</li>
</ul>
<h4 id="미션-기능">미션 기능:</h4>
<ul>
<li>미션을 받으면 미션 몬스터가 등장하고, 이 몬스터를 잡으면 그 미션에 해당하는 보상을 받을 수  있음</li>
<li>스테이지와 별개로 작동해야함. (스테이지 진행 중에 미션을 받아서 함께 진행할 수도 있고, 휴식 시간에 미션을 받아 별개로 진행할 수도 있다.)</li>
</ul>
<h4 id="문제-발생">문제 발생:</h4>
<ul>
<li>현재 스테이지에 몬스터가 사라질 때마다 몬스터 카운트를 감소시키고, 그 카운트가 0이 되면 라운드를 종료시키는 방식으로 설계함.</li>
<li>문제는 미션 몬스터 또한 같은 방식으로 생성/소멸되기 때문에 삭제되는 몬스터가 미션 몬스터인지, 스테이지 몬스터인지 판단하는 과정이 필요하게 됨</li>
</ul>
<h4 id="부분-해결">부분 해결:</h4>
<ul>
<li>몬스터를 담당한 지현님께 조언을 구해 삭제되는 몬스터의 index값을 알 수 있는 방법을 알아냄.</li>
</ul>
<h4 id="또-다른-문제">또 다른 문제:</h4>
<ul>
<li>이 index값으로 미션 몬스터에 해당하는 몬스터들을 손수 if문으로 예외처리 하는 건 너무 확장성이 떨어진다고 느껴져서 기존 방식과 다른 구조를 고민하게 됨</li>
</ul>
<br>
<br>
<br>

]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.08 (화)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.08-%ED%99%94</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.08-%ED%99%94</guid>
            <pubDate>Tue, 08 Apr 2025 13:51:16 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><strong>최종 프로젝트 3일차</strong><ul>
<li>Firebase 시작하기</li>
<li>구글 스프레드 시트 → json 변환</li>
<li>트러블 슈팅</li>
</ul>
</li>
</ul>
<h3 id="📍작업-내용">📍작업 내용</h3>
<ul>
<li><p><strong>오늘 작업 내용 요약 :</strong></p>
<ul>
<li>구글 시트 연결하려 했으나 튜터님께 <strong><code>파이어베이스</code></strong>를 추천받음</li>
<li><strong>참고한 영상:</strong> <a href="https://youtu.be/A6du3DUTIPI?si=mRirXN_pSGE3ANPI">https://youtu.be/A6du3DUTIPI?si=mRirXN_pSGE3ANPI</a></li>
<li>처음 생각한 그림은 구글 스프레드시트에서 작성한 데이터 테이블을 바로 서버와 연결해주는 것이었지만, 현재 나의 실력으로 둘을 바로 연결해줄만한 방법이 당장 떠오르지 않아 차선을 선택.</li>
<li><strong>선택한 방법:</strong> 구글스프레드시트→json 파일로 변환하고, 변환된 json파일을 파이어베이스의 서버에 올린 뒤, 서버에 있는 파일을 받아오는 방식을 이용하기로 함</li>
<li>데이터테이블의 구조는 클래스 다이어그램에서 나온 각자의 ScriptableObject를 참고하여 작성했으나, ATM을 구현할 때와 달리 데이터의 구조가 깊어서 파싱이 쉽지 않았음.</li>
</ul>
</li>
<li><p><strong>파이어베이스 시작하기</strong></p>
<ul>
<li>참고한 영상: <a href="https://youtu.be/harSUx-aIzo?si=KkW9pMJ855c-opV2">[유니티] 파이어베이스로 회원가입과 로그인 구현하기</a></li>
</ul>
</li>
<li><p><strong>구글 스프레드 시트 → json 변환</strong></p>
<ul>
<li><p><strong>(시도1)</strong>  <a href="https://youtu.be/XOUqdgnOK5c?si=sQnMmPJ9mcxAeRI-">https://youtu.be/XOUqdgnOK5c?si=sQnMmPJ9mcxAeRI-</a></p>
</li>
<li><p>해당 영상을 보니 구글 시트의 확장 프로그램을 이용하면 시트의 데이터들을 json 파일로 변환이 가능한 것 같아서 시도해보았다.</p>
</li>
<li><p>그러나 Sheets to JSON로는 내가 원하는 Key로 데이터들을 묶어줄 수 있는 방법이 없어서 다른 방법을 생각해보기로 했다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/b805fcce-7ef7-44a8-b78e-a9de59355844/image.png" alt=""></p>
</li>
<li><p><strong>(시도2)</strong> 다른 확장 프로그램을 사용해도 내가 원하는 구조로 딱 맞춰 변환해줄 수 있지는 않을 것 같아 Google Apps Script를 이용해 내가 원하는 기능을 만들어 사용해보기로 했다.</p>
</li>
<li><p><strong>이 기능을 사용하는 방법은 다음과 같다:</strong></p>
<ol>
<li><p>구글 스프레드시트 &gt; 확장 프로그램 &gt; Apps Script 선택
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/5f43c138-4fc7-432f-8ff4-f1f0a7169ec8/image.png" alt=""></p>
</li>
<li><p>나타난 Apps Script 창에 gs 파일을 추가해 메서드를 직접 코딩하면 된다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/74c73d22-a79d-4cb1-a027-5258e3127532/image.png" alt=""></p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
<ul>
<li>이걸 내가 직접 프로그래밍 할 수 있었으면 정말 좋았겠지만... Apps Script는 자바스크립트 기반으로만 코딩이 가능하기 때문에 이 부분은 부득이하게 AI의 도움을 받았다.</li>
<li><strong>AI에게 요구한 부분은 다음과 같음:</strong><ul>
<li>시트의 탭에 적힌 이름으로 데이터를 묶어줄 것</li>
<li>시트 첫번째 열에 있는 값을 키로 사용할 것</li>
<li>json 데이터로 변환 후 사용자가 json 파일을 저장할 수 있도록 할 것</li>
<li>파일명은 현재 날짜를 붙여 생성되게 할 것</li>
<li>만약 같은 이름의 파일이 있다면 이름 끝에 “(2)” 와 같은 식으로 숫자를 붙여줄 것
version탭은 version으로 묶고, 나머지 탭들은 jsonData로 묶어줄 것 (버전관리를 위해 추가해주었다.)</li>
<li>구글 시트에서 변환 작업을 바로 실행할 수 있도록 해당 기능의 UI를 추가해줄 것</li>
</ul>
</li>
<li><strong>그 결과 다음과 같은 코드가 나왔고, 내가 원하는 구조대로 json 파일을 변환할 수 있었다!:</strong><blockquote>
<pre><code class="language-cs">function exportVersionedJSONtoDrive() {
 const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
 const sheets = spreadsheet.getSheets();
 let versionValue = &quot;1.0&quot;; // 기본값

 const result = {
   version: &quot;&quot;,
   jsonData: {}
 };

 sheets.forEach(sheet =&gt; {
   const sheetName = sheet.getName();
   const data = sheet.getDataRange().getValues();

   if (sheetName.toLowerCase() === &quot;version&quot;) {
     // 버전 정보 읽기: A1 = &quot;version&quot;, B1 = 숫자
     if (data.length &gt; 0 &amp;&amp; data[0].length &gt; 1) {
       versionValue = String(data[0][1]);
     }
     return; // version 시트는 jsonData에 포함하지 않음
   }

   if (data.length &lt; 2) return;

   const headers = data[0];
   const sheetData = {};

   for (let i = 1; i &lt; data.length; i++) {
     const row = data[i];
     const key = row[0]; // 첫 번째 셀 값이 key

     const rowData = {};
     for (let j = 1; j &lt; headers.length; j++) {
       const header = headers[j];
       const keyName = header.charAt(0).toLowerCase() + header.slice(1);
       rowData[keyName] = row[j];
     }

     sheetData[key] = rowData;
   }

   result.jsonData[sheetName] = sheetData;
 });

 result.version = versionValue;
 const jsonString = JSON.stringify(result, null, 2);

 // 📅 날짜 구하기
 const today = new Date();
 const yyyy = today.getFullYear();
 const mm = String(today.getMonth() + 1).padStart(2, &#39;0&#39;);
 const dd = String(today.getDate()).padStart(2, &#39;0&#39;);
 const dateString = `${yyyy}-${mm}-${dd}`;

 // 📄 파일명 구성
 const baseFileName = `gameData_v${versionValue}_${dateString}`;
 const fileExtension = &quot;.json&quot;;

 const folderPath = &quot;17floor/DataTable&quot;;
 const targetFolder = getOrCreateFolderByPath(folderPath);

 // 동일한 이름 있으면 (2), (3)... 붙이기
 let fileName = baseFileName + fileExtension;
 let count = 2;
 while (folderHasFile(targetFolder, fileName)) {
   fileName = `${baseFileName}(${count})${fileExtension}`;
   count++;
 }

 const file = targetFolder.createFile(fileName, jsonString, &quot;application/json&quot;);
 Logger.log(&quot;✅ JSON 파일 저장 완료: &quot; + file.getUrl());
}

// 폴더 경로 생성 유틸
function getOrCreateFolderByPath(path) {
 const parts = path.split(&#39;/&#39;);
 let folder = DriveApp.getRootFolder();

 for (let i = 0; i &lt; parts.length; i++) {
   const name = parts[i];
   const folders = folder.getFoldersByName(name);
   folder = folders.hasNext() ? folders.next() : folder.createFolder(name);
 }

 return folder;
}

// 파일 존재 여부 확인
function folderHasFile(folder, fileName) {
 const files = folder.getFilesByName(fileName);
 return files.hasNext();
}

// 메뉴에 커스텀 함수 등록
function OnOpen() {
 const ui = SpreadsheetApp.getUi();
 ui.createMenu(&quot;📦 JSON 변환&quot;)
   .addItem(&quot;📁 17floor/DataTable에 저장&quot;, &quot;exportVersionedJSONtoDrive&quot;)
   .addToUi();
}</code></pre>
</blockquote>
</li>
</ul>
<ul>
<li><p><strong>UI를 추가하는 방법은 다음과 같다:</strong></p>
<ul>
<li><p><strong><code>확장 프로그램 &gt; 매크로 &gt; 메서드 이름</code></strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/ea1a7f43-4d2d-4e57-a2bc-6c3b50729842/image.png" alt=""></p>
</li>
<li><p>누르면 <code>스크립트 실행중...</code> 이 뜨는데, 조금만 더 기다리면 성공적으로 추가되는 것을 볼 수 있다.</p>
</li>
<li><p>저장 탭을 누르면 현재 로그인 되어있는 구글 계정의 G드라이브에 저장된다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/1f57065b-b206-490d-8e91-7be62c88561d/image.png" alt=""></p>
</li>
<li><p>저장된 json 파일은 파이어베이스에서 JSON 가져오기를 통해 올릴 수 있다
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/c32f1f1d-3716-4c73-9dd5-75f4d943e9bb/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p><strong>과정 GIF:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/43b3c8a8-7c3f-43fb-91fc-2f51436011b6/image.gif" alt=""></p>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><p><strong><code>google-services.json</code> 이슈</strong></p>
<ul>
<li><p><strong>코드를 짜고 실행하려는데 다음과 같은 오류가 발생함:</strong></p>
<ul>
<li><code>Unable to load Firebase app options ([A:/TeamSparta/on-the-17floor/Element_Tower_Defense/Assets/StreamingAssets\google-services-desktop.json, A:/TeamSparta/on-the-17floor/Element_Tower_Defense/Assets/StreamingAssets\google-services.json] are missing or malformed)</code></li>
</ul>
</li>
<li><p><strong>오류 번역:</strong></p>
<ul>
<li><code>Firebase 앱 옵션을 로드할 수 없습니다 ([A:/TeamSparta/17층/Element_Tower_Defense/Assets/StreamingAssets\\google-services-desktop.json, A:/TeamSparta/17층/Element_Tower_Defense/Assets/StreamingAssets\\google-services.json]이 누락되었거나 잘못된 형식입니다)</code></li>
</ul>
</li>
<li><p><strong>문제 원인:</strong></p>
<ul>
<li>Firebase SDK는 초기화 시 <code>google-services.json</code>(Android용) 혹은 <code>google-services-desktop.json</code>(유니티 에디터 또는 데스크탑 실행용) 파일을 StreamingAssets 폴더에서 찾는데, 이 파일들이 해당 폴더에 존재하지 않기 때문에 Firebase 앱 초기화에 실패한 거였음.</li>
</ul>
</li>
<li><p>*<em>해결한 방식: *</em></p>
<ul>
<li>해당 파일들을 파이어베이스에서 다시 다운받아 StreamingAssets 폴더에 넣어주니 해당 오류는 일어나지 않음.</li>
</ul>
</li>
<li><p><strong>또 다른 문제 발생:</strong></p>
<blockquote>
<pre><code>DatabaseException: Failed to get FirebaseDatabase instance:
Specify DatabaseURL within FirebaseApp or from your GetInstance() call.</code></pre></blockquote>
</li>
<li><p><strong>원인:</strong></p>
<ul>
<li>google-services.json에 Realtime Database의 URL가 포함되지 않은 상태라 데이터를 받아오지 못하고 있었음.</li>
</ul>
</li>
<li><p><strong>해결한 방식:</strong></p>
<ul>
<li>파이어베이스에서 google-services.json을 다시 다운받아 넣어줌.</li>
<li>새로 받은 파일에는 Realtime Database의 URL가 제대로 포함되어 있었다.</li>
<li>SDK를 추가할 때마다 google-services.json은 다시 받아서 넣어줘야 한다고 생각해도 무방할 듯.</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>JSON 데이터 파싱 이슈</strong></p>
<ul>
<li><p>문제를 해결하고 실행하니 또 다른 오류가 발생함 :</p>
<blockquote>
<pre><code>ArgumentException: JSON parse error: Invalid value.
JsonUtility.FromJson&lt;T&gt;(json)</code></pre></blockquote>
</li>
<li><p><strong>오류 원인:</strong></p>
<ul>
<li>JsonUtility는 딕셔너리를 지원하지 않는데, 내가 짠 자료의 구조는 Key안의 List<Key>안의 List<Value> 구조여서 굉장히 깊음.</li>
<li>때문에 래퍼 클래스를 통해 파싱해주려고 해도 현실적으로 쉽지 않았음.</li>
<li>가장 현실적인 해결 방법은 <code>JsonUtility</code>대신 딕셔너리를 지원하는 <code>Newtonsoft.Json</code>를 사용하는 것인데, 유니티에서는 JsonUtility의 성능이 압도적으로 좋다고 알고 있기 때문에 고민이 됨.</li>
<li>일단은 다른 방법이 있을지도 모르니 내일 튜터님께 찾아가서 조언을 구해보기로 함.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>내 나름대로는 기획자의 편의성을 챙긴다고 구글 스프레드 시트를 이용했지만, 역시 바로 구글 스프레드 시트를 연동해서 사용하는 방법보다는 번거로워 보여서 고민이다.</li>
<li>구글 스프레드 시트는 보안 이슈가 있을 수도 있다고 하니 어쩔 수 없는 일이지만, 그래도 레벨 테스트를 진행할 때는 구글 스프레드 시트를 연결해서 즉각적으로 테스트를 할 수 있도록 하는 게 나을까? 고민이 됨.</li>
<li>이 부분도 튜터님과 다른 팀원들과 이야기해 보면 좋을 것 같다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.07 (월)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.07-%EC%9B%94</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.07-%EC%9B%94</guid>
            <pubDate>Mon, 07 Apr 2025 13:24:54 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><strong>최종 프로젝트 2일차</strong></li>
<li><strong>스테이지 관련 클래스 작업</strong></li>
<li><strong>웨이포인트</strong></li>
</ul>
<h3 id="📍진행-작업">📍진행 작업</h3>
<ul>
<li><h4 id="스테이지-관련-클래스-작업">스테이지 관련 클래스 작업</h4>
<ul>
<li>클래스 다이어그램을 토대로 <code>UserManager</code>와 <code>StageManager</code>를 만들었다.</li>
<li>타워와 몬스터가 만들어지면 연결하고 스테이지를 관리해주어야 하기 때문에 주로 빈 메서드 위주로 얼추 흐름만 만들어 놓았음.</li>
</ul>
</li>
<li><h4 id="웨이포인트">웨이포인트</h4>
<blockquote>
<pre><code class="language-cs">using System.Linq;
using UnityEngine;

public class WayPoint : MonoBehaviour
{
   [SerializeField]
   private Transform[] wayPoint;

   private void OnValidate()
   {
       //본인을 제외하고 자식의 Transform만 가져온다.
       wayPoint = GetComponentsInChildren&lt;Transform&gt;(true)
                  .Where(t =&gt; t != transform)
                  .ToArray();
   }

   public Transform[] GetWayPoint()
   {
       return wayPoint;
   }
}</code></pre>
</blockquote>
<ul>
<li><p>우리 게임은 구불구불한 맵을 몬스터가 이리저리 방향을 틀며 이동해야 하기 때문에, 그 회전 포인트들을 지정하고 저장할 필요가 있었음.</p>
</li>
<li><p>특정 포인트에서 회전하고, 이동하는 기능은 몬스터 쪽에서 구현하기로 했기 때문에, 스테이지 쪽에서 할 일은 웨이포인트를 지정하고, 몬스터 쪽에서 접근하기 쉽도록 만드는 일밖에 없었음.</p>
</li>
<li><p>그래서 WayPoint라는 빈 오브젝트를 만들어 자식 오브젝트의 transform을 찾아내 저장할 수 있도록 함</p>
</li>
<li><p><strong>아래 사진을 보면 새로운 오브젝트가 생겨도 자동으로 추가된다:</strong></p>
<blockquote>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/41d1d6dd-ae4e-494b-a7fc-7e1981a2b4a9/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/46d61038-7485-4849-8dae-c78685a46c19/image.png" alt=""></p>
</blockquote>
</li>
<li><p><strong>StageManager를 통해 WayPoint에 접근할 수 있도록 해야 하므로 StageManager가 WayPoint를 찾을 수 있도록 다음과 같은 코드를 붙여 넣었다:</strong></p>
<blockquote>
<pre><code class="language-cs">private void OnValidate()
{
   /// &lt;summary&gt; OnValidate 관련 &lt;/summary&gt;
   ///
   /// OnValidate는 에디터 함수이기 때문에
   /// Find를 사용해도 빌드한 최종 결과물에는 영향을 주지 않음
   /// 다만 너무 남용할 경우 오류가 생길 수 있으니 GetComponent정도만 사용할 것

   wayPoint = GameObject.Find(&quot;WayPoint&quot;)?.GetComponent&lt;WayPoint&gt;();

}</code></pre>
</blockquote>
</li>
<li><p>위 방법은 오브젝트의 이름이 달라지면 WayPoint를 찾아오지 못한다는 문제점이 있지만 WayPoint의 경우 프리팹으로 만들어 메인 씬에 넣을 수 있도록 할 예정이므로 크게 문제가 되진 않으리라 생각했다.</p>
</li>
<li><p><strong>이 코드를 쓰면 아래처럼 웨이포인트를 자동으로 잘 찾아오는 것을 확인할 수 있다:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/ab013211-7db1-4de2-8b36-4500f7c84b39/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><h4 id="getcomponentsinchildren문제"><code>GetComponentsInChildren</code>문제</h4>
</li>
<li><p>웨이포인트를 받아오기 위해 <code>GetComponentsInChildren&lt;Transform&gt;</code>를 통해 자식들의 transforn 정보를 받아오려고 했는데, 막상 이 코드를 사용하니 자식뿐만 아니라 본인의 transform까지 가져오는 문제가 발생했다.</p>
</li>
<li><p><strong>찾아봤지만 자식의 컴포넌트만 검색해 주는 메서드는 따로 없는 것 같아 결국 다음과 같은 방식으로 해결했다:</strong></p>
<blockquote>
<pre><code class="language-cs">private void OnValidate()
{
   //본인을 제외하고 자식의 Transform만 가져온다.
   wayPoint = GetComponentsInChildren&lt;Transform&gt;(true)
              .Where(t =&gt; t != transform)
              .ToArray();
}</code></pre>
</blockquote>
<ul>
<li><strong>한 줄씩 설명하자면:</strong></li>
<li><strong><code>GetComponentsInChildren&lt;Transform&gt;(true)</code>:</strong> 자식들의 트랜스폼을 모두 가져오는데 (true: 비활성화된 자식까지 포함)</li>
<li><strong><code>Where(t =&gt; t != transform)</code>:</strong> transform이 아닌 것들만 가져오고 (자신의 transform과 다른 값인 transform만 가져오고)</li>
<li><strong><code>ToArray()</code>:</strong> 배열 형태로 반환하라
형태는 조금 다르지만 람다와 링큐를 다뤘던 TIL링크를 첨부: 👉<a href="https://velog.io/@gyul_is_yummy/2025.01.30-%EB%AA%A9">2025.01.30 (목)</a></li>
</ul>
</li>
<li><h4 id="폰트가-안-구워지는-문제-발생">폰트가 안 구워지는 문제 발생</h4>
<ul>
<li>옵션은 잘 설정해 준 것으로 확인</li>
<li>해당 TIL과 똑같은 옵션으로 맞춰주었는데도 오류가 떴음 👉<a href="https://velog.io/@gyul_is_yummy/2025.03.05-%EC%88%98">2025.03.05 (수)</a></li>
<li><strong>원인 발견 및 해결:</strong> 폰트 파일이 한글로 되어있었는데 혹시나 하고 폰트 파일명을 영어로 변경하니 잘 구워졌다.</li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>얼추 구조가 잡혔고 웨이포인트에 접근할 수 있도록 토대도 만들어 두었으니 계획대로 임시 데이터테이블을 만들고 연결해보려고 하는데, 내가 상대적으로 할 수 있는 일이 적은 것 같아서 팀원들에게 양해를 구하고 데이터 테이블을 유니티에 연결하는 부분을 맡아보려고 함.</li>
<li>어떤 방식이 좋을지는 튜터님과 상의해 볼 예정!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.04 (금)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.04-%EA%B8%88</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.04-%EA%B8%88</guid>
            <pubDate>Fri, 04 Apr 2025 13:22:17 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="최종-프로젝트-1일차">최종 프로젝트 1일차</h4>
<ul>
<li>제작 게임 소개</li>
<li>클래스 다이어그램</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="제작-게임-소개">제작 게임 소개</h4>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/2ffc8c29-852c-4548-be09-f8e30f69a631/image.png" alt=""></p>
</li>
<li><h4 id="역할분담">역할분담</h4>
<ul>
<li><p>프로그래머가 4명이었으므로 구현 부분을 크게 <strong><code>맵/타워</code></strong>, <strong><code>몬스터</code></strong>, <strong><code>스테이지</code></strong>, <strong><code>UI/카메라</code></strong>로 나누었다.</p>
</li>
<li><p>내가 맡게 된 부분은 <strong><code>스테이지</code></strong>이며 게임의 전반적인 흐름을 관리한다.</p>
</li>
<li><p><strong>게임의 간단한 사이클:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/86d0b419-d5c1-4030-a072-df0b5ad73625/image.png" alt=""></p>
</li>
<li><p><strong>클래스 다이어그램:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/4cfe2f07-2713-469e-9c58-f21358743378/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li>S.A 대시보드를 제출하고 TASK 분배를 해보는 것이 좋을 것 같다는 피드백을 받았으나, 이 작업에 어느 정도의 시간이 소요될지 나조차도 잘 감을 잡지 못해 난감했다.</li>
<li>또한 내가 맡은 역할인 스테이지 특성상 다른 역할과 연결된 부분이 많아 더 난감했던 것 같다.</li>
<li>일단 TASK 분배를 마치는 대로 다시 한번 튜터님께 피드백받아볼 예정!</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>지금까지 이렇게 다 같이 클래스 다이어그램을 짜고 들어간 적이 없었는데, 서로가 어떤 식으로 코드를 작성할 예정인지 대략적으로 확인할 수 있어 좋았던 것 같다.</li>
<li>그러나 익숙하지 않은 작업이라 오랫동안 붙잡고 있었던 것이 조금 아쉬웠음.</li>
<li>앞으로 코드를 짤 때는 조금씩이라도 클래스 다이어그램을 작성하고 시작하는 습관을 들여봐야겠다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.03 (목)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.03-%EB%AA%A9</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.03-%EB%AA%A9</guid>
            <pubDate>Thu, 03 Apr 2025 12:21:24 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="팀-프로젝트---클리커-게임-마감-및-발표">팀 프로젝트 - 클리커 게임 마감 및 발표</h4>
<ul>
<li>팀 프로젝트 소개</li>
<li>GitHub Link</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="팀-프로젝트-소개">팀 프로젝트 소개</h4>
<ul>
<li>GitHub Link 👉 <a href="https://github.com/2zoZombie/ZoZo">https://github.com/2zoZombie/ZoZo</a>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/428cadc5-91f5-4575-8e6f-a5bbafad15a9/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>지금 구조는 각자 PlayerData를 가져와서 데이터 안의 스킬레벨을 통해 플레이어의 스탯을 계산해 사용하고 있는데, 플레이어 스탯 클래스를 빨리 만들어두었다면 이 계산을 각자 해줄 필요는 없었을 텐데... 하는 아쉬움이 많이 든다.</li>
<li>UI 같은 경우도 각자 담당한 부분의 UI를 임시로 구현해서 사용했는데, 이 때문에 UI설정이 약간씩 달라서 똑같은 작업을 두 번 해주어야 하는 문제도 발생했었다.</li>
<li>빨리 작업에 착수하는 것도 좋지만, 문제가 일어나는 것을 방지하기 위해서는 다 같이 구조를 짜고, 합의하는 과정이 꼭 필요하다는 것을 알 수 있었던 주차였음.</li>
<li>어쩐지 아쉬운 점만 줄줄 늘어놓은 것 같지만 그래도 팀 프로젝트 자체는 정말 좋았다! 좋은 팀원들을 만나서 열심히 할 수 있었고, 게임도 이 정도면 나름 잘 뽑혔다고 생각한다.</li>
<li>아쉬운 점은 개인적으로 리팩토링 하거나 수정해도 되니까 이번 프로젝트는 이걸로 마무리! 이어질 최종 프로젝트도 힘내자!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.04.02 (수)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.04.02-%EC%88%98</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.04.02-%EC%88%98</guid>
            <pubDate>Wed, 02 Apr 2025 13:19:06 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="팀-프로젝트---클리커-게임-5">팀 프로젝트 - 클리커 게임 (5)</h4>
<ul>
<li>트러블슈팅</li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><p><strong><code>OnValidate</code> 이슈</strong></p>
<ul>
<li><p><strong>문제:</strong> 게임을 만들고 모바일 빌드를 해보니 <strong>스킬 업그레이드</strong> 부분만 제대로 작동하지 않는 것을 확인</p>
</li>
<li><p><strong>원인:</strong> <code>OnValidate</code>에서 너무 많은 작업을 진행했기 때문에 일어난 문제였다.</p>
</li>
<li><p><strong>설명:</strong></p>
<ul>
<li><strong><code>OnValidate</code></strong>는 유니티에서만 실행되는 에디터 함수이고, 나는 지금까지 <strong><code>OnValidate</code></strong>에서 할당해 준 것들은 직접 손으로 끌어서 할당해준 것과 같은 취급을 받는다고 이해하고 있었음.</li>
<li>그러나 <strong><code>OnValidate</code></strong>에서 <strong><code>GetComponent</code></strong>로 에디터에 있는 게임 오브젝트의 컴포넌트를 찾아오는 것은 문제가 되지 않지만, <strong>플레이를 할 때 세팅되어야 하는 것까지 <code>OnValidate</code>에서 세팅해준다면 문제가 발생할 수 있다</strong>는 튜터님의 피드백을 받았다.</li>
</ul>
</li>
<li><p><strong>해결 방법:</strong> <strong><code>OnValidate</code></strong>에서 처리해주던 코드 몇가지를 <strong><code>Start</code></strong>로 옮겨주니 빌드 시에도 제대로 작동하는 것을 확인!</p>
</li>
<li><p><strong>수정 전 코드</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/de52b6ca-fc29-4d56-be82-a236e250325e/image.png" alt=""></p>
</li>
<li><p><strong>수정 후 코드</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/032d0143-c7f4-47d8-ae94-60ee6d44c9f5/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/f7aa9ed1-5404-469f-b860-a6afdfd0f242/image.png" alt=""></p>
</li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li><strong><code>OnValidate</code></strong>가 에디터 함수라는 것은 알았지만 지금까지 그게 어떤 오류를 일으킬 수 있는지에 대해서는 잘 모르고 사용한 것 같다.</li>
<li>그래도 해당 문제를 최종 프로젝트에 들어가기 전에 알아서 다행이다. 앞으로는 조심할 것!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.27 (목)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.27-%EB%AA%A9</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.27-%EB%AA%A9</guid>
            <pubDate>Thu, 27 Mar 2025 13:01:38 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="팀-프로젝트---클리커-게임">팀 프로젝트 - 클리커 게임</h4>
<ul>
<li>담당 역할</li>
<li>트러블슈팅</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="담당-역할">담당 역할</h4>
<ul>
<li>플레이어 능력치 및 업그레이드 기능 (아래 사진은 예시 게임 캡쳐)
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/d678b18d-fe97-4616-9712-2101b8dedab2/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li><h4 id="구현해야-하는-기능">구현해야 하는 기능:</h4>
<ul>
<li>업그레이드 UGUI</li>
<li>업그레이드 현황 및 그에 따른 현재 능력치를 화면에 표시</li>
<li><code>ScriptableObject</code>를 통해 데이터 테이블 작성</li>
<li>재화를 소모해 플레이어의 능력치를 업그레이드</li>
<li>재화가 부족하면 업그레이드 X</li>
<li>업그레이드 현황 및 그에 따른 현재 능력치를 화면에 표시합니다.</li>
<li>구현해야할 스탯(스킬)<ul>
<li>치명타 데미지 증가</li>
<li>자동 공격 속도 증가</li>
<li>재화 획득량 증가</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><p>GetComponent를 통해 특정 자식을 찾아오는 과정에서 생긴 문제</p>
<ul>
<li><p><strong>현재 업그레이드 UI는 다음과 같은 형식으로 구현되어 있다:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/3fb290b0-a4dc-401c-a436-30be2acc3890/image.png" alt=""></p>
</li>
<li><p>나는 슬롯에 스킬 스크립트를 붙이기만 하면 하위 오브젝트들의 정보를 알아서 다 가져올 수 있도록 코드를 짜고 싶었다.</p>
</li>
<li><p>그래서 <code>OnVaildate</code>와 <code>GetComponentInChildren</code>를 통해 하위 오브젝트들의 컴포넌트를 받아와 연결해주려고 했음</p>
</li>
<li><p><strong>문제:</strong> 나는 <code>Icon</code>의 <code>Image</code>를 받아오고 싶었는데 <code>GetComponentInChildren</code>로 이미지를 찾아오려니 Slot의 이미지를 받아오는 문제가 발생함
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/61a8dbea-8e97-41cb-aa1f-142355240b4c/image.png" alt=""></p>
</li>
<li><p><strong>시도:</strong> 이번에는 <code>GameObject.Find(&quot;Icon&quot;)</code>을 통해 <code>Icon</code>의 <code>Image</code>를 찾아오려 했으나 게임오브젝트 자체는 Image 변수 안에 들어가지 않았다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/c7174ab7-30d8-4917-9d62-0784ba014339/image.png" alt=""></p>
</li>
<li><p><strong>부분 해결:</strong> 이 부분은 <code>Find</code>로 찾아온 게임 오브젝트에 다시 <code>GetComponent</code>를 시도하는 것으로 해결했다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/9e2eb28f-069a-4129-aee1-11e3f63eced7/image.png" alt=""></p>
</li>
<li><p><strong>문제2:</strong> <code>Icon</code>을 찾아오긴 하는데 다른 슬롯의 <code>Icon</code>을 찾아오기 시작...
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/92899b10-22f4-4638-a91d-f20e3c72c8c1/image.png" alt=""></p>
</li>
<li><p><strong>추정원인:</strong> <code>GameObject.Find</code>는 검색 대상이 씬 전체이므로 마지막으로 실행되는 <code>Slot(2)</code>의 <code>Icon</code>을 찾아오게 되는 것 같았다.</p>
</li>
<li><p><strong>해결:</strong> <code>GameObject.Find</code>대신 <code>transform.Find</code>를 사용했더니 자식의 <code>Icon</code>을 잘 찾아왔다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/584aab8a-c6db-4df9-b87f-1857db86a402/image.png" alt=""></p>
</li>
<li><p><strong><code>GameObject.Find</code>와 <code>transform.Find</code>의 차이점:</strong></p>
</li>
</ul>
<blockquote>
<table>
<thead>
<tr>
<th>항목</th>
<th><code>GameObject.Find(&quot;Icon&quot;)</code></th>
<th><code>transform.Find(&quot;Icon&quot;)</code></th>
</tr>
</thead>
<tbody><tr>
<td><strong>검색 대상</strong></td>
<td><strong>씬 전체</strong></td>
<td><strong>자신의 자식들만</strong></td>
</tr>
<tr>
<td><strong>검색 방식</strong></td>
<td>이름으로 전역 탐색</td>
<td><strong>자식 Transform만</strong> 탐색</td>
</tr>
<tr>
<td><strong>성능</strong></td>
<td>❌ 느림 (전역 탐색)</td>
<td>✅ 빠름 (로컬 탐색)</td>
</tr>
<tr>
<td><strong>Null 발생</strong></td>
<td>많음 (이름 중복/없으면 실패)</td>
<td>안정적 (구조만 알면 정확히 탐색 가능)</td>
</tr>
<tr>
<td><strong>경로 지원</strong></td>
<td>❌ 단일 이름만 가능</td>
<td>✅ <code>&quot;Child/Grandchild&quot;</code>처럼 경로 탐색 가능</td>
</tr>
</tbody></table>
</blockquote>
<ul>
<li><strong>완성된 코드는 다음과 같다:</strong><blockquote>
<pre><code class="language-cs">private void OnValidate()
{
    skillImage = transform.Find(&quot;Icon&quot;)?.GetComponent&lt;Image&gt;();
    levelupBtn = transform.Find(&quot;LevelUpBtn&quot;)?.GetComponent&lt;Button&gt;();
    btnText = levelupBtn.GetComponentInChildren&lt;TextMeshProUGUI&gt;();

    _name = transform.Find(&quot;Name&quot;)?.GetComponent&lt;TextMeshProUGUI&gt;();
    description = transform.Find(&quot;Description&quot;)?.GetComponent&lt;TextMeshProUGUI&gt;();

    skillImage.sprite = data.Icon;

    if (data == null) return;

    switch(data.type)
    {
        case SkillType.PercentageBuff:
            levelupBtn.onClick.AddListener(LevelUpPercentageBuff);
            break;

        case SkillType.TimedActive:
            levelupBtn.onClick.AddListener(LevelUpTimedActive);
            break;
    }
}</code></pre>
</blockquote>
</li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>유니티를 처음 시작할 때 내가 제일 어렵다고 생각했던 부분이 <strong>어떤 오브젝트나 컴포넌트를 찾아올 때 어떤건 <code>this</code>로 접근하고, 어떤건 <code>transform</code>으로 접근하고, 어떤건 <code>gameObject</code> 접근하는게 헷갈린다...</strong> 였는데, 유니티를 배우기 시작한지 꽤 지났는데도 내가 아직도 이런 부분을 제대로 이해하지 못한 채로 넘어갔다는 것을 새삼 깨달았다.</li>
<li>나중에 시간을 들여 셋의 차이점이나 컴포넌트/오브젝트의 접근법을 정리해보는 것도 좋을 것 같음! 반성하자!!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.25 (화)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.25-%ED%99%94</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.25-%ED%99%94</guid>
            <pubDate>Tue, 25 Mar 2025 14:25:16 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="스탠다드-1회차---유니티-기초">스탠다드 1회차 - 유니티 기초</h4>
<ul>
<li>유료 에셋을 사용하는 경우 주의해야 할 점</li>
<li><code>MonoBehaviour</code></li>
<li>생명주기 함수</li>
<li><code>SendMessage</code></li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="유료-에셋을-사용하는-경우-주의해야-할-점">유료 에셋을 사용하는 경우 주의해야 할 점</h4>
<ul>
<li>유료 에셋을 이용하는 경우 깃허브에 <code>public</code>으로 올리면 안 됨.</li>
<li><strong>만약 사용했는데 public으로 올려야 한다면:</strong><ul>
<li>새로운 폴더<strong>(EX-<code>99.Asset</code>)</strong>를 만들어 유료 에셋을 그 폴더에 다 넣은 뒤, <code>.gitignore</code>에 해당 폴더를 등록한다. (등록하면 해당 파일은 깃허브 서버에 올라가지 않는다.) 이 경우 함께 에셋을 사용해야 하는 팀원들에게는 유니티 패키지 파일로 에셋을 따로 전달한다.</li>
<li>일단 사용한 뒤, 외부에 노출될 때 폴더를 삭제하는 방법</li>
</ul>
</li>
<li>포트폴리오를 깃허브에 올릴 때는 꼭 신경써주기.</li>
<li>신경쓰는 만큼 기본이 되어있다는 인상을 줄 수 있다!</li>
</ul>
</li>
<li><h4 id="monobehaviour"><code>MonoBehaviour</code></h4>
<ul>
<li><strong><code>MonoBehaviour</code></strong>를 상속 받는다 = 유니티의 관리를 받는다.</li>
<li><strong><code>MonoBehaviour</code></strong>를 상속 받은 클래스만 컴포넌트가 될 수 있다. (오브젝트에 붙일 수 있다)</li>
<li><strong><code>MonoBehaviour</code></strong>를 상속 받으면 생명주기 함수를 사용 가능.</li>
<li>뿐만아니라 해당 도큐먼트 안에 쓰여있는 것들 모두를 사용할 수 있게된다.</li>
<li><strong>유니티 도큐먼트 링크</strong> 👉 <a href="https://docs.unity3d.com/kr/2023.2/Manual/class-MonoBehaviour.html">MonoBehaviour - Unity 매뉴얼</a></li>
</ul>
</li>
</ul>
<ul>
<li><h4 id="생명주기-함수">생명주기 함수</h4>
<ul>
<li><p><strong>생명주기 함수는 정말 많지만 이 정도로 정리해볼 수 있음:</strong></p>
<blockquote>
<pre><code class="language-cs">   private void Awake()
   {
       Debug.Log(&quot;가장 먼저 시작 되는 Awake&quot;);
   }
   private void OnEnable()
   {
       Debug.Log(&quot;켜질 때마다 시작하는 OnEnable&quot;);
   }
   private void Start()
   {
       Debug.Log(&quot;OnEnable 이후 시작하는 Start&quot;);
   }
   private void FixedUpdate()// 1초에 50번 호출함.
   {
       Debug.Log($&quot;물리처리를 실행하는 FixedUpdate&quot;);
   }
   private void Update()// 1초에 프레임 단위로 호출함.
   {
       Debug.Log($&quot;프레임 단위로 시작하는 Update&quot;);
   }
   private void LateUpdate()
   {
       Debug.Log(&quot;Update이후 시작하는 LateUpdate&quot;);
   }
   private void OnDisable()
   {
       Debug.Log(&quot;꺼질 때마다 시작하는 OnDisalbe&quot;);
   }
   private void OnDestroy()
   {
       Debug.Log(&quot;파괴되면 시작하는 OnDestroy&quot;);
   }</code></pre>
</blockquote>
</li>
<li><p><strong>게임 오브젝트에 붙이고 실행시:</strong></p>
<ul>
<li><strong><code>Awake</code>  &gt; <code>OnEnable</code>  &gt; <code>Start</code>  &gt; <code>FixedUpdate</code>  &gt; <code>Update</code>  &gt; <code>LateUpdate</code></strong> 순서로 실행
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/bebe3960-c4b0-4ca0-b241-d73db210ea85/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/b7a7091f-88e0-41b1-a3f4-824e8f26cee5/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>게임 오브젝트를 끄면:</strong></p>
<ul>
<li><strong><code>OnDisable</code></strong> 실행됨
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/358e440d-856d-4a73-805d-925406c86404/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>다시 키면:</strong></p>
<ul>
<li><strong><code>OnEnable</code></strong> 실행
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/a1676636-c2cf-4d2f-be79-f8ecec6f61d5/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>킨 채로 파괴하면:</strong></p>
<ul>
<li><strong><code>OnDisable</code> &gt; <code>OnDestroy</code></strong> 순서로 실행됨
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/6c0aa24c-e744-4bdb-982d-b02653a48c4a/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>만약 오브젝트가 꺼진채로 프로젝트를 실행한다면:</strong></p>
<ul>
<li>활성화  되지 않았으므로 아무것도 실행되지 않음 (<code>Awake</code>도 실행되지 않음)
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/0a575a10-6965-4581-a81c-ce37c8e18fe8/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>이 상태에서 키면:</strong></p>
<ul>
<li>처음과 마찬가지로 <strong><code>Awake</code> &gt; <code>OnEnable</code> &gt; <code>Start</code> &gt; <code>FixedUpdate</code> &gt; <code>Update</code> &gt; <code>LateUpdate</code></strong> 순서로 실행
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/67c965df-229b-476c-af76-b49c81cb99ca/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>끈 상태에서 삭제하면:</strong></p>
<ul>
<li><strong><code>OnDestroy</code></strong>만 실행
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/7e4d6bcd-27fe-46c3-bc8a-951e7b1cce0b/image.png" alt=""></li>
</ul>
</li>
<li><p><strong>끈 상태에서 실행한 뒤 바로 삭제한다면:</strong></p>
<ul>
<li>마찬가지로 한 번도 활성화 된 적이 없기 때문에 아무것도 뜨지 않는다. (<code>OnDestroy</code> 도 실행되지 않는다.)
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/cdbc0c8a-a8d8-4a8f-a3e4-532e8a2e0d62/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li><p><strong><code>SendMessage</code> :</strong></p>
<ul>
<li>유니티 공식 <strong>Documentation</strong> 👉 <a href="https://docs.unity3d.com/kr/2023.2/ScriptReference/Component.SendMessage.html">Component-SendMessage - Unity 스크립팅 API</a></li>
<li><strong><code>MonoBehaviour</code></strong>를 상속 받은 클래스에 있는 메서드 모두를 검색해서 실행시킴</li>
<li><strong><code>public</code></strong>이 아닌 메서드도 문자열로 검색해 강제로 호출하기 때문에 성능적으로 좋지 않고, 디버깅도 어려움.</li>
<li>그냥 <strong>있다는 것만 알아두고 사용하진 말 것!!</strong></li>
<li><strong>팁:</strong> 생명주기함수는 <strong><code>SendMessage</code></strong>로 호출하는 것과 유사한 형식으로 돌아가기 때문에 불필요한 생명주기 함수를 지우지 않고 놓아두면 성능적으로 좋지 않다.</li>
</ul>
</li>
<li><p><strong>Vector :</strong> </p>
<ul>
<li><p>크기와 방향만 존재할 뿐이지 위치에 대한 정보가 없다는 특징이 있음.</p>
</li>
<li><p>플레이어 좌표에서 몬스터 좌표를 빼면 몬스터 좌표에서 플레이어를 바라보는 벡터가 나옴
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/2d53ba86-9315-4704-a70b-a1411947d08e/image.png" alt=""></p>
</li>
<li><p><strong>만약 플레이어는 하나인데 몬스터는 여러마리 일 경우 다음과 같은 경우가 발생할 수 있음:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/c21c6747-a06f-4905-8541-46028ac602ee/image.png" alt=""></p>
<ul>
<li>이 경우 벡터의 방향은 같지만 크기가 각자 다름</li>
<li>이렇게 되면 몬스터 들의 속도가 달라짐.</li>
<li>벡터의 크기가 작은 녀석은 느린 속도로 플레이어를 향해 이동할 것이고,</li>
<li>벡터의 크기가 큰 녀석은 빠른 속도로 플레이어를 향해 이동할 것임.</li>
</ul>
</li>
<li><p>이 경우 필요한 것이 <strong>정규화</strong>인데, 정규화를 하면 크기는 초기화 하고 방향만을 남길 수 있게 된다.</p>
</li>
</ul>
<blockquote>
<h3 id="q-정규화">Q. 정규화?</h3>
<p>👉 어떠한 벡터를 단위 벡터로 만드는 것을 <strong>정규화(Normalization)</strong>라고 하는데, 여기서 <strong>단위 벡터(unit vector)</strong>란, 방향을 가지는 크기가 1인 벡터를 뜻한다.</p>
</blockquote>
<ul>
<li><strong><code>vector</code>의 크기만 남기기:</strong> <code>magnitude</code></li>
<li><strong><code>vector</code>의 방향만 남기기:</strong> <code>normalized</code></li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><p><strong><code>new</code>로 새로운 객체를 만들어주면 일어나는 일:</strong></p>
<ul>
<li>내가 겪은 어려움은 아니지만, 셍명주기 함수의 순서를 정리하던 중 다른 분이 가르쳐주셨던 신기한 오류를 기록해둔다.</li>
<li>다음과 같이 new를 통해 test에 TestScript 를 할당해준다.</li>
<li>여기서 문제: 그럼 test는 null인가?</li>
<li>나는 test 변수 안에 new로 TestScript를 넣어줬으니 당연히 null이 아닐 것이라고 대답했다.</li>
<li><strong>그리고 결과는... :</strong><blockquote>
<pre><code class="language-cs">using UnityEngine;

public class TestScript2 : MonoBehaviour
{
   TestScript test;

   private void Awake()
   {
       test = new TestScript();
   }

   private void Start()
   {
       if(test == null)
       {
           Debug.Log(&quot;test은 null이다.&quot;);
       }
       else
       {
           Debug.Log(&quot;test은 null이 아니다.&quot;);
       }
   }
}</code></pre>
</blockquote>
</li>
</ul>
</li>
<li><p><strong>대체 왜?! :</strong></p>
<ul>
<li><p>Unity에서는 <code>MonoBehaviour</code>를 new 키워드로 직접 생성할 수 없움</p>
</li>
<li><p><code>MonoBehaviour</code>는 Unity가 씬(Scene) 위에 붙는 컴포넌트로, 반드시 <strong><code>GameObject.AddComponent&lt;&gt;()</code></strong>로 생성해야 한다.</p>
</li>
<li><p><code>MonoBehaviour</code>는 Unity 엔진 내부에서 생성과 수명 관리를 하기 때문에, 개발자가 <code>new</code>로 만들면 Unity가 인식하지 못해서 문제가 생긴다!</p>
</li>
<li><p>때문에 test에 무언가가 들어있음을 제대로 체크하려면 TestScript에서 <code>MonoBehaviou</code>를 상속받는 부분을 없애던가, 아래 코드처럼 AddComponent를 통해 붙여주어야 함!!</p>
</li>
</ul>
<blockquote>
<pre><code class="language-cs">GameObject obj = new GameObject(&quot;MyObject&quot;);
TestScript script = obj.AddComponent&lt;TestScript&gt;();</code></pre>
</blockquote>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>암호화 공부를 못했다... 특강이 있었던 것을 감안해도  하루열 두시간이 적은 시간은 아닌데, 그 시간에 온전히 집중하지 못한 것 같아서 조금 아쉬움.</li>
<li>내일은 꼭 ATM 코드 리펙토링 및 리드미 작성까지 끝낸 다음에, 과제 제출 하고, 2주차 강의 마저 듣고, 암호화 공부까지 하는 걸로!</li>
<li>최종 전 마지막 팀플이 얼마 남지 않았으니 최선을 다하자!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.24 (월)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.24-%EC%9B%94</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.24-%EC%9B%94</guid>
            <pubDate>Mon, 24 Mar 2025 14:21:19 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="atm-만들기-3">ATM 만들기 (3)</h4>
<ul>
<li>회원가입 기능 구현</li>
<li>로그인 기능 구현</li>
<li>송금 기능 구현</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><p><strong>기능 구현 전 기존 코드 수정:</strong></p>
<ul>
<li>로그인/회원가입 기능 구현을 위해 <code>UserData</code>에 <code>ID</code>, <code>PW</code>정보를 추가</li>
<li>저장 또한 이름이 아닌 <code>ID</code>로 저장되도록 만듦.</li>
</ul>
<blockquote>
<pre><code class="language-cs">public UserData(string _name, int _cash, int _bankBalance, string _id, string _pw)
{
    userName = _name;
    usercash = _cash;
    bankBalance = _bankBalance;

    userID = _id;
    userPW = _pw;
}</code></pre>
</blockquote>
<ul>
<li><strong>반복적으로 안내/경고 창을 띄워줘야 하는 상황이 발생해서 <code>ErrorPopup</code> 메서드를 다음과 같이 수정함:</strong></li>
</ul>
<blockquote>
<pre><code class="language-cs">public void ErrorPopup(string message)
{
   errorText.text = message;
   popupError.SetActive(true);   
}</code></pre>
</blockquote>
</li>
<li><h4 id="회원가입-기능">회원가입 기능</h4>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/604f3089-6f71-4dc9-84f6-a2719a1ee220/image.png" alt=""></p>
<ul>
<li><strong>있어야 하는 기능:</strong><ul>
<li>ID, Name , PW , PW Confirm 으로 구성</li>
<li>하나라도 비어있으면 “정보를 확인해주세요” 라는 안내 메세지가 뜸</li>
<li>PW 와 PW Confirm 가 다를 경우에도 안내 메세지가 떠야함</li>
<li>ID , Name , PS 에 공백이 있다면 다시 입력</li>
<li>기존에 사용하는 ID 라면 “다른 ID를 입력해주세요”</li>
<li>PW 와 PW Confirm 는 값을 입력하면 ***로 보이도록 해야함
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/f2772d0e-a9f2-45dc-9cb0-a1958f726cd8/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>코드(Login.cs):</strong></p>
<blockquote>
<pre><code class="language-cs">using UnityEngine;
using UnityEngine.UI;

#nullable enable

public class Login : MonoBehaviour
{
   public InputField loginField_ID;
   public InputField loginField_PW;

   public void Start()
   {
       this.gameObject.SetActive(true);
   }

   public void OnClickLoginBtn()
   {
       UserData? user = DataManager.Load(loginField_ID.text);

       if (user == null)
       {
           GameManager.Instance.popupBank.ErrorPopup(&quot;존재하지 않는 회원입니다.&quot;);
           return;
       }
       else if(user.userPW != loginField_PW.text)
       {
           GameManager.Instance.popupBank.ErrorPopup(&quot;올바르지 않은 비밀번호입니다.&quot;);
           return;
       }

       GameManager.Instance.StartBank(user);
       this.gameObject.SetActive(false);
   }
}</code></pre>
</blockquote>
</li>
</ul>
<ul>
<li><h4 id="로그인-기능">로그인 기능</h4>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/6823da18-a3ee-4682-8031-0fb7695cf42a/image.png" alt=""></p>
<ul>
<li><p><strong>있어야 하는 기능:</strong></p>
<ul>
<li>PS 입력시 ***로 표기</li>
<li>입력한 ID 가 데이터에 없을 시 “존재하지 않는 회원입니다.”</li>
<li>ID 는 맞았지만 PW 가 틀렸을 시 “올바르지 않은 PW입니다.”</li>
<li>로그인 성공시 로그인한 회원정보를 통해 ATM 기능을 이용할 수 있도록 함</li>
</ul>
</li>
<li><p><strong>코드(SignUp.cs):</strong></p>
<blockquote>
<pre><code class="language-cs">using UnityEngine;
using UnityEngine.UI;

public class SignUp : MonoBehaviour
{
   public InputField idField;
   public InputField nameField;
   public InputField pwField;
   public InputField confirmField;

   public Text guideText;

   private InputField[] signUpFields;

   public void OnClickSignUpBtn()
   {
       if(InputCheck())
       {
           //기본 현금 10만원 / 예금 5만원
           UserData newUser = new UserData(nameField.text,
                                            100000,
                                            50000,
                                            idField.text,
                                            pwField.text);
           DataManager.Save(newUser);

           GameManager.Instance.popupBank.ErrorPopup(&quot;가입이 완료되었습니다.&quot;);
           this.gameObject.SetActive(false);
       }
       else
       {
           //에러창을 띄운다.
           GameManager.Instance.popupBank.ErrorPopup(&quot;정보를 확인해주세요.&quot;);
       }
   }

   public bool InputCheck()
   {
       signUpFields = new InputField[4] { idField, nameField, pwField, confirmField };
       for (int i = 0; i &lt; signUpFields.Length; i++)
       {
           //만약 아무것도 입력하지 않았거나, 입력값에 공백이 포함되어 있다면
           if (signUpFields[i].text.Length &lt;= 0 || signUpFields[i].text.Contains(&quot; &quot;))
           {
               Debug.Log($&quot;{signUpFields[i].name}을 확인해주세요.&quot;);
               guideText.text = $&quot;{signUpFields[i].name}을 확인해주세요.&quot;;
               return false;
           }
       }

       if (pwField.text != confirmField.text)
       {
           guideText.text = &quot;PS가 일치하지 않습니다.&quot;;
           return false;
       }

       if(DataManager.CheckData(idField.text))
       {
           guideText.text = &quot;이미 존재하는 ID입니다.&quot;;
           return false;
       }
       return true;      
   }
}</code></pre>
</blockquote>
</li>
</ul>
</li>
<li><h4 id="송금-기능">송금 기능</h4>
<p><img src="https://velog.velcdn.com/images/gyul_is_yummy/post/b1c83221-414b-418b-a56d-924eb84bf159/image.png" alt=""></p>
<ul>
<li><strong>있어야 하는 기능:</strong><ul>
<li>송금 대상이 존재하지 않는 ID라면 <strong>“대상이 없습니다.”</strong></li>
<li>송금하려는 금액이 내 계좌 잔액보다 크면 <strong>“잔액이 부족합니다”</strong></li>
<li>송금 대상/금액을 입력하지 않았거나 공백이 포함되어 있을 경우 <strong>“입력 정보를 확인해주세요.”</strong></li>
</ul>
</li>
<li><strong>코드(PopupBank.cs):</strong></li>
</ul>
<blockquote>
<pre><code class="language-cs">public void OnClickSendBtn()
{
   //입금할 상대의 정보를 불러옴
   UserData? user = DataManager.Load(opponentID.text);
   int money = int.Parse(sendMoney.text); 

   if (user == null)
   {
       ErrorPopup(&quot;대상이 없습니다.&quot;);
       return;
   }
   if(GameManager.Instance.Balance &lt; money)
   {
       ErrorPopup(&quot;잔액이 부족합니다.&quot;);
       return;
   }

   //Load한 상대 계좌에 입금
   user.bankBalance += money;
   //내 계좌에서 입금하는 만큼의 돈을 차감
   GameManager.Instance.Balance -= money;

   //Load하고 수정한 유저 정보를 저장
   DataManager.Save(user);

   Debug.Log($&quot;{user.userID}에게 {money}원 송금완료&quot;);
   ErrorPopup(&quot;송금완료&quot;);
}</code></pre>
</blockquote>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><p><strong>Button에 등록할 메서드의 매개변수 문제</strong></p>
<ul>
<li>송금 기능을 구현하려던 중 다음과 같이 코드를 짜고 등록해 주려니 메서드 목록에 안 뜨는 것을 발견.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/f80c96c9-e7a4-4696-8c5c-1ef661c4bddf/image.png" alt=""></li>
</ul>
<blockquote>
<pre><code class="language-cs">public void OnClickSendBtn(InputField opponentID, InputField sendMoney)
{
   UserData? user = DataManager.Load(opponentID.text);
   int money = int.Parse(sendMoney.text);

   if (user == null)
   {
       ErrorPopup(&quot;대상이 없습니다.&quot;);
       return;
   }
   if (GameManager.Instance.Balance &lt; money)
   {
       ErrorPopup(&quot;잔액이 부족합니다.&quot;);
       return;
   }

   user.bankBalance += int.Parse(sendMoney.text);
   GameManager.Instance.Balance -= money;

   DataManager.Save(user);

   Debug.Log($&quot;{user.userID}에게 {money}원 송금완료&quot;);
   ErrorPopup(&quot;송금완료&quot;);
}</code></pre>
</blockquote>
<ul>
<li><p>InputField를 매개변수로 받아서 안 뜨는 것이라기엔 이미 InputField를 매개변수로 받는 메서드가 존재했음.</p>
</li>
<li><p>지금까지의 메서드와 다른 점이라고는 매개변수가 두 개라는 것 뿐이라 해당 코드에서 매개변수 하나만 빼 보았더니 이번엔 정상적으로 뜨는 것이 확인됨</p>
</li>
<li><p><strong>그래서 두 개를 넣을 수 있는 방법을 찾기 위해 좀 더 실험해봄:</strong></p>
<ul>
<li>배열 안됨</li>
<li>리스트 안됨</li>
<li>파람스 안됨</li>
<li>튜플 안됨</li>
<li>스트럭트로 묶어서 받기 안 됨</li>
</ul>
</li>
<li><p>구글링을 통해 좀 더 찾아보니 원래 버튼에 붙이는 메서드는 매개변수를 한 개만 쓸 수 있다고 함...</p>
</li>
<li><p>*<em>안되는 이유: *</em></p>
<ul>
<li><code>Button.onClick</code>은 <code>UnityEvent</code>, 내부적으로는 매개변수 없는 <code>UnityEvent.Invoke()</code>를 호출함.</li>
<li>인스펙터에서는 1개의 인자까지만 바인딩할 수 있도록 설계됨.</li>
<li>따라서 <code>void SomeMethod(int, string)</code>처럼 2개 이상은 지원하지 않음.</li>
</ul>
</li>
<li><p><strong>매개변수 두 개 이상 사용하려면:</strong></p>
<ul>
<li>아래와 같이 <code>AddListener</code>로 코드에서 붙여준다면 매개변수를 여러 개 받아올 수 있음.</li>
</ul>
</li>
</ul>
<blockquote>
<pre><code class="language-cs">public Button myButton;

void Start()
{
   // 람다를 통해 다중 인자 전달
   myButton.onClick.AddListener(() =&gt; RealMethod(123, &quot;Hello&quot;)); 
}

private void RealMethod(int number, string message)
{
   Debug.Log($&quot;{number}, {message}&quot;);
}</code></pre>
</blockquote>
<ul>
<li><p><strong>해결방식:</strong> InputField <code>opponentID</code>와 <code>sendMoney</code>를 스크립트에서 public으로 받아와 매개변수 없이 구현해주었다.</p>
</li>
<li><p><strong>수정한 코드:</strong></p>
</li>
</ul>
<blockquote>
<pre><code class="language-cs">   public void OnClickSendBtn()
   {
       UserData? user = DataManager.Load(opponentID.text);
       int money = int.Parse(sendMoney.text); 

       if (user == null)
       {
           ErrorPopup(&quot;대상이 없습니다.&quot;);
           return;
       }
       if(GameManager.Instance.Balance &lt; money)
       {
           ErrorPopup(&quot;잔액이 부족합니다.&quot;);
           return;
       }

       user.bankBalance += int.Parse(sendMoney.text);
       GameManager.Instance.Balance -= money;

       DataManager.Save(user);

       Debug.Log($&quot;{user.userID}에게 {money}원 송금완료&quot;);
       ErrorPopup(&quot;송금완료&quot;);
   }</code></pre>
</blockquote>
</li>
<li><h4 id="유니티에서의-nullable-사용">유니티에서의 <code>nullable</code> 사용</h4>
<ul>
<li><strong><code>nullable</code>을 사용하려니 다음과 같은 오류가 뜸:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/fec532a9-2826-4484-8cf4-170be755ecea/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li><strong>해결:</strong> 코드 상단에 <code>#nullable enable</code> 를 붙여주니 오류가 뜨지 않았다.</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>드디어 ATM을 완성했지만 <code>GameManager.cs</code>의 역할이 모호하고 <code>PopupBank.cs</code>에 많은 기능이 몰려있어 구조 리펙토링이 필요해 보인다...</li>
<li>그래도 이번 과제를 통홰 <strong><code>json</code>을 이용한 직렬화</strong>를 공부하고 적용해볼 수 있어서 좋았다!</li>
<li>남은 시간 동안에는 밀린 특강도 듣고, 이번 ATM 코드도 리팩토링 하고, 저장한 유저 정보를 암호화 하는 기술도 개인적으로 공부해서 적용해보고 싶음.</li>
<li>암호화는 최종 프로젝트 때도 필요할 테니 이번 프로젝트에 적용하지 못하더라도 꼭 공부해볼 것!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.21 (금)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025-03-21-%EA%B8%88</link>
            <guid>https://velog.io/@gyul_is_yummy/2025-03-21-%EA%B8%88</guid>
            <pubDate>Fri, 21 Mar 2025 13:19:24 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="atm-만들기-2">ATM 만들기 (2)</h4>
<ul>
<li>입/출금 기능 구현</li>
<li>json을 이용한 이용자 정보 저장</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="입출금-기능-구현">입/출금 기능 구현</h4>
<ul>
<li>이미 프로퍼티를 통해 유저의 현금/계좌금액이 변동되면 자동으로 UI가 갱신되도록 만들어 둔 상태였기 때문에 입/출금 UI와 해당 버튼에 연결해줄 스크립트만 작성하는 되는 상황이었음.</li>
<li><strong>버튼에 붙여준 스크립트는 다음과 같다:</strong></li>
</ul>
<blockquote>
<pre><code class="language-cs">   public void Withdraw(int money)
   {
       if(GameManager.Instance.Balance &lt; money)
       {
           ErrorPopup();
           return;
       }

       GameManager.Instance.Balance -= money;
       GameManager.Instance.Cash += money;
   }

   public void Deposit(int money)
   {
       if (GameManager.Instance.Cash &lt; money)
       {
           ErrorPopup();
           return;
       }

       GameManager.Instance.Balance += money;
       GameManager.Instance.Cash -= money;
   }

   public void CustomWithdraw(InputField inputText)
   {
       if(int.TryParse(inputText.text, out int money))
       {
           Withdraw(money);
           inputText.text = &quot;&quot;;
       }
   }

   public void CustomDeposit(InputField inputText)
   {
       if (int.TryParse(inputText.text, out int money))
       {
           Deposit(money);
           inputText.text = &quot;&quot;;
       }
   }</code></pre>
</blockquote>
</li>
<li><h4 id="json을-이용한-이용자-정보-저장">json을 이용한 이용자 정보 저장</h4>
<ul>
<li>직렬화 특강에서 배웠던 방식을 이용했다. (아직 제대로 정리하지 못해서 TIL로 올리진 못했지만 추후 올리게 되면 링크를 달아두겠음.)</li>
<li><strong>내가 작성한 코드:</strong></li>
</ul>
<blockquote>
<pre><code class="language-cs">using UnityEngine;
using System.IO; // 파일 입출력을 위한 네임스페이스

public static class DataManager
{
   public static void Save(UserData data)
   {
       // 저장 폴더가 존재하지 않으면 생성
       if (!Directory.Exists(Application.persistentDataPath + $&quot;/UserData&quot;))
       {
           Directory.CreateDirectory(Application.persistentDataPath + $&quot;/UserData&quot;);
       }

       // saveDataf JSON 문자열로 변환
       string saveData = JsonUtility.ToJson(data, true); // true: 보기 좋은 포맷(줄 바꿈 포함)

       // JSON 문자열을 파일로 저장
       File.WriteAllText(Application.persistentDataPath + $&quot;/UserData/{data.userName}.txt&quot;, saveData);

       Debug.Log(&quot;데이터 저장됨&quot;);
       Debug.Log(Application.persistentDataPath);
   }

   public static UserData Load(string userName)
   {
       //사용자 데이터가 없으면 기본 데이터를 들려 보내줌
       if (!File.Exists(Application.persistentDataPath + $&quot;/UserData/{userName}.txt&quot;))
       {
           Debug.Log($&quot;사용자 {userName}의 데이터가 없습니다! 기본 데이터를 생성합니다.&quot;);

           return new UserData(&quot;윤혜진&quot;, 100000, 50000);
       }

       string loadData = File.ReadAllText(Application.persistentDataPath + $&quot;/UserData/{userName}.txt&quot;);

       Debug.Log($&quot;사용자 {userName} 데이터 로드됨&quot;);
       return JsonUtility.FromJson&lt;UserData&gt;(loadData);
   }
}</code></pre>
</blockquote>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><h4 id="입출금-버튼-스크립트">입/출금 버튼 스크립트</h4>
<ul>
<li>처음에는 아래 코드처럼 1,3,5만원 입/출금 버튼에 붙일 메서드들을 일일이 하나씩 만들어주었음.</li>
<li><strong>아래는 수정 전 코드:</strong></li>
</ul>
<blockquote>
<pre><code class="language-cs">public void OnClickDeposit_10000()
{
   Deposit(10000);
}

public void OnClickDeposit_30000()
{
   Deposit(30000);
}

public void OnClickDeposit_50000()
{
   Deposit(50000);
}

public void OnClickWithdraw_10000()
{
   Withdraw(10000);
}

public void OnClickWithdraw_30000()
{
   Withdraw(30000);
}

public void OnClickWithdraw_50000()
{
   Withdraw(50000);
}

public void Withdraw(int money)
{
   if(GameManager.Instance.Balance &lt; money)
   {
       ErrorPopup();
       return;
   }

   GameManager.Instance.Balance -= money;
   GameManager.Instance.Cash += money;
}

public void Deposit(int money)
{
   if (GameManager.Instance.Cash &lt; money)
   {
       ErrorPopup();
       return;
   }

   GameManager.Instance.Balance += money;
   GameManager.Instance.Cash -= money;
}</code></pre>
</blockquote>
<ul>
<li><p>하지만 입출금 버튼을 위해 총 8개의 메서드를 만들어주어야 한다는 것이 굉장히 비효율 적으로 느껴졌기 때문에 다른 방법을 강구해보기로 함</p>
</li>
<li><p>이 문제로 주변에 도움을 구했을 때는 버튼 컴포넌트를 받아온 뒤 <code>AddListener</code>를 통해 연결하라는 조언을 받았는데, 두 가지가 마음에 걸려 후순위로 미뤄두었다.</p>
<ol>
<li>스크립트 내에서 버튼의 이벤트를 연결하면 해당 버튼에 어떤 메서드가 연결되어있는지 인스펙터 창에서 직관적으로 확인할 수 없게되므로 디버깅이 어려워질 수도 있다던 튜터님의 말씀이 생각나서 (사실 이 부분은 취향차이일 수 있지만 나 또한 공감하는 바가 있었으므로 최대한 지키고 싶었다.)</li>
<li>메서드 수는 줄일 수 있겠지만 결국 스크립트에 버튼 6개를 연결해주어야 해서 (6개나 손수 할당해주기 싫었다)</li>
</ol>
</li>
<li><p>다른 방법을 고민하던 중, 문득 버튼에 연결하는 메서드도 매개변수를 받아올 수 있을까? 하는 의문을 가지게 됨</p>
</li>
<li><p>실험 결과 매개변수가 있는 메서드를 넣으면 다음과 같이 인스펙터 창에 값을 입력할 수 있는 란이 생기는 것을 확인할 수 있었다:
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/f4a0a124-4c27-466b-a552-62a5dea434eb/image.png" alt=""></p>
</li>
<li><p>생각보다 간단한 방법으로 해결하게 되었지만, 나처럼 몰랐던 사람도 분명히 있었을 것 같아서 기록해둔다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>최근들어 과제를 빨리 완성하고 싶다는 조바심 때문인지, TIL을 작성하는 중에도 내가 온전히 집중하지 못하고 있다는 기분이 든다.</li>
<li>그러다보니 시간은 더 오래 걸리고, 오래 걸리는 만큼 TIL을 쓰는 것이 거추장스럽게 느껴지고...</li>
<li>하지만 TIL은 잘 잊는 나를 위해 그날 무엇을 했는지 정리하고, 후에 되새길 수 있도록 기록하는 행위임을 잊지 말자!</li>
<li>하나의 프로젝트를 진행하고 완성하는 것도 중요하지만, 그 경험을 기억하지 못하고 흘려보낸다면 의미가 없으니 TIL에 소홀해지지 말 것!</li>
<li>느린건 익숙해지는 만큼 빨라질 수 있고, 오늘의 기록은 내 미래의 헛발질을 줄여줄테니까😊 아자아자!!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.20 (목)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.20-%EB%AA%A9</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.20-%EB%AA%A9</guid>
            <pubDate>Thu, 20 Mar 2025 13:39:29 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="atm-만들기-1">ATM 만들기 (1)</h4>
<ul>
<li>UI 배치</li>
<li>데이터와 UI 연동</li>
<li>버튼을 통한 입/출금 UI창 변경</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="ui-배치">UI 배치</h4>
<ul>
<li><strong>내가 구현한 방식:</strong><ul>
<li>나름 앵커를 설정해서 잘 구현했다고 생각했지만, Free Aspect로 설정하고 화면을 위 아래로 늘리니 보이지 않는 UI가 생겼음.</li>
<li>튜터님의 피드백을 통해 아래처럼 늘려도 UI가 잘 보이도록 앵커를 변경해주었다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/f8a9f86b-9541-446c-9ce2-6cfbb12295d6/image.png" alt="">
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/bb473d13-df59-4a72-a939-fa2384c5d1cb/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><strong>받았던 피드백:</strong><ul>
<li>앵커를 신경써주어야 함</li>
<li>신경쓰지 않으면 어떤 환경에서는 UI가 제대로 출력되지 않을 수 있음</li>
<li>요즘은 디스플레이 크기가 다양하기 때문에 1:1~1:2.5 비율까지 신경써주면 좋음</li>
<li>일단 1920:1080 크기로 작업하고, 테스트할 때는 Free Aspect로 설정하고 늘리고 줄이며 테스트 하는 습관을 들일 것</li>
<li>BackGround 같은 경우에는 아래처럼 앵커를 설정해줄 것!!
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/757518ed-8621-4347-9aab-d29771e14f42/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li><h4 id="데이터와-ui-연동">데이터와 UI 연동</h4>
<ul>
<li><strong>내가 구현한 방식:</strong><ul>
<li>Canvas에 UI를 관리해주는 UIManager 스크립트를 붙여 GameManager에서 값을 변경할 때마다 UIManager에 있는 프로퍼티를 통해 UI의 값 또한 변경해주는 코드를 짬.</li>
<li>자세한 코드는 아래 겪은 어려움 란에서!!</li>
</ul>
</li>
<li><strong>받았던 피드백:</strong><ul>
<li>잘 구현해주었으나 조금 돌아가는 느낌이 듦.</li>
<li>클래스를 두 개 나눌 필요 없이 GameManager에서 바로 Text 컴포넌트를 받아와서 수정해주어도 충분!</li>
</ul>
</li>
</ul>
</li>
<li><h4 id="버튼을-통한-입출금-ui창-변경">버튼을 통한 입/출금 UI창 변경</h4>
<ul>
<li><p><strong>내가 구현한 방식:</strong></p>
<ul>
<li>OnValidate()에서 Find() 메서드를 통해 UI창들을 찾아서 스크립트로 연결해줌.</li>
</ul>
<blockquote>
<pre><code class="language-cs">private void OnValidate()
{
    UI_Deposit = transform.Find(&quot;Deposit&quot;).gameObject;
    UI_Withdraw = transform.Find(&quot;Withdraw&quot;).gameObject;
    ATM = transform.Find(&quot;ATM&quot;).gameObject;
}</code></pre>
</blockquote>
</li>
<li><p><strong>받았던 피드백:</strong></p>
<ul>
<li>Find()로 찾아 연결하는 방식은 나중에 이름이 바뀌었을 때 오류가 날 수도 있음</li>
<li>이 정도는 Inspector창에서 직접 연결해주는 편이 낫다.</li>
<li>만약 연결하려는 객체가 기능이 많아서 각자의 클래스를 들고 있는 상태라면, 자식 객체가 생성되는 순간 자식의 Awake에서 부모에게 걔가 나다! 하고 연결해주는 방식을 사용하는 편이 좋다. =&gt; 기억했다가 나중에 써먹어볼것!</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><h4 id="프로퍼티를-이용한-데이터-연동에서-일어난-오류">프로퍼티를 이용한 데이터 연동에서 일어난 오류</h4>
<ul>
<li>프로퍼티는 데이터가 변경되는 것을 감지할 수 있기 때문에 데이터와 UI를 연결하는 부분에서도 써먹을 수 있을 거라 생각했음.</li>
<li><strong>나의 계획:</strong> GameManager에서 UsetData를 변경하면, set에서 UI의 텍스트를 변경시켜주는 함수를 실행시킨다.</li>
<li><strong>결과:</strong> 그런데 데이터를 변경시켜주어도 UI의 텍스트는 변경되지 않았다.</li>
<li><strong>추정:</strong> 프로퍼티가 get과 set을 감지하려면 프로퍼티 본인을 변경하거나 가져와야 하는데, UsetData나 Text의 프로퍼티를 만들어봤자 본인이 아닌 그 안의 string 변수를 건들이기 때문에 제대로 감지하지 못하는 것 같았다. </li>
<li><strong>그렇게 생각한 이유:</strong></li>
</ul>
<blockquote>
<pre><code class="language-cs">public Text balanceText;
public Text BalanceText
{
    get { return balanceText; }
    set
    {
        BalanceText = balanceText;
        Test();
    }
}

private void Start()
{
    BalanceText.text = &quot;10000&quot;;
}

public void Test()
{
    Debug.Log(&quot;Text.text를 변경해도 프로퍼티가 제대로 감지함!&quot;);
}</code></pre>
</blockquote>
<ul>
<li>프로퍼티가 BalanceText.text를 변경할 때도 변화를 감지한다면 Test 메서드가 실행되면서 설정된 log가 뜰 것임. </li>
<li><strong>결과:</strong> 보다시피 Balance의 text값은 바뀌었지만 Text() 메서드는 실행되지 않았다...
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/2b36cab2-7d57-42af-9415-f1bf3f395f26/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>수정한 방식:</strong></p>
<blockquote>
<pre><code class="language-cs">public Text balanceText;
private int balance;
public int Balance
{
    get { return balance; }
    set
    {
        balance = value;
        balanceText.text = Formating(balance);
        Test();
    }
}

private void Start()
{
    balance = 10000;
}
private string Formating(int num)
{
    return string.Format(&quot;{0:N0}&quot;, num); 
}

public void Test()
{
    Debug.Log(&quot;프로퍼티가 제대로 작동함&quot;);
}</code></pre>
</blockquote>
</li>
<li><p>UserData에서 각 필드의 프로퍼티를 만들거나, UIManager에서 Text 프로퍼티를 만드는 대신, int형 프로퍼티를 새로 만들고 이 int형 Balance 프로퍼티가 수정될 때마다 balanceText.text의 값도 수정되도록 만들었다. (해당 코드에서 사용된 Formating 메서드는 천 단위마다 ,를 찍어주는 메서드이다.)</p>
</li>
<li><p><strong>결과:</strong> 잘 변경된다!!!!!!
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/2af76655-6ef1-4c97-90e8-af5e920fc2f0/image.png" alt=""></p>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>UI 수업 때 배웠던 디자인 패턴도 잘 써보고 싶었는데 생각만큼 잘 되지 않았다... (옵저버 패턴 같은 거)</li>
<li>그래도 구현하고 비교해보니 내가 프로퍼티로 구현한 것도 MVC라는 패턴에 속하는 것 같았음.</li>
<li>시간이 남는대로 디자인 패턴도 공부하고 정리해보고 싶다!</li>
<li>적용할만한 디자인 패턴을 발견하면 과거 작업물들을 리팩토링을 하며 적용해보는 것도 괜찮지 않을까? 일단 염두에 두기!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.19 (수)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.19-%EC%88%98</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.19-%EC%88%98</guid>
            <pubDate>Wed, 19 Mar 2025 13:03:38 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><strong>팀 프로젝트 마감 및 발표</strong><ul>
<li>팀 프로젝트 소개</li>
<li>GitHub Link</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="팀-프로젝트-마감-및-발표">팀 프로젝트 마감 및 발표</h4>
<ul>
<li>GitHub Link 👉 <a href="https://github.com/STTBI/STB_Potal">https://github.com/STTBI/STB_Potal</a>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/5b96399c-d332-45e9-b4ae-56fa3d35c11c/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>사실 우리 팀이 포탈 모작을 만들긴 했지만 포탈 부분을 내가 구현한 것은 아니므로 포탈을 구현하는 법에 대해서 완벽하게 이해하거나 통달하지는 못했다...</li>
<li>그 점이 아쉽기는 하지만, 포탈의 원리를 함께 찾아보고, 고민하고, 구현하는 모습을 지켜보는 과정에서 얻는 점은 확실히 있었던 것 같다. (진짜 아이디어만 있다면 뭐든 구현할 수 있구나 싶더라.)</li>
<li>이번에는 인터페이스를 이용한 오브젝트간의 상호작용을 중점으로 코딩하게 되었고, 이 역할을 맡으면서 배우는 것도 많았지만 내 실력이 는다면 포탈 기능을 구현하는 것도 해보고 싶다!</li>
<li>이건 우리 팀이 포탈을 어떤 식으로 구현해야 할 지 고민했을 때 많은 도움을 받은 영상!!</li>
<li>기록을 위해 링크를 남겨둔다 👉 <a href="https://youtu.be/IZ4mjwZBWtk?si=ujDbUK-Yd95w6WQg">https://youtu.be/IZ4mjwZBWtk?si=ujDbUK-Yd95w6WQg</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.14 (금)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.14-%EA%B8%88</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.14-%EA%B8%88</guid>
            <pubDate>Fri, 14 Mar 2025 11:54:53 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><strong>Unity 숙련 주차 - 팀 프로젝트</strong><ul>
<li><strong>Title UI 작업</strong><ul>
<li>버튼 애니메이션</li>
<li>스크롤뷰</li>
</ul>
</li>
<li><strong>트러블슈팅</strong></li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li><h4 id="버튼-애니메이션">버튼 애니메이션</h4>
<ul>
<li>포탈 느낌을 내고싶었기 때문에 UI 디자인은 다음 사진을 참고했다.
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/d3369044-5987-43b6-9496-dc28282a3c4e/image.png" alt=""></li>
</ul>
<h4 id="1-버튼-컴포넌트-추가">1. 버튼 컴포넌트 추가</h4>
<h4 id="2-버튼의-자식으로-있는-텍스트에-각-메뉴-이름을-써준다">2. 버튼의 자식으로 있는 텍스트에 각 메뉴 이름을 써준다.</h4>
<h4 id="3-버튼의-transition을-animation으로-변경해준다-👉-관련-til-링크">3. 버튼의 <strong><code>transition</code></strong>을 <strong><code>Animation</code></strong>으로 변경해준다. 👉 <a href="https://velog.io/@gyul_is_yummy/2025.01.22-%EC%88%98">관련 TIL 링크</a></h4>
<h4 id="4-auto-generate-animation-버튼을-클릭">4. <strong><code>Auto Generate Animation</code></strong> 버튼을 클릭</h4>
<p>  <img src="https://velog.velcdn.com/images/gyul_is_yummy/post/e153ed8a-6b21-4f9e-a32b-8fc2abb20a39/image.png" alt=""></p>
<h4 id="5-클릭하면-애니메이터와-애니메이션이-자동으로-생성된다">5. 클릭하면 애니메이터와 애니메이션이 자동으로 생성된다.</h4>
<p>  <img src="https://velog.velcdn.com/images/gyul_is_yummy/post/ddcc2a6a-1f3d-438c-bd96-e4780f8983d7/image.png" alt=""> <img src="https://velog.velcdn.com/images/gyul_is_yummy/post/3e057b8f-b1d1-4217-b9be-9b3bba390636/image.png" alt=""></p>
<p>  <strong>6. 마우스를 올렸을 때 효과를 주고 싶은 것이므로 **<code>Hightghted</code></strong> 애니메이션을 다음과 같이 설정해주었다.** (Add Property를 하나하나 설정해주면 귀찮았겠지만 빨간색 녹화 버튼을 누르면 수정한 프로퍼티가 자동으로 추가되므로 이걸 잘 이용하면 편하다)<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/dd5c1ad1-e92d-4f2d-acd2-bcd606dca00f/image.png" alt=""></p>
</li>
</ul>
<pre><code>**7. 마우스가 떨어질 때도 정확히 반대의 애니메이션을 넣어주고 싶었는데, 자동으로 만들어지는 상태로는 잘 구현되지 않아 `Exit`라는 애니메이션을 만들어 다음과 같이 추가로 만들어주었다.** (상태가 nomal이 되면 해당 화살표를 타고 Exit 애니메이션을 실행한다.) ![](https://velog.velcdn.com/images/gyul_is_yummy/post/d87293b1-2198-49c0-b6ef-fdca0f542132/image.png)
**8. 나온 결과물 :**
![](https://velog.velcdn.com/images/gyul_is_yummy/post/7ebc1843-77db-4d0f-8550-556a0cab81d6/image.gif)</code></pre><ul>
<li><h4 id="스크롤뷰">스크롤뷰</h4>
<ul>
<li><p>버튼을 누르면 나타날 UI를 배치하는데, 수없이 만들어질 세이브 파일들을 저 화면에 다 욱여넣는 건 무리라고 생각했음.</p>
</li>
<li><p>그래서 유니티에서 스크롤 UI를 만드는 방법을 찾아서 적용했다.</p>
<ul>
<li><strong>참고 블로그</strong> 👉 <a href="https://m.blog.naver.com/eastfever5/222095602409">Unity UGUI 스크롤뷰(ScrollView) 사용법 간단 정리</a> <img src="https://velog.velcdn.com/images/gyul_is_yummy/post/21becdff-36a3-4d66-be7c-8280638b3cb6/image.png" alt=""></li>
</ul>
</li>
<li><p><strong><code>Movement Type (이동 유형)</code></strong></p>
<ul>
<li><strong>Unrestricted</strong> : 콘텐츠가 제한 없이 이동 (끝없는 스크롤 가능)</li>
<li><strong>Elastic</strong> : 콘텐츠가 끝에 닿으면 튕기는 효과</li>
<li><strong>Clamped</strong> : 콘텐츠가 끝에 닿으면 더 이상 이동하지 않음</li>
</ul>
</li>
<li><p><strong><code>Visibility (스크롤바 표시 옵션)</code></strong></p>
<ul>
<li><strong>Permanent</strong> : 항상 표시</li>
<li><strong>Auto Hide</strong> : 사용하지 않을 때 숨김</li>
<li><strong>Auto Hide And Expand Viewport</strong> : 사용하지 않을 때 숨기고 Viewport 크기 확장</li>
</ul>
</li>
<li><p><strong>위와 같이 설정을 하고 나온 결과물은 다음과 같다:</strong>
<img src="https://velog.velcdn.com/images/gyul_is_yummy/post/15348ff3-3287-4063-9077-b78a7c14225b/image.gif" alt=""></p>
<ul>
<li>세로 스크롤만 필요했기 때문에 <strong><code>Vertical</code></strong> 만 체크하고</li>
<li>목록의 끝에서 휠을 굴리면 탄성 효과가 나오길 원했기 때문에 <strong><code>Elastic</code></strong> 으로 설정했다.</li>
<li>목록이 동적으로 늘어나기를 원했기 때문에 <strong><code>Auto Hide And Expand Viewport</code></strong> 으로 설정했다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="📍겪은-어려움">📍겪은 어려움</h3>
<ul>
<li><h4 id="메뉴-버튼을-못-찾아오는-현상-발생">메뉴 버튼을 못 찾아오는 현상 발생</h4>
<p>  <img src="https://velog.velcdn.com/images/gyul_is_yummy/post/dccc8d50-18ea-403a-a377-243c9665fcba/image.png" alt=""></p>
<ul>
<li><strong><code>OnValidate</code></strong>를 통해 코드로 <strong><code>Button</code></strong> 컴포넌트를 등록해주었는데, 딱히 잘못된 부분이 없어보임에도 버튼을 찾아오지 못했다. (각 메뉴들 또한 버튼 컴포넌트가 잘 붙어있었다.)</li>
<li><strong><code>시도1</code></strong>:<ul>
<li><strong>Find</strong>로 자식 오브젝트를 찾아올 수 있는 걸로 알고 있지만, 혹시나 잘못 알고 있는 걸지도 모른다는 생각이 들어 손자까지 찾을 수 있는 메서드를 따로 만들어 실행해보았다.</li>
<li><strong>손자까지 찾을 수 있는 메서드 (Utill.cs) :</strong></li>
<li><strong>결과 :</strong> 여전히 찾아오지 못했다.</li>
</ul>
</li>
<li><strong><code>시도2</code></strong>:<ul>
<li>혹시 문자열로 찾아오기 때문에 문자가 생기는건가 싶어 <code>transform.GetChild(index)</code>를 통해 찾아오도록 코드를 변경함</li>
<li><strong>결과 :</strong> 여전히 못 찾아옴...</li>
</ul>
</li>
<li><strong><code>진짜 원인</code></strong>:<ul>
<li>문득 내가 어제 버튼과 문의 상호작용을 구현하며 버튼에 붙여줄 클래스명을 Button으로 했다는 사실이 기억났다.</li>
<li>아니나 다를까 <code>using</code>문에도 <code>UnityEngine.UI</code>가 포함되어있지 않았다.</li>
<li>즉, <code>GetComponent</code>는 우리가 아는 일반적인 버튼 컴포넌트가 아닌 내가 만든 클래스 Button이 붙은 녀석을 찾고 있었기 때문에 메뉴들을 찾지 못하고 인스펙터창에 None을 띄운 것.</li>
<li><strong>해결방법 :</strong> Button 클래스의 이름을 TriggerButton으로 바꿔주었더니 잘 돌아갔다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="📍회고-및-반성">📍회고 및 반성</h3>
<ul>
<li>앞으로 클래스 명을 정할 때에는 기존 컴포넌트와 겹치지 않는지 확인하고 짓는 습관을 들여야 할 것 같다...</li>
<li>팀 프로젝트에 들어가고 바빠지면서 TIL에 소홀해진 것 같다. 완벽하지 않아도 되고, 그냥 메모장처럼 간단히 쓴 내용이어도 좋으니 꾸준히 올리려고 노력할 것! 완성도에 집착하지 말자!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.13 (목)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.13-%EB%AA%A9</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.13-%EB%AA%A9</guid>
            <pubDate>Thu, 13 Mar 2025 14:22:58 GMT</pubDate>
            <description><![CDATA[<h3 id="📍오늘의-학습-키워드">📍오늘의 학습 키워드</h3>
<ul>
<li><h4 id="unity-숙련-주차---팀-프로젝트">Unity 숙련 주차 - 팀 프로젝트</h4>
<ul>
<li>트러블슈팅</li>
</ul>
</li>
</ul>
<h3 id="📍학습-내용">📍학습 내용</h3>
<ul>
<li>우리 팀은 이번 3D 팀 프로젝트에서 포탈을 구현해보기로 했는데, 내가 맡은 부분은 버튼과 문의 상호작용이었다.</li>
<li><strong>내가 구현해야 했던 것:</strong><ul>
<li>박스나 플레이어가 버튼 위로 올라오면 충돌 처리를 해서 내려가야 함</li>
<li>문은 버튼이 눌리고 떨어지는 것을 실시간으로 감지하고 열려야 함</li>
<li>때문에 <code>Button</code>과 <code>Door</code>클래스를 만들어 <code>Button</code>에서 눌림을 감지하는 <code>bool</code> 변수가 <code>true</code>가 되면 <code>Door</code> 스크립트에서 문을 열어주고, <code>flase</code>가 되면 문을 닫아주는 형식으로 해당 기능을 구현함.</li>
</ul>
</li>
<li><strong>생긴 문제:</strong><ul>
<li>나는 <strong>버튼 하나와 문 하나를 연결해주는 경우</strong>만 생각하고 코드를 짰는데, 원작 게임인 포탈을 다시 살펴보니 <strong>버튼이 하나가 아닌 경우</strong>도 있었고 <strong>문을 여는 오브젝트가 버튼이 아니라 다른 오브젝트인 경우</strong>도 있었다.</li>
</ul>
</li>
<li><strong>해결 방식:</strong><ul>
<li>Door와 연결된 오브젝트들을 모두 모아 관리해주고, Door에게 신호를 보내주는 <code>DoorManager</code> 라는 클래스를 만들어 주었다.</li>
<li>또한 버튼이 아니라 다른 오브젝트들이 사용될 경우를 상정해 <code>ITriggerObject</code> 라는 인터페이스를 만들어주었다.</li>
<li>DoorManager에서는 <code>triggerObject = GetComponentsInChildren&lt;ITriggerObject&gt;();</code> 코드를 통해 ITriggerObject를 상속받은 자식들을 전부 찾아와 저장하므로, 버튼이 아닌 다른 오브젝트로 문을 열고 싶다면 ITriggerObject 인터페이스를 상속시키기만 한다면 버튼처럼 쓸 수 있다.</li>
</ul>
</li>
<li><strong>실제 작성 코드:</strong></li>
</ul>
<blockquote>
<pre><code class="language-cs">using UnityEngine;

public interface ITriggerObject
{
   bool TriggerCheck { get; set; }
   void Enter();
   void Exit();
}

public class DoorManager : MonoBehaviour
{
   public ITriggerObject[] triggerObject;
   public Door door;

   private void OnValidate()
   {
       triggerObject = GetComponentsInChildren&lt;ITriggerObject&gt;();
       door = GetComponentInChildren&lt;Door&gt;();
   }

   private void Update()
   {
       Decision();
   }
   public void Decision()
   {
       for (int i = 0; i &lt; triggerObject.Length; i++)
       {
           if (triggerObject[i].TriggerCheck == false)
           {
               door.Close();
               return;
           }
       }
       door.Open();
   }
}</code></pre>
</blockquote>
<ul>
<li><strong>개선해야 할 사항:</strong><ul>
<li>Decision 이 Update 에서 계속 돌고 있으므로, 이 부분을 리팩토링해야 할 필요성이 있음. 나중에 꼭 개선할 것!!</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.10 (월)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.10-%EC%9B%94</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.10-%EC%9B%94</guid>
            <pubDate>Mon, 10 Mar 2025 14:01:10 GMT</pubDate>
            <description><![CDATA[<p>작성중</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025.03.07 (금)]]></title>
            <link>https://velog.io/@gyul_is_yummy/2025.03.07-%EA%B8%88</link>
            <guid>https://velog.io/@gyul_is_yummy/2025.03.07-%EA%B8%88</guid>
            <pubDate>Fri, 07 Mar 2025 12:58:43 GMT</pubDate>
            <description><![CDATA[<p><a href="https://getupnote.com/share/notes/wGmWB63lcWZdrdl7SrsWAuZGgp83/fd58d714-2073-4e10-b936-b247166626b2">TIL 링크</a></p>
<p>진짜 도저히 못 옮기겠음
Velog에는 시간 나면 옮겨보도록 하겠습니다,,,</p>
]]></description>
        </item>
    </channel>
</rss>