<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Jangmine_z.Devlog</title>
        <link>https://velog.io/</link>
        <description>Unity, C#</description>
        <lastBuildDate>Tue, 08 Jul 2025 01:15:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Jangmine_z.Devlog</title>
            <url>https://velog.velcdn.com/images/jangmine_z/profile/a76ab487-1bc3-4d93-9314-f0cce3391716/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Jangmine_z.Devlog. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jangmine_z" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Programmers - 괄호 회전하기(C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-%EA%B4%84%ED%98%B8-%ED%9A%8C%EC%A0%84%ED%95%98%EA%B8%B0C-3m06x86g</link>
            <guid>https://velog.io/@jangmine_z/Programmers-%EA%B4%84%ED%98%B8-%ED%9A%8C%EC%A0%84%ED%95%98%EA%B8%B0C-3m06x86g</guid>
            <pubDate>Tue, 08 Jul 2025 01:15:30 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>다음 규칙을 지키는 문자열을 올바른 괄호 문자열이라고 정의합니다.</p>
<p><code>()</code>, <code>[]</code>, <code>{}</code> 는 모두 올바른 괄호 문자열입니다.
만약 <code>A</code>가 올바른 괄호 문자열이라면, <code>(A)</code>, <code>[A]</code>, <code>{A}</code> 도 올바른 괄호 문자열입니다. 예를 들어, <code>[]</code> 가 올바른 괄호 문자열이므로, <code>([])</code> 도 올바른 괄호 문자열입니다.
만약 <code>A</code>, <code>B</code>가 올바른 괄호 문자열이라면, <code>AB</code> 도 올바른 괄호 문자열입니다. 예를 들어, <code>{}</code> 와 <code>([])</code> 가 올바른 괄호 문자열이므로, <code>{}([])</code> 도 올바른 괄호 문자열입니다.
대괄호, 중괄호, 그리고 소괄호로 이루어진 문자열 <code>s</code>가 매개변수로 주어집니다. 이 <code>s</code>를 왼쪽으로 x (0 ≤ x &lt; (<code>s</code>의 길이)) 칸만큼 회전시켰을 때 <code>s</code>가 올바른 괄호 문자열이 되게 하는 x의 개수를 return 하도록 solution 함수를 완성해주세요.</p>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li>s의 길이는 1 이상 1,000 이하입니다.</li>
</ul>
<hr>
<h2 id="💻-나의-풀이-c">💻 나의 풀이 (C#)</h2>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;

public class Solution {
    public int solution(string s) {
        int answer = 0;

        for(int i = 0; i &lt; s.Length; i++)
        {
            List&lt;char&gt; temp = new List&lt;char&gt;();
            Stack&lt;char&gt; stack = new Stack&lt;char&gt;();
            int idx = i;

            for(int j = 0; j &lt; s.Length; j++)
            {
                temp.Add(s[idx]);
                idx++;

                if(idx &gt;= s.Length)
                    idx = 0;
            }

            foreach(char c in temp)
            {
                if(c == &#39;(&#39; || c == &#39;{&#39; || c == &#39;[&#39;)
                    stack.Push(c);
                else
                {
                    if(stack.Count == 0)
                    {
                        stack.Push(&#39;X&#39;);
                        break;
                    }

                    char peek = stack.Peek();
                    char comparison = &#39; &#39;;

                    switch(peek)
                    {
                        case &#39;(&#39;:
                            comparison = &#39;)&#39;;
                            break;

                        case &#39;{&#39;:
                            comparison = &#39;}&#39;;
                            break;

                        case &#39;[&#39;:
                            comparison = &#39;]&#39;;
                            break;
                    }

                    if(c == comparison)
                    {
                        stack.Pop();
                    }
                }   
            }

            if(stack.Count == 0)
            {
                answer++;
            }
        }

        return answer;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity 버튼 이벤트에서 발생한 람다 캡처 문제와 해결 [TIL 39일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity-%EB%B2%84%ED%8A%BC-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-%EB%9E%8C%EB%8B%A4-%EC%BA%A1%EC%B2%98-%EB%AC%B8%EC%A0%9C%EC%99%80-%ED%95%B4%EA%B2%B0-TIL-39%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity-%EB%B2%84%ED%8A%BC-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%9C-%EB%9E%8C%EB%8B%A4-%EC%BA%A1%EC%B2%98-%EB%AC%B8%EC%A0%9C%EC%99%80-%ED%95%B4%EA%B2%B0-TIL-39%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 23 Jun 2025 13:47:01 GMT</pubDate>
            <description><![CDATA[<h3 id="🔷-문제-발생">🔷 문제 발생</h3>
<p>Unity에서 여러 개의 탭 버튼을 생성하고, 각각 클릭했을 때 해당 인덱스의 탭으로 전환되도록 코드를 작성했다.</p>
<pre><code class="language-csharp">for (int i = 0; i &lt; tabButtons.Length; i++)
{
    tabButtons[i].onClick.AddListener(() =&gt; SwitchTab(i)); // 문제 발생
}</code></pre>
<p><strong>문제</strong>는, 버튼을 클릭했을 때 항상 마지막 인덱스 (<code>tabButtons.Length</code>)가 전달되어
모든 버튼이 같은 탭을 여는 현상이 발생했다.</p>
<hr>
<h3 id="🔷-왜-이런-문제가-발생했을까">🔷 왜 이런 문제가 발생했을까?</h3>
<p>C#의 <strong>람다 캡처 특성</strong> 때문이다.</p>
<ul>
<li>위 코드에서 <code>i</code>는 람다 내부에서 참조(캡처)되고 있음</li>
<li>하지만 <code>i</code>는 <code>for</code> 루프의 <strong>하나의 변수</strong>로 존재하고, 반복문이 끝나면 <code>i</code>는 <code>Length</code> 값으로 고정됨</li>
<li>즉, 버튼 클릭 시점엔 이미 <code>i == tabButtons.Length</code>가 되어버림</li>
<li>결과적으로 모든 버튼이 <strong>같은 <code>SwitchTab(i)</code> 값</strong>을 호출하게 됨</li>
</ul>
<hr>
<h3 id="🔷-해결-방법-지역-변수로-캡처-분리하기">🔷 해결 방법: 지역 변수로 캡처 분리하기</h3>
<p>반복문 안에서 <strong>매 반복마다 새로운 지역 변수</strong>를 만들어서 캡처해야 한다.</p>
<pre><code class="language-csharp">for (int i = 0; i &lt; tabButtons.Length; i++)
{
    int index = i; // i를 지역 변수로 복사
    tabButtons[i].onClick.AddListener(() =&gt; SwitchTab(index)); // 정확한 값 전달
}</code></pre>
<p>이제 각각의 버튼은 자신에게 맞는 index를 기억하고, <code>SwitchTab(index)</code>가 의도한 대로 호출된다.</p>
<hr>
<h3 id="🔷-수정된-전체-코드-예시">🔷 수정된 전체 코드 예시</h3>
<pre><code class="language-csharp">private void Start()
{
    for (int i = 0; i &lt; tabButtons.Length; i++)
    {
        int index = i;
        tabButtons[i].onClick.AddListener(() =&gt; SwitchTab(index));
    }

    SwitchTab(2); // 초기 탭 선택
}</code></pre>
<hr>
<h3 id="🔷-정리">🔷 정리</h3>
<ul>
<li><strong>람다식 안에서 반복문 변수를 직접 참조하지 말자</strong></li>
<li>대신, <strong>지역 변수로 복사해서 캡처</strong>하도록 하자</li>
<li>Unity 이벤트 할당 시에는 특히 자주 마주치는 이슈이므로 반드시 주의할 것!</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers - 주차 요금 계산 (C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-%EC%A3%BC%EC%B0%A8-%EC%9A%94%EA%B8%88-%EA%B3%84%EC%82%B0-C</link>
            <guid>https://velog.io/@jangmine_z/Programmers-%EC%A3%BC%EC%B0%A8-%EC%9A%94%EA%B8%88-%EA%B3%84%EC%82%B0-C</guid>
            <pubDate>Thu, 12 Jun 2025 01:55:16 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>주차장의 <strong>요금표</strong>와 <strong>차량의 입/출차 기록</strong>이 주어졌을 때, <strong>차량별로 주차 요금을 계산</strong>하세요.</p>
<h4 id="요금표">요금표</h4>
<table>
<thead>
<tr>
<th>기본 시간(분)</th>
<th>기본 요금(원)</th>
<th>단위 시간(분)</th>
<th>단위 요금(원)</th>
</tr>
</thead>
<tbody><tr>
<td>180</td>
<td>5000</td>
<td>10</td>
<td>600</td>
</tr>
</tbody></table>
<h4 id="입출차-기록">입/출차 기록</h4>
<table>
<thead>
<tr>
<th>시각</th>
<th>차량 번호</th>
<th>내역</th>
</tr>
</thead>
<tbody><tr>
<td>05:34</td>
<td>5961</td>
<td>입차</td>
</tr>
<tr>
<td>06:00</td>
<td>0000</td>
<td>입차</td>
</tr>
<tr>
<td>06:34</td>
<td>0000</td>
<td>출차</td>
</tr>
<tr>
<td>07:59</td>
<td>5961</td>
<td>출차</td>
</tr>
<tr>
<td>07:59</td>
<td>0148</td>
<td>입차</td>
</tr>
<tr>
<td>18:59</td>
<td>0000</td>
<td>입차</td>
</tr>
<tr>
<td>19:09</td>
<td>0148</td>
<td>출차</td>
</tr>
<tr>
<td>22:59</td>
<td>5961</td>
<td>입차</td>
</tr>
<tr>
<td>23:00</td>
<td>5961</td>
<td>출차</td>
</tr>
</tbody></table>
<hr>
<h3 id="자동차별-주차-요금">자동차별 주차 요금</h3>
<table>
<thead>
<tr>
<th>차량 번호</th>
<th>누적 주차 시간(분)</th>
<th>주차 요금(원)</th>
</tr>
</thead>
<tbody><tr>
<td>0000</td>
<td>34 + 300 = 334</td>
<td>5000 + ⌈(334 - 180) / 10⌉ × 600 = 14600</td>
</tr>
<tr>
<td>0148</td>
<td>670</td>
<td>5000 + ⌈(670 - 180) / 10⌉ × 600 = 34400</td>
</tr>
<tr>
<td>5961</td>
<td>145 + 1 = 146</td>
<td>5000</td>
</tr>
</tbody></table>
<ol>
<li><p><strong>입차 후 출차 내역이 없으면 23:59에 출차된 것으로 간주</strong>합니다.</p>
</li>
<li><p>누적 주차 시간이 <strong>기본 시간 이하</strong>인 경우: <code>기본 요금</code>만 청구합니다.</p>
</li>
<li><p><strong>초과 시간</strong>이 있는 경우:</p>
<ul>
<li>초과 시간은 <code>(누적 시간 - 기본 시간)</code></li>
<li>요금 = <code>기본 요금 + ⌈초과 시간 / 단위 시간⌉ × 단위 요금</code></li>
</ul>
</li>
<li><p>⌈a⌉는 <strong>올림 함수</strong>, 즉 <code>a보다 크거나 같은 최소 정수</code></p>
</li>
</ol>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li><code>fees.Length == 4</code></li>
<li><code>1 ≤ fees[0] ≤ 1439</code> (기본 시간)</li>
<li><code>0 ≤ fees[1] ≤ 100000</code> (기본 요금)</li>
<li><code>1 ≤ fees[2] ≤ 1439</code> (단위 시간)</li>
<li><code>1 ≤ fees[3] ≤ 10000</code> (단위 요금)</li>
<li><code>1 ≤ records.Length ≤ 1000</code></li>
<li><code>records[i]</code>는 <code>&quot;HH:MM 차량번호 IN/OUT&quot;</code> 형식</li>
<li>차량 번호는 <code>&quot;0000&quot;</code> ~ <code>&quot;9999&quot;</code> 사이 문자열</li>
<li><code>records</code>는 시각 오름차순 정렬</li>
</ul>
<hr>
<h2 id="💻-나의-풀이-c">💻 나의 풀이 (C#)</h2>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;
using System.Linq;

public class Solution {
    public int[] solution(int[] fees, string[] records) {
        List&lt;int&gt; answer = new List&lt;int&gt;();
        Dictionary&lt;string, TimeSpan&gt; timeDict = new Dictionary&lt;string, TimeSpan&gt;();
        Dictionary&lt;string, int&gt; feeDict = new Dictionary&lt;string, int&gt;();

        for(int i = 0; i &lt; records.Length; i++)
        {
            string[] recordInfo = records[i].Split(&#39; &#39;);

            TimeSpan time = TimeSpan.Parse(recordInfo[0]);
            string carNum = recordInfo[1];

            if(timeDict.ContainsKey(carNum))
            {
                TimeSpan outTime = time;
                TimeSpan inTime = timeDict[carNum];
                TimeSpan totalTime = outTime - inTime;

                int totalM = (int)totalTime.TotalMinutes;

                if(feeDict.ContainsKey(carNum))
                    feeDict[carNum] += totalM;
                else
                    feeDict.Add(carNum, totalM);

                timeDict.Remove(carNum);
            }

            else
            {
                timeDict.Add(carNum, time);
            }
        }

        if(timeDict.Count &gt; 0)
        {
            foreach(var carNum in timeDict.Keys)
            {
                TimeSpan outTime = TimeSpan.Parse(&quot;23:59&quot;);
                TimeSpan inTime = timeDict[carNum];
                TimeSpan totalTime = outTime - inTime;

                int totalM = (int)totalTime.TotalMinutes;

                if(feeDict.ContainsKey(carNum))
                    feeDict[carNum] += totalM;
                else
                    feeDict.Add(carNum, totalM);
            }
        }

        foreach(var carNum in new List&lt;string&gt;(feeDict.Keys).OrderBy(k =&gt; k))
        {
            feeDict[carNum] = CalculateFee(feeDict[carNum], fees);
            answer.Add(feeDict[carNum]);
        }

        return answer.ToArray();
    }

    public int CalculateFee(int minutes, int[] fees)
    {
        int basicTime = fees[0];
        int basicFee = fees[1];
        int unitTime = fees[2];
        int unitFee = fees[3];

        if(minutes &lt;= basicTime)
            return basicFee;
        else
        {
            int extraTime = minutes - basicTime;
            int extraUnits = (int)Math.Ceiling((double)extraTime / unitTime);

            return basicFee + extraUnits * unitFee;
        }
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity 상태 패턴을 활용한 간단한 FSM[TIL 38일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity-%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-FSMTIL-38%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity-%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4%EC%9D%84-%ED%99%9C%EC%9A%A9%ED%95%9C-%EA%B0%84%EB%8B%A8%ED%95%9C-FSMTIL-38%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 09 Jun 2025 12:28:50 GMT</pubDate>
            <description><![CDATA[<p>이번 3D 방치형 프로젝트에 몬스터와 플레이어 AI에 상태 패턴을 적용하여 FSM(Finite State Machine)을 간단하게 구현해보았다.</p>
<h3 id="✅-상태-패턴state-pattern이란">✅ 상태 패턴(State Pattern)이란?</h3>
<ul>
<li>객체의 상태에 따라 행위가 달라지는 상황에서, 각 상태를 클래스로 분리하고 <strong>상태 전이를 명확하게 만드는 패턴</strong></li>
<li><strong>FSM</strong>은 게임 <strong>AI</strong>나 <strong>UI 상태 전</strong>이 등에 자주 사용되며, <strong>상태 간의 전이를 명확하게 정의</strong>하고 <strong>관리하기 쉬워짐</strong>.</li>
</ul>
<h3 id="📝-구조-요약">📝 구조 요약</h3>
<h4 id="1-기반-클래스-unitbase">1. 기반 클래스: UnitBase</h4>
<ul>
<li>모든 유닛(플레이어, 몬스터)의 공통 기능을 담당합니다. 주요 역할:</li>
<li><code>NavMeshAgent</code>, <code>Animator</code> 등 컴포넌트 관리</li>
<li>현재 유닛의 상태 전이 관리 <code>stateMachine</code></li>
<li>공통 스탯 (<code>UnitStatSO</code>, <code>unitStat</code>) 및 체력 관리</li>
<li>상태 클래스들을 초기화 및 보유</li>
</ul>
<pre><code class="language-csharp">public class UnitBase : MonoBehaviour 
{
    public StateMachine stateMachine { get; private set; }
    public IdleState IdleState { get; private set; }
    public ChasingState ChasingState { get; private set; }
    public AttackState AttackState { get; private set; }

    protected virtual void Awake() 
    {
        stateMachine = new StateMachine();
        IdleState = new IdleState(this);
        ChasingState = new ChasingState(this);
        AttackState = new AttackState(this);
    }
}</code></pre>
<h4 id="2-상태-인터페이스-istate">2. 상태 인터페이스: IState</h4>
<ul>
<li>모든 상태 클래스들이 구현하는 인터페이스입니다.</li>
</ul>
<pre><code class="language-csharp">public interface IState 
{
    void Enter();
    void Update();
    void Exit();
}</code></pre>
<h4 id="3-각-상태-클래스">3. 각 상태 클래스</h4>
<p>타겟이 감지되면 ChasingState로 전이</p>
<p><strong>IdleState</strong></p>
<pre><code class="language-csharp">public void Update() 
{
    if (unit.Target != null &amp;&amp; unit.IsTargetInRange(unit.unitStat.chaseRange)) 
    {
        unit.stateMachine.ChangeState(unit.ChasingState);
    }
}</code></pre>
<p><strong>ChasingState</strong></p>
<ul>
<li><p>타겟을 향해 이동</p>
</li>
<li><p>공격 사거리 내에 들어오면 <code>AttackState</code>로 전이</p>
<pre><code class="language-csharp">public void Update() 
{
  unit.Agent.SetDestination(unit.Target.position);

  if (unit.IsTargetInRange(unit.unitStat.attackRange)) 
  {
      unit.stateMachine.ChangeState(unit.AttackState);
  }
}</code></pre>
</li>
</ul>
<p><strong>AttackState</strong></p>
<ul>
<li>일정한 공격 쿨타임을 기반으로 애니메이션 호출 (Animation Event로 Attack 호출)</li>
<li>타겟이 사거리 밖이면 다시 <code>IdleState</code>로 전이</li>
</ul>
<pre><code class="language-csharp">public void Update() 
{
    if (Time.time - lastAttackTime &gt;= attackCooldown) 
    {
        unit.Attack();
        lastAttackTime = Time.time;
    }
}</code></pre>
<h3 id="📃-상태-전이-관리-statemachine">📃 상태 전이 관리: StateMachine</h3>
<p>상태 간 전이를 처리(<code>Enter</code>, <code>Exit</code>)하며 현재 상태의 <code>Update()</code>를 매 프레임 호출합니다.</p>
<pre><code class="language-csharp">public class StateMachine 
{
    private IState currentState;

    public void ChangeState(IState newState) 
    {
        currentState?.Exit();
        currentState = newState;
        currentState.Enter();
    }

    public void Update() 
    {
        currentState?.Update();
    }
}</code></pre>
<h3 id="정리">정리</h3>
<ul>
<li>플레이어도 자동으로 적을 찾아 이동하므로 몬스터와 공통된 로직 <code>UnitBase</code> 클래스로 묶어서 처리</li>
<li>상태 패턴은 각 상태를 <strong>클래스로 분리</strong>함으로써 <strong>코드 가독성</strong>과 <strong>유지보수성</strong>을 높여줍니다.</li>
<li>FSM 구조는 <strong>AI 행동 전이</strong>를 깔끔하게 구현할 수 있어, 몬스터나 NPC 행동 설계에 매우 유용</li>
<li><code>UnitBase</code>에 공통 로직을 묶고, FSM은 상태 전이만 관리하도록 <strong>책임 분리</strong>가 잘 되어 있어 확장에 용이</li>
<li><code>AttackState</code>에선 쿨타임 계산을 통해 프레임마다 공격하지 않도록 조절 가능</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자료구조 복습 [TIL 37일차]]]></title>
            <link>https://velog.io/@jangmine_z/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EB%B3%B5%EC%8A%B5-TIL-37%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0-%EB%B3%B5%EC%8A%B5-TIL-37%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 05 Jun 2025 11:31:43 GMT</pubDate>
            <description><![CDATA[<p>프로그래밍을 잘하고 싶다면, <strong>자료구조는 선택이 아니라 필수</strong>
많은 사람들이 <code>List</code>, <code>Dictionary</code>만 쓰면서 그 내부 동작이나 다른 대안 구조를 잘 모르는 경우가 많다.<br>특히 <strong>게임 개발이나 알고리즘 문제 풀이, 실무 최적화</strong>에서 자료구조 선택은 성능과 직결된다.</p>
<p>오늘은 주요 자료구조를 간단히 정리하고, <strong>어떤 상황에서 유용하게 쓰일 수 있는지</strong>를 다시 복습해보는 시간을 가져보았다.</p>
<hr>
<h3 id="1-배열-array">1. <strong>배열 (Array)</strong></h3>
<ul>
<li><strong>고정된 크기</strong>의 연속적인 메모리 공간</li>
<li>빠른 인덱스 접근 (<code>O(1)</code>), 하지만 크기 변경 불가</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>맵 타일 데이터 (고정된 크기), 색상 팔레트 등  </li>
<li>성능이 중요한 루프에서 단순 배열이 유리할 때 사용</li>
</ul>
<hr>
<h3 id="2-리스트-list">2. <strong>리스트 (List)</strong></h3>
<ul>
<li>C#의 <strong>동적 배열</strong>  </li>
<li>내부적으로 크기 자동 조절 → 배열 복사 발생 가능</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>인벤토리 아이템 리스트  </li>
<li>적군 목록, 퀘스트 목록 등 가변적인 데이터에 적합</li>
</ul>
<hr>
<h3 id="3-연결-리스트-linkedlist">3. <strong>연결 리스트 (LinkedList)</strong></h3>
<ul>
<li>노드 간 참조로 구성 (next, prev)</li>
<li><strong>중간 삽입/삭제는 빠르지만 접근은 느림</strong></li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>순서 변경이 잦은 UI 요소나 이벤트 큐  </li>
<li>Undo/Redo 기록 관리 (양방향 리스트)</li>
</ul>
<hr>
<h3 id="4-스택-stack">4. <strong>스택 (Stack)</strong></h3>
<ul>
<li><strong>후입선출 (LIFO)</strong> 구조</li>
<li><code>Push()</code>, <code>Pop()</code> 으로 데이터를 다룸</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>상태머신 처리 (ex: 상태 push &amp; pop)  </li>
<li>씬 전환, UI 창 스택, 함수 호출 스택</li>
</ul>
<hr>
<h3 id="5-큐-queue-우선순위-큐">5. <strong>큐 (Queue), 우선순위 큐</strong></h3>
<ul>
<li><strong>선입선출 (FIFO)</strong> 구조  </li>
<li><code>Enqueue()</code>, <code>Dequeue()</code> 사용</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>몬스터 스폰 큐  </li>
<li>네트워크 패킷 처리  </li>
<li>유닛 명령 대기열 등</li>
</ul>
<hr>
<h3 id="6-딕셔너리-dictionary">6. <strong>딕셔너리 (Dictionary)</strong></h3>
<ul>
<li><strong>해시 테이블 기반 자료구조</strong></li>
<li>키 → 해시 → 인덱스 → 값 저장</li>
<li>평균 <code>O(1)</code> 접근 속도</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>오브젝트 이름 → Prefab 참조  </li>
<li>유저 ID → 상태 정보  </li>
<li>NPC 대사, 스킬 ID → 효과 등</li>
</ul>
<blockquote>
<p>⚠️ 해시 충돌 시 성능 저하 가능성 있음 → 대량 데이터일수록 주의</p>
</blockquote>
<hr>
<h3 id="7-sorteddictionary--sortedlist">7. <strong>SortedDictionary / SortedList</strong></h3>
<ul>
<li>자동 정렬된 상태로 키 저장</li>
<li>내부적으로 <strong>이진 탐색 트리(레드블랙 트리)</strong> 사용</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>순차적으로 정렬된 데이터를 빠르게 조회하고 싶을 때  </li>
<li>랭킹 시스템 (정렬된 유저 점수 목록 등)</li>
</ul>
<hr>
<h3 id="8-트리-tree">8. <strong>트리 (Tree)</strong></h3>
<ul>
<li>계층 구조 표현에 적합</li>
<li>대표적: 이진 탐색 트리, AVL, 레드블랙 트리</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>스킬 트리, UI 메뉴 구조  </li>
<li>상태 전이 트리 (FSM), 애니메이션 트리</li>
</ul>
<hr>
<h3 id="9-그래프-graph">9. <strong>그래프 (Graph)</strong></h3>
<ul>
<li>정점과 간선으로 이루어진 구조</li>
<li>DFS / BFS / 다익스트라 / A*</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>길찾기 알고리즘 (네비게이션 메시, A*)  </li>
<li>소셜 관계망 (플레이어 간 친구 시스템)  </li>
<li>스테이지 간 연결 구조</li>
</ul>
<hr>
<h3 id="10-힙-heap-→-우선순위-큐">10. <strong>힙 (Heap) → 우선순위 큐</strong></h3>
<ul>
<li>최소값 또는 최대값을 빠르게 찾는 구조</li>
<li>삽입, 삭제 후에도 자동으로 재정렬 유지</li>
</ul>
<p>🎮 <em>활용 예:</em>  </p>
<ul>
<li>AI 우선순위 작업 큐 (위험도 높은 유닛 우선 대응)  </li>
<li>경로 탐색 알고리즘 (A*, 다익스트라)  </li>
<li>리소스 관리 시스템</li>
</ul>
<hr>
<h3 id="🧠-느낀점">🧠 느낀점</h3>
<ul>
<li>자료구조는 단순히 외워서 쓰는 게 아니라, <strong>언제 어떤 상황에서 적합한지를 판단</strong></li>
<li>특히 게임 개발에서는 구조 하나 잘못 고르면 <strong>성능 문제나 유지보수 난이도</strong>가 크게 달라진다.</li>
<li>앞으로는 알고리즘 문제 풀이뿐만 아니라, <strong>구현 설계 시에도 자료구조를 먼저 고민</strong>해야겠다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers - 할인 행사(C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-%ED%95%A0%EC%9D%B8-%ED%96%89%EC%82%ACC</link>
            <guid>https://velog.io/@jangmine_z/Programmers-%ED%95%A0%EC%9D%B8-%ED%96%89%EC%82%ACC</guid>
            <pubDate>Thu, 05 Jun 2025 02:38:22 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>XYZ 마트는 일정한 금액을 지불하면 10일 동안 회원 자격을 부여합니다. XYZ 마트에서는 회원을 대상으로 매일 한 가지 제품을 할인하는 행사를 합니다. 할인하는 제품은 하루에 하나씩만 구매할 수 있습니다. 알뜰한 정현이는 자신이 원하는 제품과 수량이 할인하는 날짜와 10일 연속으로 일치할 경우에 맞춰서 회원가입을 하려 합니다.</p>
<p>예를 들어, 정현이가 원하는 제품이 바나나 3개, 사과 2개, 쌀 2개, 돼지고기 2개, 냄비 1개이며, XYZ 마트에서 14일간 회원을 대상으로 할인하는 제품이 날짜 순서대로 치킨, 사과, 사과, 바나나, 쌀, 사과, 돼지고기, 바나나, 돼지고기, 쌀, 냄비, 바나나, 사과, 바나나인 경우에 대해 알아봅시다. 첫째 날부터 열흘 간에는 냄비가 할인하지 않기 때문에 첫째 날에는 회원가입을 하지 않습니다. 둘째 날부터 열흘 간에는 바나나를 원하는 만큼 할인구매할 수 없기 때문에 둘째 날에도 회원가입을 하지 않습니다. 셋째 날, 넷째 날, 다섯째 날부터 각각 열흘은 원하는 제품과 수량이 일치하기 때문에 셋 중 하루에 회원가입을 하려 합니다.</p>
<p>정현이가 원하는 제품을 나타내는 문자열 배열 <code>want</code>와 정현이가 원하는 제품의 수량을 나타내는 정수 배열 <code>number</code>, XYZ 마트에서 할인하는 제품을 나타내는 문자열 배열 <code>discount</code>가 주어졌을 때, 회원등록시 정현이가 원하는 제품을 모두 할인 받을 수 있는 회원등록 날짜의 총 일수를 return 하는 solution 함수를 완성하시오. 가능한 날이 없으면 0을 return 합니다.</p>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li>1 ≤ <code>want</code>의 길이 = <code>number</code>의 길이 ≤ 10<ul>
<li>1 ≤ <code>number</code>의 원소 ≤ 10</li>
<li><code>number[i]</code>는 <code>want[i]</code>의 수량을 의미하며, <code>number</code>의 원소의 합은 10입니다.</li>
</ul>
</li>
<li>10 ≤ <code>discount</code>의 길이 ≤ 100,000</li>
<li><code>want</code>와 <code>discount</code>의 원소들은 알파벳 소문자로 이루어진 문자열입니다.<ul>
<li>1 ≤ <code>want</code>의 원소의 길이, <code>discount</code>의 원소의 길이 ≤ 12</li>
</ul>
</li>
</ul>
<hr>
<h2 id="💻-나의-풀이-c">💻 나의 풀이 (C#)</h2>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;

public class Solution {
    public int solution(string[] want, int[] number, string[] discount) {
        int answer = 0;
        Dictionary&lt;string, int&gt; wantDict = new Dictionary&lt;string, int&gt;();

        for(int i = 0; i &lt; want.Length; i++)
        {
            if(!wantDict.ContainsKey(want[i]))
            {
                wantDict.Add(want[i], number[i]);
            }
            else    
                wantDict[want[i]] += number[i];
        }

        for(int day = 0; day &lt;= discount.Length - 10; day++)
        {
            Dictionary&lt;string, int&gt; tempDict = new Dictionary&lt;string, int&gt;(wantDict);

            for (int i = day; i &lt; day + 10; i++)
            {
                string item = discount[i];

                if(tempDict.ContainsKey(item) &amp;&amp; tempDict[item] &gt; 0)
                {
                    tempDict[item] -= 1;
                }
            }

            if(CheckRegister(tempDict))
                answer++;
        }

        return answer;
    }

    public bool CheckRegister(Dictionary&lt;string, int&gt; itemDict)
    {
        foreach(var wantItem in itemDict.Keys)
        {
            if(itemDict[wantItem] &gt; 0)
            {
                return false;
            }
        }

        return true;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers - 연속 부분 수열 합의 개수 (C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98-C</link>
            <guid>https://velog.io/@jangmine_z/Programmers-%EC%97%B0%EC%86%8D-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4-%ED%95%A9%EC%9D%98-%EA%B0%9C%EC%88%98-C</guid>
            <pubDate>Thu, 05 Jun 2025 01:58:13 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>철호는 수열을 가지고 놀기 좋아합니다. 어느 날 철호는 어떤 자연수로 이루어진 원형 수열의 연속하는 부분 수열의 합으로 만들 수 있는 수가 모두 몇 가지인지 알아보고 싶어졌습니다. 원형 수열이란 일반적인 수열에서 처음과 끝이 연결된 형태의 수열을 말합니다. 예를 들어 수열 [7, 9, 1, 1, 4] 로 원형 수열을 만들면 다음과 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/jangmine_z/post/af86b5d0-17c4-4cd1-8657-0714d32b222a/image.png" alt=""></p>
<p>원형 수열은 처음과 끝이 연결되어 끊기는 부분이 없기 때문에 연속하는 부분 수열도 일반적인 수열보다 많아집니다.
원형 수열의 모든 원소 <code>elements</code>가 순서대로 주어질 때, 원형 수열의 연속 부분 수열 합으로 만들 수 있는 수의 개수를 return 하도록 solution 함수를 완성해주세요.</p>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li>3 ≤ <code>elements</code>의 길이 ≤ 1,000</li>
<li>1 ≤ <code>elements</code>의 원소 ≤ 1,000</li>
</ul>
<hr>
<h2 id="💻-나의-풀이-c">💻 나의 풀이 (C#)</h2>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;

public class Solution {
    public int solution(int[] elements) {
        int len = elements.Length;
        HashSet&lt;int&gt; temp = new HashSet&lt;int&gt;(elements);

        for(int length = 2; length &lt;= len; length++)
        {
            for(int start = 0; start &lt; len; start++)
            {
                int sum = 0;

                for(int i = 0; i &lt; length; i++)
                {
                    sum += elements[(start + i) % len];

                }
                temp.Add(sum);
            }
        }
        return temp.Count;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity 프로젝트 구조 설계 방법 [TIL 36일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EC%A1%B0-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95-TIL-36%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B5%AC%EC%A1%B0-%EC%84%A4%EA%B3%84-%EB%B0%A9%EB%B2%95-TIL-36%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 04 Jun 2025 13:40:43 GMT</pubDate>
            <description><![CDATA[<p><strong>효율적인 Unity 프로젝트 구조 설계</strong>에 대해 학습하고, 정리해보았다.
특히 유지보수성과 성능, 확장성 관점에서 중요한 요소들을 중심으로 정리하였다.</p>
<hr>
<h3 id="1-enum--struct-통합-관리">1. Enum &amp; Struct 통합 관리</h3>
<h4 id="🔹-enum의-장점">🔹 Enum의 장점</h4>
<ul>
<li>자동완성 지원으로 <strong>오타를 줄이고 가독성을 향상</strong></li>
<li>상수 값보다 <strong>타입 안정성 확보</strong></li>
<li>코드 작성 시 <strong>값의 명확한 의미 부여</strong></li>
</ul>
<h4 id="🔹-struct의-장점">🔹 Struct의 장점</h4>
<ul>
<li><strong>값 타입으로 스택 메모리 활용</strong>, GC 부담 감소</li>
<li>참조 공유로 인한 데이터 오염 방지</li>
<li>직렬화가 가능하여 ScriptableObject나 JSON 등에 활용하기 유리</li>
</ul>
<blockquote>
<p><strong>Enum과 Struct는 하나의 파일에서 모아서 관리하는 것이 유지보수에 효과적</strong></p>
</blockquote>
<hr>
<h3 id="2-유틸리티-함수--글로벌-상수-관리">2. 유틸리티 함수 &amp; 글로벌 상수 관리</h3>
<h4 id="🔹-유틸리티-함수-static-class">🔹 유틸리티 함수 (static class)</h4>
<ul>
<li><strong>중복 제거와 재사용성 강화</strong></li>
<li>어디서든 접근 가능하여 <strong>간단한 기능 처리에 유리</strong></li>
<li>주의: 남용 시 의존성 증가 가능성</li>
</ul>
<h4 id="🔹-글로벌-상수">🔹 글로벌 상수</h4>
<ul>
<li><code>public static readonly</code> 또는 <code>const</code>를 활용하여 <strong>불변값만 전역 관리</strong></li>
<li>예: Tag, Layer, 서버 주소, 씬 이름, UI 키 값 등</li>
</ul>
<hr>
<h3 id="3-싱글톤--dontdestroyonload-패턴">3. 싱글톤 + DontDestroyOnLoad 패턴</h3>
<ul>
<li><strong>게임 전반에 걸쳐 유지되어야 하는 객체</strong> (예: GameManager, AudioManager 등)에 사용</li>
<li><code>DontDestroyOnLoad(this.gameObject)</code>로 씬 전환에도 유지</li>
</ul>
<blockquote>
<p>남용 시 <strong>결합도 증가와 디버깅 난이도 상승</strong></p>
</blockquote>
<h4 id="generic-싱글톤">Generic 싱글톤</h4>
<pre><code class="language-csharp">using UnityEngine;

public class Singleton&lt;T&gt; : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                // 해당 컴포넌트를 가지고 있는 게임 오브젝트 반환
                instance = (T)FindAnyObjectByType(typeof(T));

                if (instance == null)
                {
                    // 새로운 게임 오브젝트 생성 및 컴포넌트 추가
                    GameObject obj = new GameObject(typeof(T).Name, typeof(T));
                    // 새로운 오브젝트의 컴포넌트 인스턴스에 지정
                    instance = obj.GetComponent&lt;T&gt;();
                }
            }

            return instance;
        }
    }

    protected virtual void Awake()
    {
        // 인스턴스가 없을 때 해당 오브젝트로 설정
        if (instance == null)
        {
            instance = this as T;
            DontDestroyOnLoad(this.gameObject);
        }

        // 인스턴스가 존재한다면 현재 오브젝트 파괴
        else if (instance != null)
            Destroy(gameObject);
    }
}</code></pre>
<hr>
<h3 id="4-scriptableobject--데이터-컨테이너">4. ScriptableObject &amp; 데이터 컨테이너</h3>
<h4 id="🔹-scriptableobject">🔹 ScriptableObject</h4>
<ul>
<li>설정 값, 능력치, 아이템 데이터 등 <strong>불변 데이터 관리</strong>에 최적</li>
<li>인스펙터에서 관리 가능하며, 에디터 친화적</li>
</ul>
<h4 id="🔹-데이터-컨테이너">🔹 데이터 컨테이너</h4>
<ul>
<li>여러 <code>ScriptableObject</code>를 하나의 큰 묶음으로 관리</li>
<li>예: <code>StageDataList</code>, <code>ItemDatabase</code> 등</li>
</ul>
<blockquote>
<p>메모리 절약을 위해 <strong>불필요한 데이터까지 함께 로드하지 않도록 설계</strong></p>
</blockquote>
<hr>
<h3 id="5-리소스-및-오브젝트-풀링">5. 리소스 및 오브젝트 풀링</h3>
<h4 id="🔹-리소스-그룹화">🔹 리소스 그룹화</h4>
<ul>
<li>사운드, 이펙트, 아이콘 등을 <code>ScriptableObject</code>나 <code>Dictionary</code> 등으로 묶어 <strong>중앙 집중식 관리</strong></li>
</ul>
<h4 id="🔹-오브젝트-풀링">🔹 오브젝트 풀링</h4>
<ul>
<li>자주 생성/파괴되는 오브젝트는 <code>Pool</code>로 관리하여 <strong>GC</strong> 호출 방지 및 성능 최적화</li>
</ul>
<hr>
<h3 id="6-씬--ui-관리">6. 씬 &amp; UI 관리</h3>
<h4 id="🔹-멀티-씬-구조">🔹 멀티 씬 구조</h4>
<ul>
<li>UI, 월드, 매니저 등을 독립적인 씬으로 분리하면 모듈화와 로딩 최적화에 유리</li>
</ul>
<h4 id="🔹-ui-최적화">🔹 UI 최적화</h4>
<ul>
<li>변경이 잦은 UI는 <code>Canvas</code> 분리</li>
</ul>
<blockquote>
<p><strong>EventTrigger</strong>보다 <strong>UnityEvent</strong>, <strong>AddListener</strong> 사용 권장</p>
</blockquote>
<hr>
<h3 id="😁-느낀점">😁 느낀점</h3>
<p>이번 정리를 통해 <strong>프로젝트 구조의 중요성</strong>을 다시금 깨달았다. 더 체계적으로 설계하고, <strong>유지보수성</strong>과 <strong>확장성</strong>을 고려한 개발 습관을 만들어나가는 방향으로 공부해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers - N개의 최소공배수 (C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-N%EA%B0%9C%EC%9D%98-%EC%B5%9C%EC%86%8C%EA%B3%B5%EB%B0%B0%EC%88%98-C</link>
            <guid>https://velog.io/@jangmine_z/Programmers-N%EA%B0%9C%EC%9D%98-%EC%B5%9C%EC%86%8C%EA%B3%B5%EB%B0%B0%EC%88%98-C</guid>
            <pubDate>Wed, 04 Jun 2025 00:35:23 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>두 수의 최소공배수(Least Common Multiple)란 입력된 두 수의 배수 중 공통이 되는 가장 작은 숫자를 의미합니다. 예를 들어 2와 7의 최소공배수는 14가 됩니다. 정의를 확장해서, n개의 수의 최소공배수는 n 개의 수들의 배수 중 공통이 되는 가장 작은 숫자가 됩니다. n개의 숫자를 담은 배열 arr이 입력되었을 때 이 수들의 최소공배수를 반환하는 함수, solution을 완성해 주세요.</p>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li>arr은 길이 1이상, 15이하인 배열입니다.</li>
<li>arr의 원소는 100 이하인 자연수입니다.</li>
</ul>
<hr>
<h2 id="💻-나의-풀이-c">💻 나의 풀이 (C#)</h2>
<pre><code class="language-csharp">using System.Linq;

public class Solution {
    public int solution(int[] arr) {
        int answer = 0;
        int maxNum = arr.Max();

        while(true)
        {
            bool canDivide = true;
            answer += maxNum;

            for(int i = 0; i &lt; arr.Length; i++)
            {
                if(answer % arr[i] != 0)
                {
                    canDivide = false;
                }
            }

            if(canDivide)
                break;
        }

        return answer;
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers - 멀리뛰기 (C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-%EB%A9%80%EB%A6%AC%EB%9B%B0%EA%B8%B0-C-t21896tc</link>
            <guid>https://velog.io/@jangmine_z/Programmers-%EB%A9%80%EB%A6%AC%EB%9B%B0%EA%B8%B0-C-t21896tc</guid>
            <pubDate>Tue, 03 Jun 2025 08:57:45 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>효진이는 멀리 뛰기를 연습하고 있습니다. 효진이는 한번에 1칸, 또는 2칸을 뛸 수 있습니다. 칸이 총 4개 있을 때, 효진이는
(1칸, 1칸, 1칸, 1칸)
(1칸, 2칸, 1칸)
(1칸, 1칸, 2칸)
(2칸, 1칸, 1칸)
(2칸, 2칸)
의 5가지 방법으로 맨 끝 칸에 도달할 수 있습니다. 멀리뛰기에 사용될 칸의 수 n이 주어질 때, 효진이가 끝에 도달하는 방법이 몇 가지인지 알아내, 여기에 1234567를 나눈 나머지를 리턴하는 함수, solution을 완성하세요. 예를 들어 4가 입력된다면, 5를 return하면 됩니다.</p>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li>n은 1 이상, 2000 이하인 정수입니다.</li>
</ul>
<hr>
<h2 id="💻-나의-풀이-c">💻 나의 풀이 (C#)</h2>
<pre><code class="language-csharp">public class Solution {
    public long solution(int n) {
        long divider = 1234567;
        long[] temp = new long[n + 1];
        temp[0] = 1;
        temp[1] = 1;

        for(int i = 2; i &lt;= n; i++)
        {
            temp[i] = (temp[i - 1] + temp [i - 2]) % divider;
        }

        return temp[n];
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SOLID 원칙 [TIL 35일차]]]></title>
            <link>https://velog.io/@jangmine_z/SOLID-%EC%9B%90%EC%B9%99-TIL-35%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/SOLID-%EC%9B%90%EC%B9%99-TIL-35%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 02 Jun 2025 11:30:15 GMT</pubDate>
            <description><![CDATA[<p>오늘은 객체지향 설계의 핵심 원칙인 <strong>SOLID</strong>에 대해 공부했고, 이를 게임 개발 관점에서 어떻게 적용할 수 있는지도 함께 정리해봤다.
이 원칙들은 Unity 같은 엔진에서 개발할 때도 <strong>유지보수성</strong>과 <strong>확장성</strong>을 높이기 위해 매우 중요하다.</p>
<hr>
<h3 id="📝-solid">📝 SOLID?</h3>
<ul>
<li><strong>객체지향 설계의 5가지 핵심원칙</strong></li>
</ul>
<table>
<thead>
<tr>
<th>원칙</th>
<th>이름</th>
<th>핵심 개념</th>
</tr>
</thead>
<tbody><tr>
<td>S</td>
<td>Single Responsibility Principle</td>
<td>하나의 클래스는 하나의 책임만 가져야 한다</td>
</tr>
<tr>
<td>O</td>
<td>Open/Closed Principle</td>
<td>기존 코드를 수정하지 않고 기능을 확장할 수 있어야 한다</td>
</tr>
<tr>
<td>L</td>
<td>Liskov Substitution Principle</td>
<td>자식 클래스는 부모 클래스를 대체할 수 있어야 한다</td>
</tr>
<tr>
<td>I</td>
<td>Interface Segregation Principle</td>
<td>불필요한 인터페이스 구현을 강요하지 않아야 한다</td>
</tr>
<tr>
<td>D</td>
<td>Dependency Inversion Principle</td>
<td>구체 클래스가 아닌 추상화에 의존해야 한다</td>
</tr>
</tbody></table>
<hr>
<h3 id="🎮-게임-개발에서의-적용-예시">🎮 게임 개발에서의 적용 예시</h3>
<h4 id="1️⃣-srp---단일-책임-원칙">1️⃣ SRP - 단일 책임 원칙</h4>
<p>하나의 스크립트/컴포넌트는 <strong>하나의 역할만 담당</strong>해야 한다.</p>
<pre><code class="language-csharp">// ❌ 이동, 공격, 저장 로직이 한 클래스에 몰려 있음
public class Player : MonoBehaviour {
    void Move() { }
    void Attack() { }
    void SaveData() { }
}

// ✅ 역할을 명확히 분리
public class PlayerMovement : MonoBehaviour { void Move() { } }
public class PlayerCombat : MonoBehaviour { void Attack() { } }
public class PlayerSave : MonoBehaviour { void SaveData() { } }</code></pre>
<ul>
<li><strong>장점</strong>: 기능별 책임 분리로 유지보수가 쉬움, 재사용도 가능</li>
</ul>
<h4 id="2️⃣-ocp---개방-폐쇄-원칙">2️⃣ OCP - 개방-폐쇄 원칙</h4>
<p>코드를 <strong>수정하지 않고</strong>, 기능을 <strong>확장할 수 있어야 한다</strong></p>
<pre><code class="language-csharp">// 공격방식을 인터페이스로 추상화
public interface IAttackStrategy 
{
    void Attack();
}

public class MeleeAttack : IAttackStrategy { public void Attack() { } }
public class RangedAttack : IAttackStrategy { public void Attack() { } }

public class Enemy {
    private IAttackStrategy attackStrategy;

    public Enemy(IAttackStrategy strategy) 
    {
        this.attackStrategy = strategy;
    }
    public void Attack() =&gt; attackStrategy.Attack();
}</code></pre>
<ul>
<li><strong>장점</strong>: 새로운 공격 방식이 생겨도 기존 코드를 건드릴 필요 없음</li>
</ul>
<h4 id="3️⃣-lsp---리스코프-치환-원칙">3️⃣ LSP - 리스코프 치환 원칙</h4>
<p>부모 클래스를 자식 클래스로 <strong>치환해도 동작이 깨지면 안 된다</strong>.</p>
<pre><code class="language-csharp">// X 자식 클래스가 부모의 계약을 깨뜨림
public class Enemy { public virtual void Die() { } }
public class ImmortalEnemy : Enemy 
{
    public override void Die() =&gt; throw new Exception(&quot;죽지 않음&quot;);
}

// O 공통 기능만 인터페이스로 분리
public interface IDamageable 
{
    void TakeDamage(int amount);
}

public class RegularEnemy : MonoBehaviour, IDamageable { }
public class ImmortalEnemy : MonoBehaviour { }</code></pre>
<ul>
<li><strong>포인트</strong>: 상속보다는 인터페이스 분리와 조합을 더 많이 사용하자</li>
</ul>
<h4 id="4️⃣-isp---인터페이스-분리-원칙">4️⃣ ISP - 인터페이스 분리 원칙</h4>
<p>필요한 기능만 <strong>명확하게 나눈 인터페이스</strong>를 사용하자.</p>
<pre><code class="language-csharp">// X 모든 엔티티에 다 필요한 메서드는 아님
public interface IGameEntity {
    void Move();
    void Attack();
    void CastSpell();
}

// O 기능별로 인터페이스를 분리
public interface IMovable { void Move(); }
public interface IAttackable { void Attack(); }
public interface ISpellCaster { void CastSpell(); }</code></pre>
<ul>
<li><strong>장점</strong>: 필요 없는 기능 구현을 강제하지 않아 클래스가 깔끔해짐</li>
</ul>
<h4 id="5️⃣-dip---의존-역전-원칙">5️⃣ DIP - 의존 역전 원칙</h4>
<p>클래스는 <strong>구체적인 구현보다 인터페이스(추상화)</strong>에 의존해야 한다.</p>
<pre><code class="language-csharp">// X GameManager가 AudioManager에 직접 의존
public class GameManager 
{
    private AudioManager audioManager = new AudioManager();
}

// O 추상화에 의존하여 유연성 확보
public interface IAudioManager 
{
    void Play(string clip);
}

public class GameManager 
{
    private IAudioManager audio;

    public GameManager(IAudioManager audio) 
    {
        this.audio = audio;
    }
}</code></pre>
<ul>
<li><strong>포인트</strong>: DI(의존성 주입)를 통해 테스트와 유연성 확보</li>
</ul>
<hr>
<h3 id="📃-정리">📃 정리</h3>
<blockquote>
<p><strong>게임도 결국 소프트웨어다. 설계가 깔끔해야 확장도 쉽다</strong>.</p>
</blockquote>
<ul>
<li><code>SRP</code> → 기능별 컴포넌트 분리로 유지보수 편리</li>
<li><code>OCP</code> → 새로운 무기/스킬/몬스터도 쉽게 추가 가능</li>
<li><code>LSP</code> → 상속보다 인터페이스 조합이 유리한 경우가 많음</li>
<li><code>ISP</code> → 엔티티마다 필요한 기능만 제공</li>
<li><code>DIP</code> → 테스트 가능한 구조, 교체 가능한 모듈 구성 가능</li>
</ul>
<blockquote>
<p>항상 지키며 작업할 수는 없겠지만 최대한 지켜려고 노력하고,
앞으로는 <code>interface</code>를 더 활용하여 설계를 해봐야겠다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity Player 시스템 설계 정리[TIL 34일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity-Player-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%A0%95%EB%A6%ACTIL-34%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity-Player-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%84%A4%EA%B3%84-%EC%A0%95%EB%A6%ACTIL-34%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 29 May 2025 13:32:05 GMT</pubDate>
            <description><![CDATA[<p>오늘은 현재 진행중인 Unity 프로젝트에서 구성한 Player 시스템 전체 구조를 정리해보았다. Player 컴포넌트를 중심으로 각각의 기능을 전담하는 모듈을 컴포넌트 기반 설계로 분리하여 유지보수성과 확장성을 높였다.</p>
<h3 id="🧩-핵심-구조">🧩 핵심 구조</h3>
<p>모든 플레이어 관련 로직은 Player 스크립트가 중심이 되며, 아래의 구성요소들을 참조 및 초기화합니다:</p>
<pre><code class="language-plaintext">[Player] : 중심 허브 역할
 ├── PlayerController         // 입력 처리
 ├── PlayerStatus            // 체력/스태미나 등 상태 관리
 ├── PlayerMovement          // 이동 처리
 ├── PlayerWeaponHandler     // 무기 해제/장착/투척
 ├── PlayerVehicleHandler    // 탈것 호출 및 탑승
 ├── PlayerAnimationHandler  // 애니메이션 상태 설정
 ├── PlayerAudioHandler      // 사운드 처리
 └── PlayerEventHandler      // 커스텀 이벤트 트리거</code></pre>
<h3 id="🧠-playercs---메인-허브">🧠 Player.cs - 메인 허브</h3>
<p>모든 기능 스크립트들을 연결하고 초기화하는 중심 클래스
현재 상태를 나타내는 PlayerState를 관리하여 기능 분기 제어</p>
<h4 id="주요-동작">주요 동작</h4>
<ul>
<li>이동 및 회전</li>
<li>대시, 투척, 자원 채집, 아이템 소비</li>
<li>피해 처리 및 사망 처리</li>
</ul>
<h3 id="🔄-주요-시스템-연결-예시">🔄 주요 시스템 연결 예시</h3>
<h4 id="🎮-입력-→-동작-연결">🎮 입력 → 동작 연결</h4>
<ul>
<li>PlayerController → 사용자 입력 수신</li>
<li>PlayerMovement → 이동 및 회전 처리</li>
<li>PlayerWeaponHandler → 무기 투척 로직</li>
<li>PlayerVehicleHandler → 탈것 호출 및 탑승 제어</li>
</ul>
<h4 id="🩺-상태-관리">🩺 상태 관리</h4>
<ul>
<li>PlayerStatus → 체력, 스태미나, 수분 등 자원 관리</li>
<li>UseStamina, TakeDamage, SetItemStat 등 주요 기능 제공</li>
</ul>
<h4 id="🧨-무기-시스템">🧨 무기 시스템</h4>
<ul>
<li>PlayerWeaponHandler → 무기 해금, 교체, 투척 제어</li>
<li>WeaponController + Grenade로 투척체 기능 완성</li>
</ul>
<h4 id="🎥-애니메이션-처리">🎥 애니메이션 처리</h4>
<ul>
<li>PlayerAnimationHandler → Animator 연결</li>
<li>이동, 대시, 채집, 던지기, 사망 등 트리거 제어</li>
<li>피격 시 시각 효과도 포함 (Coroutine 기반)</li>
</ul>
<h4 id="🔉-사운드-처리">🔉 사운드 처리</h4>
<ul>
<li>PlayerAudioHandler → 각 행동에 맞는 사운드 처리 (추가 예정)</li>
</ul>
<h4 id="📌-상태-기반-제어-예시">📌 상태 기반 제어 예시</h4>
<p>PlayerState 열거형을 통해 상태에 따라 행동을 제한하고 분기 처리합니다.</p>
<pre><code class="language-csharp">public enum PlayerState
{
    Idle, Walk, Dash, Gathering, Throw, Vehicle, Death
}

public bool CanMove =&gt; CurState != PlayerState.Vehicle &amp;&amp; CurState != PlayerState.Gathering &amp;&amp; CurState != PlayerState.Death &amp;&amp; CurState != PlayerState.Throw;

void FixedUpdate()
{
    if (CanMove)
        playerMovement.Move(playerController.MoveDirection, playerStatus.MoveSpeed);
}

void Update()
{
    if (CanMove)
        playerMovement.Rotate(playerController.LookDirection);
}</code></pre>
<h3 id="💻-플레이어-동작-화면">💻 플레이어 동작 화면</h3>
<p><img src="https://velog.velcdn.com/images/jangmine_z/post/310136ce-9247-4e75-b536-9f1fba87a9cf/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jangmine_z/post/4c0f87fd-7726-4527-91b1-db5ba92a9dba/image.gif" alt=""></p>
<h3 id="📌-설계에서-배운-점">📌 설계에서 배운 점</h3>
<ul>
<li>컴포넌트 기반 설계는 각 기능을 분리하여 <strong>명확한 책임 분담</strong> 가능</li>
<li>Player 클래스는 <strong>허브 역할</strong>만 담당하고 <strong>각 기능은 전담 컴포넌트가 수행</strong></li>
<li>애니메이션과 상태, 입력과 로직의 결합을 느슨하게 유지하니 <strong>유지보수성이 높음</strong></li>
</ul>
<blockquote>
<p>이렇게 구성해서 플레이어를 구축해나가니 확실히 새로운 기능들을 추가할 때 큰 어려움이 없이 잘 진행되었던거 같다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity 투척 무기 궤도 시각화[TIL 33일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity-%ED%88%AC%EC%B2%99-%EB%AC%B4%EA%B8%B0-%EA%B6%A4%EB%8F%84-%EC%8B%9C%EA%B0%81%ED%99%94TIL-33%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity-%ED%88%AC%EC%B2%99-%EB%AC%B4%EA%B8%B0-%EA%B6%A4%EB%8F%84-%EC%8B%9C%EA%B0%81%ED%99%94TIL-33%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 28 May 2025 13:39:40 GMT</pubDate>
            <description><![CDATA[<h3 id="✅-오늘의-작업-요약">✅ 오늘의 작업 요약</h3>
<ul>
<li>플레이어가 조준 시 마우스 방향에 따라 <strong>투척 궤도(trajectory)</strong> 를 시각적으로 보여주는 시스템 구현</li>
<li>궤도 계산에 <strong>물리 엔진을 고려한 포물선</strong> 적용</li>
<li>마우스 위치를 기반으로 <strong>45도 위쪽으로 향하는 투척 방향 계산 로직</strong> 구현</li>
</ul>
<hr>
<h3 id="⚙️-trajectorycontroller-핵심-기능-요약">⚙️ TrajectoryController 핵심 기능 요약</h3>
<h4 id="클래스-trajectorycontroller">클래스: <code>TrajectoryController</code></h4>
<table>
<thead>
<tr>
<th>기능</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>Init(throwPoint)</code></td>
<td>시작 위치 설정, LineRenderer 참조 및 비활성화</td>
</tr>
<tr>
<td><code>Show()</code> / <code>Hide()</code></td>
<td>궤도 라인 표시 여부 설정</td>
</tr>
<tr>
<td><code>Update()</code></td>
<td>현재 투척 방향과 힘에 따라 궤도 포인트 계산</td>
</tr>
<tr>
<td><code>GetAimDirection(out float force)</code></td>
<td>마우스 위치 → 투척 방향 및 힘 반환</td>
</tr>
<tr>
<td><code>GetThrowForce(targetPos)</code></td>
<td>목표 위치 거리 기반으로 투척 힘 산정</td>
</tr>
</tbody></table>
<hr>
<h3 id="💡-투척-궤도-계산">💡 투척 궤도 계산</h3>
<pre><code class="language-csharp">void Update()
{
    if (!trajectoryLine.enabled) return;

    Vector3 direction = GetAimDirection(out float force);
    Vector3[] points = new Vector3[pointCount];

    Vector3 pos = throwPoint.position;
    Vector3 velocity = direction * force;
    for (int i = 0; i &lt; pointCount; i++)
    {
        points[i] = pos;
        pos += velocity * timeStep;
        velocity += Physics.gravity * timeStep;
    }

    trajectoryLine.positionCount = pointCount;
    trajectoryLine.SetPositions(points);
}</code></pre>
<ul>
<li>매 프레임마다 <code>pointCount</code> 만큼의 궤도 점 계산</li>
<li>기본적인 투사체 운동 공식 적용 (<code>v = v0 + at</code>, <code>x = x0 + vt</code>)</li>
</ul>
<h3 id="📝-방향-및-힘-계산">📝 방향 및 힘 계산</h3>
<h4 id="🔄-방향-계산">🔄 방향 계산</h4>
<pre><code class="language-csharp">public Vector3 GetAimDirectionForce(out float force)
{
    Ray ray = cam.ScreenPointToRay(Input.mousePosition);
    Plane groundPlane = new Plane(Vector3.up, throwPoint.position);
    if (groundPlane.Raycast(ray, out float enter))
    {
        // 평면에서 위쪽으로 45에 향하는 벡터 구하기
        Vector3 target = ray.GetPoint(enter);
        Vector3 dir = (target - throwPoint.position).normalized;

        Quaternion tilt = Quaternion.AngleAxis(-45f, Vector3.Cross(Vector3.up, dir));
        Vector3 finalDirection = tilt * dir;

        force = GetThrowForce(target);
        return finalDirection;
    }

    force = minThrowForce;
    return transform.forward;
}</code></pre>
<ul>
<li><strong><code>dir</code></strong>: 마우스 클릭 위치까지의 수평 방향 벡터</li>
<li><strong><code>Vector3.Cross(Vector3.up, dir)</code></strong>: 수평 벡터를 기준으로 회전할 축(axis) 계산<ul>
<li>즉, <strong><code>dir</code></strong> 벡터를 위로(45도) 기울이기 위한 회전축</li>
</ul>
</li>
<li><strong><code>Quaternion.AngleAxis(-45f, axis)</code></strong>: 해당 축을 기준으로 -45도 회전하는 회전값(쿼터니언) 생성</li>
<li><strong><code>tilt * dir</code></strong>: 수평 벡터를 위쪽으로 45도 기울인 벡터 반환</li>
</ul>
<blockquote>
<p>목표점 방향을 그대로 향하게 하되, &quot;위로 던지는 느낌&quot;을 주기 위함</p>
</blockquote>
<h4 id="💪-힘-계산">💪 힘 계산</h4>
<pre><code class="language-csharp">public float GetThrowForce(Vector3 targetPos)
{
    float distance = Vector3.Distance(throwPoint.position, targetPos);
    float t = Mathf.Clamp01(distance / maxDistance);
    return Mathf.Lerp(minThrowForce, maxThrowForce, t);
}</code></pre>
<ul>
<li><strong><code>disctance</code></strong>: 던지는 위치와 목표간의 거리 계산</li>
<li><strong><code>t</code></strong>: 위 거리와 최대 거리간의 비율 계산</li>
<li><strong><code>Mathf.Lerp(minThrowForce, maxThrowForce, t)</code></strong>: 최소 파워와 최대 힘 사이의 보간 값</li>
</ul>
<blockquote>
<p>거리에 따른 던지는 힘을 계산하기 위함</p>
</blockquote>
<h3 id="💻-투척-무기-결과물">💻 투척 무기 결과물</h3>
<p><img src="https://velog.velcdn.com/images/jangmine_z/post/12e27436-f341-4cc8-a7f4-f863ecc3d6f8/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity 매니저 설계 원칙과 역할 [TIL 32일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity-%EB%A7%A4%EB%8B%88%EC%A0%80-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99%EA%B3%BC-%EC%97%AD%ED%95%A0-TIL-32%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity-%EB%A7%A4%EB%8B%88%EC%A0%80-%EC%84%A4%EA%B3%84-%EC%9B%90%EC%B9%99%EA%B3%BC-%EC%97%AD%ED%95%A0-TIL-32%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 27 May 2025 14:13:46 GMT</pubDate>
            <description><![CDATA[<p>게임 개발에서 <strong>Manager 클래스</strong>는 여러 시스템과 객체들을 조율하는 핵심 역할을 수행한다.<br>하지만 <strong>&quot;매니저는 관리만 하고, 직접 일하지 않는다&quot;</strong>는 원칙을 명확히 이해하는 것이 중요하다.</p>
<hr>
<h3 id="🔧-매니저의-역할">🔧 매니저의 역할</h3>
<p>매니저는 실제 기능을 수행하기보다는, <strong>객체나 시스템 간의 연결을 관리</strong>하고 <strong>흐름을 조율</strong>하는 것이 핵심이다.</p>
<h4 id="✅-1-의존성-관리">✅ 1. 의존성 관리</h4>
<ul>
<li>서로 관련 있는 객체들 간의 참조를 연결하거나 해제하는 역할</li>
<li>예: UIManager가 모든 UI 패널을 보유하고 필요할 때 Show/Hide 호출</li>
</ul>
<h4 id="✅-2-리소스-관리">✅ 2. 리소스 관리</h4>
<ul>
<li><strong>로드/언로드, 캐싱</strong> 등 리소스 생명 주기를 조절</li>
<li>예: AudioManager가 배경음악 및 효과음을 미리 로드하여 필요한 순간에 재생</li>
</ul>
<h4 id="✅-3-이벤트-조율">✅ 3. 이벤트 조율</h4>
<ul>
<li>게임 전체에서 발생하는 <strong>이벤트 흐름</strong>을 관리</li>
<li>예: GameManager가 게임 시작/종료 시 관련 매니저에 알림을 전달</li>
</ul>
<hr>
<h3 id="🚫-매니저가-해서는-안-되는-일">🚫 매니저가 해서는 안 되는 일</h3>
<p>매니저가 <strong>기능을 직접 구현</strong>하거나 <strong>세부 데이터를 처리</strong>하게 되면<br>책임이 무거워지고 유지보수가 어려워진다.</p>
<ul>
<li>직접적인 게임 로직 구현  </li>
<li>UI 요소의 세부 애니메이션이나 상태 처리  </li>
<li>객체 내부의 세부 데이터를 가공/처리  </li>
</ul>
<blockquote>
<p>즉, 매니저는 <strong>말 그대로 관리자의 역할</strong></p>
</blockquote>
<hr>
<h3 id="📝-좋은-매니저-설계-원칙">📝 좋은 매니저 설계 원칙</h3>
<h4 id="1-단일-책임-원칙-srp">1. 단일 책임 원칙 (SRP)</h4>
<ul>
<li>하나의 매니저는 오직 <strong>하나의 종류의 관리</strong>만 수행해야 한다.</li>
<li>예: AudioManager는 오직 오디오만, UIManager는 오직 UI만 관리</li>
</ul>
<h4 id="2-중앙-집중화-centralization">2. 중앙 집중화 (Centralization)</h4>
<ul>
<li>관련된 리소스/기능을 <strong>한 곳에 모아 효율적으로 조율</strong></li>
<li>예: DataManager가 모든 기획 데이터를 로드/파싱하여 제공</li>
</ul>
<h4 id="3-확장-가능성">3. 확장 가능성</h4>
<ul>
<li>매니저는 추후 시스템이 확장되더라도 쉽게 <strong>기능 추가/교체</strong>가 가능해야 한다.</li>
<li>예: 이벤트 기반 구조를 적용해 유연한 알림 시스템 구축</li>
</ul>
<hr>
<h3 id="🧠-정리">🧠 정리</h3>
<ul>
<li>매니저는 <strong>관리자이지, 기능 담당자가 아니다.</strong></li>
<li>설계 시 <strong>책임 분리와 중앙화</strong>를 잘 지키면 시스템이 깔끔하고 확장 가능해진다.</li>
<li>Unity 개발에서 <strong>&quot;매니저 남용&quot;은 코드 복잡도를 급격히 높이는 요인</strong>이므로<br>항상 목적에 맞는 최소한의 매니저만 설계하자.</li>
</ul>
<hr>
<blockquote>
<p>매니저는 <strong>관리와 조율에 집중하고, 세부 구현은 각 객체에게 맡기자!</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity3D 마우스를 기준으로 회전하는 플레이어[TIL 31일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity3D-%EB%A7%88%EC%9A%B0%EC%8A%A4%EB%A5%BC-%EA%B8%B0%EC%A4%80%EC%9C%BC%EB%A1%9C-%ED%9A%8C%EC%A0%84%ED%95%98%EB%8A%94-%ED%94%8C%EB%A0%88%EC%9D%B4%EC%96%B4TIL-31%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity3D-%EB%A7%88%EC%9A%B0%EC%8A%A4%EB%A5%BC-%EA%B8%B0%EC%A4%80%EC%9C%BC%EB%A1%9C-%ED%9A%8C%EC%A0%84%ED%95%98%EB%8A%94-%ED%94%8C%EB%A0%88%EC%9D%B4%EC%96%B4TIL-31%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 26 May 2025 13:47:00 GMT</pubDate>
            <description><![CDATA[<h3 id="✅-마우스를-기준으로-회전하는-플레이어-만들기">✅ 마우스를 기준으로 회전하는 플레이어 만들기</h3>
<p>3D 공간에서 <strong>마우스 위치를 기준</strong>으로 캐릭터 <strong>회전 방향을 결정</strong>하고 싶을 때,
마우스 포지션을 <strong>월드 좌표의 평면상 위치로 변환</strong>하는 것이 필요하다.
이때, Unity의 Plane과 Ray를 활용하여 구현 할 수 있다.</p>
<h3 id="📌-planeraycast">📌 Plane.Raycast</h3>
<pre><code class="language-csharp">Plane groundPlane = new Plane(Vector3.up, Vector3.zero);</code></pre>
<ul>
<li>이 코드는 y = 0에 해당하는 가상의 바닥 평면을 생성한다.</li>
<li>Vector3.up은 평면의 법선 벡터이며, Vector3.zero는 평면이 지나는 점.<blockquote>
<p>즉, 씬에 별도로 바닥 오브젝트가 없어도, 수학적으로 평면을 하나 만들어 사용할 수 있다.</p>
</blockquote>
</li>
</ul>
<pre><code class="language-csharp">Ray ray = cam.ScreenPointToRay(mouseScreenPos);</code></pre>
<ul>
<li>마우스 화면 좌표를 기준으로 <strong>카메라에서 쏘는 Ray</strong>를 만든다.</li>
</ul>
<pre><code class="language-csharp">if (groundPlane.Raycast(ray, out float point))
{
    Vector3 hitPoint = ray.GetPoint(point);
}</code></pre>
<ul>
<li>이 Ray가 groundPlane과 충돌하는 <strong>지점까지의 거리(point)</strong>를 계산하고,</li>
<li>실제 월드상의 <strong>충돌 위치 (hitPoint)</strong>를 구할 수 있다.</li>
</ul>
<blockquote>
<p>왜 사용하는가 ?</p>
</blockquote>
<ul>
<li>마우스 포지션은 2D 화면 좌표</li>
<li>이를 3D 공간의 위치로 변환하려면,<ul>
<li>어떤 &quot;기준 평면&quot;에 마우스가 닿는 지점을 계산</li>
</ul>
</li>
<li>Plane.Raycast는 그 평면과 Ray가 만나는 지점을 찾음</li>
<li>씬에 별도의 Collider가 없어도 동작</li>
</ul>
<h3 id="📝-회전-처리-코드">📝 회전 처리 코드</h3>
<pre><code class="language-csharp">// Input 관련 코드
public void OnLook(InputAction.CallbackContext context)
{
    Vector2 mouseScreenPos = context.ReadValue&lt;Vector2&gt;();

    Ray ray = cam.ScreenPointToRay(mouseScreenPos);
    Plane groundPlane = new Plane(Vector3.up, Vector3.zero); // y=0인 평면

    if (groundPlane.Raycast(ray, out float point))
    {
        Vector3 hitPoint = ray.GetPoint(point); // 바닥과의 교점
        Vector3 direction = hitPoint - transform.position;
        direction.y = 0; // y축 제거

        if (direction.magnitude &lt; 0.9f)
        {
            lookDirection = Vector3.zero; // 너무 가까우면 무시
        }
        else
        {
            lookDirection = direction.normalized;
        }
    }
}

// 실제 회전
private void Rotate()
{
    float angle = Mathf.Atan2(lookDirection.x, lookDirection.z) * Mathf.Rad2Deg;
    transform.rotation = Quaternion.Euler(0f, angle, 0f);
}</code></pre>
<h3 id="💻-동작-화면">💻 동작 화면</h3>
<p><img src="https://velog.velcdn.com/images/jangmine_z/post/2ae930b3-0ea1-489f-9569-1bf68d095eb4/image.gif" alt=""></p>
<h3 id="💡-느낀-점">💡 느낀 점</h3>
<p>Plane.Raycast를 활용해 가상의 평면을 만들고, 해당 평면에 닿는 지점을 기준으로 플레이어의 회전 방향을 계산하는 방식이 <strong>Collider 없이 동작한다는 점</strong>이 신기했다.</p>
<p>이 방식을 활용해서, 나중에 NavMesh와 결합하여 <strong>마우스 클릭으로 원하는 위치로 이동하는 시스템</strong>도 만들어보고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[게임 데이터 설계 [TIL 30일차]]]></title>
            <link>https://velog.io/@jangmine_z/%EA%B2%8C%EC%9E%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%84%A4%EA%B3%84-TIL-30%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/%EA%B2%8C%EC%9E%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%84%A4%EA%B3%84-TIL-30%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Fri, 23 May 2025 12:29:07 GMT</pubDate>
            <description><![CDATA[<h3 id="🔧-변하지-않는-데이터-vs-변하는-데이터">🔧 변하지 않는 데이터 vs 변하는 데이터</h3>
<p>게임 데이터를 설계할 때는 불변 데이터와 변동 데이터를 구분하는 것이 중요하다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>설명</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td><strong>변하지 않는 데이터</strong></td>
<td>게임 룰, 밸런스처럼 게임 실행 중에는 바뀌지 않는 데이터</td>
<td>캐릭터 레벨별 스탯, 몬스터 출현 정보, 스테이지 구성</td>
</tr>
<tr>
<td><strong>변하는 데이터</strong></td>
<td>플레이어의 상태처럼 플레이 도중 바뀌는 데이터</td>
<td>현재 경험치, 체력, 아이템, 퀘스트 진행도, 클리어한 스테이지, 설정값 등</td>
</tr>
</tbody></table>
<hr>
<h3 id="🗂️-기획-테이블-활용-방식">🗂️ 기획 테이블 활용 방식</h3>
<p>기획자 중심의 콘텐츠 제작을 위해 <strong>데이터 수정이 쉽고 직관적</strong>이어야 하며, 보통 다음과 같은 형태로 데이터를 관리한다.</p>
<h4 id="✅-csv-엑셀-기반">✅ CSV (엑셀 기반)</h4>
<ul>
<li>기획자가 엑셀로 작성한 데이터를 CSV로 저장 후 Unity에서 파싱</li>
<li>가장 쉬운 접근법이며 가볍고 빠름</li>
</ul>
<h4 id="✅-json-파일">✅ JSON 파일</h4>
<ul>
<li>CSV보다 구조화된 데이터 표현이 가능</li>
<li>중첩된 배열, 복잡한 구성에 적합</li>
</ul>
<pre><code class="language-csharp">// 저장
PlayerSaveData data = new PlayerSaveData();
string json = JsonUtility.ToJson(data);
File.WriteAllText(path, json);

// 불러오기
if (File.Exists(path)) {
    string json = File.ReadAllText(path);
    PlayerSaveData data = JsonUtility.FromJson&lt;PlayerSaveData&gt;(json);</code></pre>
<h4 id="✅-scriptableobject">✅ ScriptableObject</h4>
<ul>
<li>Unity 에디터 상에서 직접 데이터를 관리할 수 있는 에셋</li>
<li>변하지 않는 데이터 관리에 적합</li>
<li><code>Resources</code> 또는 <code>Addressables</code>를 통해 로드</li>
<li>외부 JSON을 파싱한 데이터를 ScriptableObject에 적용하는 방식도 가능</li>
</ul>
<h4 id="✅-서버-데이터">✅ 서버 데이터</h4>
<ul>
<li>실시간 변경이 필요한 데이터는 서버에서 관리</li>
<li>원격 수정 및 유저 개인화 데이터에 적합 (예: 이벤트 정보, 랭킹 등)</li>
</ul>
<blockquote>
<p><strong>직렬화용 클래스를 따로 만들어 데이터 포맷을 관리하고 검증 필요</strong></p>
</blockquote>
<hr>
<h3 id="📝-파싱과-참조-설계">📝 파싱과 참조 설계</h3>
<p>게임 데이터를 저장하거나 불러올 때는
<strong>전체 데이터를 복사하거나 중복 저장하는 대신, 참조 기반 구조로 설계</strong>하는 것이 좋다.</p>
<p>예를 들어, 아이템의 이름, 설명, 기본 능력치 등은
<strong>변하지 않는 기준 데이터</strong>로 <code>ScriptableObject</code>나 <code>JSON</code> 파일을 통해 관리하고,</p>
<p>플레이어가 실제로 획득한 아이템은
해당 <strong>기준 데이터를 참조하는 인스턴스 클래스</strong>(<code>ItemInstance</code> 등)로 관리</p>
<p>이 인스턴스는 <strong>플레이 중 바뀌는 값만 따로 저장한다.</strong></p>
<blockquote>
<p>즉, <strong>&quot;불변 데이터는 참조로, 변하는 값만 별도로 저장하는 구조&quot;</strong>가 좋다.</p>
</blockquote>
<hr>
<h3 id="📌-정리">📌 정리</h3>
<ul>
<li><p>게임 데이터는 <strong>불변 데이터</strong>(룰, 밸런스 등)와 <strong>가변 데이터</strong>(플레이어 상태 등)를 <strong>구분하여 설계</strong>해야 <strong>유지보수성</strong>과 <strong>확장성</strong>이 높아진다.</p>
</li>
<li><p><code>CSV</code>, <code>JSON</code>, <code>ScriptableObject</code>, <code>서버 데이터</code> 등 다양한 방식으로 기획 데이터를 관리하며, 각 방식의 특성과 용도에 맞게 선택해야 한다.</p>
</li>
<li><p><strong>직렬화 클래스를 따로 두어 데이터를 안정적으로 저장</strong>하고, 검증을 통해 <strong>데이터 무결성을 보장</strong>해야 한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Data Driven 방식 [TIL 29일차]]]></title>
            <link>https://velog.io/@jangmine_z/Data-Driven-%EB%B0%A9%EC%8B%9D-TIL-29%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Data-Driven-%EB%B0%A9%EC%8B%9D-TIL-29%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 22 May 2025 12:26:48 GMT</pubDate>
            <description><![CDATA[<h2 id="📚-data-driven">📚 Data Driven</h2>
<ul>
<li>게임의 다양한 설정 값이나 로직 <strong>데이터를 코드에 직접 하드코딩하지 않고</strong>,</li>
<li><em>JSON*</em>, <strong>CSV</strong>, <strong>ScriptableObject</strong> 등의 <strong>외부 데이터로 분리하여 처리하는 설계 방식</strong>이다.</li>
</ul>
<hr>
<h3 id="✅-데이터-드리븐-방식의-장점">✅ 데이터 드리븐 방식의 장점</h3>
<ul>
<li><p>빠른 수정과 반복 테스트 용이
→ 코드 수정 없이 밸런스나 콘텐츠 수치를 변경할 수 있어 작업 속도가 빨라진다.</p>
</li>
<li><p>비개발자도 접근 가능
→ 기획자나 밸런서 등 개발 지식이 없는 인원도 데이터를 손쉽게 수정해 테스트 가능.</p>
</li>
<li><p>하드코딩 방지 → 코드가 깔끔해짐
→ 수치가 코드에 직접 박혀 있지 않기 때문에 유지보수성과 가독성이 향상된다.</p>
</li>
<li><p>빌드 없이 콘텐츠 수정 가능
→ 실시간으로 데이터만 교체하여 빠르게 게임에 적용 가능. 빠른 A/B 테스트와 튜닝에 유리하다.</p>
</li>
</ul>
<hr>
<h2 id="parser--parser-tool">Parser &amp; Parser Tool</h2>
<h3 id="🧾-파싱parsing이란">🧾 파싱(Parsing)이란?</h3>
<blockquote>
<p>파싱은 <strong>데이터를 읽어서 이해할 수 있는 형태로 바꾸는 작업</strong></p>
</blockquote>
<p>보통 우리가 사용하는 JSON, CSV, 구글 시트 같은 데이터는 <strong>텍스트</strong>로 되어있음,
이걸 게임에서 쓰려면, 숫자나 문자열을 꺼내서 <strong>코드에서 쓸 수 있는 형태</strong>로 바꿔야함
이 과정을 바로 <strong>파싱(Parsing)</strong> 이라고 한다.</p>
<pre><code class="language-json">{ &quot;name&quot;: &quot;Slime&quot;, &quot;hp&quot;: 50, &quot;attack&quot;: 10 }</code></pre>
<p>이런 JSON 데이터를 읽어서,
<code>MonsterData</code>라는 클래스에 <code>name: string</code>, <code>hp: float</code>, <code>attack: float</code> 같은 변수로 채워 넣는 것</p>
<h3 id="🔧-파서-툴parser-tool이란">🔧 파서 툴(Parser Tool)이란?</h3>
<blockquote>
<p>파서 툴은 위처럼 데이터를 읽어서 클래스나 데이터 파일로 <strong>자동으로 변환해주는 프로그램</strong></p>
</blockquote>
<ul>
<li>구글 시트 → CSV → 게임용 데이터 클래스 생성</li>
<li>JSON 파일 → ScriptableObject 생성</li>
<li>CSV → <code>.cs</code> 클래스 자동 생성 + 인스턴스화</li>
</ul>
<p>즉, <strong>기획자가 만든 수많은 데이터를 자동으로 가져와 코드에 쓸 수 있도록 만들어주는 도구</strong></p>
<hr>
<h3 id="⚠️-parser-tool파서-툴-설계-시-유의사항">⚠️ Parser Tool(파서 툴) 설계 시 유의사항</h3>
<ul>
<li><strong>Unity 에디터 내부보다는 독립적인 외부 툴(C# WinForm, HTML, 독립 실행 파일 등)을 권장</strong><ul>
<li>에디터 내부에 파서를 만들 경우, 시트 변경 → 즉시 클래스 반영 → 게임에 바로 영향이라는 구조가 너무 위험할 수 있다.</li>
<li>파서 자체가 에러를 내면 Unity에서 스크립트 전체가 컴파일되지 않아 테스트 자체가 불가능해질 수 있다.</li>
</ul>
</li>
<li><strong>데이터는 자동 반영보다 수동 반영 구조가 안정적</strong><ul>
<li>빌드 시점 또는 명확한 검증 절차를 거친 후 반영하는 것이 안전하다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="📌-정리">📌 정리</h3>
<p>위 방식들은 <strong>실무에서 사용되는 방식</strong>들이므로,
우선 최대한 데이터를 직접 코드에 집어넣는 하드코딩의 방식대신 <strong>데이터를 한 곳에 모아 관리</strong>하며 사용하는 방식으로 먼저 코딩 습관을 들이고,
관련한 내용을 좀 더 학습하며 사용해보아야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers - 귤고르기 (C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-%EA%B7%A4%EA%B3%A0%EB%A5%B4%EA%B8%B0-C</link>
            <guid>https://velog.io/@jangmine_z/Programmers-%EA%B7%A4%EA%B3%A0%EB%A5%B4%EA%B8%B0-C</guid>
            <pubDate>Thu, 22 May 2025 00:22:26 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>경화는 과수원에서 귤을 수확했습니다. 경화는 수확한 귤 중 &#39;k&#39;개를 골라 상자 하나에 담아 판매하려고 합니다. 그런데 수확한 귤의 크기가 일정하지 않아 보기에 좋지 않다고 생각한 경화는 귤을 크기별로 분류했을 때 서로 다른 종류의 수를 최소화하고 싶습니다.</p>
<p>예를 들어, 경화가 수확한 귤 8개의 크기가 [1, 3, 2, 5, 4, 5, 2, 3] 이라고 합시다. 경화가 귤 6개를 판매하고 싶다면, 크기가 1, 4인 귤을 제외한 여섯 개의 귤을 상자에 담으면, 귤의 크기의 종류가 2, 3, 5로 총 3가지가 되며 이때가 서로 다른 종류가 최소일 때입니다.</p>
<p>경화가 한 상자에 담으려는 귤의 개수 <code>k</code>와 귤의 크기를 담은 배열 <code>tangerine</code>이 매개변수로 주어집니다. 경화가 귤 k개를 고를 때 크기가 서로 다른 종류의 수의 최솟값을 return 하도록 solution 함수를 작성해주세요.</p>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li>1 ≤ <code>k</code> ≤ <code>tangerine</code>의 길이 ≤ 100,000</li>
<li>1 ≤ <code>tangerine</code>의 원소 ≤ 10,000,000</li>
</ul>
<hr>
<h2 id="💻-나의-풀이-c">💻 나의 풀이 (C#)</h2>
<pre><code class="language-csharp">using System;
using System.Linq;

public class Solution {
    public int solution(int k, int[] tangerine) {
        int answer = 0;
        int count = 0;
        int curTangerine = 0;

        int[] sortedTangerine = tangerine
            .GroupBy(x =&gt; x)
            .OrderByDescending(g =&gt; g.Count())
            .SelectMany(g =&gt; g)
            .ToArray();

        while(count &lt; k)
        {
            if(sortedTangerine[count] != curTangerine)
            {
                curTangerine = sortedTangerine[count];
                answer++;
            }

            count++;   
        }


        return answer;
    }
}</code></pre>
<blockquote>
<p><code>GroupBy</code>로 같은 원소끼리 묶고,
<code>OrderByDescending</code>으로 묶은 원소의 개수가 많은 순으로 정렬,
<code>SelectMany</code>로 다시 그룹 해제하여,
종류가 가장 많은 순으로 정렬하여 문제해결</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity 아이템 툴바 만들기 [TIL 28일차]]]></title>
            <link>https://velog.io/@jangmine_z/Unity-%EC%95%84%EC%9D%B4%ED%85%9C-%ED%88%B4%EB%B0%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-TIL-28%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jangmine_z/Unity-%EC%95%84%EC%9D%B4%ED%85%9C-%ED%88%B4%EB%B0%94-%EB%A7%8C%EB%93%A4%EA%B8%B0-TIL-28%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 21 May 2025 12:55:55 GMT</pubDate>
            <description><![CDATA[<h3 id="🎯-목표">🎯 목표</h3>
<ul>
<li>게임 플레이 도중 <strong>숫자 키(1~0)를 이용해 슬롯을 선택</strong>하고, 선택된 아이템을 사용할 수 있는 <strong>툴바 시스템을 구현</strong>하는 것이 목표였다.</li>
<li>아이템은 포션과 같은 <strong>소비형 아이템</strong>과 <strong>장비형 아이템</strong>으로 나뉘며, 툴바 슬롯에 등록된 아이템을 사용하면 해당 효과가 적용되도록 만들었다.</li>
</ul>
<h2 id="🧱-주요-클래스-구성">🧱 주요 클래스 구성</h2>
<h3 id="📃-toolbar-클래스">📃 ToolBar 클래스</h3>
<ul>
<li><strong><code>InputAction</code></strong>: 숫자 키 입력 감지 및 슬롯 선택 </li>
<li><strong><code>UseItem()</code></strong>: 아이템 사용 처리 </li>
<li><strong><code>ApplyPotionEffect()</code></strong>: 아이템 효과 적용</li>
<li>*<em><code>AddItemToSlot()</code>: *</em>슬롯 관리 및 아이템 추가 </li>
</ul>
<h4 id="inputaction을-통해-현재-선택된-슬롯-판별">InputAction을 통해 현재 선택된 슬롯 판별</h4>
<pre><code class="language-csharp">public void OnSelectSlot(InputAction.CallbackContext context)
{
    if (context.phase == InputActionPhase.Started)
    {
        string keyString = context.control.name;

        if (int.TryParse(keyString, out int keyNum))
        {
            int slotIdx = keyNum - 1 != -1 ? keyNum - 1 : itemSlots.Length - 1;

            ItemSlot slot = itemSlots[slotIdx];

            if (selectedSlot != null &amp;&amp; selectedSlot != slot)
            {
                selectedSlot.DeSelectSlot();
                ClearHand();
            }

            selectedSlot = slot;
            selectedSlot.SelectSlot();
        }
    }
}</code></pre>
<ul>
<li><code>InputAction.CallbackContext</code>는 <code>Input System</code>에서 이벤트가 발생했을 때 전달되는 입력 정보 컨테이너</li>
<li>어떤 키가 눌렸는지를 판별해서 <strong>해당 인덱스의 아이템 슬롯을 선택</strong>하는 역할을 수행한다.</li>
</ul>
<hr>
<h3 id="📃-itemslot-클래스">📃 ItemSlot 클래스</h3>
<ul>
<li><strong><code>SetSlot()</code></strong>: 슬롯에 아이템 등록, 아이템 수량 표시 및 스택 처리</li>
<li><strong><code>SelectSlot() / DeSelectSlot()</code></strong>: 슬롯 선택/해제 시 외곽선 효과</li>
<li><strong><code>ClearSlot()</code></strong>: 아이템 사용 시 수량 감소 및 초기화</li>
</ul>
<h4 id="슬롯에-아이템이-추가-가능한지-판별하는-메서드">슬롯에 아이템이 추가 가능한지 판별하는 메서드</h4>
<pre><code class="language-csharp">public bool CanAddItem(ItemData data)
{
    if (dataSO != null)
    {
        if (data == dataSO &amp;&amp; quantity &lt; dataSO.maxAmount)
            return true;
        else
            return false;
    }

    return true;
}</code></pre>
<hr>
<h3 id="💻-실제-화면">💻 실제 화면</h3>
<p><img src="https://velog.velcdn.com/images/jangmine_z/post/297370a2-0c81-4650-b72b-4d3f12ff57fe/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Programmers - 주식가격 (C#)]]></title>
            <link>https://velog.io/@jangmine_z/Programmers-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9-C</link>
            <guid>https://velog.io/@jangmine_z/Programmers-%EC%A3%BC%EC%8B%9D%EA%B0%80%EA%B2%A9-C</guid>
            <pubDate>Wed, 21 May 2025 02:53:45 GMT</pubDate>
            <description><![CDATA[<h3 id="📝-문제-설명">📝 문제 설명</h3>
<blockquote>
<p>아래는 프로그래머스에서 제공한 문제 설명입니다.</p>
</blockquote>
<p>초 단위로 기록된 주식가격이 담긴 배열 prices가 매개변수로 주어질 때, 가격이 떨어지지 않은 기간은 몇 초인지를 return 하도록 solution 함수를 완성하세요.</p>
<hr>
<h3 id="❌-제한사항">❌ 제한사항</h3>
<ul>
<li>prices의 각 가격은 1 이상 10,000 이하인 자연수입니다.</li>
<li>prices의 길이는 2 이상 100,000 이하입니다.</li>
</ul>
<hr>
<h2 id="💻-완전-탐색-c">💻 완전 탐색 (C#)</h2>
<pre><code class="language-csharp">using System;

public class Solution {
    public int[] solution(int[] prices) {
        int[] answer = new int[prices.Length];

        for(int i = 0; i &lt; prices.Length; i++)
        {
            bool isDrop = false;

            for(int j = i + 1; j &lt; prices.Length; j++)
            {
                if(prices[i] &gt; prices[j])
                {
                    answer[i] = j - i;
                    isDrop = true;
                    break;
                }
            }

            if(!isDrop)
                answer[i] = prices.Length - (i + 1);
        }
        return answer;
    }
}</code></pre>
<h2 id="💻-stack-활용-c">💻 Stack 활용 (C#)</h2>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;

public class Solution {
    public int[] solution(int[] prices) {
        int length = prices.Length;
        int[] answer = new int[length];
        Stack&lt;int&gt; stack = new Stack&lt;int&gt;();

        for(int i = 0; i &lt; length; i++)
        {
            while(stack.Count &gt; 0 &amp;&amp; prices[i] &lt; prices[stack.Peek()])
            {
                int idx = stack.Pop();
                answer[idx] = i - idx;
            }

            stack.Push(i);
        }

        while(stack.Count &gt; 0)
        {
            int idx = stack.Pop();
            answer[idx] = length - (idx + 1);
        }

        return answer;
    }
}</code></pre>
<blockquote>
<p>완전 탐색의 경우 <strong>시간 복잡도가 최악의 경우 O(n^2)</strong> 으로 <code>prices[]</code>의 크기가 너무 큰 입력값이 들어오면 시간 초과가 발생할 수 있다.</p>
<p>하지만 Stack을 활용한 경우 <strong>시간 복잡도는 O(N)</strong>으로 효율성이 훨씬 좋다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>