<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hipop1109-dev.log</title>
        <link>https://velog.io/</link>
        <description>쑥쑥 개발자</description>
        <lastBuildDate>Sat, 28 Feb 2026 12:56:55 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hipop1109-dev.log</title>
            <url>https://velog.velcdn.com/images/hipop1109-dev/profile/53eccbaa-9229-4ffe-b20e-c3c8d9030152/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hipop1109-dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hipop1109-dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[3D 신작 개발일지 (1) 블렌더 Cell fracture]]></title>
            <link>https://velog.io/@hipop1109-dev/3D-%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-1-%EB%B8%94%EB%A0%8C%EB%8D%94-Cell-fracture</link>
            <guid>https://velog.io/@hipop1109-dev/3D-%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-1-%EB%B8%94%EB%A0%8C%EB%8D%94-Cell-fracture</guid>
            <pubDate>Sat, 28 Feb 2026 12:56:55 GMT</pubDate>
            <description><![CDATA[<p>3D 신작 게임을 개발하던 중 생각했던 기획을 위해 총이 어떤 물건에 맞으면 산산조각 부숴지는 기능을 구현해야 했다</p>
<p>그러나 이를 유니티 자체에서 해결할 수는 없어서 블렌더에서 미리 오브젝트를 쪼개서 유니티에 넣어보는 방식을 채택했다</p>
<p>그러기 위해 필요한 방식이 바로 블렌더의 플러그인인 Cell Fracture이다</p>
<p>블렌더 4.3.0 기준으로
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/d307b47e-67d6-4e32-830f-487a5c9fcb12/image.png" alt="">
Get Extentions로 Cell Fracture를 다운받아서 
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/45e0b5db-56ca-4be0-b38a-a41c612a0b0c/image.png" alt="">
Add-ons로 이를 적용한다
그렇게 한 후 원하는 모델을 블렌더에 끌고오면 되는데</p>
<p>여기서 주의할 점은 오브젝트를 쪼갤 때 기본 오브젝트가 너무 작으면 조각들의 틈이 벌어져서 어색한 상황이 생긴다, 이를 방지하기 위해 오브젝트 자체를 크게 해서 적용하고 유니티에서 축소시키는 방식을 추천한다.
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/78663f61-ed7d-4a4e-a8ce-979fb92acb0c/image.png" alt="">
끌고 오면 보이는 화면에서 Scale을 미리 조정하면 조금 더 편하다</p>
<p>이 작업을 거친 후 Objects -&gt; Quick Effect -&gt; Cell Fracuture로 들어간다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/725346fe-52b9-472c-a5a1-6fb544a86cda/image.png" alt="">
 그렇게 하면 이런 창이 뜬다
 <img src="https://velog.velcdn.com/images/hipop1109-dev/post/75557892-1671-4767-b670-14b6fbf52568/image.png" alt="">
위쪽 Point Source는 구분할 기준을 나누는 것이다
verts는 버텍스, particles는 파티클을 기준으로 나누니 본인의 오브젝트의 특성을 보고 결정하면 될 것이다.</p>
<p>Source Limit은 조각의 제한을 두는 것인데 몇 번 해보니 완벽하게 저 숫자의 제한을 가지지는 않지만 그래도 어느 정도 비슷한 값이 나온다</p>
<p>Noise는 조각 내부의 곡선값을 노이즈라고 명명해 더해주는 듯 하다, 내 기준에서는 높일수록 자연스러운 부분들이 나왔다</p>
<p>Recursive Shatter는 재귀 분할에 대한 부분인데
Recursion은 몇 번 재귀해 분할할 것이냐를 나타내는 부분이다, 이를 넓힐수록 조각 자체가 재귀되어 분리돼 더 자연스러운 조각을 나누게 한다</p>
<p>아래 부분은 재귀 시 어떤 조각 크기로 잡을 것인지, 랜덤값을 얼마나 줄 것인지 등등의 요소들을 정하는 것이다</p>
<p>아래 부분들은 그렇게 중요한 부분은 아니니 패스하고...</p>
<p>중요한 부분은 머터리얼 쪽에 있다
기본적으로 우리가 쓰는 에셋들은 겉면에만 머터리얼이 존재한다
그렇기 때문에 쪼개게 되면 안쪽에도 머터리얼 색을 발라줘야 한다, 그러나 기본적으로 에셋 내장 머터리얼은 그런 것이 포함되어 있지 않을 것이기 때문에 미리 밑작업을 해줘야 한다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/1af70f40-4328-4d2b-9fb2-b778dfc36db5/image.png" alt="">
오브젝트 자체에 머터리얼을 하나 더 추가한 후
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/ebfd2202-ae71-41c7-9422-bcb00d81d6a0/image.png" alt="">
Mesh Data 내 Material을 하나 추가해서 분리하면 된다</p>
<p>결론적으로 분리해보자면
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/4ffacd9e-6d60-49e9-a5b2-a5e71979f1b2/image.png" alt="">
이런 식으로 치즈가 분리가 되는 모습을 볼 수 있다
그러나 지금 오브젝트 목록에 원본이 남아있는 상태이기 때문에 편하게 가져가고 싶으면 이를 지우는 것도 좋은 선택이 될 수 있고
안쪽에 카메라와 라이트도 제거하고 가는 것이 편하다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/113e02e2-577c-4245-b549-54bde94474b2/image.png" alt="">
대신 라이트를 제거하고 가면 오른쪽 Object Types에서 Lamp를 꺼야 Export가 된다</p>
<p>그렇게 유니티에서 보면
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/7ef7f29b-c219-476b-9a57-7197f65d0f7d/image.png" alt="">
그럼 머터리얼을 기본적으로 2개를 넣어준 채로 유니티에서 적용할 수 있다
그리고 적절하게 따로 머터리얼을 만들어서 적용하면 안쪽까지 원하는 색을 넣은 부숴진 조각을 만들 수 있다</p>
<p>물론 따로 적용하지 않는다면 단색 머터리얼밖에 집어넣지 못해서 단색 속살만 볼 수 있다는 단점은 있지만 그 부분은 감수해야 하는 부분이라고 생각한다</p>
<p>그리고 가장 짜증났던 점은 지금 하고 있는 야채들은 버텍스가 좀 들쑥날쑥한 경향이 있기 때문에 쪼갤 때 많은 오류가 일어났던 것이다</p>
<p>예를 들어
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/0628fc69-d61d-4bca-b7ed-dbb5161a54ba/image.png" alt="">
이렇게 쪼갤 때 생기는 잔상들이 지워지지 않고 남아있다는 점이다
이를 해결하는 법은.. 없더라고요ㅋㅋ
그냥 랜덤으로 계속 뽑다보면 가끔 괜찮은게 나오더라고요? 화이팅입니다ㅎㅎ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[유니티 개발일지 외전 2 : 안티그라비티 + 클로드 or 코덱스]]></title>
            <link>https://velog.io/@hipop1109-dev/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%EC%99%B8%EC%A0%84-2-%EC%95%88%ED%8B%B0%EA%B7%B8%EB%9D%BC%EB%B9%84%ED%8B%B0-%ED%81%B4%EB%A1%9C%EB%93%9C-or-%EC%BD%94%EB%8D%B1%EC%8A%A4</link>
            <guid>https://velog.io/@hipop1109-dev/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%EC%99%B8%EC%A0%84-2-%EC%95%88%ED%8B%B0%EA%B7%B8%EB%9D%BC%EB%B9%84%ED%8B%B0-%ED%81%B4%EB%A1%9C%EB%93%9C-or-%EC%BD%94%EB%8D%B1%EC%8A%A4</guid>
            <pubDate>Fri, 20 Feb 2026 22:54:51 GMT</pubDate>
            <description><![CDATA[<p>요즘 안티그라비티를 통해 개발을 아주 편하게 진행하고 있다, 그러나 결론적으로는 한 AI만을 활용해 개발을 진행하는 것이기 때문에 오차를 발견하지 못하는 경우가 많다, 또 계속 한 AI만 쓰면 토큰이 빠지는 속도도 가속화될거다</p>
<p>그래서 현재 아직은 무료로 재미나이 최신버전을 제공해주는 안티그라비티를 챗지피티를 활용하는 코덱스와 겹쳐쓰면서 각 AI들을 둘 다 활용해 더 질 좋은 코드를 만들어보려고 한다</p>
<p>사실 원래 클로드를 쓰는게 나을거 같긴 한데 이미 지피티를 구독해놨기 때문에 이를 먼저 활용해보도록 하겠다</p>
<p>우선 안티그라비티에서 왼쪽 확장을 누른 후 코덱스를 다운받는다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/a143f386-5f40-4ea4-b5b9-c9f4330284cc/image.png" alt="">
그럼 왼쪽에 코덱스 아이콘이 뜰 거고, 이를 클릭해 로그인 과정을 거치면 된다, 클로드도 마찬가지로 적용된다</p>
<p>이 시점에서 내가 만들고 싶은 코드를 예시로 설명해보았다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/3035bb20-e6ad-4879-832d-9bea0a8ba18d/image.png" alt="">
이런 식으로 KeySound_Plan이라는 문서를 직접 만들어 넣어달라고 안티그라비티에 요청해서 넣게 되면 재미나이가 문서를 직접 만들어주게 된다, 이를 보완하기 위해 클로드에서 다시 이를 받아
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/69314a4a-c5fc-4db9-867a-ee54c8054643/image.png" alt="">
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/4e1227fe-3bb0-427e-ae1b-2b5f386e5d17/image.png" alt="">
이런 식으로 설계 자체를 리팩토링해줄 수 있게 된다, 이는 명령하는 방향성에 따라 달라질 수 있지만 테스트적으로 적용해봤을 때 나름 내가 놓치거나 필요해보이는 허점들을 잘 잡아주었다</p>
<p>이렇게 바뀐 KeySound_Plan을 안티그라비티에서 코드를 작성해달라고 부탁한다, 그리고 이렇게 바뀐 코드를 다시 코덱스에 부탁하고.. 이렇게 교차검증을 하면서 코드를 강화하는 방식을 활용해보았다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/a69729b3-7ceb-4f79-975c-c95b3f0989cb/image.png" alt="">
이런 식으로 안티그라비티가 낸 오류들이나 허점들을 수정해주는 것을 볼 수 있다, 사실 두 코드를 다 비교해봤을 때 그렇게 어려운 코드들이 아니라 쓸데없는 것을 바꾸거나 그냥 방어코드를 몇 개 더 넣는 모습도 보여주고 있었다</p>
<p>그러나 확실히 버그 자체를 고쳐줄 수 있고 다른 AI의 시점에서 코드를 다시 체크해볼 수 있다는 점은 이득일 수 있을 듯 하다</p>
<p>그리고 지금 두 AI에게 모두 계속 물어보는 과정을 거쳐야 하는데, 이를 자동화하는 방법에 대해 조금 고민해보려 한다</p>
<p>음.. 아무튼 더 깎아봐야 잘 쓸 수 있을듯?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 (5) 1322 X와 K]]></title>
            <link>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-5-1322-X%EC%99%80-K</link>
            <guid>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-5-1322-X%EC%99%80-K</guid>
            <pubDate>Sat, 14 Feb 2026 09:19:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/4d789235-4525-4a94-9413-9156fd507942/image.png" alt="">
안 약한 부분이 있겠냐만 비트 쪽은 잘 모르는 분야라 일단 머리를 부딪혀서 풀어보았다</p>
<pre><code>    int X, K;
    cin &gt;&gt; X &gt;&gt; K;
    // K번째 작은 숫자니까


    while (K != 0) {
        Y++;
        int result = X &amp; Y;
        if (result == 0) K--;
    }

    cout &lt;&lt; Y;</code></pre><p>근데 이러면 하나씩 보는거라 시간초과가 당연히 나는 구조이다</p>
<p>그래서 다음 발상은 비트 연산자를 쭉쭉 펼쳐서 0과 1을 비교 구분해 밀어넣는 방식을 사용해봤다</p>
<pre><code>vector&lt;int&gt; Xbits;
vector&lt;int&gt; Kbits;
vector&lt;int&gt; Ansbits;
int leftK = K;

while (X &gt; 0) {
    Xbits.push_back(X &amp; 1);
    X &gt;&gt;= 1;
}

while (K &gt; 0) {
    Kbits.push_back(K &amp; 1);
    K &gt;&gt;= 1;
}
int kIdx = 0;
int xSize = (int)Xbits.size();
int kSize = (int)Kbits.size();


for (int i = 0; ; i++) {
    int xbit = (i &lt; xSize) ? Xbits[i] : 0;

    if (xbit == 1) {
        // X가 1인 자리는 Y는 무조건 0
        Ansbits.push_back(0);
    }
    else {
        // X가 0인 자리면 K의 현재 비트를 채움
        int kbit = (kIdx &lt; kSize) ? Kbits[kIdx] : 0;
        Ansbits.push_back(kbit);
        if (kIdx &lt; kSize) kIdx++; // K 비트 &quot;소비&quot;는 X가 0일 때만
    }

    // K 비트를 다 썼고, X의 최고비트까지도 처리 끝났으면 종료
    if (kIdx &gt;= kSize &amp;&amp; i + 1 &gt;= xSize) break;
}

long long Y = 0;
long long bit = 1;
for (int i = 0; i &lt; (int)Ansbits.size(); i++) {
    if (Ansbits[i]) Y += bit;
    bit &lt;&lt;= 1;
}

cout &lt;&lt; Y &lt;&lt; &quot;\n&quot;;
return 0;</code></pre><p>사실상 문제의 구조 자체가 X 비트의 0인 부분을 비집고 들어가는 구조였다</p>
<p>그렇기때문에 K를 비트화해서 X 비트가 1인 부분은 0, 0인 부분은 K의 첫 비트부터 끼워넣는 구조로 쌓아서 K만 끝까지 털면 답을 도출할 수 있었다</p>
<p>그렇게 하기 위해 X, K 비트를 풀고 Ans에 조건에 따라 쏙쏙 밀어넣는 구조로 코드를 짰다</p>
<p>음.. 비트 변환 방식에 대해서 공부가 좀 필요할 듯 싶다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[신작 개발일지 (4) 단어 지우기 게임과 키보드 히트맵 로직 구현]]></title>
            <link>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-4-%EB%8B%A8%EC%96%B4-%EC%A7%80%EC%9A%B0%EA%B8%B0-%EA%B2%8C%EC%9E%84%EA%B3%BC-%ED%82%A4%EB%B3%B4%EB%93%9C-%ED%9E%88%ED%8A%B8%EB%A7%B5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-4-%EB%8B%A8%EC%96%B4-%EC%A7%80%EC%9A%B0%EA%B8%B0-%EA%B2%8C%EC%9E%84%EA%B3%BC-%ED%82%A4%EB%B3%B4%EB%93%9C-%ED%9E%88%ED%8A%B8%EB%A7%B5-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Thu, 12 Feb 2026 10:53:15 GMT</pubDate>
            <description><![CDATA[<p>오랜만에 써보는 개발일지인데 꽤 구현을 많이 했는데 요즘 명조하느라 일지 올릴 생각을 못했다, 에이메스덱 다 짤때까지 이런 페이스를 유지할수도?</p>
<p>암튼 한번에 막 올리자면 첫 번째는 과거 한컴타자연습 내에서 보이던 단어 디펜스를 구현해보았다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/9dc1ab35-4f60-44f9-9de1-18d8b5ef0bcf/image.png" alt="">
먼저 가챠 시스템을 통해 구현한 몬스터들을 골라서 출전시킬 수 있게 드랍다운에 자동으로 내가 가진 몬스터를 인식해서 나열하는 기능을 구현했다</p>
<p>또한 그 능력치도 옆에 그대로 붙여놔서 눈으로 보면서 인식할 수 있게 배치해놓았다</p>
<pre><code> private void RefreshMonsterList()
    {
        _availableMonsters.Clear();
        monsterDropdown.ClearOptions();

        if (monsterManager != null)
        {
            _availableMonsters.AddRange(monsterManager.ActiveMonsters);
        }

        List&lt;string&gt; options = new List&lt;string&gt;();
        if (_availableMonsters.Count &gt; 0)
        {
            foreach (var mon in _availableMonsters)
            {
                options.Add(mon.monsterName);
            }
        }
        else
        {
            options.Add(&quot;몬스터 없음&quot;);
        }

        monsterDropdown.AddOptions(options);

        if (_availableMonsters.Count &gt; 0)
        {
            monsterDropdown.value = 0;
            OnMonsterSelected(0); 
        }
        else
        {
            ResetStatUI();
        }
    }

    private void OnMonsterSelected(int index)
    {
        if (index &lt; 0 || index &gt;= _availableMonsters.Count) return;

        _selectedMonster = _availableMonsters[index];

        // [Added] 몬스터 이미지 교체
        if (monsterPreviewImage != null)
        {
            if (_selectedMonster.spriteRenderer != null)
                monsterPreviewImage.sprite = _selectedMonster.spriteRenderer.sprite;
        }

        UpdateStatUI(_selectedMonster);
    }

    private void UpdateStatUI(Monster mon)
    {
        if (statHPText) statHPText.text = $&quot;{mon.curHP:F0}&quot;;
        if (statAtkText) statAtkText.text = $&quot;{mon.curAtk:F0}&quot;;
        if (statDefText) statDefText.text = $&quot;{mon.curDef:F0}&quot;;

        if (statHPBar) statHPBar.fillAmount = Mathf.Clamp01(mon.curHP / MAX_STAT_HP);
        if (statAtkBar) statAtkBar.fillAmount = Mathf.Clamp01(mon.curAtk / MAX_STAT_ATK);
        if (statDefBar) statDefBar.fillAmount = Mathf.Clamp01(mon.curDef / MAX_STAT_DEF);
    }

    private void ResetStatUI()
    {
        if (statHPText) statHPText.text = &quot;-&quot;;
        if (statAtkText) statAtkText.text = &quot;-&quot;;
        if (statDefText) statDefText.text = &quot;-&quot;;

        if (statHPBar) statHPBar.fillAmount = 0;
        if (statAtkBar) statAtkBar.fillAmount = 0;
        if (statDefBar) statDefBar.fillAmount = 0;

        _selectedMonster = null;

        // [Added] 몬스터 이미지 리셋 (물음표로)
        if (monsterPreviewImage != null &amp;&amp; defaultQuestionMark != null)
        {
            monsterPreviewImage.sprite = defaultQuestionMark;
        }
    }</code></pre><p>요런 식으로 구현했다</p>
<p>그리고 전투를 진행하면
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/c2639ea5-1891-46c1-acfb-add034273c7f/image.png" alt="">
요런 식으로 단어를 달고있는 몬스터가 캐릭터를 향해 달려오고, 플레이어는 이를 쳐서 잡아내는 개념으로 진행할 수 있다</p>
<p>이렇게 되면 저 몬스터 프리펩을 만들어 소환하는 법이 가장 편하기에, 몬스터를 프리펩화하고 엑셀로 매핑한 단어를 랜덤으로 적용하는 개념으로 진행했다</p>
<pre><code>  private void LoadWordsFromFile()
    {
        if (wordFile == null) return;

        var lines = wordFile.text.Split(new char[] { &#39;\n&#39;, &#39;\r&#39; }, System.StringSplitOptions.RemoveEmptyEntries);

        if (lines.Length &gt; 0)
        {
            wordList.Clear();
            foreach (var line in lines)
            {
                string cleanWord = line.Trim();
                if (!string.IsNullOrEmpty(cleanWord))
                {
                    wordList.Add(cleanWord);
                }
            }
            Debug.Log($&quot;Loaded {wordList.Count} words from file.&quot;);
        }
    }</code></pre><p>이런 식으로 단어를 받아서 매핑한다, 뭐 이건 저번에 쓴 연동법과 비슷하니 넘어가고</p>
<pre><code> public void Initialize(string word, float hp, float dmg, float speed, Transform target, BattleManager manager)
    {
        _targetWord = word;
        maxHP = hp; 
        _currentHP = maxHP;
        damage = dmg;
        moveSpeed = speed;
        _target = target;
        _battleManager = manager;

        if (textMesh != null)
        {
            textMesh.text = _targetWord;
        }
    }</code></pre><p>이렇게 몬스터에서 배틀매니저 받고 타겟워드를 잡고 데려오는 개념으로 진행했다</p>
<pre><code> // BattleManager가 호출: &quot;이 단어 맞은 놈 있어?&quot;
    public bool CheckHit(string typedWord, float damage)
    {
        // 일치하는 적 찾기
        // 여러 마리일 경우 가장 가까운 놈부터? 혹은 먼저 생성된 놈?
        // 여기서는 리스트 순서(먼저 생성된 순)대로 체크
        for (int i = 0; i &lt; _activeEnemies.Count; i++)
        {
            Enemy e = _activeEnemies[i];
            if (e == null) continue;

            if (e.TargetWord.Equals(typedWord, System.StringComparison.OrdinalIgnoreCase))
            {
                e.TakeDamage(damage);
                if (e == null) // 죽어서 삭제됨 (즉시 Destroy 호출된 경우)
                {
                    _activeEnemies.RemoveAt(i);
                }
                else if (e.gameObject == null) // Destroy 예약된 경우 안전 제거
                {
                     _activeEnemies.RemoveAt(i);
                }

                return true; // 명중
            }
        }
        return false; // 빗나감
    }</code></pre><p>적은 이렇게 StringComparison.ordinalIgnoreCase를 받아 인식한다</p>
<p>그 외에는 평범한 전투 로직을 차용했기 때문에 패스하고
그 다음에 고민을 조금 했던 로직에 대해 소개하려 한다</p>
<p>아무래도 키보드와 연관된 게임이다 보니 저번에 만들었던 가장 많이 친 키보드에 대한 가시성을 높이고 싶었다
그래서 텍스트로 나오는 대신 히트맵 로직을 구현하려 했다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/3f9731d5-eafd-4ea6-a88f-0a80a7f7f041/image.png" alt=""></p>
<p>그렇기 때문에 먼저 키보드를 그리고, 키보드 위에 키 하나마다 같은 키 이미지를 올려놓고 옆에 저번에 만든 로직도 남겨놓았다</p>
<p>여기서 일단 매핑을 하나하나 하기엔 상당히 귀찮기 때문에  지피티의 도움을 받아 자동 매핑 로직을 구현했다</p>
<p>이런 느낌으로 매핑을 했었는데 싹 다 인식하지는 못했다, 뭐 예를 들어 F1 ~ 12나 Num Lock 이런거는 인식을 못해서 이건 각자 매핑을 해주었다</p>
<p>그렇게 하는 김에 왼쪽 오른쪽 Ctrl이나 Shift 등을 구분해야 히트맵을 보기가 수월해지기 때문에 이를 구분해주는 코드를 구현하였다</p>
<p>또한 우리는 원래 한글과 영어를 구분하지만 여기서 그걸 구분하면 히트맵을 구현하기 쉽지 않아지길래 쉬프트를 통해 2개로 나뉠 수 있는 많은 키들은 그냥 합쳐서 계산하기로 했다</p>
<p>일단 코드 전문을 올려보는데.. 뭐 나중에 다시 고칠거 같긴 하다</p>
<pre><code>using UnityEngine;
using UnityEngine.UI;
using System.Collections.Generic;

public class KeyboardHeatmap : MonoBehaviour
{
    [Header(&quot;References&quot;)]
    public KeyboardStackManager keyboardManager;

    // 키보드 UI의 부모 Transform (각 키는 하위 오브젝트로 존재)
    // 예: &quot;Key_A&quot;, &quot;Key_B&quot; 혹은 그냥 &quot;A&quot;, &quot;B&quot; 이름으로 찾음
    public Transform keyboardRoot;

    [System.Serializable]
    public class KeyHeatmapEntry
    {
        public string keyName;    // &quot;A&quot;, &quot;ENTER&quot;, etc.
        public Image targetImage; // 색상이 변할 이미지 (Keycap or Highlight)
    }

    [Header(&quot;Mapped Keys (Auto or Manual)&quot;)]
    public List&lt;KeyHeatmapEntry&gt; mappedKeys = new List&lt;KeyHeatmapEntry&gt;();

    // 한영 키 매핑 (한글 자모 -&gt; 영어 키)
    private Dictionary&lt;char, char&gt; _korToEng = new Dictionary&lt;char, char&gt;()
    {
        {&#39;ㅂ&#39;, &#39;q&#39;}, {&#39;ㅈ&#39;, &#39;w&#39;}, {&#39;ㄷ&#39;, &#39;e&#39;}, {&#39;ㄱ&#39;, &#39;r&#39;}, {&#39;ㅅ&#39;, &#39;t&#39;}, {&#39;ㅛ&#39;, &#39;y&#39;}, {&#39;ㅕ&#39;, &#39;u&#39;}, {&#39;ㅑ&#39;, &#39;i&#39;}, {&#39;ㅐ&#39;, &#39;o&#39;}, {&#39;ㅔ&#39;, &#39;p&#39;},
        {&#39;ㅁ&#39;, &#39;a&#39;}, {&#39;ㄴ&#39;, &#39;s&#39;}, {&#39;ㅇ&#39;, &#39;d&#39;}, {&#39;ㄹ&#39;, &#39;f&#39;}, {&#39;ㅎ&#39;, &#39;g&#39;}, {&#39;ㅗ&#39;, &#39;h&#39;}, {&#39;ㅓ&#39;, &#39;j&#39;}, {&#39;ㅏ&#39;, &#39;k&#39;}, {&#39;ㅣ&#39;, &#39;l&#39;},
        {&#39;ㅋ&#39;, &#39;z&#39;}, {&#39;ㅌ&#39;, &#39;x&#39;}, {&#39;ㅊ&#39;, &#39;c&#39;}, {&#39;ㅍ&#39;, &#39;v&#39;}, {&#39;ㅠ&#39;, &#39;b&#39;}, {&#39;ㅜ&#39;, &#39;n&#39;}, {&#39;ㅡ&#39;, &#39;m&#39;},
        {&#39;ㅃ&#39;, &#39;Q&#39;}, {&#39;ㅉ&#39;, &#39;W&#39;}, {&#39;ㄸ&#39;, &#39;E&#39;}, {&#39;ㄲ&#39;, &#39;R&#39;}, {&#39;ㅆ&#39;, &#39;T&#39;}, {&#39;ㅒ&#39;, &#39;O&#39;}, {&#39;ㅖ&#39;, &#39;P&#39;}
    };

    private void Awake()
    {
        if (keyboardRoot == null) keyboardRoot = transform;

        // 리스트가 비어있으면 자동 찾기 시도
        if (mappedKeys.Count == 0)
        {
            AutoFindKeys();
        }
    }

    [ContextMenu(&quot;Auto Find Keys&quot;)]
    public void AutoFindKeys()
    {
        if (keyboardRoot == null) keyboardRoot = transform;
        mappedKeys.Clear();
        FindKeyImagesRecursive(keyboardRoot);
        Debug.Log($&quot;Found {mappedKeys.Count} keys.&quot;);
    }

    private void FindKeyImagesRecursive(Transform parent)
    {
        foreach (Transform child in parent)
        {
            Image img = child.GetComponent&lt;Image&gt;();

            // 이름이 유효한 키인지 확인
            // &quot;Key_A&quot; -&gt; &quot;A&quot;
            // &quot;Overlay&quot; -&gt; 부모가 &quot;Key_A&quot;면 &quot;A&quot;? (이건 사용자가 직접 넣는게 나을듯)
            // 일단은 오브젝트 이름 기반으로 동작

            string rawName = child.name;
            string keyName = rawName.Replace(&quot;Key_&quot;, &quot;&quot;).ToUpper();

            // 유효성 체크: 1글자 혹은 특수키 리스트에 포함
            bool isValid = (keyName.Length == 1 &amp;&amp; char.IsLetterOrDigit(keyName[0])) || IsSpecialKey(keyName);

            if (isValid &amp;&amp; img != null)
            {
                // 중복 체크
                if (!mappedKeys.Exists(x =&gt; x.keyName == keyName))
                {
                    mappedKeys.Add(new KeyHeatmapEntry { keyName = keyName, targetImage = img });
                }
            }

            if (child.childCount &gt; 0)
            {
                FindKeyImagesRecursive(child);
            }
        }
    }

    private bool IsSpecialKey(string name)
    {
        // 간단한 특수키 목록 체크
        string[] specials = new string[] { 
            &quot;ENTER&quot;, &quot;SPACE&quot;, &quot;BACKSPACE&quot;, &quot;TAB&quot;, &quot;ESC&quot;, &quot;SHIFT&quot;, &quot;CTRL&quot;, &quot;ALT&quot;, &quot;CAPSLOCK&quot;,
            &quot;INS&quot;, &quot;DEL&quot;, &quot;HOME&quot;, &quot;END&quot;, &quot;PGUP&quot;, &quot;PGDN&quot;, &quot;UP&quot;, &quot;DOWN&quot;, &quot;LEFT&quot;, &quot;RIGHT&quot; 
        };
        return System.Array.Exists(specials, s =&gt; s == name);
    }

    private void OnEnable()
    {
        UpdateHeatmap();
    }

    public void UpdateHeatmap(Dictionary&lt;string, int&gt; externalStats = null)
    {
        Dictionary&lt;string, int&gt; stats = externalStats;

        if (stats == null)
        {
            if (keyboardManager != null)
            {
                stats = keyboardManager.GetAllKeyStats();
            }
            else
            {
                Debug.LogWarning(&quot;KeyboardHeatmap: No stats provided and Manager is null.&quot;);
                return;
            }
        }

        if (mappedKeys.Count == 0) return;

        // 1. 물리적 키 기준으로 카운트 집계
        Dictionary&lt;string, int&gt; physicalCounts = new Dictionary&lt;string, int&gt;();
        int maxCount = 0;

        foreach (var pair in stats)
        {
            string rawKey = pair.Key;
            int count = pair.Value;

            string normalizedKey = NormalizeKey(rawKey);

            if (physicalCounts.ContainsKey(normalizedKey))
                physicalCounts[normalizedKey] += count;
            else
                physicalCounts[normalizedKey] = count;

            if (physicalCounts[normalizedKey] &gt; maxCount)
                maxCount = physicalCounts[normalizedKey];
        }

        // Debug.Log($&quot;Max Key Count: {maxCount}&quot;);

        // 2. 리스트 순회하며 색상 적용
        foreach (var entry in mappedKeys)
        {
            if (entry.targetImage == null) continue;

            // [Modified] 대소문자 구분 없이 매칭 (Inspector에 &#39;Enter&#39;라고 적어도 &#39;ENTER&#39;로 인식)
            string keyName = entry.keyName.ToUpper(); 

            if (physicalCounts.ContainsKey(keyName))
            {
                int count = physicalCounts[keyName];
                float ratio = (maxCount &gt; 0) ? (float)count / maxCount : 0f;

                entry.targetImage.color = GetHeatmapColor(ratio);
            }
            else
            {
                entry.targetImage.color = Color.white; 
            }
        }
    }

    private string NormalizeKey(string raw)
    {
        // 1. L/R 구분 키 처리 (KeyboardStackManager에서 보낸 이름 그대로 사용)
        string upper = raw.ToUpper();
        if (upper == &quot;LSHIFT&quot; || upper == &quot;RSHIFT&quot; || 
            upper == &quot;LCTRL&quot; || upper == &quot;RCTRL&quot; || 
            upper == &quot;LALT&quot; || upper == &quot;RALT&quot;)
        {
            return upper;
        }

        if (raw.Length == 1)
        {
            char c = raw[0];

            // [Added] Shift 심볼 -&gt; 기본 키 매핑 (Physical Key 기준)
            if (&quot;!@#$%^&amp;*()_+{}|:\&quot;&lt;&gt;?~&quot;.IndexOf(c) &gt;= 0)
            {
                c = SymbolToKey(c);
            }

            // 한글 -&gt; 영어 변환
            if (_korToEng.ContainsKey(c))
            {
                c = char.ToUpper(_korToEng[c]);
                return c.ToString();
            }
            // 영어 -&gt; 대문자
            return char.ToUpper(c).ToString();
        }

        // 특수키 처리 매핑 (필요 시 추가)
        switch (raw.ToLower())
        {
            case &quot;back&quot;: return &quot;BACKSPACE&quot;;
            case &quot;←&quot;: return &quot;BACKSPACE&quot;;
            case &quot;esc&quot;: return &quot;ESC&quot;;
            case &quot;tab&quot;: return &quot;TAB&quot;;
            case &quot;caps&quot;: return &quot;CAPSLOCK&quot;;
            case &quot;shift&quot;: return &quot;SHIFT&quot;; // 구버전 호환 (혹은 Global Hook)
            case &quot;ctrl&quot;: return &quot;CTRL&quot;;
            case &quot;alt&quot;: return &quot;ALT&quot;;
            case &quot;space&quot;: return &quot;SPACE&quot;;
            case &quot;enter&quot;: return &quot;ENTER&quot;;
            case &quot;ins&quot;: return &quot;INS&quot;;
            case &quot;del&quot;: return &quot;DEL&quot;;
            case &quot;home&quot;: return &quot;HOME&quot;;
            case &quot;end&quot;: return &quot;END&quot;;
            case &quot;pgup&quot;: return &quot;PGUP&quot;;
            case &quot;pgdn&quot;: return &quot;PGDN&quot;;
            case &quot;▲&quot;: return &quot;UP&quot;;
            case &quot;▼&quot;: return &quot;DOWN&quot;;
            case &quot;◄&quot;: return &quot;LEFT&quot;;
            case &quot;►&quot;: return &quot;RIGHT&quot;;
            // [Added] 특수문자 이름 매핑 (혹시 모를 상황 대비)
            case &quot;slash&quot;: return &quot;/&quot;;
            case &quot;period&quot;: return &quot;.&quot;;
            case &quot;comma&quot;: return &quot;,&quot;;
            case &quot;backquote&quot;: return &quot;`&quot;;
            case &quot;quote&quot;: return &quot;&#39;&quot;;
            case &quot;semicolon&quot;: return &quot;;&quot;;
            case &quot;leftbracket&quot;: return &quot;[&quot;;
            case &quot;rightbracket&quot;: return &quot;]&quot;;
            case &quot;minus&quot;: return &quot;-&quot;;
            case &quot;equals&quot;: return &quot;=&quot;;
        }

        return raw.ToUpper(); // 기본적으로 대문자로 변환
    }

    private char SymbolToKey(char s)
    {
        switch (s)
        {
            case &#39;!&#39;: return &#39;1&#39;;
            case &#39;@&#39;: return &#39;2&#39;;
            case &#39;#&#39;: return &#39;3&#39;;
            case &#39;$&#39;: return &#39;4&#39;;
            case &#39;%&#39;: return &#39;5&#39;;
            case &#39;^&#39;: return &#39;6&#39;;
            case &#39;&amp;&#39;: return &#39;7&#39;;
            case &#39;*&#39;: return &#39;8&#39;;
            case &#39;(&#39;: return &#39;9&#39;;
            case &#39;)&#39;: return &#39;0&#39;;
            case &#39;_&#39;: return &#39;-&#39;;
            case &#39;+&#39;: return &#39;=&#39;;
            case &#39;{&#39;: return &#39;[&#39;;
            case &#39;}&#39;: return &#39;]&#39;;
            case &#39;|&#39;: return &#39;\\&#39;;
            case &#39;:&#39;: return &#39;;&#39;;
            case &#39;&quot;&#39;: return &#39;\&#39;&#39;;
            case &#39;&lt;&#39;: return &#39;,&#39;;
            case &#39;&gt;&#39;: return &#39;.&#39;;
            case &#39;?&#39;: return &#39;/&#39;;
            case &#39;~&#39;: return &#39;`&#39;;
            default: return s; 
        }
    }

    private Color GetHeatmapColor(float ratio)
    {
        // 0.0 (White) -&gt; 0.5 (Yellow) -&gt; 1.0 (Red)
        if (ratio &lt;= 0.5f)
        {
            // White -&gt; Yellow
            // White: (1, 1, 1)
            // Yellow: (1, 1, 0)
            return Color.Lerp(Color.white, Color.yellow, ratio * 2f);
        }
        else
        {
            // Yellow -&gt; Red
            // Yellow: (1, 1, 0)
            // Red: (1, 0, 0)
            return Color.Lerp(Color.yellow, Color.red, (ratio - 0.5f) * 2f);
        }
    }
}
</code></pre><p>이런 식으로 색 변환까지 적용하고 결과물을 보여주자면
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/ff9cfd5d-704c-4288-a491-d13f4760cd22/image.png" alt="">
이런 식으로 히트맵 로직을 구현했다, 상당히 예뻐서 마음에 들었는데 바로 눈에 걸리는 저 아래 텍스트메쉬를 보다시피 절대로 렉이 안 걸릴 수 있는 구조가 아니라고 생각했다</p>
<p>그런데 일단 칠 때 떨어지는건 메쉬로 바꿔서 렉이 많이 줄었는데, 문제는 저장 후 다시 시작할 때 저장했던 것을 다시 다 떨어뜨리는 로직을 구현했는데, 한번에 몇 백개를 쏟아내다 보니 렉이 엄청났다</p>
<p>그래서 초반에 렉이 걸리다보니 배경을 투명하게 하거나 태스크바 위로 땡겨올리는 그런 로직들이 씹히는 경향이 심해져서 일단은 게임을 켰을 때 떨어지는 걸 2초 정도 미루고 한번이 아닌 천천히 10개씩 묶어서 떨어뜨리는 방향으로 구현을 했다, 아직 좀 걸리긴 하는데 계속 고쳐봐야할 듯 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[신작 개발일지 (3) 최적화 및 지루하고 현학적인 스프레드시트 연동]]></title>
            <link>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-3-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%B0%8F-%EC%A7%80%EB%A3%A8%ED%95%98%EA%B3%A0-%ED%98%84%ED%95%99%EC%A0%81%EC%9D%B8-%EC%8A%A4%ED%94%84%EB%A0%88%EB%93%9C%EC%8B%9C%ED%8A%B8-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-3-%EC%B5%9C%EC%A0%81%ED%99%94-%EB%B0%8F-%EC%A7%80%EB%A3%A8%ED%95%98%EA%B3%A0-%ED%98%84%ED%95%99%EC%A0%81%EC%9D%B8-%EC%8A%A4%ED%94%84%EB%A0%88%EB%93%9C%EC%8B%9C%ED%8A%B8-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Fri, 06 Feb 2026 08:57:32 GMT</pubDate>
            <description><![CDATA[<p>뭔가 엄청 특별한 무언가는 없지만 너무 안 쓴 것 같아서 그나마 고치려고 했던 무언가들을 올려보려 한다</p>
<ol>
<li>TextMeshPro 관련 최적화
초반에 작업을 할 때에는 그냥 키마다 캔버스를 넣어서 월드 캔버스로 작업을 진행해 연동했었다, 그런데 그러면 월드 캔버스를 수천개 소환하는 괴랄한 작업량이 펼쳐져서 고민을 하던 중, TextMesh를 캔버스에 붙이는 게 아닌 메쉬를 활용한 오브젝트 형식으로 붙일 수 있다는 것을 알게 되었다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/0b488ed5-b9f4-4741-b3c9-de73bd2d6909/image.png" alt="">
이 텍스트메쉬를 키의 자식으로 놓으면 <img src="https://velog.velcdn.com/images/hipop1109-dev/post/ee618280-b04e-498c-9cdc-01a081f52507/image.png" alt="">
메쉬 렌더러로 택스트 머터리얼을 받는 오브젝트 취급이 되어 캔버스로 연산하는 것보다 훨씬 싸게 먹힐 수 있다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/610329c5-575d-4c7a-93e0-1f0ee583b5d6/image.png" alt="">
코드도 딱히 다를 것 없이 textmesh 데리고 와서 텍스트 형식으로 그대로 적용해 넣을 수 있는 장점이 있다</li>
</ol>
<p>그리고 아무래도 방치형 뽑기 게임을 지향하다 보기 몬스터마다 스탯이 필요했다, 그래서 스크립터블 오브젝트로 하나하나 구성하는것보다는 역시 엑셀을 활용해보는게 낫지 않을까 싶어서 활용해봤다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/a528db10-89fc-4421-aec1-8996d757257a/image.png" alt="">
포켓몬과 비슷한 형식으로 3단 진화 형식을 차용했고
레벨업당 수치 상승폭과 진화 시 보너스 스탯, 경험치통 등 목표치들을 설정해 일단 몬스터 수만큼 복사해 두었다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/1329c1b4-08fb-418b-9564-7e767b470e5a/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/05eefef5-23c9-45ed-814e-a73172f5f995/image.png" alt="">
이런 식으로 줄마다 Parse한 후 이걸 몬스터 소환시 Init 해서 적용하는 과정을 거쳤다</p>
<p>저번에 올린 영상들에 비해 상당히 평범해서 짧게 올리려 한다
다음 전투 로직 제작은 조금 특별한걸 할 수 있길 바란다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[신작 개발일지 (2) 게임 백그라운드 작동 로직]]></title>
            <link>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%EA%B2%8C%EC%9E%84-%EB%B0%B1%EA%B7%B8%EB%9D%BC%EC%9A%B4%EB%93%9C-%EC%9E%91%EB%8F%99-%EB%A1%9C%EC%A7%81</link>
            <guid>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%EA%B2%8C%EC%9E%84-%EB%B0%B1%EA%B7%B8%EB%9D%BC%EC%9A%B4%EB%93%9C-%EC%9E%91%EB%8F%99-%EB%A1%9C%EC%A7%81</guid>
            <pubDate>Sun, 01 Feb 2026 08:24:41 GMT</pubDate>
            <description><![CDATA[<p>이 게임의 핵심은 백그라운드 재생이다
내가 원하는 백그라운드 재생을 위한 처음 생각했던 조건은 크게 2가지다</p>
<ol>
<li>게임 화면이 없는 부분은 투명해지고 모니터 화면이 클릭이 되어야 한다</li>
<li>게임이 아닌 모니터 내에서 타이핑을 해도 게임에서 반응이 와야한다</li>
</ol>
<p>우선 1번 로직은 세팅법이 몇 가지 필요하다
유니티부터 세팅하자면 2가지의 설정이 필요하다</p>
<ol>
<li>카메라 설정 
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/266e9ddd-75fa-4e50-97d3-ca57b37af14d/image.png" alt="">
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/6649657f-6f95-486c-bd57-0e13a2f9955c/image.png" alt=""></li>
</ol>
<p>카메라에서 백그라운드 타입을 Sloid Color로 하고 백그라운드를 투명하게 해야한다, 사실 기본적으로 이렇게 되어있는데 꼭 확인해보아야한다</p>
<ol start="2">
<li>프로젝트 세팅 설정
Edit -&gt; Project Setting -&gt; Resolution and Presentation
탭에서 2가지를 설정해야 한다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/c4037c61-d69c-4d2d-a4d3-af7b0ad6bbe7/image.png" alt=""><ol>
<li>Fullscreen mode : Windowed으로 설정해야 한다</li>
<li>Use DXGI 탭 : 체크를 해제해야 한다</li>
</ol>
</li>
</ol>
<p>그러나 필자는 유니티 6.2를 쓰고 있는데 이 때문에 추가적인 설정을 해주어야 했다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/9c2456cb-1d1b-450d-ae0a-4348eccd6711/image.png" alt="">
Auto Graphics API For Windows를 끄면 Direct3D11, Directd3D12 2개가 나오는데 이 중에서 12를 삭제해야 한다</p>
<p>그 후 Color Space를 Gamma로 바꿔주면 원할하게 세팅할 수 있다</p>
<p>이렇게 되면 유니티 내 설정은 마무리가 되고 코드로 짜주어야 하는데 그 전에 내가 이 게임을 만들면서 생긴 여러 가지 이슈에 대해 적어보면서 이를 해결한 법에 대해 적어보려고 한다</p>
<ol>
<li>듀얼 모니터 내 상호작용</li>
<li>듀얼 모니터의 화면 해상도가 다를 때 대응법</li>
<li>모니터 내 타이핑 상호작용 꼬임 이슈</li>
<li>게임과 모니터 사이의 마우스 인식 로직</li>
<li>태스크바 가림 이슈</li>
</ol>
<p>이 모든걸 적용한 코드를 미리 적어두고 아래에 이슈들에 대한 해결책을 알려주도록 하겠다</p>
<pre><code>using System;
using System.Collections;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem; // [중요] New Input System 네임스페이스 추가

public class WindowClickThrough : MonoBehaviour
{
    private struct MARGINS { public int cxLeftWidth; public int cxRightWidth; public int cyTopHeight; public int cyBottomHeight; }

    [DllImport(&quot;user32.dll&quot;)] private static extern IntPtr GetActiveWindow();
    [DllImport(&quot;user32.dll&quot;, EntryPoint = &quot;SetWindowLongPtr&quot;)] private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
    [DllImport(&quot;user32.dll&quot;, EntryPoint = &quot;GetWindowLongPtr&quot;)] private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex);
    [DllImport(&quot;Dwmapi.dll&quot;)] private static extern uint DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS margins);
    [DllImport(&quot;user32.dll&quot;, SetLastError = true)] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
    [DllImport(&quot;user32.dll&quot;)] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    const int GWL_STYLE = -16;
    const int GWL_EXSTYLE = -20;
    const uint WS_POPUP = 0x80000000;
    const uint WS_VISIBLE = 0x10000000;
    const uint WS_EX_LAYERED = 0x00080000;
    const uint WS_EX_TRANSPARENT = 0x00000020;
    static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
    const uint SWP_NOSIZE = 0x0001;
    const uint SWP_NOMOVE = 0x0002;
    const uint SWP_NOZORDER = 0x0004;
    const uint SWP_SHOWWINDOW = 0x0040;
    const uint SWP_FRAMECHANGED = 0x0020;

    private IntPtr _hWnd;
    private bool _isClickThrough = false;

    // DPI Awareness
    [DllImport(&quot;user32.dll&quot;)] private static extern bool SetProcessDpiAwarenessContext(IntPtr value);
    private static readonly IntPtr DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = new IntPtr(-4);

    // Monitor API
    delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData);
    [DllImport(&quot;user32.dll&quot;)]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    [DllImport(&quot;user32.dll&quot;)]
    private static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumDelegate lpfnEnum, IntPtr dwData);
    [DllImport(&quot;user32.dll&quot;)] private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);

    [StructLayout(LayoutKind.Sequential)]
    public struct MONITORINFO
    {
        public int cbSize;
        public RECT rcMonitor;
        public RECT rcWork;
        public uint dwFlags;
    }

    private void Awake()
    {
        // [DPI 설정] Windows 10 (1703+) 이상에서 동작. 구버전에서는 무시됨.
        try {
            SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
        } catch { /* 구버전 윈도우 호환성 처리: 무시 */ }
    }

    IEnumerator Start()
    {
        Screen.fullScreenMode = FullScreenMode.Windowed; 
        Application.runInBackground = true; 

        if (Camera.main != null)
        {
            Camera.main.clearFlags = CameraClearFlags.SolidColor;
            Camera.main.backgroundColor = Color.clear; 
            Camera.main.allowHDR = false;
        }

        yield return null; 

        _hWnd = GetActiveWindow();
        SetupWindowTransparency();

        // 초기 시작 시 기본 모니터 설정 (0번 모니터로 이동)
        MoveToMonitor(0);

        _isClickThrough = false;
    }

    // [Fix] 투명창 설정이 풀리는 현상 방지를 위해 별도 메소드로 분리
    private void SetupWindowTransparency()
    {
        MARGINS margins = new MARGINS { cxLeftWidth = -1 };
        DwmExtendFrameIntoClientArea(_hWnd, ref margins);

        SetWindowLongPtr(_hWnd, GWL_STYLE, new IntPtr(WS_POPUP | WS_VISIBLE));
        SetWindowLongPtr(_hWnd, GWL_EXSTYLE, new IntPtr(WS_EX_LAYERED));

        // [Fix] 스타일을 강제로 리셋했으므로, 내부 상태 변수도 &#39;클릭 투과 아님(Interactive)&#39;으로 동기화해야
        // 다음 프레임에 SetClickThrough가 정상적으로 작동함.
        _isClickThrough = false;
    }

    [DllImport(&quot;user32.dll&quot;)] private static extern short GetAsyncKeyState(int vKey);
    private const int VK_1 = 0x31;
    private const int VK_2 = 0x32;

    void Update()
    {
        // 1번, 2번 키로 모니터 이동
        // Input.GetKeyDown은 포커스가 없으면 작동하지 않으므로 CheckGlobalInput으로 대체하거나
        // 여기서는 간단히 GetAsyncKeyState 사용 (KeyboardStackManager와 별개로 창 이동용)

        // 1번 키 (Top numeric)
        bool key1State = (GetAsyncKeyState(0x31) &amp; 0x8000) != 0;
        if (key1State &amp;&amp; !_key1Pressed)
        {
            MoveToMonitor(0);
        }
        _key1Pressed = key1State;

        // 2번 키
        bool key2State = (GetAsyncKeyState(0x32) &amp; 0x8000) != 0;
        if (key2State &amp;&amp; !_key2Pressed)
        {
            MoveToMonitor(1);
        }
        _key2Pressed = key2State;


        // 매 프레임 마우스 위치에 따라 클릭 투과 여부 결정
        CheckInteractive();
    }

    private bool _key1Pressed = false;
    private bool _key2Pressed = false;

    private void MoveToMonitor(int monitorIndex)
    {
        var monitors = new System.Collections.Generic.List&lt;MONITORINFO&gt;();

        EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, 
            (IntPtr hMonitor, IntPtr hdcMonitor, ref RECT lprcMonitor, IntPtr dwData) =&gt; 
            {
                MONITORINFO mi = new MONITORINFO();
                mi.cbSize = Marshal.SizeOf(mi);
                if (GetMonitorInfo(hMonitor, ref mi))
                {
                    monitors.Add(mi);
                }
                return true;
            }, IntPtr.Zero);

        if (monitorIndex &gt;= 0 &amp;&amp; monitorIndex &lt; monitors.Count)
        {
            MONITORINFO targetMonitor = monitors[monitorIndex];

            // [수정] rcMonitor(전체 화면) 대신 rcWork(작업 영역, 태스크바 제외)를 사용하여
            // 윈도우가 태스크바를 가리지 않도록 조정합니다.
            int width = targetMonitor.rcWork.Right - targetMonitor.rcWork.Left;
            int height = targetMonitor.rcWork.Bottom - targetMonitor.rcWork.Top;

            // 윈도우 이동 및 크기 조정
            SetWindowPos(_hWnd, HWND_TOPMOST, 
                targetMonitor.rcWork.Left, 
                targetMonitor.rcWork.Top, 
                width, height, 
                SWP_SHOWWINDOW | SWP_FRAMECHANGED); 

            // [중요] Unity 내부 해상도도 맞춰줘야 Raycast가 정확함
            if (Screen.width != width || Screen.height != height)
            {
                Screen.SetResolution(width, height, FullScreenMode.Windowed);
            }

            // [Fix] 해상도 변경 혹은 이동 후 배경이 하얗게 변하는 문제 해결을 위해 스타일 재적용
            SetupWindowTransparency();

            // [포커스 복구] 이동 후 포커스를 잃어 입력을 못 받는 현상 방지
            SetForegroundWindow(_hWnd);

            Debug.Log($&quot;Moved to Monitor {monitorIndex} (WorkArea): {width}x{height} at ({targetMonitor.rcWork.Left}, {targetMonitor.rcWork.Top})&quot;);
        }
    }

    private void SetClickThrough(bool isTransparent)
    {
        // [Fix] 내부 변수(_isClickThrough)에 의존하지 않고, 실제 윈도우 스타일을 매번 확인하여
        // Unity 엔진(SetResolution 등)에 의해 스타일이 리셋되었을 경우 즉시 복구합니다.

        long currentStyle = GetWindowLongPtr(_hWnd, GWL_EXSTYLE).ToInt64();

        // WS_EX_LAYERED는 투명 및 클릭 투과를 위해 필수 (항상 포함되어야 함)
        long newStyle = currentStyle | WS_EX_LAYERED;

        if (isTransparent) 
        {
            // 투명(클릭 투과) 상태 -&gt; WS_EX_TRANSPARENT 추가
            newStyle |= WS_EX_TRANSPARENT;
        }
        else 
        {
            // 상호작용 가능 상태 -&gt; WS_EX_TRANSPARENT 제거
            newStyle &amp;= ~WS_EX_TRANSPARENT;
        }

        // 실제 스타일이 목표와 다를 경우에만 API 호출 (성능 최적화)
        if (currentStyle != newStyle)
        {
            SetWindowLongPtr(_hWnd, GWL_EXSTYLE, new IntPtr(newStyle));

            // 스타일 변경 시 DWM 투명 영역도 확실하게 연장 (하얀 배경 방지)
            if ((currentStyle &amp; WS_EX_LAYERED) == 0) // Layered 속성이 없었다가 생겼다면
            {
                MARGINS margins = new MARGINS { cxLeftWidth = -1 };
                DwmExtendFrameIntoClientArea(_hWnd, ref margins);
            }
            SetWindowPos(_hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_FRAMECHANGED);
        }
    }

    // Win32 API Definitions for Hardware Mouse
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT { public int X; public int Y; }

    [DllImport(&quot;user32.dll&quot;)] private static extern bool GetCursorPos(out POINT lpPoint);
    [DllImport(&quot;user32.dll&quot;)] private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
    [DllImport(&quot;user32.dll&quot;)] private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);

    private void CheckInteractive()
    {
        bool isPointerOverUI = false;

        POINT p;
        if (GetCursorPos(out p)) // 마우스 글로벌 좌표
        {
            // [Fix] GetWindowRect 대신 ClientToScreen을 사용하여
            // 윈도우의 테두리/그림자 영역을 배제하고 실제 Unity가 렌더링되는 Client 영역 기준으로 좌표를 계산합니다.

            POINT clientTopLeft = new POINT { X = 0, Y = 0 };
            ClientToScreen(_hWnd, ref clientTopLeft);

            RECT clientRect;
            GetClientRect(_hWnd, out clientRect); // clientRect.Left/Top은 항상 0
            int clientWidth = clientRect.Right - clientRect.Left;
            int clientHeight = clientRect.Bottom - clientRect.Top;

            // Unity Screen 해상도와 실제 Client 영역 크기 비교 (비율 보정)
            float scaleX = (float)Screen.width / clientWidth;
            float scaleY = (float)Screen.height / clientHeight;

            // 로컬 좌표 계산 (Unity는 좌하단이 0,0)
            // 글로벌 마우스 Y가 증가할수록(아래로 갈수록) Unity Y는 감소해야 함
            // Client Bottom의 글로벌 Y = clientTopLeft.Y + clientHeight

            float localX = (p.X - clientTopLeft.X) * scaleX;
            int globalClientBottom = clientTopLeft.Y + clientHeight;
            float localY = (globalClientBottom - p.Y) * scaleY;

            // 화면 범위 내에 있는지 먼저 체크 (벗어났다면 굳이 레이캐스트 할 필요 없음)
            if (localX &gt;= 0 &amp;&amp; localX &lt;= Screen.width &amp;&amp; localY &gt;= 0 &amp;&amp; localY &lt;= Screen.height)
            {
                Vector2 vectorPos = new Vector2(localX, localY);
                PointerEventData pointerData = new PointerEventData(EventSystem.current)
                {
                    position = vectorPos
                };

                System.Collections.Generic.List&lt;RaycastResult&gt; results = new System.Collections.Generic.List&lt;RaycastResult&gt;();
                EventSystem.current.RaycastAll(pointerData, results);

                foreach(var result in results)
                {
                    if (result.gameObject != null) 
                    {
                        isPointerOverUI = true;
                        break;
                    }
                }

                if (!isPointerOverUI &amp;&amp; Camera.main != null)
                {
                    Vector2 worldPoint = Camera.main.ScreenToWorldPoint(vectorPos);
                    RaycastHit2D hit = Physics2D.Raycast(worldPoint, Vector2.zero);
                    if (hit.collider != null)
                    {
                        isPointerOverUI = true;
                    }
                }
            }
        }

        SetClickThrough(!isPointerOverUI);
    }

    // 종료 버튼 연결용 함수
    public void QuitGame()
    {
        #if UNITY_EDITOR
            UnityEditor.EditorApplication.isPlaying = false;
        #else
            Application.Quit();
        #endif
    }
}</code></pre><h3 id="1-듀얼-모니터-환경에서의-상호작용-문제">1. 듀얼 모니터 환경에서의 상호작용 문제</h3>
<p>게임 창을 한 모니터에서 다른 모니터로 이동시키면 다음과 같은 문제가 발생했다.</p>
<ul>
<li>마우스 위치가 어긋남</li>
<li>UI 클릭 판정이 틀어짐</li>
<li>게임 오브젝트 클릭 판정 실패</li>
</ul>
<p>이를 해결하기 위해</p>
<pre><code>SetWindowPos(...)
Screen.SetResolution(width, height, FullScreenMode.Windowed);</code></pre><p>윈도우 창을 모니터 위치로 이동시키고</p>
<ol>
<li>Unity 내부 해상도를 해당 모니터 크기로 맞춘다.</li>
<li>이 과정을 거쳐야 Raycast와 마우스 좌표가 정확히 일치하게 된다.</li>
</ol>
<h3 id="2-듀얼-모니터-해상도가-서로-다른-경우">2. 듀얼 모니터 해상도가 서로 다른 경우</h3>
<p>모니터마다 해상도가 다른 경우, 단순히 창만 이동시키면 입력 좌표가 크게 틀어진다.
이를 해결하기 위해 마우스 좌표를 다음 순서로 변환했다</p>
<ol>
<li>Windows 전역 마우스 좌표 획득</li>
<li>현재 창의 클라이언트 영역 좌표 계산</li>
<li>Unity 해상도 기준으로 스케일 보정</li>
<li>Y축 좌표 반전 처리</li>
</ol>
<p>이를 위해</p>
<pre><code>float localX = ...
float localY = ...</code></pre><p>이를 통해 어느 모니터로 이동해도 좌표 판정이 유지되도록 만들었다</p>
<h3 id="3-게임이-아닌-프로그램에서-타이핑할-때-입력-꼬임-문제">3. 게임이 아닌 프로그램에서 타이핑할 때 입력 꼬임 문제</h3>
<p>기본 Unity 입력 시스템은 창이 포커스를 잃으면 입력을 받지 못한다.
하지만 이 프로젝트는 다음과 같은 상황을 전제로 한다.</p>
<ul>
<li>사용자는 브라우저, 메신저, IDE 등을 사용한다.</li>
<li>동시에 게임도 계속 입력을 받아야 한다.</li>
</ul>
<p>즉, 포커스가 없어도 입력이 필요하다.</p>
<p>이를 위해 Unity 입력 대신 Windows API를 사용했다</p>
<pre><code>GetAsyncKeyState(...)</code></pre><p>이 함수는 특정 창이 아니라 OS 전체 기준 키 상태를 읽어온다.</p>
<p>그래서 알트탭 후에도 입력 감지 가능, 다른 창 클릭 중에도 게임 입력 유지가 가능해진다</p>
<h3 id="4-게임과-모니터-사이-마우스-인식-문제">4. 게임과 모니터 사이 마우스 인식 문제</h3>
<p>게임 영역이 아닌 부분은 클릭이 모니터로 통과되어야 하고,
게임 영역에서는 정상 상호작용이 이루어져야 한다</p>
<p>이를 위해 매 프레임 다음 과정을 수행한다</p>
<ol>
<li>마우스 위치 획득</li>
<li>Unity 좌표로 변환</li>
<li>UI Raycast 검사</li>
<li>Physics2D Raycast 검사<pre><code>EventSystem.current.RaycastAll(...)
Physics2D.Raycast(...)</code></pre>이를 통해 UI 또는 오브젝트가 감지되면 클릭을 게임이 받게 하고,
아니면 클릭을 모니터로 통과시킨다</li>
</ol>
<h3 id="5-클릭-투과-상태-토글-구조">5. 클릭 투과 상태 토글 구조</h3>
<p>클릭 투과는 Windows 창 스타일 중 하나인</p>
<pre><code>WS_EX_TRANSPARENT</code></pre><p>속성으로 제어되고
속성이 있으면 클릭 통과, 없으면 게임이 클릭을 받는 구조이다
그래서</p>
<pre><code>long currentStyle = GetWindowLongPtr(...)
long newStyle = ...</code></pre><p>이 코드로 현재 스타일을 읽고 필요한 상태로 적용하는 구조이다</p>
<p><strong>왜 내부 상태 변수를 믿지 않는가?</strong></p>
<p>Unity에서 해상도를 변경하거나 창을 이동하면
윈도우 스타일이 중간에 변경되는 경우가 있다
그래서 코드에서는 내부 변수 대신
실제 윈도우 스타일을 매번 읽고 복구하도록 설계했다
이 방식 덕분에 클릭 투과가 풀리는 현상이 거의 발생하지 않는다</p>
<h3 id="6-모니터-이동-후-배경이-하얗게-변하는-문제">6. 모니터 이동 후 배경이 하얗게 변하는 문제</h3>
<p>특정 상황에서 창을 이동하면 투명 배경이 하얗게 변하는 현상이 발생했다
이를 해결하기 위해 다음 처리를 추가했다</p>
<pre><code>DwmExtendFrameIntoClientArea(...)</code></pre><p>Layered 스타일이 새로 적용될 때마다 DWM 확장을 다시 수행하여
투명 상태가 유지되도록 했다</p>
<h3 id="7-update-루프를-통한-안정성-확보">7. Update 루프를 통한 안정성 확보</h3>
<p>이 시스템은 매 프레임 다음을 수행한다</p>
<ol>
<li>키 입력 체크</li>
<li>마우스 위치 계산</li>
<li>UI/오브젝트 판정 </li>
<li>클릭 투과 상태 조정</li>
</ol>
<p>이 구조는 약간 무거워 보일 수 있지만,
실제로는 다음 최적화가 적용되어 있다</p>
<p>화면 밖에서는 Raycast를 수행하지 않음
스타일 변경이 필요한 경우에만 Win32 API 호출
즉, 매 프레임 확인은 하지만 실제 비용은 최소화한 로직으로 구현되었다</p>
<p>이런 느낌으로 구현을 해서 결론적으로 나온 결과물은
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/0fa3e89e-0a50-4a75-bc86-3ae0055ea895/image.gif" alt="">
요런 느낌으로 완성이 된다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 재활치료 (5) 25682 체스판 다시 칠하기 2]]></title>
            <link>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-5-25682-%EC%B2%B4%EC%8A%A4%ED%8C%90-%EB%8B%A4%EC%8B%9C-%EC%B9%A0%ED%95%98%EA%B8%B0-2</link>
            <guid>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-5-25682-%EC%B2%B4%EC%8A%A4%ED%8C%90-%EB%8B%A4%EC%8B%9C-%EC%B9%A0%ED%95%98%EA%B8%B0-2</guid>
            <pubDate>Sun, 01 Feb 2026 06:07:33 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/5fcc8d84-6b16-4c10-a5b0-fb7ac64b82a9/image.png" alt="">
일단 간단히 보이는대로만 풀면 4중 for문이 나온다, 아무리 생각해도 그건 아닌거 같고 살펴보니 이 문제는 누적합 개념을 활용해야 하는 문제라고 한다</p>
<h3 id="누적합이란">누적합이란?</h3>
<p>보통 어떤 배열의 구간합을 구할 때 i ~ j 까지 원소를 하나씩 더하는 개념으로 작용한다, 그러나 이 구간합을 미리 더해놓는다면 더 간단해질 수 있다</p>
<p>Ex) A = [1 2 3 4 5] 배열이 있다고 치면 원래 A[1] ~ A[3] 를 더하고 싶으면
2 + 3 + 4 즉 3번을 돌려야 한다</p>
<p>그런데 미리 각 구역까지의 합을 미리 세팅해둔다고 하면</p>
<p>S[1 3 6 10 15] 이런 식으로 세팅해둘 수 있다</p>
<p>이렇게 됐을 때 위 식을 다시 해보면 S[4] - S[1], 즉 S[j] - S[i-1] 이런 식으로 한번만 구하면 되는 것이다</p>
<p>결국 구간합의 시간을 줄여줄 수 있는 로직인 것이다</p>
<p>결론 함수부터 보여주고 분석해보도록 하겠다</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;queue&gt;
using namespace std;

int main() {
    int N, M, K;
    cin &gt;&gt; N &gt;&gt; M &gt;&gt; K;

    vector&lt;string&gt; Chess(N);
    for(int i = 0; i &lt; N; ++i) {
        cin &gt;&gt; Chess[i];
    }

    vector&lt;vector&lt;int&gt;&gt; B(N + 1, vector&lt;int&gt;(M + 1, 0));
    vector&lt;vector&lt;int&gt;&gt; W(N + 1, vector&lt;int&gt;(M + 1, 0));

    for (int i = 1; i &lt;= N; i++) {
        for (int j = 1; j &lt;= M; j++) {
            char current = Chess[i - 1][j - 1];

            char expectB = ((i + j) % 2 == 0) ? &#39;B&#39; : &#39;W&#39;;
            char expectW = ((i + j) % 2 == 0) ? &#39;W&#39; : &#39;B&#39;;

            int diffB = (current != expectB);
            int diffW = (current != expectW);

            B[i][j] = B[i - 1][j] + B[i][j - 1] - B[i - 1][j - 1] + diffB;
            W[i][j] = W[i - 1][j] + W[i][j - 1] - W[i - 1][j - 1] + diffW;
        }
    }

    int ans = 1e9;

    for (int i = 1; i &lt;= N - K + 1; i++) {
        for (int j = 1; j &lt;= M - K + 1; j++) {
            int r2 = i + K - 1;
            int c2 = j + K - 1;

            int repaintB = B[r2][c2] - B[i - 1][c2] - B[r2][j - 1] + B[i - 1][j - 1];
            int repaintW = W[r2][c2] - W[i - 1][c2] - W[r2][j - 1] + W[i - 1][j - 1];

            ans = min(ans, min(repaintB, repaintW));
        }
    }

    cout &lt;&lt; ans &lt;&lt; &#39;\n&#39;;
}
</code></pre><pre><code>vector&lt;string&gt; Chess(N);
    for(int i = 0; i &lt; N; ++i) {
        cin &gt;&gt; Chess[i];
    }
</code></pre><p>한글자씩 넣지 말고 string화 해서 한줄씩 넣는 개념으로 다가가면 편하게 풀 수 있을 것이다</p>
<pre><code>vector&lt;vector&lt;int&gt;&gt; B(N + 1, vector&lt;int&gt;(M + 1, 0));
vector&lt;vector&lt;int&gt;&gt; W(N + 1, vector&lt;int&gt;(M + 1, 0));</code></pre><p>각각 시작점이 B인 경우, W인 경우를 가정해 완벽히 정상인 체스판을 만든다고 가정한다
이렇게 하고 틀린 부분과 비교해서 틀린것만 집어내는게 계산에 더 유리할 수 있기 때문에 적용해본다
+1씩 하는 이유는 한바퀴를 0으로 둘러서 계산에 용이하게 하려는 것이다</p>
<pre><code>for (int i = 1; i &lt;= N; i++) {
        for (int j = 1; j &lt;= M; j++) {
            char current = Chess[i - 1][j - 1];

            char expectB = ((i + j) % 2 == 0) ? &#39;B&#39; : &#39;W&#39;;
            char expectW = ((i + j) % 2 == 0) ? &#39;W&#39; : &#39;B&#39;;

            int diffB = (current != expectB);
            int diffW = (current != expectW);

            B[i][j] = B[i - 1][j] + B[i][j - 1] - B[i - 1][j - 1] + diffB;
            W[i][j] = W[i - 1][j] + W[i][j - 1] - W[i - 1][j - 1] + diffW;
        }
    }</code></pre><p>이제 한바퀴를 0으로 돌린 것을 제외하고 1 ~ N, 1 ~ M에 적용을 해보기 시작한다
char current은 0을 감싼걸 빼서 제외하고 쭉쭉 넣어보는 것이다</p>
<p>그리고 expectB, W은 원래 거기에 들어가야할 것들에 대해 적용해보는 것이다
앞이 B인 경우와 W인 경우를 둘 다 비교한다</p>
<p>그 아래에는 current와 방금 구한 expect와 다른지 구한다
c++에서는 bool문이 맞으면 1, 틀리면 0을 int로 뱉을 수 있기 때문에 뱉는다</p>
<p>이제 이 아래에 누적합 업데이트 공식을 구하는 것이다
앞이 B일 때 2차원 벡터의 누적합을 구하는 공식을 적용해본다
계속 누적합이 될 테니 B[i-1][j]는 왼쪽의 모든 줄을 더한 시점의 값이 될 것이다
마찬가지로 B[i][j-1]도 위쪽의 모든 줄을 더한 시점의 값이 될 것이다
그렇게 되면 간단히 숫자로 예시를 들어보자면</p>
<p><strong>a a a **b</strong><br>a a a <strong>b</strong><br>a a a** b<br>a a a c = B[i-1][j]</p>
<p><strong>a a a **a</strong>
a a a <strong>a</strong>
a a a **a
b b b c = B[i][j-1]</p>
<p>을 둘 다 더한다고 하면 진한 부분은 2번 더하니 B[i - 1][j - 1]을 빼야 하는 것이다
그 후 방금 diff을 더하면 깔끔히 2차원 벡터의 누적합을 구할 수 있다</p>
<pre><code>    int ans = 1e9;

    for (int i = 1; i &lt;= N - K + 1; i++) {
        for (int j = 1; j &lt;= M - K + 1; j++) {
            int r2 = i + K - 1;
            int c2 = j + K - 1;

            int repaintB = B[r2][c2] - B[i - 1][c2] - B[r2][j - 1] + B[i - 1][j - 1];
            int repaintW = W[r2][c2] - W[i - 1][c2] - W[r2][j - 1] + W[i - 1][j - 1];

            ans = min(ans, min(repaintB, repaintW));
        }
    }

    cout &lt;&lt; ans &lt;&lt; &#39;\n&#39;;
}</code></pre><p>이제 ans는 최대값으로 잡고
2중 for문을 돌면서 N, M에서 판 크기만큼 빼고 돌리기 시작한다</p>
<p>r2, c2는 지금 어디까지 누적합을 더해야하는지 잡아주는 x, y축을 잡아준다
그리고 위에서 합을 더할 때 더하고 더하고 뺀 것과 마찬가지로 실제 값을 구하려면 빼고 빼고 2번 뺀 걸 더한다고 계산하면 조금 편하게 보일 것이다</p>
<p>이렇게 하고 ans와 B 기준, W 기준 중 작은걸 골라 또 min을 구하고, 이걸 돌리면 결론적으론 답을 알 수가 있다</p>
<p>어렵네요..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[신작 개발일지 (1) 키보드 입력 인식 심화편]]></title>
            <link>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-1-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%9E%85%EB%A0%A5-%EC%9D%B8%EC%8B%9D-%EC%8B%AC%ED%99%94%ED%8E%B8</link>
            <guid>https://velog.io/@hipop1109-dev/%EC%8B%A0%EC%9E%91-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-1-%ED%82%A4%EB%B3%B4%EB%93%9C-%EC%9E%85%EB%A0%A5-%EC%9D%B8%EC%8B%9D-%EC%8B%AC%ED%99%94%ED%8E%B8</guid>
            <pubDate>Thu, 29 Jan 2026 09:57:43 GMT</pubDate>
            <description><![CDATA[<p>이번에 포폴 겸 해보고싶은 기능들에 대해 시험해보고자 새로운 신작을 개발중에 있다, 특히 평소 개발할 때 딱히 쓰지 않을 여러 가지 기능들을 많이 활용할 예정이라 기능을 만들 때마다 하나씩 업로드해볼까 한다</p>
<p>이번 게임의 핵심 기능 중 하나는 키보드의 입력을 받아 이를 블럭화해 떨어뜨리는 기능이다.
이와 함께 그 블럭에 현재 친 키보드 글자를 입혀서 떨어뜨리는 기능을 만들려 한다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/2e9835a1-aad4-4537-b588-a802f5f4d2f0/image.png" alt=""></p>
<p>썸네일용</p>
<p>그러나 기본적으로 우리가 쓰는 자판들은 글자 키보드 외 숫자, 특수문자, 기호, F1~F12, NumLock 같은 특수 문자들 등등 많은 예외사항들이 있다</p>
<p>이를 인식하는 법에 대해 알아보도록 하겠다</p>
<p>우선 유니티의 New Input System을 사용할 것이다</p>
<pre><code> void Start()
    {
        Input.imeCompositionMode = IMECompositionMode.On;
        Keyboard.current.onTextInput += OnTextInput;
    }
</code></pre><p>이러한 코드를 우선 세팅해주었다</p>
<p>IME는 조합형 단어를 표현하는 유니티 InputSystem 내 기능이다</p>
<p>예를 들어 한글, 일본어 중국어 같은 언어들이 이 과정을 거쳐서 문자를 만든다
이를 On 처리를 해야 한영키로 바꾸고 한글을 입력했을 때 유니티가 이를 인식하고 뱉을 수가 있다</p>
<p>처음 이를 켜고 한글을 입력했을 때 한글이 자꾸 ㄹ, ㅁ 이렇게 나오지 않고 ㄻ 이렇게 나오는 현상이 생겼다.</p>
<p>그래서 처음 설명만 듣고 조합을 안 하고 문자별로 뱉을 것을 대비해 이를 Off로 처리해봤는데 한글 자체를 뱉지 못하는 현상이 일어났다, 그래서 이를 On하고 코드를 수정하는 방식으로 방향을 꺾었다</p>
<pre><code>Keyboard.current.onTextInput</code></pre><p>은 문자 입력 루트를 뜻한다
Keyboard 내 입력하는 텍스트 (영문/숫자/기호/한글 등)을 처리하는 과정을 거친다</p>
<p>보통 우리가 키 코드를 받을 때
KeyCode.E 이런 식으로 받는 경우가 대부분이다, 하지만 지금 우리가 진행해야 하는 것은 진짜 문자 그 자체를 받아야 하기 때문에 이를 통해 받아내야 하는 것이다</p>
<p>여기서 어떻게 Shift를 누르거나 caplock을 눌러야 소환되는 기호들을 유니티가 인식하는지에 대해 개발하려 찾아본 결과 컴퓨터 OS 자체에서 보내는 신호 자체는 기호든 뭐든 그대로 보내진다</p>
<p>이 보내진 정보는 유니티 내 IME (Input Method Editor)를 통해 유니티에 전달된다</p>
<p>우리가 여기서 바꾼 것은 키코드를 받는것이 아닌 진짜 current.onTextInput, 즉 현재 텍스트를 OS를 통해 전달받으면서 이를 뽑아낼 수 있는 것이다</p>
<pre><code>  if (char.IsControl(c) || c == &#39; &#39;) 
        {
            // Space, Enter 등은 CheckSpecialKeys에서 처리 (LongKeyPrefab 사용을 위해)
            return; 
        }
</code></pre><p>주석에서 나와있듯이 눌렸는데 아무것도 입력을 보내지 않는다면 결국 특수한 키일 것이므로 돌려보내고 스페셜키인지 체크를 하는 과정을 거친다</p>
<p>이렇게 모든 입력을 받고 나머지 특수한 버튼들은 후술할 SpecialKey에서 처리할 것이다</p>
<pre><code> private void CheckSpecialKeys()
    {
      if (Keyboard.current == null) return;
      // Space
      if (Keyboard.current.spaceKey.wasPressedThisFrame)  SpawnKey(&quot;Space&quot;, true);
     // Enter
     if (Keyboard.current.enterKey.wasPressedThisFrame || Keyboard.current.numpadEnterKey.wasPressedThisFrame) SpawnKey(&quot;Enter&quot;, true);
     // Backspace
     if (Keyboard.current.backspaceKey.wasPressedThisFrame) SpawnKey(&quot;←&quot;, true); // Backspace
     // Tab
     if (Keyboard.current.tabKey.wasPressedThisFrame) SpawnKey(&quot;Tab&quot;, true);
     // Shift (Left/Right)
     if (Keyboard.current.leftShiftKey.wasPressedThisFrame) SpawnKey(&quot;Shift&quot;, true);
     if (Keyboard.current.rightShiftKey.wasPressedThisFrame) SpawnKey(&quot;Shift&quot;, true);
     // Ctrl
    if (Keyboard.current.insertKey.wasPressedThisFrame) SpawnKey(&quot;Ins&quot;, false);
    if (Keyboard.current.deleteKey.wasPressedThisFrame) SpawnKey(&quot;Del&quot;, false);
    if (Keyboard.current.homeKey.wasPressedThisFrame) SpawnKey(&quot;Home&quot;, false);
}</code></pre><p>이런 식으로 일부 코드만 가져왔는데
유니티의 키보드 인식 형식은 모든 키를 대응할 수 있게 설계되어있다, Ins나 이런 키까지 모두
그래서 이를 통해 스페셜한 키인지 분리해서 뱉으면 간단하게 문제를 해결할 수 있다</p>
<p>그런데 이런 식으로 한글을 뱉을 때 문제가 조금 생겼다</p>
<pre><code>Input.imeCompositionMode = IMECompositionMode.On;</code></pre><p>이 코드는 조합 문자를 허용하는 구조라고 했었다, 그래서 한 블럭씩 받는 구조를 짜도 상술한 ㄻ 이런 식으로 뱉는 문제가 생겼다</p>
<p>그래서 이를 해결하기 위해 HangulUtils라는 스태틱 코드를 새로 짜 이를 검사해서 뜯어내는 구조를 짰다</p>
<pre><code>{&#39;ㄳ&#39;, new char[] {&#39;ㄱ&#39;, &#39;ㅅ&#39;}},
{&#39;ㄵ&#39;, new char[] {&#39;ㄴ&#39;, &#39;ㅈ&#39;}},
{&#39;ㄶ&#39;, new char[] {&#39;ㄴ&#39;, &#39;ㅎ&#39;}},

{&#39;ㅘ&#39;, new char[] {&#39;ㅗ&#39;, &#39;ㅏ&#39;}},
{&#39;ㅙ&#39;, new char[] {&#39;ㅗ&#39;, &#39;ㅐ&#39;}},
{&#39;ㅚ&#39;, new char[] {&#39;ㅗ&#39;, &#39;ㅣ&#39;}},</code></pre><p>이런 것들의 딕셔너리를 짠 후 복합 자음, 모음인지 테스트를 한 후 이를 뜯어내는 과정을 거쳤다, 코드에 대해 간단히 설명하자면 ㄻ이라는 단일 문자가 들어올 때 리스트의 범위를 한칸 높여 뒤의 것을 밀어서 하나 더 떨어뜨리는 그런 플로우로 만들었다</p>
<p>결론적으로 아직 고칠 부분이 조금 많지만 모든 글자들, 특수기호, 백스페이드 등 키보드의 모든 과정들을 인식할 수 있는 코드를 제작했다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/65c6f122-dcc0-46c3-9b31-f9f01e724ee7/image.gif" alt=""></p>
<p>아마 이런 특이한 기능들을 만들때만 올릴듯 하기도 하고.. 아직 방향성을 잡지는 못했지만 일단은 쭉 진행해보도록 하겠다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 재활치료 (4) A + B - 투 스텝 (31430)]]></title>
            <link>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-4-A-B-%ED%88%AC-%EC%8A%A4%ED%85%9D-31430</link>
            <guid>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-4-A-B-%ED%88%AC-%EC%8A%A4%ED%85%9D-31430</guid>
            <pubDate>Tue, 27 Jan 2026 13:42:01 GMT</pubDate>
            <description><![CDATA[<p>이번엔 처음 풀어보는 투 스텝 문제이다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/5a31e7f8-b1bd-4b38-822e-97ec91426a78/image.png" alt="">
저 13자리 숫자에 매몰되어 있다가 각각 알파벳에 따른 26진수로 묶고 다시 26진수로 풀어서 내보내면 될 것이라고 생각을 했다</p>
<p>그래서 만든 코드는</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;vector&gt;
using namespace std;

int main() {
    int T;
    long long A, B;
    int C;
    string a(13, &#39;a&#39;);
    string b = &quot;&quot;;

    cin &gt;&gt; T;

    if (T == 1) {
        cin &gt;&gt; A &gt;&gt; B;
        long long Sum;
        Sum = A + B;

        int index = 0;
        while (Sum &gt; 0 &amp;&amp; index &lt; 13) {
            int r = Sum % 26;
            a[index] = char(&#39;a&#39; + r);
            Sum /= 26;
            index++;
        }

        if (a.length() != 13) {
            string as = &quot;&quot;;
            for (int i = 0; i &lt; 13 - a.length(); i++) {
                as += &#39;a&#39;;
            }
            a = as + a;
        }
        cout &lt;&lt; a;
    }
    else if (T == 2) {
        cin &gt;&gt; b;

        int count = 0;
        while (count &lt; b.size() &amp;&amp; b[count] == &#39;a&#39;) {
            count++;
        }

        if (count == 13) {
            cout &lt;&lt; &quot;0&quot;;
            return 0;
        }

        string as = b.substr(count);
        long long result = 0;
        long long mul = 1;

        for (int i = 0; i &lt; 13; i++) {
            int dig = b[i] - &#39;a&#39;;
            result += dig * mul;
            mul *= 26;
        }
        cout &lt;&lt; result;
    }
}</code></pre><p>우선 a가 13개 들어있는 string문을 만든다
우선 T == 1일 때 A, B를 받은 후 Sum을 구한다
그리고 앞자리부터 변환해서 남은 뒷자리가 a인 26진수를 만들어서 보관해놓는다</p>
<p>풀다가 수정한게 남아서 13이 아닐 경우가 남아있는데 어차피 절대 못 이뤄질 조건문이라 패스해도 무방할듯
그래서 a를 전체 출력한 후 다시 T가 2일 때</p>
<p>b를 입력받을 시
일단 전체 다 a일 때는 무조건 0을 출력하고 끝내버린다
count는 뒷자리 a를 싹 뺄 목적으로 계산한 변수이다
그래서 깔끔히 정리된 as를 가공하는 과정으로 진행이 될 것이다</p>
<p>우선 결과를 넣을 result랑 변환에 핵심이 되는 mul을 지정한 후
result에 첫 자리부터 mul * 변환 숫자값을 한 후
mul에 26을 곱해 다시 더하는 방식으로 26진수를 해체한다</p>
<p>그렇게 되면 결과값을 뽑아낼 수 있다
잘 읽으면 풀기 그렇게 어렵지는 않았던거 같다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 재활치료 (3) 2636번 : 치즈]]></title>
            <link>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-3-2636%EB%B2%88-%EC%B9%98%EC%A6%88</link>
            <guid>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-3-2636%EB%B2%88-%EC%B9%98%EC%A6%88</guid>
            <pubDate>Sun, 25 Jan 2026 06:41:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/60f18d5f-d8dd-4e7e-8539-d9bab08c65e7/image.png" alt="">
이런 문제이고, 그래프고 파고들어갈 부분이기 때문에 BFS일 가능성이 커보인다
여러 가지 규칙을 찾아보고 조금 분석해봤지만
결론적으로는 이 문제의 핵심은 0,0, 즉 원점에서 아무것도 뚫지 않고 딱 닿을 수 있는 치즈조각을 하나씩 모아서 싹 없애는게 포인트라고 생각했다</p>
<p>그렇다면 정석적인 BFS 로직을 짜보자면</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;queue&gt;
using namespace std;

int main() {
    int W, H;
    cin &gt;&gt; H &gt;&gt; W;

    vector&lt;vector&lt;int&gt;&gt; Cheese(H, vector&lt;int&gt;(W));

    for(int i = 0; i &lt; H; i++) {
        for(int j = 0; j &lt; W; j++) {
            cin &gt;&gt; Cheese[i][j];
        }
    }

    int time = 0;
    int leftCheese = 0;

    int dr[4] = { -1, 1, 0, 0 };
    // 세로 변화량 잡아두기, 
    int dc[4] = { 0, 0, -1, 1 };
    // 가로 변화량 잡아두기

    while (true) {
        //외부 공기 BFS를 위한 visited
        //방문 체크용
        vector&lt;vector&lt;int&gt;&gt; visited(H, vector&lt;int&gt;(W, 0));
        //이번 시간에 녹을 치즈 위치 모으기
        vector&lt;pair&lt;int, int&gt;&gt; melt;

        queue&lt;pair&lt;int, int&gt;&gt; q;
        //페어 큐를 만든 후
        q.push({ 0, 0 });
        //시작점에 0, 0 을 넣는다
        visited[0][0] = 1;
        // 방문에 체크

        while (!q.empty()) {
            //큐가 빌때까지 반복한다
            pair&lt;int, int&gt; cur = q.front();
            q.pop();
            int h = cur.first;
            int w = cur.second;
            // 큐에서 하나 꺼내서 현재 위치를 h, w로 잡고
            // 그 칸에서 4방향 탐색 시작

            for (int k = 0; k &lt; 4; k++) {
                int nr = h + dr[k];
                int nc = w + dc[k];
                //위, 아래, 왼쪽, 오른쪽 이웃칸의 nr, nc 좌표 계산

                if (nr &lt; 0 || nr &gt;= H || nc &lt; 0 || nc &gt;= W) continue;
                //범위 벗어나면 패스
                if (visited[nr][nc]) continue;
                // 방문했었으면 패스

                if (Cheese[nr][nc] == 0) {
                    //공기면 계속 퍼짐
                    visited[nr][nc] = 1;
                    //방문 표시
                    q.push({ nr, nc });
                    //큐에 넣기
                }
                else if(Cheese[nr][nc] == 1) {
                    //치즈를 만나면 녹을 후보에 추가
                    //바로 0으로 바꾸면 안됨
                    visited[nr][nc] = 1;
                    //방문 표시
                    melt.push_back({ nr, nc });
                    //녹을 치즈 후보에 추가
                }
            }
        }

        if (melt.empty()) break; //더 이상 녹을 치즈가 없으면 종료

        // 한꺼번에 녹이기
        leftCheese = (int)melt.size();

        for (int i = 0; i &lt; (int)melt.size(); i++) {
            // 녹이기
            int h = melt[i].first;
            // 녹일 좌표 h
            int w = melt[i].second;
            // 녹일 좌표 w
            Cheese[h][w] = 0;
            // 녹이기
        }

        time++;
        // 시간 증가
    }

    cout &lt;&lt; time &lt;&lt; &#39;\n&#39; &lt;&lt; leftCheese &lt;&lt; &#39;\n&#39;;
    return 0;
}
</code></pre><p>이런식의 코드를 짜보았다
아직 BFS류의 코드에 약해서 한줄한줄 분석해보자면</p>
<blockquote>
</blockquote>
<p>우선 H, W를 만들고 치즈라는 2차원 벡터에 넣어서 기본적인 구조를 만들고 거기에 삽입을 했다</p>
<blockquote>
</blockquote>
<pre><code>int W, H;
    cin &gt;&gt; H &gt;&gt; W;
    vector&lt;vector&lt;int&gt;&gt; Cheese(H, vector&lt;int&gt;(W));
    for(int i = 0; i &lt; H; i++) {
        for(int j = 0; j &lt; W; j++) {
            cin &gt;&gt; Cheese[i][j];
        }
    }</code></pre><p>그리고 time, leftCheese로 기본 구조를 잡았다</p>
<blockquote>
</blockquote>
<pre><code>int time = 0;
int leftCheese = 0;</code></pre><p>그리고 dr로 가로, 즉 x축 이동을 구하고 dy로 세로, 즉 y축 이동을 구한다</p>
<blockquote>
</blockquote>
<pre><code>    int dr[4] = { -1, 1, 0, 0 };
    // 세로 변화량 잡아두기, 
    int dc[4] = { 0, 0, -1, 1 };
    // 가로 변화량 잡아두기</code></pre><p>그리고 이제 While true인 동안 일단 방문 체크를 위한 Cheese랑 똑같은 visited 벡터를 만들었다</p>
<blockquote>
</blockquote>
<pre><code>while (true) {
    //외부 공기 BFS를 위한 visited
    //방문 체크용
    vector&lt;vector&lt;int&gt;&gt; visited(H, vector&lt;int&gt;(W, 0));</code></pre><p>그리고 지금 이 순간에 녹을 치즈의 위치, melt 페어를 만들었다</p>
<blockquote>
</blockquote>
<pre><code>vector&lt;pair&lt;int, int&gt;&gt; melt;</code></pre><p>그리고 페어 큐를 하나 더 만든 후 시작점에 0,0을 넣고 visited에도 0, 0 에 1, 즉 방문 표시를 한다</p>
<blockquote>
</blockquote>
<pre><code>queue&lt;pair&lt;int, int&gt;&gt; q;
//페어 큐를 만든 후
q.push({ 0, 0 });
//시작점에 0, 0 을 넣는다
visited[0][0] = 1;
// 방문에 체크</code></pre><p>그래서 이 큐가 빌 때까지 반복을 하는데 또 cur라는 페어를 만드는데 q.front를 대입하고 q를 pop하면서 맨 앞에걸 쓰는 걸 명시한다</p>
<blockquote>
</blockquote>
<pre><code>while (!q.empty()) {
    //큐가 빌때까지 반복한다
    pair&lt;int, int&gt; cur = q.front();
    q.pop();</code></pre><p>int h는 cur,즉 방금 나온 q.front의 first, w는 second를 넣고 결국 지금 현재 위치를 h, w로 잡고 4방향을 탐색한다 </p>
<blockquote>
</blockquote>
<pre><code>    int h = cur.first;
    int w = cur.second;</code></pre><p>그리고 다시 4방향을 반복 탐색하는데 int nr는 높이 + dr[k]고 nc는 w + dc[k]로 적용해 구한다</p>
<blockquote>
</blockquote>
<pre><code>    for (int k = 0; k &lt; 4; k++) {
        int nr = h + dr[k];
        int nc = w + dc[k];
        //위, 아래, 왼쪽, 오른쪽 이웃칸의 nr, nc 좌표 계산</code></pre><p>그리고 아래 범위는 그냥 주변 봤을 때 좌표 자체를 벗어나면 넘긴다 </p>
<blockquote>
</blockquote>
<pre><code>if (nr &lt; 0 || nr &gt;= H || nc &lt; 0 || nc &gt;= W) continue;
//범위 벗어나면 패스</code></pre><p>방문했으면 넘긴다</p>
<blockquote>
</blockquote>
<pre><code>    if (visited[nr][nc]) continue;
    // 방문했었으면 패스</code></pre><p>일단 공기라면 계속 지나갈 수 있어 그리고 방문 표시를 하고 큐에 넣어,
그래야 반복문에 다시 대입해 다음 칸으로 가서 검사할 수 있기 때문</p>
<blockquote>
</blockquote>
<pre><code>if (Cheese[nr][nc] == 0) {
                //공기면 계속 퍼짐
                visited[nr][nc] = 1;
                //방문 표시
                q.push({ nr, nc });
                //큐에 넣기
}</code></pre><p>그리고 치즈를 만나면 녹을 후보에 추가하고 방문 표시를 해 대신 바로 녹이면 안되고 melt라는 녹을 치즈 후보에 추가한다</p>
<blockquote>
</blockquote>
<pre><code>    else if(Cheese[nr][nc] == 1) {
        //치즈를 만나면 녹을 후보에 추가
        //바로 0으로 바꾸면 안됨
        visited[nr][nc] = 1;
        //방문 표시
        melt.push_back({ nr, nc });
        //녹을 치즈 후보에 추가
    }
}</code></pre><p>그리고 melt.empty, 즉 더 이상 녹일 치즈가 없으면 종료</p>
<blockquote>
</blockquote>
<pre><code>    if (melt.empty()) break; //더 이상 녹을 치즈가 없으면 종료</code></pre><p>leftCheese는 지금까지 녹이는 후보에 넣은 모든 좌표의 개수</p>
<blockquote>
</blockquote>
<pre><code>leftCheese = (int)melt.size();</code></pre><p>그래서 저기에 대입하는거고 그리고 아래는 녹이는 과정</p>
<blockquote>
</blockquote>
<pre><code>for (int i = 0; i &lt; (int)melt.size(); i++) {
    // 녹이기
    int h = melt[i].first;
    // 녹일 좌표 h
    int w = melt[i].second;
    // 녹일 좌표 w
    Cheese[h][w] = 0;
    // 녹이기
}</code></pre><p>그리고 time을 더하고 다음 껍질에 또 같은 과정을 돌린다</p>
<blockquote>
</blockquote>
<pre><code>time++;</code></pre><p>다 한 후 돌린 시간과 마지막에 남은 leftcheese를 대입하면 끝</p>
<blockquote>
</blockquote>
<pre><code>cout &lt;&lt; time &lt;&lt; &#39;\n&#39; &lt;&lt; leftCheese &lt;&lt; &#39;\n&#39;;
return 0;</code></pre><p>아무튼 이런 과정이다, 아마 기본적인 BFS 문제인거 같은데 지식 부족으로 도움을 많이 받았다, 공부가 필요해보이는 부분</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6개월 유니티 게임 개발일지 : Addressable]]></title>
            <link>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-Addressable</link>
            <guid>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-Addressable</guid>
            <pubDate>Wed, 21 Jan 2026 03:11:41 GMT</pubDate>
            <description><![CDATA[<p>오늘은 어드레서블에 대해 알아보려고 한다</p>
<h3 id="addressables">Addressables</h3>
<p>유니티가 제공하는 에셋 관리 및 로딩 시스템
기존 리소스, 에셋번들 문제를 개선해 주소 기반으로 에셋을 로드, 언로드하고 동적 컨텐츠 업데이트까지 가능하게 해주는 구조</p>
<ul>
<li>에셋이 실제 어떤 위치에 있든 상관없이 문자열 주소(address)로 에셋을 불러오고 관리할 수 있는 시스템</li>
<li>코드가 에셋 번들 구조에 의존하지 않아도 된다는 장점이 있음</li>
</ul>
<p>기존 방식의 문제점으로는</p>
<p><strong>Resources:</strong></p>
<ul>
<li>빌드 시 모든 에셋이 포함되어 앱 크기가 불필요하게 커짐</li>
<li>에셋 이름 변경 or 이동 시 참조 깨지기 쉬움</li>
<li>메모리 관리가 수동적, 복잡함</li>
<li>빌드 후 에셋 업데이트 불가능</li>
</ul>
<p><strong>에셋 번들</strong></p>
<ul>
<li>구현, 관리 복잡, 오류 발생 가능성 높음</li>
<li>의존성 관리 어려움 (중복 에셋 문제)</li>
<li>빌드 파이프라인 설정이 까다로움</li>
</ul>
<p>이를 해결하는 것이 Addressable</p>
<h3 id="addressable을-쓰는-이유">Addressable을 쓰는 이유</h3>
<blockquote>
</blockquote>
<p><strong>1. 주소 기반 로딩</strong></p>
<ul>
<li>에셋을 직접 참조하지 않고 문자열 키로 로딩 -&gt; 코드와 에셋을 분리할 수 있음
  -&gt; 에셋을 변경해도 코드를 수정할 필요가 없음<blockquote>
</blockquote>
</li>
<li><em>2. 자동 종속성 관리*</em></li>
<li>Addressable은 에셋의 의존 관계를 자동으로 파악해 필요한 에셋을 함께 로드 / 언로드함<blockquote>
</blockquote>
</li>
<li><em>3. 앱 초기 용량 축소*</em></li>
<li>필요한 에셋만 로컬/원격에서 다운로드 -&gt; 초기 설치 크기 감소, 런타임 컨텐츠 추가 가능<blockquote>
</blockquote>
</li>
<li><em>4. 메모리 효율적 관리*</em></li>
<li>로딩된 에셋은 레퍼런스 카운팅으로 관리, 필요없어지면 안전하게 언로드 가능<blockquote>
</blockquote>
</li>
<li><em>5. 빠른 개발, 테스트 반복*</em></li>
<li>에디터에서 빌드 없어도 플레이 모드에서 실제 addressable 로딩 시뮬레이션 가능<blockquote>
</blockquote>
</li>
<li><em>6. 에셋번들의 단점 보완*</em></li>
<li>에셋번들의 하드코딩, 번들 종속성 문제 등을 해결 가능<blockquote>
</blockquote>
</li>
<li><em>7. 동적 콘텐츠 업데이트*</em><blockquote>
<ul>
<li>앱 재배포 및 재빌드 없이 원격 서버의 에셋 번들을 업데이트해 게임 컨텐츠를 동적으로 추가하거나 수정 가능</li>
</ul>
</blockquote>
</li>
</ul>
<h3 id="addressable의-주요-개념-이해하기">Addressable의 주요 개념 이해하기</h3>
<blockquote>
</blockquote>
<p><strong>1. 주소(Address)</strong></p>
<ul>
<li>각 에셋을 식별하는 고유한 문자열 키</li>
<li>파일 경로, GUID, 또는 사용자 정의 문자열 등 다양한 형태 지정 가능</li>
<li>이 주소를 통해 런타임에 에셋을 로드함<blockquote>
</blockquote>
</li>
<li><em>2. 레이블(Label)*</em></li>
<li>하나 이상의 주소에 할당할 수 있는 태그</li>
<li>특정 카테고리의 에셋들을 그룹화 -&gt; 한번에 로드하는 데 유용함<blockquote>
</blockquote>
</li>
<li><em>3. 어드레서블 그룹(Addressable Group)*</em></li>
<li>에셋 번들을 빌드하는 단위</li>
<li>어떤 에셋을 어떤 번들에 포함시키고, 어떻게 빌드, 로드할지 설정</li>
<li>Local / Remote, 압축 방식 등 결정<blockquote>
</blockquote>
</li>
<li><em>4. 에셋 레퍼런스 (AssetReference)*</em></li>
<li>어드레서블 에셋에 대한 직렬화 가능한 참조</li>
<li>인스펙터창에서 직접 어드레서블 에셋 할당 -&gt; 코드에서는 이 참조를 통해 에셋 로드 가능</li>
<li>AssetReferenceGameObject 등 특정 타입에 대한 참조도 제공<blockquote>
</blockquote>
</li>
<li><em>5. 카탈로그 (Catalog)*</em></li>
<li>어드레서블 에셋의 주소, 레이블, 번들 정보 등을 담고 있는 메타데이터 파일</li>
<li>런타임에 이 카탈로그를 로드 -&gt; 어떤 에셋을 어디서 가져올지 결정</li>
<li>catalog.json, catalog.hash<blockquote>
</blockquote>
</li>
<li><em>6. 프로필 (Profile)*</em></li>
<li>빌드 및 로드 경로 관련 변수들을 관리하는 설정세트</li>
<li>로컬빌드, 개발서버, 운영서버 등 다양한 환경에 맞는 경로 설정을 쉽게 전환 가능</li>
</ul>
<h3 id="사용법">사용법</h3>
<h4 id="1-로컬-기준">1. 로컬 기준</h4>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/75caa561-2504-42ff-bba2-41003f587ffd/image.png" alt="">
우선 어드레서블을 깐다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/1bb7e3c2-8f9a-43ac-a21f-4b13ce971ebb/image.png" alt="">
그 후 저 루트로 들어가 Create Addressables Settings을 눌러 넣는다</p>
<p>그 후 에셋 중 머터리얼이 있는 에셋 아래에 강조해둔 Addressable 탭이 뜬다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/852a7b44-075a-4102-888c-562966c7efec/image.png" alt="">
이를 클릭하면
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/770da718-e606-4ff9-9512-7f1c610ddca8/image.png" alt="">
이런 식으로 디폴트 로컬 그룹에 들어가고 인스펙터에도 적용된다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/af93de46-ebe8-40a0-bf1f-887eca5b696c/image.png" alt="">
그 후 어드레서블 그룹 탭을 누르면 인스펙터에 설정들이 뜬다
이 쪽에는 빌드, 로드 경로, 번들링 모드, 압축 방식 등을 적용한다
보통은 로컬 빌드용, 원격 다운로드용 그룹을 분리해 관리하는게 일반적이라고 한다</p>
<p>탭별로 살짝 분석해보자면</p>
<blockquote>
<p><strong>Include in Build</strong></p>
</blockquote>
<ul>
<li>어드레서블 빌드에 포함할지 여부 결정<blockquote>
</blockquote>
</li>
<li><em>Active Profile*</em></li>
<li>빌드 / 로드 경로 세트</li>
<li>Default, Local, Remote, CDN 등 이런 식으로 나눠서 따로 프로필을 만들어 씀</li>
<li>이 그룹이 어디서 빌드되고 어디서 로드되느냐를 환경별로 바꾸는 기능<blockquote>
</blockquote>
</li>
<li><em>Build and Load Path*</em></li>
<li>이 그룹의 빌드, 로드 경로를 지정해줌</li>
<li>Local일 때는 로컬 생성, 로드 / Remote일 때 서버 생성, 로드<blockquote>
</blockquote>
</li>
<li><em>Path Preview*</em></li>
<li>Build Path : 어드레서블 빌드 결과가 생성되는 실제 위치 (에디터 내부 경로)</li>
<li>Load Path : 런타임에서 실제로 읽는 경로<ul>
<li>RuntimePath = 플랫폼별 실행 위치</li>
</ul>
</li>
<li>이 둘이 어긋나면 로딩이 실패함<blockquote>
</blockquote>
</li>
<li><em>Advanced Option*</em></li>
<li>Use Default 
  -체크하면 유니티 기본값으로 고정, 해제하면 아래 탭 이용<blockquote>
</blockquote>
</li>
<li>Asset Bundle Compression<ul>
<li>LZ4 : 빠른 로딩<ul>
<li>LZMA : 압축률이 높고 로딩이 느림<ul>
<li>Uncompressed : 테스트용으로 사용</li>
<li>보통 게임 실행 중 로드할 때는 LZ4, 최초 설치는 LZMA를 가끔 쓴다고 함<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li>Use Asset Bundle Cache 
  -다운로드한 번들을 디스크 캐시에 저장<ul>
<li>재실행 시 다시 다운로드 안함<ul>
<li>안 켜면 매번 다시 받는 현상이 일어나 활용이 어렵다<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li>Cache Clear Behavior<ul>
<li>Clear When Space is Needed in Cache : 캐시 공간 부족할 때만 자동 삭제, 가장 안전<ul>
<li>Clear On Init : 실행할 때마다 캐시 삭제</li>
<li>Never Clear : 클리어하지 않음</li>
</ul>
</li>
</ul>
</li>
<li>Asset Bundle CRC<ul>
<li>Enabled, Including Cached : 번들 무결성 체크, 손상된 파일 로드 방지</li>
</ul>
</li>
<li>Bundle Naming Mode<ul>
<li>Append Hash의 경우 번들 이름에 해시값을 추가하는 개념<ul>
<li>업데이트 시 캐시 충돌을 방지하고 CDN 캐시 갱신 문제를 해결할 수 있음</li>
</ul>
</li>
</ul>
</li>
<li>Strip Bundle Download<ul>
<li>번들 다운로드 시 일부 데이터 제거</li>
</ul>
</li>
<li>Include Address in Catalog<ul>
<li>주소 문자열을 카탈로그에 포함</li>
<li>런타임에서 Address -&gt; Asset으로 매핑 가능하다</li>
</ul>
</li>
<li>Include GUIDs in Catalog<ul>
<li>GUID 기반 접근 가능<ul>
<li>내부 참조 안정성 증대</li>
</ul>
</li>
</ul>
</li>
<li>Include Labels in Catalog<ul>
<li>라벨 기반 로딩 가능</li>
</ul>
</li>
<li>Bunlde Mode <ul>
<li>Pack Together : 그룹 전체를 하나의 번들로 쌈<ul>
<li>Pack Separately : 에셋 하나당 번들로 만듬</li>
<li>Pack Togather By Label : 라벨 기준으로 묶음</li>
</ul>
</li>
</ul>
</li>
<li>Content Update Restriction<ul>
<li>Prevent Updates : 체크 시 콘텐츠 업데이트 대상에서 제외 -&gt; 패치 불가</li>
</ul>
</li>
</ul>
<p>   사실 뭐 이런것도 중요하지만 그래도 중요한건 일단 어떻게 쓰느냐인거니까
   이 부분은 내일 한번 쫙 정리해서 실전 사용을 목적으로 해보도록 하겠다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 재활치료 (2) 31836번 : 피보나치 기념품]]></title>
            <link>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-2-31836%EB%B2%88-%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98-%EA%B8%B0%EB%85%90%ED%92%88</link>
            <guid>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-2-31836%EB%B2%88-%ED%94%BC%EB%B3%B4%EB%82%98%EC%B9%98-%EA%B8%B0%EB%85%90%ED%92%88</guid>
            <pubDate>Tue, 20 Jan 2026 06:14:06 GMT</pubDate>
            <description><![CDATA[<p>바로 이어가는 2번째 문제
피보나치라고 하는거 보니 당연히 dp쪽 문제 아닐까? 아직 읽기 전이다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/131523de-3cb1-40e5-a2a0-6f3d62ccac4e/image.png" alt="">
애드 혹 문제였다, 이러한 문제들은 대부분 패턴이 존재한다</p>
<p>그래서 한 9번째까지 스스로 돌리면서 패턴을 찾아봤다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/0e542699-c75c-45fc-8b6d-91f77e9bb5cb/image.png" alt="">
글씨가 이상해서 아쉽지만 아무튼
2라는 예외를 제외하고 3의 배수를 중심으로
N을 오른쪽 / N-1, N-2를 왼쪽으로 보내며
3의 배수를 기준으로
나머지가 0이면 딱 맞고
나머지가 1이면 맨 앞 부분만 빼고
나머지가 2이면 1,2번을 각각 왼, 오른쪽에 분배하고 나머지를 분배하면
정확히 조건에 맞는다는 것을 알 수 있었다</p>
<p>그래서 정의한 식이</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;cmath&gt;
#include &lt;algorithm&gt;
using namespace std;

int main() {
    int N;
    cin &gt;&gt; N;

    int a = 0;
    int c = 0;
    vector&lt;int&gt; b(0);
    vector&lt;int&gt; d(0);

    if (N == 2) {
        cout &lt;&lt; 1 &lt;&lt; &#39;\n&#39;;
        cout &lt;&lt; 1 &lt;&lt; &#39;\n&#39;;
        cout &lt;&lt; 1 &lt;&lt; &#39;\n&#39;;
        cout &lt;&lt; 2 &lt;&lt; &#39;\n&#39;;
        return 0;
    }

    int r = N % 3;

    if (r == 0) {
        for (int i = N; i &gt; 0; i -= 3) {
            d.push_back(i);
            b.push_back(i - 1);
            b.push_back(i - 2);
        }
    }

    else if (r == 1) {

        for (int i = N; i &gt; 4; i -= 3) {
            d.push_back(i);
            b.push_back(i - 1);
            b.push_back(i - 2);
        }      
        d.push_back(4);
        b.push_back(1);
        b.push_back(3);
    }


    else if (r == 2) {

        b.push_back(1);
        d.push_back(2);

        for (int i = N; i &gt; 2; i -= 3) { 
            d.push_back(i);
            b.push_back(i - 1);
            b.push_back(i - 2);
        }
    }

    sort(b.begin(), b.end());
    sort(d.begin(), d.end());

    a = (int)b.size();
    c = (int)d.size();

    cout &lt;&lt; a &lt;&lt; &#39;\n&#39;;
    for (int i = 0; i &lt; a; i++) {
        cout &lt;&lt; b[i] &lt;&lt; &quot; &quot;;
    }
    cout &lt;&lt; &#39;\n&#39;;

    cout &lt;&lt; c &lt;&lt; &#39;\n&#39;;
    for (int i = 0; i &lt; c; i++) {
        cout &lt;&lt; d[i] &lt;&lt; &quot; &quot;;
    }
    cout &lt;&lt; &#39;\n&#39;;

    return 0;
}
</code></pre><p>이런 식으로 조건에 맞춰 push_back 하는 방향으로 계산을 했다
간단히 설명하자면
2를 예외로 두어 답을 출력하고
3부터 N부터 -2씩 내려가며 분배를 하며 방금 구한 3의 배수를 기준으로 각 항을 맞춰주었다</p>
<p>그 후 숫자를 정렬해 맞춰주며 출력하며 마무리했다</p>
<p>여기서 한번 틀린 예외는 4의 예외였는데
생각해보니 4에서 들어가는 1은 1번의 1, 2번의 1 둘 다 가능한 부분이였어서 문제 자체의 규칙대로 넣어두기로 했다
문제 자체는 1을 넣는 것을 기준으로 하는 것 같아서 4 이상부터 정상적으로 쌓이고 4는 그냥 직접 넣어버리며 문제를 해결했다</p>
<p>패턴 찾는거 자체는 막 어렵진 않았는데 이를 식화시키는 부분에서 조금 애를 먹었던 것 같다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 재활치료 (1) 1699번: 제곱수의 합]]></title>
            <link>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-1-1699%EB%B2%88-%EC%A0%9C%EA%B3%B1%EC%88%98%EC%9D%98-%ED%95%A9</link>
            <guid>https://velog.io/@hipop1109-dev/%EB%B0%B1%EC%A4%80-%EC%9E%AC%ED%99%9C%EC%B9%98%EB%A3%8C-1-1699%EB%B2%88-%EC%A0%9C%EA%B3%B1%EC%88%98%EC%9D%98-%ED%95%A9</guid>
            <pubDate>Tue, 20 Jan 2026 04:18:14 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/cb72ee50-7027-4d1f-8bf4-7dce37993ac9/image.png" alt="">
정석적인 느낌에 제곱이라는 변수를 준 다이나믹 프로그래밍 문제로 보인다
먼저 든 생각은 어차피 아무리 커져도 N의 제곱근의 내림을 초과할 수는 없기 때문에 이를 먼저 구하려 했다</p>
<p>그렇다면</p>
<pre><code>int a = int(sqrt(N));</code></pre><p>으로 정리해서 대입하면 되지 않을까? 생각을 했다
이걸 어디에 넣을까 싶다가 깔끔하게 for 안에서 정리하고, 0 ~ N까지 빼볼 때 접근하기 쉬우려면 for 앞에 넣는게 쉬울 것 같았다</p>
<p>아무튼 이렇게 해서 완성한 코드는</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;cmath&gt;
using namespace std;

int main() {
    int N;
    cin &gt;&gt; N;

    const int INF = 1e9;
    vector&lt;int&gt; dp(N + 1, INF);

    dp[0] = 0;

    for(int i = 1; i &lt;= N; i++) {
        int a = (int)sqrt(i);

        for (int j = 1; j &lt;= a; j++) {

            dp[i] = min(dp[i], dp[i - j * j] + 1);
        }
    }

    cout &lt;&lt; dp[N] &lt;&lt; &#39;\n&#39;;
}</code></pre><p>한줄 한줄 설명해보자면
결론적으론 더한 갯수의 최솟값을 구하려면 min을 잡아야 하기 때문에
 0 ~ N+1, 즉 실제 벡터에 명시된 수가 N이 될 때까지 벡터를 넓히고 모두 INF 변수를 넣는다</p>
<p> 0이라는 숫자를 만드는데는 당연히 0번이 쓰이기 때문에 0을 넣는다</p>
<p> 아래에는 N번 반복한다, 위에서 0을 한번 넣었기 때문에 1부터 N 이하까지 해서 N+1에 모두 채운다</p>
<p> 여기에서 a를 구해서 지금 현재 쓸 숫자의 제곱근의 내림을 구한다</p>
<p>예를 들어 N이 7인 경우를 구해보자면</p>
<p>i = 1인 경우
아직 a = 1이니까
dp[1]이랑 dp[0] + 1을 비교하면 당연히 1이 되고</p>
<p>i = 2인 경우에도
a = 1이니까
dp[2] = min(dp[2], dp[1] + 1)이니까 2가 되된다</p>
<p>i = 3인 경우에도
a = 1이네?
dp[3] = min(dp[3], dp[2]+1) 이니 3이고</p>
<p>i가 4인 경우에 j반복문이 한번 돌아간다
a = 2이니까
for문을 2번 돌려야한다
dp[4] = min(dp[4], dp[4-1]+1)
dp[4] = min(dp[4], dp[4-4] + 1)
이 2개를 돌리니까 dp[4]인 경우에는 1이 된다</p>
<p>이 모든 과정에서 j의 제한을 잡아주기 위해 a를 계속 업데이트해주는 개념이다</p>
<p>이렇게 하면 원하는 수까지 계속 숫자가 쌓이면서 제곱근이 있는 숫자라면 알아서 1로 다시 바꿔주고 다시 1씩 더하는 개념으로 진행한다
그러다가 예를 들어 8같이 4 4로 더해지는 수가 나타난다면
그것도 dp의 기능으로 2로 다시 만들어주는 개념으로 진행이 된다</p>
<p>결론적으로 dp에 제곱근만 빼면 되는 꽤나 간단한 문제였지만 아직 dp의 개념이 잘 잡히지 않아 수식 정의에 고민을 좀 했던 문제이다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6개월 유니티 게임 개발일지 : 파티클 모작을 통한 실전 기능 분석기]]></title>
            <link>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%ED%8C%8C%ED%8B%B0%ED%81%B4-%EB%AA%A8%EC%9E%91%EC%9D%84-%ED%86%B5%ED%95%9C-%EC%8B%A4%EC%A0%84-%EA%B8%B0%EB%8A%A5-%EB%B6%84%EC%84%9D%EA%B8%B0</link>
            <guid>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%ED%8C%8C%ED%8B%B0%ED%81%B4-%EB%AA%A8%EC%9E%91%EC%9D%84-%ED%86%B5%ED%95%9C-%EC%8B%A4%EC%A0%84-%EA%B8%B0%EB%8A%A5-%EB%B6%84%EC%84%9D%EA%B8%B0</guid>
            <pubDate>Thu, 15 Jan 2026 05:55:59 GMT</pubDate>
            <description><![CDATA[<p>이제 파티클의 탭에 대해 분석해봤으니, 상용화되는 가장 많이 쓰는 파티클을 그 파티클의 메인 기능을 강조하는 형식으로 구분하면서 모작 형식으로 공부를 진행해보려 한다
저작권에 꽤나 중대한 침해를 하는 듯 해보이는 방법이라 비공개로..</p>
<p>에셋 자체는 자주 쓰이는 이 에셋을 기반으로 진행해보려 한다
<a href="https://assetstore.unity.com/packages/vfx/particles/cartoon-fx-remaster-free-109565">https://assetstore.unity.com/packages/vfx/particles/cartoon-fx-remaster-free-109565</a></p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/0ffe0d21-7732-4c33-851e-3279f21bd882/image.gif" alt=""></p>
<p>먼저 단일 이미지를 사용하는 비교적 쉬운 파티클인 이 파티클을 한번 분석해보고자 한다</p>
<p>우선 에셋에서 주어진 렌더러 설정부터 적용하자면
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/df885ce4-03cd-4e69-9f64-04a076a7b52c/image.png" alt=""></p>
<blockquote>
<p><strong>Renderer</strong></p>
</blockquote>
<ul>
<li>Billboard로 기본적인 모양을 쓴다<blockquote>
</blockquote>
</li>
<li><em>Material*</em></li>
<li>머터리얼을 주어진 해골 모양으로 깔아둔다<blockquote>
</blockquote>
</li>
<li><em>Sorting Fudge*</em></li>
<li>파티클을 정렬한다<blockquote>
</blockquote>
</li>
<li><em>Min, Max Particle Size*</em></li>
<li>단일 파티클이고 커졌다 사라지는 구조기 때문에 0 ~ 2로 해둔다<blockquote>
</blockquote>
</li>
<li><em>Render Alignment*</em></li>
<li>View로 해 파티클이 평면 앞을 보게 설정한다<blockquote>
</blockquote>
</li>
<li><em>Flip*</em></li>
<li>X을 0.5 넣어 일부를 Flip시킨다<blockquote>
</blockquote>
</li>
<li><em>Allow Roll*</em></li>
<li>Z축 이동을 허용한다<blockquote>
</blockquote>
</li>
<li><em>Pivot*</em></li>
<li>피벗은 고정<blockquote>
</blockquote>
</li>
<li><em>Masking*</em></li>
<li>No Masking으로 해놓아 씬의 스프라이트 마스크와 대응하지 않게 함<blockquote>
</blockquote>
</li>
<li><em>Apply Active Color Space*</em></li>
<li>활성화해놓는다, 상황에 따라 색감이 달라지는게 자연스럽기 때문<blockquote>
</blockquote>
</li>
<li><em>Custom Vertex Streams*</em></li>
<li>저번에 못 붙인 설명을 덧붙이자면 Custom Vertex Streams는 이 파티클의 정보를 쉐이더, GPU에 보내는 통로 같은 개념이다</li>
<li>현재 머터리얼에 담겨 있는 셰이더와 상호작용해 작동하는 구조로 이루어진다</li>
</ul>
<p>그렇다면 지금 이 에셋이 사용하고 있는
Particle UberShader라는 쉐이더를 열어보겠다
쉐이더 코드 전문을 다 올리기엔 무리가 있으니 간단한 설명과 함께 핵심 코드와 그에 따른 쉐이더 탭 결과물과 비교하며 찍어보도록 하겠다
에셋을 분석하는게 의미가 있을까 싶지만 꽤나 범용적인 여러 설정들이 보여서 한번 해보려 한다</p>
<p>원문 코드가 필요하다면 직접 에셋을 깔아서 보는게 편할지도..?</p>
<p>Blending / Depth
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/12cb3d31-5b70-43c6-bde6-8d1950027b93/image.png" alt=""></p>
<blockquote>
</blockquote>
<h3 id="blending">Blending</h3>
<ul>
<li><p>지금 그리는 픽셀 (Src)과 이미 화면에 있는 픽셀 (Dst)를 어떤 공식으로 섞을지 정하는 규칙</p>
</li>
<li><p>이때 쓰는 수식 중 Blend Source / Blend Destination 탭이 있다</p>
<ul>
<li>아주 단순화한 수식으로 진짜 섞어서 나올 색을 구하자면<ul>
<li>Src x Blend Source + Dst x Blend Destination</li>
<li>이런 식으로 정리할 수 있겠다<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><p><em>Source*</em> : 지금 그리고 있는 파티클</p>
</li>
<li><p><em>Destination*</em> : 이미 그려진 배경 혹은 오브젝트를 뜻함
결론적으로 얼마나 파티클 고유색을 쓸지, 그리고 얼마나 배경을 남길지를 적용하는 것이다</p>
<blockquote>
</blockquote>
</li>
<li><p>탭 중 Src---이런 느낌의 탭은 Src의 요소를 쓰겠다는 의미이다</p>
<ul>
<li>Ex) SrcAlpha = 파티클의 알파값을 기준으로 섞는다<ul>
<li>마찬가지로 반대도 그런 의미</li>
</ul>
</li>
</ul>
</li>
<li><p>One = 그대로 100퍼센트 더함</p>
</li>
<li><p>OneMinus--- 은 100에서 저 값 요소를 빼는 개념으로 보면 된다</p>
</li>
<li><p>이런 식으로 여러 탭으로 이 값을 정리해두는 개념</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Blending Type*</em>
이 에셋에서 섞는 타입을 이런 식으로 나눠둔다</p>
</li>
<li><p>Alpha Blending</p>
<ul>
<li>가장 기본적인 투명<ul>
<li>색이 섞이게 적용한다</li>
</ul>
</li>
</ul>
</li>
<li><p>Premultiplied Alpha</p>
<ul>
<li>색에 이미 알파를 곱하는 형식<ul>
<li>경계가 깨끗해짐</li>
</ul>
</li>
</ul>
</li>
<li><p>Additive</p>
<ul>
<li>색을 그냥 더함<ul>
<li>겹칠수록 밝아지는 개념</li>
</ul>
</li>
</ul>
</li>
<li><p>Multiplicative</p>
<ul>
<li>배경을 어둡게 곱하는 개념<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><p><em>Depth Write*</em>
깊이를 기록할지에 대해 물어보는 탭</p>
</li>
<li><p>체크하면 깊이에 기록되며 파티클끼리 겹치는 부분들이 정리된다</p>
</li>
<li><p>끄면 기록하지 않고 기본적인 투명으로 처리된다</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Alpha Clipping*</em>
알파값이 일정 값 이하면 버려버리고, 그 이상이면 100프로 그리는 설정</p>
</li>
<li><p>부드러운 투명을 제외해 경계를 또렷히 할 수 있음</p>
</li>
<li><p>그러나 계단 현상이 생기고 말 그대로 부드러움이 좀 줄 수 있다</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Soft Particles*</em>
파티클이 벽, 바닥을 뚫고 들어갈 때 경계가 잘리는 문제를 해결 가능</p>
</li>
<li><p>카메라 깊이 텍스쳐를 인식하고 너무 가까워지면 알파를 줄이며 사라지게 하는 기능</p>
<ul>
<li><p>Near Fade : 언제부터 사라질지</p>
<ul>
<li>Far : 언제부터 완전히 사라질지</li>
<li>값이 클수록 더 멀리서부터 부드럽게 페이드된다<ul>
<li>Edge Fade : 가장자리 알파를 줄일 수 있는 기능</li>
</ul>
</li>
</ul>
<h3 id="effects">Effects</h3>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/dd2d6b2f-0358-43ef-9e71-e69c88cd15c4/image.png" alt=""></p>
<blockquote>
<p><strong>Enable Dissolve</strong>
디졸브 기능 온오프 탭</p>
</blockquote>
</li>
</ul>
</li>
<li><p>켜면 아래 탭 작동, 끄면 일반 파티클 취급</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Dissolve Texture*</em>
어디서부터 사라질 지 정하는 지도의 개념</p>
</li>
<li><p>보여줄 이미지가 아님</p>
</li>
<li><p>어두운 곳 -&gt; 먼저 사라짐 / 밝은 곳 -&gt; 나중에 사라지는 개념으로 적용</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Tiling*</em></p>
</li>
<li><p>디졸브 패턴 반복 횟수, 값 높아질수록 패턴 촘촘해짐</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Offset*</em></p>
</li>
<li><p>디졸브 패턴의 위치 이동</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Dissolve Smoothing*</em>
사라지는 경계가 얼마나 부드러운가에 대한 수치 조정</p>
</li>
<li><p>낮음 -&gt; 경계가 날카롭고 조각조각 잘리는 느낌</p>
</li>
<li><p>높음 -&gt; 경계가 흐릿하고 연기처럼 보임</p>
</li>
<li><p>속도를 잡는게 아닌 경계의 폭을 조정하는 부분이다</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Invert Dissolve Texture*</em>
밝고 어두운 기준 뒤집음</p>
</li>
<li><p>켜면 위 이미지의 밝은 곳이 먼저 사라짐, 반대로 적용시킬수 있는 구조</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Double Dissolve*</em>
디졸브 경계를 두 겹으로 만든다</p>
</li>
<li><p>쓰면 단일 경계가 아닌 바깥, 안쪽 두 단계의 경계가 생긴다</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Dissolve offest along X*</em>
디졸브를 한 방향으로 진행시키는 탭</p>
</li>
<li><p>디졸브를 X축 방향으로 미는 개념으로 흐르거나 밀리는 효과를 주는 개념</p>
<blockquote>
</blockquote>
</li>
<li><p><em>Enable UV Distortion*</em></p>
</li>
<li><p>다음에 쓸 때 한번 더 설명해보겠다</p>
</li>
</ul>
<p>일단은 요기까지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6개월 유니티 게임 개발일지 : 파티클에 대해 6]]></title>
            <link>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-6</link>
            <guid>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-6</guid>
            <pubDate>Tue, 13 Jan 2026 05:11:13 GMT</pubDate>
            <description><![CDATA[<h3 id="noise">Noise</h3>
<p>파티클에 노이즈를 끼워 역동성을 더하는 기능
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/0a9835eb-3d6f-4fe2-a3cd-19097004b1ba/image.png" alt="">
탭 자체는 위에 나온 많은 것들과 비슷하지만 하나씩 설명하자면</p>
<blockquote>
<p>** Separeate Axes **</p>
</blockquote>
<ul>
<li>각 축에서 움직이는 노이즈 세기를 각각 조절하는 탭, 켜면 x,y,z축이 생김<blockquote>
</blockquote>
</li>
<li><em>Strength*</em></li>
<li>얼마나 분산시킬지 주는 힘<blockquote>
</blockquote>
</li>
<li><em>Frequency*</em></li>
<li>값이 낮을 때 : 부드럽고 매끄러운 노이즈 생성</li>
<li>값이 높을 때 : 빠르게 변하는 노이즈 생성</li>
<li>파티클이 얼마나 갑작스럽게 바뀌느냐를 제어함
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/f47f0796-4ff0-4c29-83bb-12c1a5ce615b/image.png" alt="">
요런식으로 프리뷰도 바뀜<blockquote>
</blockquote>
</li>
<li><em>Scroll Speed*</em></li>
<li>노이즈 필드를 시간의 흐름에 따라 이동시켜 파티클을 더욱 예측 불가하게 만든다<blockquote>
</blockquote>
</li>
<li><em>Damping*</em></li>
<li>탭을 켭면 세기가 빈도에 비례하게 된다</li>
<li>Scroll Speed와 조절하면 파티클의 동작을 그대로 유지하면서 크기는 달라지게 만들 수 있다<blockquote>
</blockquote>
</li>
<li><em>Octaves*</em></li>
<li>최종 노이즈를 생성하기 위해 몇 개의 노이즈 레이어를 겹칠지 지정한다</li>
<li>많이 쌓을수록 더욱 맛있게 만들 수 있지만, 리소스를 많이 차지한다는 단점이 있다<blockquote>
</blockquote>
</li>
<li>*Ocatve Multiplyer</li>
<li>노이즈 레이어를 하나 추가할 때마다 세기가 이 수치만큼 줄어듦</li>
<li>0~1의 범위<blockquote>
</blockquote>
</li>
<li><em>Octave Scale*</em></li>
<li>마찬가지로 노이즈 레이어를 추가할 때마다 빈도가 이 멀티플라이어만큼 조절됨</li>
<li>Octaves만큼 크기 조절 가능<blockquote>
</blockquote>
</li>
<li><em>Quality*</em></li>
<li>품질을 설정하는 칸</li>
<li>품질 설정 낮추면 성능 줄일 수 있지만 노이즈의 효과 자체가 안 좋아질 수 있다<blockquote>
</blockquote>
</li>
<li><em>Remap*</em></li>
<li>최종 노이즈 값을 다른 범위에 다시 매핑하는 기능</li>
<li>아래 Remap Curve랑 같이 사용<blockquote>
</blockquote>
</li>
<li>*Remap Curve</li>
<li>최종 노이즈 값이 나타나는 방법을 나타내는 커브로 활용</li>
<li>Ex) 큰 값 -&gt; 작은 값으로 하면 크게 시작하고 0으로 수렴하는 커브를 만들어 높은 쪽으로 가는 범위를 무시할 수 있다</li>
<li>더 조사해보고 추가 자료 정리 필요<blockquote>
</blockquote>
</li>
<li><em>Position Amount*</em></li>
<li>노이즈가 파티클 위치에 주는 영향 조정<blockquote>
</blockquote>
</li>
<li><em>Rotation Amount*</em></li>
<li>노이즈가 파티클 회전력에 주는 영향 조정<blockquote>
</blockquote>
</li>
<li><em>Size Amount*</em></li>
<li>노이즈가 파티클 크기에 주는 영향 조정</li>
</ul>
<p>결론적으로 Randomize와 비슷하지만 조금 더 디테일한 부분들을 건드릴 수 있고, 매 프레임마다 조정할 수 있고 시간성을 가진다는 차이점이 있다</p>
<h3 id="collision">Collision</h3>
<p>씬에서 파티클이 게임오브젝트와 충돌했을 때의 현상을 제어하는 탭
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/71ebad9f-7045-4cb2-8139-a2e50df7061e/image.png" alt=""></p>
<blockquote>
<p><strong>Type</strong></p>
</blockquote>
<ul>
<li>충돌을 직접 지정한 Planes에 할 것인지 World로 할 것인지 정함</li>
<li>World로 하면 Collision Mode 메뉴로 2D, 3D 정할 수 있음</li>
<li>Planes로 할 경우 Transform 리스트가 나와 그 곳에 부딪힐 평면을 넣어 정의 가능<blockquote>
</blockquote>
</li>
<li><em>Dampen*</em></li>
<li>파티클이 충돌한 후 줄어드는 속도의 비율<blockquote>
</blockquote>
</li>
<li><em>Bounce*</em></li>
<li>파티클이 충돌한 후 표면에서 튕기는 속도의 비율<blockquote>
</blockquote>
</li>
<li><em>Lifetime Loss*</em></li>
<li>파티클이 충돌한 후 줄어드는 전체 수명의 비율<blockquote>
</blockquote>
</li>
<li><em>Min Kill Speed*</em></li>
<li>충돌한 후 이 속도 이하로 이동하는 파티클은 제거된다<blockquote>
</blockquote>
</li>
<li><em>Max Kill Speed*</em></li>
<li>충돌한 후 이 속도 이상으로 이동하는 파티클은 시스템에서 제거된다<blockquote>
</blockquote>
</li>
<li><em>Radius Scale*</em></li>
<li>파티클 충돌 구체의 반지름 조절 가능</li>
<li>시각적 가장자리에 잘 맞게 조절해 현실적인 효과 구현 가능<blockquote>
</blockquote>
</li>
<li><em>Send Collision Message*</em></li>
<li>OnParticleCollision을 통해 파티클 충돌 감지 가능<blockquote>
</blockquote>
</li>
<li><em>Scene Tools*</em></li>
<li>말 그대로 범위나 보이는 것을 조정하는 기능</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/3f1c7967-6c37-4c83-b2e1-37c2a0525be5/image.png" alt="">
결론적으론 이렇게 특정 평면에서 막혀서 떨어지는 느낌의 파티클 효과를 줄 수가 있다</p>
<h3 id="triggers">Triggers</h3>
<p>특정 컴포넌트를 Trigger 처리할 수 있는 기능
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/358d897c-f04b-46f2-9fc2-ea9719cfc7a5/image.png" alt="">
저 탭 속에 트리거로 인식되길 원하는  컴포넌트들을 넣으면 됨</p>
<blockquote>
<p><strong>Inside</strong> : 파티클이 콜라이더 안에 있을 떄
<strong>Outside</strong> : 파티클이 콜라이더 경계 밖에 있을 떄
<strong>Enter</strong> : 파티클이 콜라이더의 경계 안으로 들어갈 때
<strong>Exit</strong> : 파티클이 콜라이더 경계 밖으로 나갈 때</p>
</blockquote>
<p><strong>Callback</strong> : OnParticleTrigger() 콜백 함수에서 파티클에 엑세스 가능
<strong>Kill</strong> : 파티클 파괴, 엑세스 불가
<strong>Ignore</strong> : 파티클 무시, 엑세스 불가</p>
<blockquote>
</blockquote>
<p><strong>Collider Query Mode</strong></p>
<ul>
<li>파티클이 상호작용하는 콜라이더에 대한 정보를 가져올 때 사용하는 방법 지정</li>
<li>트리거 모듈 처리 리소스 소모량이 증가함, 조심해야함</li>
<li>Disabled : 각 파티클이 상호작용하는 콜라이더 정보 가져오지 않음</li>
<li>One : 각 파티클이 상호작용하는 첫 번째 콜라이더 정보 가져옴<ul>
<li>여러 콜라이더와 상호작용하는 경우 저 위 리스트 중 우선인 쪽 가져옴</li>
</ul>
</li>
<li>All : 각 파티클 상호작용하는 모든 콜라이더에 대한 정보 가져옴</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/ea25fc1a-0799-457f-b4e6-5dc7aca8046e/image.png" alt="">
이런 식으로 이 박스를 Collider를 놓으면 저 위로 올라갈 땐 Kill되는 것을 알 수 있다</p>
<h3 id="sub-emitters">Sub Emitters</h3>
<p>이 파티클 수명의 특정 단계에서 파티클 위치에 생성되는 추가 파티클을 명명하는 탭이다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/dc41e6b0-f0c2-47ab-ad3c-400fe51b4461/image.png" alt=""></p>
<blockquote>
<p><strong>Birth</strong> : 파티클이 생성되는 시점에 발생
<strong>Collision</strong> : 파티클이 오브젝트에 충돌하는 시점
<strong>Death</strong> : 파티클이 파괴되는 시점
<strong>Trigger</strong> : 파티클이 트리거 콜라이더와 상호작용하는 시점
<strong>Manual</strong> : 스크립트를 통해 요청 (TriggerSubEmitter)</p>
</blockquote>
<p><strong>Inherit</strong></p>
<ul>
<li>새로 생성된 파티클에 부모 파티클의 프로퍼티 (회전, 크기, 수명 등) 이전 가능<blockquote>
</blockquote>
</li>
<li><em>Emit Probability*</em></li>
<li>Sub Emitter 이벤트가 트리거되는 확률을 지정할 수 있음</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/e455c53f-4f94-4588-a7da-97e5f1e76f64/image.png" alt="">
간단하게 이런 식으로 Birth에 빨간 똑같은 파티클을 넣어두면 부모인 하얀 파티클이 하나가 생성될 때마다 저 빨간 파티클이 같이 생성되는 개념으로 적용된다</p>
<h3 id="texture-sheet-animation">Texture Sheet Animation</h3>
<p>파티클 그래픽 한 알 자체를 애니메이션 프레임으로 재생할 수 있는 기능
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/917454d7-85c0-45f6-97b1-541d7e1f9f0b/image.png" alt=""></p>
<blockquote>
<p>Mode </p>
</blockquote>
<ul>
<li>Grid 모드를 선택, 스프라이트로 하면 안에 스프라이트를 넣을 수 있는 추가 탭이 나옴<blockquote>
</blockquote>
Time Mode</li>
<li>파티클 시스템이 애니메이션 프레임을 샘플링하는 방식을 선택<pre><code>  - Lifetime : 파티클 수명 동안 애니메이션 커브를 사용
  - Speed : 파티클 속도에 기반한 프레임
  - FPS : 지정된 초당 프레임 수 값에 기반한 샘플프레임</code></pre></li>
</ul>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/38106f89-794e-4345-b955-9ea9959c7880/image.png" alt="">
이런 식으로 파티클을 생명 주기 단위로 애니메이션을 꽂아넣을 수 있는 개념</p>
<h3 id="lights">Lights</h3>
<p>특정 비율의 파티클에 실시간으로 광원을 추가하는 개념
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/3197ae1d-a499-48ae-826c-e4f75ab94db7/image.png" alt=""></p>
<blockquote>
<p><strong>Light</strong></p>
</blockquote>
<ul>
<li>광원 프리펩 넣기<blockquote>
</blockquote>
</li>
<li><em>Ratio*</em></li>
<li>광원에 영향을 받는 파티클의 비율 조절 (0 ~ 1)<blockquote>
</blockquote>
</li>
<li><em>Random Distribution*</em></li>
<li>광원이 임의 할당되는지, 주기적으로 할당되는지 선택</li>
<li>true -&gt; 모든 파티클이 비율에 따라 광원을 받을 확률 있음</li>
<li>false -&gt; 새로 만들어진 파티클이 광원을 받는 빈도가 비율에 따라 결정<ul>
<li>Ex) 매 N번째 파티클이 광원을 받음<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><em>Use Particle Color*</em></li>
<li>True로 설정 -&gt; 광원 최종 색깔이 해당 파티클 컬러에 따라 조정됨</li>
<li>False -&gt; 광원 컬러가 조정 없이 사용됨<blockquote>
</blockquote>
</li>
<li><em>Size Affects Range*</em></li>
<li>활성화하면 광원에 지정된 Range에 파티클 크기가 곱해짐<blockquote>
</blockquote>
</li>
<li><em>Alpha Affects Intensity*</em></li>
<li>활성화하면 광원 Intensity에 파티클 알파값이 곱해짐<blockquote>
</blockquote>
</li>
<li><em>Rnage Multiplier*</em></li>
<li>지정한 탭의 커브를 통해 광원 범위를 파티클과 융합<blockquote>
</blockquote>
</li>
<li><em>Intensity Multiplier*</em></li>
<li>지정한 탭의 커브를 통해 광원 강도를 파티클과 융합<blockquote>
</blockquote>
</li>
<li><em>Maximum Lights*</em></li>
<li>이 설정을 사용해 너무 크거나 많은 광원이 들어가지 않도록 조정</li>
</ul>
<h3 id="trails">Trails</h3>
<p>파티클에 트레일을 씌울 수 있는 탭
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/bef1946d-e5fa-482e-b7a5-371f180d0e52/image.png" alt=""></p>
<blockquote>
<p><strong>Mode</strong></p>
</blockquote>
<ul>
<li>Particles : 각 파티클이 움직이지 않는 잔상을 경로에 남김</li>
<li>Ribbon : 각 파티클을 나이에 따라 연결하는 줄이 생김
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/7df2840c-b762-4800-ae60-665b8dc7477e/image.png" alt="">
각각 파티클, 리본</li>
<li>리본 선택 시 Ribbon Count가 나오는데, 이는 늘리면 늘릴수록 연결이 느슨해진다<blockquote>
</blockquote>
</li>
<li><em>Ratio*</em> </li>
<li>잔상을 남길 파티클 비율을 0~1 사이의 값으로 설정함 (확률)<blockquote>
</blockquote>
</li>
<li><em>Lifetime*</em></li>
<li>잔상이 속한 파티클의 수명에 곱하는 수<blockquote>
</blockquote>
</li>
<li><em>Minimum Vertex Distance*</em></li>
<li>잔상에 새로운 추가 잔상이 생길 때까지 최소한으로 이동해야하는 거리<blockquote>
</blockquote>
</li>
<li><em>World Space*</em></li>
<li>잔상이 월드 공간에 놓여지게 하는 탭<blockquote>
</blockquote>
</li>
<li><em>Die With Particles*</em></li>
<li>파티클 소멸 즉시 잔상이 사라지게 하는 개념<blockquote>
</blockquote>
</li>
<li><em>Texture Mode*</em></li>
<li>트레일에 적용될 텍스쳐 결정<ul>
<li>Stretch : 텍스쳐를 전체 트레일 길이에 따라 스트레치<ul>
<li>Tile : 텍스처를 N 단위로 반복 (타일 개념<ul>
<li>Repeat per Segment : 트레일을 따라 텍스쳐 반복, 트레일 끝마다 한번씩 반복하는 개념</li>
<li>Distribute per Segment : 모드 트레일의 전체 길이를 따라 텍스처를 한번 매핑, 모든 점이 균일하게 배치되어 있다고 가정함<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><em>Size affects Width*</em></li>
<li>잔상 너비에 파티클 크기를 곱함<blockquote>
</blockquote>
</li>
<li><em>Size affects Lifetime*</em></li>
<li>잔상 수명에 파티클 크기를 곱함<blockquote>
</blockquote>
</li>
<li><em>Inherit Particle Color*</em></li>
<li>잔상 색깔이 파티클 색상으로 조정됨<blockquote>
</blockquote>
</li>
<li><em>Color over Lifetime*</em></li>
<li>잔상이 추가된 파티클 수명에 따라 전체 잔상의 색상을 제어함<blockquote>
</blockquote>
</li>
<li><em>Width over Trail*</em></li>
<li>잔상의 너비를 길이에 따라 제어하는 탭<blockquote>
</blockquote>
</li>
<li><em>Color over Trail*</em></li>
<li>잔상의 컬러를 길이에 따라 제어하는 탭<blockquote>
</blockquote>
</li>
<li><em>Generate Lighting Data*</em></li>
<li>셰이더를 활용해 씬 조명을 활용하는 머터리얼을 사용 가능하게 하는 탭</li>
</ul>
<p>공식문서 번역이 개판이라 나중에 실제로 적용해보고 수정해보도록 하겠다</p>
<h3 id="custom-data">Custom Data</h3>
<p>파티클에 연결할 커스텀 데이터 포멧을 정의할 수 있다
이 데이터를 셰이더에서 사용하도록 넘겨주는 개념
이는 셰이더와 통합해 설명하도록 하겠다</p>
<h3 id="renderer">Renderer</h3>
<p>파티클 이미지나 메시가 어떻게 변환되고 처리되는지 조절하는 탭
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/dadcd728-2d46-4811-aac3-b71b81a414e6/image.png" alt=""></p>
<blockquote>
<p><strong>Render Mode</strong></p>
</blockquote>
<ul>
<li>유니티가 그래픽으로부터 랜더링된 이미지를 생성하는 방법<ul>
<li>Billboard : 파티클을 빌보드로 랜더링, 일반적<ul>
<li>Stretched Billboard : 카메라에 따라 파티클 늘림</li>
<li>Horizontal Billboard : 파티클이 카메라와 평면이 됨</li>
<li>Vertical Billboard : 파티클이 Y축에 그대로 섬</li>
<li>Mesh : 3D 메시에서 파티클 랜더링</li>
<li>None : 파티클 랜더링하지 않음, 트레일만 보고싶을 때 가끔 씀<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><em>Normal Direction*</em></li>
<li>파티클 조명이 빛을 쐬는 방향</li>
<li>1이면 카메라를 향함, 0이면 화면 중앙을 향함<blockquote>
</blockquote>
</li>
<li><em>Material*</em></li>
<li>파티클 랜더링 시 사용하는 머터리얼<blockquote>
</blockquote>
</li>
<li><em>Trail Material*</em></li>
<li>트레일에 사용하는 머터리얼<blockquote>
</blockquote>
</li>
<li><em>Sort Mode*</em></li>
<li>파티클을 화면에 올리는 순서<ul>
<li>None : 정렬 X<ul>
<li>By Distance : 카메라까지 거리 토대로 정렬</li>
<li>Oldest In Front : 파티클 시스템 앞 가장 오래 있었던 파티클 랜더링</li>
<li>Youngest In Front : 파티클 시스템 앞 가장 짧게 있었던 파티클 랜더링</li>
<li>By Depth : 카메라 평면부터의 거리를 토대로 파티클 랜더링<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><em>Sorting Fudge*</em></li>
<li>파티클 시스템 정렬 순서, 투명 머터리얼에서 겹칠 때 이를 살짝 밀어줘서 이상한 깜빡임을 방지하는 시스템<blockquote>
</blockquote>
</li>
<li><em>Min Particle Size*</em></li>
<li>표시되는 파티클의 최소 크기<blockquote>
</blockquote>
</li>
<li><em>Max Particle Size*</em></li>
<li>표시되는 파티클의 최대 크기<blockquote>
</blockquote>
</li>
<li><em>Render Alignment*</em></li>
<li>파티클 빌보드가 향하는 방향을 결정한다<ul>
<li>View : 파티클은 카메라 평면을 향함</li>
<li>World : 파티클 월드 축에 맞게 정렬</li>
<li>Local : 파티클 Transform 컴포넌트에 맞게 정렬</li>
<li>Facing : 액티브 카메라의 게임 오브젝트에 있는 Transform 따라감</li>
<li>Velocity : 속도 방향과 같은 방향을 사용함<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><em>Flip*</em></li>
<li>지정된 축 따라 파티클의 일부만을 Flip, 커질수록 더 많이 Flip<blockquote>
</blockquote>
</li>
<li><em>Allow Roll*</em></li>
<li>파티클이 Z축도 같이, 즉 굴러도 되는지 표시하는 옵션<blockquote>
</blockquote>
</li>
<li><em>Pivot*</em></li>
<li>회전하는 중심축 조정, 아래 Visulize을 통해 피벗 위치를 볼 수 있음<blockquote>
</blockquote>
</li>
<li><em>Masking*</em></li>
<li>스프라이트 마스크와 상호작용 시 파티클에서 렌더링된 파티클 동작 설정<ul>
<li>No Masking : 파티클 시스템이 씬의 스프라이트 마스크와 상호작용 X<ul>
<li>Visible inside Mask : 스프라이트 마스크가 안쪽에 파티클 표시, 밖에는 표시 X</li>
<li>Visible Outside Mask : 파티클이 스프라이트 마스크 외부에는 표시, 내부에는 표시되지 않음<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><em>Apply Active Color Space*</em></li>
<li>리니어 색 공간에서 렌더링하는 경우 시스템이 파티클을 GPU에 업로드하기 전에 감마 공간의 파티클 컬러를 전환합니다.</li>
<li>공식문서의 문헌인데 무슨 뜻인지는 약간 애매해 더 알아보겠다<blockquote>
</blockquote>
</li>
<li><em>Custom Vertex Streams*</em></li>
<li>셰이더와 함께 씀, 같이 설명 예정<blockquote>
</blockquote>
</li>
<li><em>Cast Shadows*</em></li>
<li>파티클 시스템이 그림자를 만들어냄<ul>
<li>On: 활성<ul>
<li>Off: 비활성</li>
<li>To-Sided : 양쪽에 캐스팅</li>
<li>Shadows Only : 그림자만 보임, 원물은 보이지 않음<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><em>Shadow Bias*</em></li>
<li>조명 방향으로 그림자가 움직이는 효과<blockquote>
</blockquote>
</li>
<li><em>Motion Vectors*</em></li>
<li>모션 벡터를 활용해 이 파티클이 프레임 동안 어떻게 움직였는지 후처리 데이터에 넘겨주는 개념</li>
<li>파티클은 엄청 많은 게 움직이기 때문에 이를 무시하는게 안정적일 수 있어서 생긴 탭<ul>
<li>Camera Motion Only : 카메라 움직임만 반영, 파티클 이동 자체는 무시<ul>
<li>Per Object Motion : 파티클 시스템을 하나의 덩어리처럼 계산, 진짜로 그렇게 움직일 때만 의미가 있음</li>
<li>Force No Motion : 모션벡터 생성 안함 (후처리에서 제외)<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li><em>Sorting Layer ID, Order In Layer*</em></li>
<li>평소에 쓰던 Sort 탭<blockquote>
</blockquote>
</li>
<li><em>Light Probes*</em></li>
<li>씬의 light Probes 정보를 샘플링해 파티클이 그 위치에 간접적으로 빛을 받게 하는 기능<blockquote>
</blockquote>
</li>
<li><em>Anchor Override*</em></li>
<li>Light Probes를 사용할 경우 보간 위치를 결정해주는 트랜스폼</li>
</ul>
<p>드디어 얼추 정리가 되었으니.. 다음 걸로 넘어가자</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[유니티 개발일지 외전 : 상용화되는 AI에 대한 활용법]]></title>
            <link>https://velog.io/@hipop1109-dev/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%EC%99%B8%EC%A0%84-%EC%83%81%EC%9A%A9%ED%99%94%EB%90%98%EB%8A%94-AI%EC%97%90-%EB%8C%80%ED%95%9C-%ED%99%9C%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@hipop1109-dev/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%EC%99%B8%EC%A0%84-%EC%83%81%EC%9A%A9%ED%99%94%EB%90%98%EB%8A%94-AI%EC%97%90-%EB%8C%80%ED%95%9C-%ED%99%9C%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Tue, 13 Jan 2026 00:31:06 GMT</pubDate>
            <description><![CDATA[<p>유니티뿐만 아니라 다른 프로그래밍 요소들 중에서 이젠 AI를 활용하지 않으면 바보인 수준까지 도달했다, 그런 느낌으로 요즘은 AI 툴들이 정말 많이 나왔는데, 이를 대비해 많은 AI 툴과 그에 포함해 가성비로 쓸 수 있는 툴들에 대해 알아보려고 한다</p>
<h3 id="1-antigravity">1. Antigravity</h3>
<p>최근 각광받는 AI툴로써, 코파일럿과 같은 구글에서 나온 통합 개발 환경(IDE)로써, 여러 코딩의 무게를 파격적으로 줄인다고 평가받는 툴이다</p>
<h4 id="코파일럿과의-차이점">코파일럿과의 차이점</h4>
<ul>
<li><p>개발 주도권</p>
<ul>
<li>코파일럿 : 개발을 보조하는 개념<ul>
<li>안티그래비티 : AI가 개발의 축을 담당하고 인간이 감독하는 개념</li>
</ul>
</li>
</ul>
</li>
<li><p>지시 방식</p>
<ul>
<li>코파일럿 : 목표 제시 외 세부적 단계별 지시가 필요함<ul>
<li>안티그래비티 : 원하는 목표 제시하면 AI가 스스로 실행 및 계획 목록화 및 생성</li>
</ul>
</li>
</ul>
</li>
<li><p>핵심 역할</p>
<ul>
<li>코파일럿 : 코드 조각 제안 및 자동 생성<ul>
<li>안티 그라비티 : 요구 사항을 이해하고 계획 생성, 코드 작성, 테스트 및 검증까지 자동화</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="안티그라비티의-특징">안티그라비티의 특징</h4>
<ol>
<li><p>브라우저 서브 에이전트 </p>
<ul>
<li>AI가 직접 개발 결과물을 옆 브라우저에 띄우거 테스트 및 검증하는 기능<ul>
<li>자동 테스트 : 결과물 띄우고 주변 코드를 탐색 등 이런 귀찮은 과정들을 바로 잡아줌</li>
<li>실시간 피브댁 : 테스트 과정 실제로 녹화해 에러메세지 인식, 개발자에게 피드백 줌</li>
</ul>
</li>
</ul>
<ol start="2">
<li><p>구글 출신 AI들을 고루 활용</p>
<ul>
<li>제미나이, 나노바나나이 적용되어 있어 이를 바로 활용 가능하다</li>
</ul>
</li>
<li><p>다중 에이전트 동시 작업</p>
<ul>
<li>UI, 백엔드, 리펙토링 에이전트가 분리되어 있어 속도가 빨라짐</li>
</ul>
</li>
</ol>
</li>
</ol>
<h4 id="설치-방법">설치 방법</h4>
<p>안티그라비티 사이트에 들어가 지정된 버전으로 다운받은 후 vs코드에 연결한다
설치 후 안티그라비티 화면이 나오면 2가지 체크 부분이 있다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/d697c4fe-0e5b-45e7-907d-835d71dc0ad4/image.png" alt="">
이 AI 모델 중 3 Pro High 가 가장 성능이 좋다고 하니 이를 설정해준다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/fe2420d4-6356-49d8-b374-73d63c9a4608/image.png" alt="">
왼쪽 아래 탭에서 Extentions 깔아주고 한글패치 해준다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/53610e34-2ee5-494e-89b9-81ce17bc9564/image.png" alt="">
여기서 Terminal Command Auto Execution이 꽤 중요한 부분인데, 이 안티그라비티에서 나오는 터미널적 수정 부분을 허용하느냐 안하느냐의 문제인데, 이를 뚫으면 편리하게 이용 가능하지만 보안 위협이 크기 때문에 잘 조절해보고 써야 한다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/d126c303-cf0a-46a7-860c-6b55f29a14bc/image.png" alt="">
지금 이 툴이 꽤나 많은 것을 허용하는 만큼 조심할건 조심해야 한다고 한다</p>
<ol start="2">
<li>제미나이, 지피티 등 상용되는 AI
어차피 안티그라비티, 코파일럿 등이 생기면서 직접 물어블 일은 많이 줄겠지만 언제든 사용 가능하면 편하게 사용해도 될 듯 하다</li>
</ol>
<h3 id="3-제미나이-캔버스">3. 제미나이 캔버스</h3>
<p>현재 제미나이에서 운영중인 AI 툴로, 사용자의 요구에 따라 게임의 프로토타입을 제작해줄 수 있는 프로그램이다.
탭에 요구사항을 이런 식으로 입력하면 옆에서 간단한 게임으로 띄워주는 시스템이다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/4014f7e3-37b8-4f16-9629-76dd046ed490/image.png" alt="">
2D, 3D등 여러 게임들을 만들 수 있고, 관련 버그이나 디테일적인 부분들을 추가하는 것들은 같은 방식으로 타이핑을 통해 해결할 수 있다
이러한 방식으로 실제 게임을 만들기 전 프로토타이핑 관련한 많은 것들을 진행할 수 있다
개발자가 개발의 중간을 담당하는것이 아닌 처음과 끝을 담당하는 시대가 곧 올것이라고 생각한다</p>
<h3 id="4-아트-ai-제작-툴">4. 아트 AI 제작 툴</h3>
<p>개인 개발자들이 가장 필요한것은 아트 부분이 클 것이라고 생각한다
먼저 가장 기본적인 재미나이, 지피티의 이미지 생성 툴이 가장 보편적인 방법으로써, 실제로도 괜찮은 효율을 보여준다
이 외에도 게임 전문 AI 툴 gameaify같이 게임을 전문으로 여러 탭들을 제공하는 사이트들도 여럿 존재하니, 이를 잘 활용해도 좋을 듯 싶다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/09d91835-5428-4844-a0ea-77f8cf83b80c/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6개월 유니티 게임 개발일지 : 파티클에 대해 5]]></title>
            <link>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-5</link>
            <guid>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-5</guid>
            <pubDate>Tue, 06 Jan 2026 12:01:43 GMT</pubDate>
            <description><![CDATA[<p>오늘 분석 싹 끝내고 내일은 반드시 모작으로 들어가는걸로 할거임</p>
<h3 id="lifetime-by-emitter-speed">Lifetime by Emitter Speed</h3>
<p>에미터 스피드에 따른 생명주기를 나타냄
파티클이 생성될 때의 에미터 속도에 따라 각 파티클의 초기 수명을 제어함
파티클을 생성한 오브젝트의 속도에 기반하는 값에 파티클의 시작 수명을 곱하는 개념
쉽게 풀자면 파티클 시스템 자체가 움직이는 속도에 따라 파티클의 수명을 자동으로 조정해주는 개념
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/6e509c29-1c45-4fa3-948f-9ca596270589/image.png" alt=""></p>
<blockquote>
<p><strong>Multiplier</strong>
파티클의 초기 수명에 적용할 멀티플라이어
모듈은 설정된 커브 모드에 따라 이 값을 다르게 사용함</p>
</blockquote>
<ul>
<li>Constant
항상 같은 배율로 적용
속도에 비례하지만 변동성이 필요없을 때 적용
.</li>
<li>Curve
X축 : Emitter Speed(정규화)
Y축 : Multiplier 값
결과값 : 느릴 때엔 거의 안남고, 빠를 땐 길게 늘어지는 개념으로 적용됨
.<ul>
<li>Random Between Two Constants(상수 랜덤)
파티클 하나가 생성될 때의 랜덤 배율을 결정
같은 속도여도 파티클마다 수명이 다르다
속도와는 무관하고 랜덤은 생성시 1회 고정
.</li>
<li>Random Between Two Curves(커브 랜덤)
두 개의 커브 사이에서 랜덤 보간
파티클마다 서로 다른 커브를 사용하는 개념
랜덤은 파티클 생성 시 적용 -&gt; 
생성 후에는 그 커브를 끝까지 유지하는 개념
비슷한 모양을 피하고 속도에 반응하고 개체별 개성을 주기 위한 개념
.</li>
</ul>
</li>
<li><ul>
<li>Speed Range**
파티클 시스템이 Multiplier 커브를 따라 값에 따라 조정되는 최소 ~ 최대 에미터 속도
Curve 또는 Random Curves 모드일 때만 적용되는 모드
파티클 시스템 오브젝트 자체가 얼마나 빨리 움직이느냐를 정규화하는 구간이라고 해석
속도를 얼마나 민감하게 받아들일지 정하는 스케일 기준을 잡음</li>
</ul>
</li>
</ul>
<h3 id="force-over-lifetime">Force Over Lifetime</h3>
<p>파티클의 생존 시간 동안 지속적으로 가속을 가하는 힘
위치를 직접 바꾸는 것이 아닌 속도를 계속 바꾸는 힘 (가속을 주는 개념)
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/19e5cae0-e9d3-49a8-9b90-8b243e7112a4/image.png" alt="">
Velocity Over Lifetime -&gt; 속도를 강제로 설정
Force over Lifetime -&gt; 속도를 점점 변화시킴
가속과 강제 힘의 차이를 줌</p>
<blockquote>
<p>X, Y, Z : 그 방향으로 가속 줌
.
Space : 로컬, 월드에서 어느 방향으로 줄지를 정함
.
Randomize : 각 파티클마다 Force 방향, 세기를 조금씩 랜덤화시켜주는 옵션
매 프레임마다가 아닌 한번 생성될 때 쳐주는 개념으로 적용</p>
</blockquote>
<h3 id="color-over-lifetime">Color over lifetime</h3>
<p>생명주기에 따른 색깔과 투명도를 적용해줌
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/f819755d-bc06-4a0a-8082-d0bacf8794b4/image.png" alt="">
이런 식으로 들어가서 색깔을 넣으면 4개의 조절 탭이 보인다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/a1a4f115-c6cb-4570-b43f-73a00d532c34/image.png" alt="">
아래 탭은 색을 조절하는 부분으로, 생명 주기 동안 어느 타이밍에 어느 색을 줄지 세밀하게 조정할 수 있다</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/8930e09c-8d23-4a40-9834-bf2f40ec6d41/image.png" alt="">
위 탭은 투명도 탭으로, 생명 주기 동안 어느 타이밍에 어느 정도의 투명도를 줄지 세밀하게 조정할 수 있다</p>
<p>이 두 탭 모두 무한정으로 복사가 가능해 생명 주기 동안 마음대로 투명도를 늘렸다 줄렸다, 색을 조절할 수 있다, 꽤나 자주 쓰이는 옵션 중 하나</p>
<h3 id="color-by-speed">Color by Speed</h3>
<p>위 탭과 똑같지만 차이가 있다
Color over lifetime : 생명주기 동안 색의 변화, 모든 파티클은 모두 이 시간을 지킴
Color by Speed : 속도에 따른 색의 변화, 파티클 조각마다 다른 효과를 줄 수 있음
요런 느낌으로 통일성과  랜덤성을 원하는 대로 조정 가능하다</p>
<h3 id="size-over-lifetime-size-by-speed">Size Over Lifetime, Size by Speed</h3>
<p>위 탭의 설명을 사이즈로만 바꾸면 완벽하게 설명이 될것이다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/4fdcc89a-1b69-421c-bace-417a3bf3bac3/image.png" alt=""></p>
<p>조금 설명을 덧붙이자면
Separate Axes를 하면 세부적으로 크기를 조정할 수 있고
마찬가지로 아래 Size by Speed는 속도별로 크기가 달라지는 효과로 볼 수 있다</p>
<h3 id="rotation-over-lifetime-rotation-by-speed">Rotation Over Lifetime, Rotation by Speed</h3>
<p>위 탭의 설명을 다시 각도로 바꾼 개념
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/9f2d8da2-a4c0-48be-8718-664a54264c9e/image.png" alt="">
똑같이 Separate Axes하면 축별로 각도를 잡아줄 수 있다
Speed Range는 위 Lifetime Emitter에서 본듯이 시간별 변화 강도를 조절 가능하다</p>
<h3 id="external-forces">External Forces</h3>
<p>얘는 써본적이 없어서 이런 저런 문헌을 참고해왔다
씬에 존재하는 Force Field 컴포넌트의 힘을 파티클이 실시간으로 받아 움직이게 하는 모듈
한마디로 파티클 시스템 내가 아닌 외부 오브젝트가 힘을 제공하는 개념
여러 파티클이 한 환경적인 힘을 공유할 수 있는 개념으로 다가감
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/eb4ebcf2-9c56-49b6-9734-f4cb1a288a47/image.png" alt="">
이 옵션을 활용하기 위해선 파티클 시스템 그 자체와 Particle System Force Field가 필요하다</p>
<p>이 포스 필드를 참조해서 받아오는 모듈이다</p>
<p>일단 External Force 탭 자체부터 분석하자면 </p>
<blockquote>
<p>Multiplier : 받아내는 강도를 조절
.
Influence Filter : 어떤 포스 필드의 영향을 받을 지 선택하는 필터</p>
</blockquote>
<ol>
<li>Layer Mask : 포스 필드의 게임오브젝트 레이어<ul>
<li>레이어별로 나눈 포스필드 전체의 영향을 받는 개념</li>
</ul>
</li>
<li>List : 포스 필드 개별 지정<ul>
<li>드래그 앤 드랍한 포스필드의 영향을 받음</li>
</ul>
</li>
<li>Layer Mask and List : 둘다 만족해야 함
.
Influence Mask : 이 포스 필드가 어디까지 영향을 끼칠 지 조정해주는 개념</li>
</ol>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/ae0a7cd7-b8b8-4b4c-bd2c-b8ac68be736c/image.png" alt="">
포스 필드 그 자체에 대해 분석해보자면 공간 안에 존재하는 보이지 않는 힘의 영역을 뜻한다
파티클이 이 영역 안에 들어오면 지속적인 힘을 받음 -&gt; 여러 파티클 시스템이 같은 환경 규칙을 공유할 수 있다</p>
<blockquote>
<ol>
<li>Shape : Force Field의 영향 범위와 형태를 정의함</li>
</ol>
</blockquote>
<ul>
<li>Sphere / Hemisphere / Box / Cylinder : 모양</li>
<li>Start Range / End Range : 시작, 끝 범위 지정
.</li>
</ul>
<ol start="2">
<li>Direction : 지정한 방향으로 파티클을 계속 밀어주는 개념</li>
</ol>
<ul>
<li>X, Y, Z 축을 지정해 밀어주는 개념
.</li>
</ul>
<ol start="3">
<li>Gravity : 필드 내에서 중력을 적용함</li>
</ol>
<ul>
<li>Strength은 까는 힘</li>
<li>Focus은 0에 가까울수록 균일하고 값이 커질수록 중심 쪽으로 집중됨
.</li>
</ul>
<ol start="4">
<li>Rotation : 회전값</li>
</ol>
<ul>
<li>Speed은 회전 속도, 값이 클수록 빠르게 회전함</li>
<li>Attraction은 회전하면서 중심으로 끌어당길지의 여부 지정</li>
<li>0이면 단순하게 회전하고, 값이 커지면 빨려들어가는 소용돌이를 만들어줌</li>
<li>Randomness은 회전축의 랜덤성을 줌, 값이 커지면 회전히 흐트러진다
.</li>
</ul>
<ol start="5">
<li>Drag : 공기 저항</li>
</ol>
<ul>
<li>Strength 은 감속 강도임, 클수록 파티클이 빠르게 느려지는 개념</li>
<li>Multiply by Size 은 파티클 크기에 따라 저항감을 다르게 하는 개념</li>
<li>Multiply by Velocity 는 빠를수록 저항이 커지는 개념, 자연스러운 감속을 지정해줌
.</li>
</ul>
<ol start="6">
<li>Vector Field : 텍스쳐 기반의 힘의 흐름을 줌</li>
</ol>
<ul>
<li><p>Volume Texture (Texture 3D)는 벡터 필드 텍스쳐, 각 픽셀이 힘의 방향을 지정해줌</p>
</li>
<li><p>Speed은 벡터 흐름을 적용하는 강도이다</p>
</li>
<li><p>Attraction은 벡터 방향으로 끌리는 정도를 지정해준다
-&gt; 되게 무겁다고 하네요? 모바일에서는 하지 말래요</p>
<p>얘를 따로 실험해보고 싶은것도 있고 분량이 너무 많아져서 내일로 끊어보겠습니다 수고하십쇼</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[6개월 유니티 게임 개발일지 (2) : 파티클에 대해 4]]></title>
            <link>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-4</link>
            <guid>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-4</guid>
            <pubDate>Sat, 03 Jan 2026 04:42:18 GMT</pubDate>
            <description><![CDATA[<p>Shape 다음 탭들에 대해 소개해보도록 하겠다</p>
<h3 id="velocity-over-lifetime">Velocity Over Lifetime</h3>
<p>파티클이 태어난 후 속도가 전체 수명 동안 어떻게 조절되는지 제어함
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/82ae8b11-43fd-421e-bcc6-2972cc5f334c/image.png" alt=""></p>
<blockquote>
<p><strong>Linear: 파티클에 지속적으로 더해지는 직선 속도</strong>
Force가 아니라 순수 속도값을 전달
Y를 늘릴 경우 파티클이 위로 이동, X을 올리면 오른쪽으로 이동하는 등 효과 줌
.
.
<strong>Space : 속도의 좌표 기준</strong>
Local : 파티클 시스템의 위치 기준, 오브젝트 회전하면 같이 회전
World : 월드 좌표 기준, 파티클 회전해도 속도, 방향 고정
.
.
<strong>Orbital : 파티클이 중심을 기준으로 회전하도록 만드는 속도</strong>
파티클이 중심을 기준으로 회전하도록 하는 속도 -&gt; 빙글빙글 도는 힘
축 기준: X / Y / Z 축 기준으로 회전 -&gt; 값이 클수록 더 빠르게 회전
Ex) Y: 2이면 수직축 기준으로 도는 궤도 운동
.
.
<strong>Offset : Orbital 회전의 중심축 이동</strong>
회전의 반경 중심을 옮기는 개념
Orbital와 같이 사용, 비대칭 회전을 주는데 사용
.
.
<strong>Radial : 중심에서 바깥 혹은 안쪽으로 퍼지거나 끌어당기는 속도</strong>
양수 : 바깥으로 확산 / 음수 : 안쪽으로 수렴
.
.
<strong>Speed Modifier : 지금까지 선텍한 힘에 곱해지는 배수 정도</strong>
Ex) 0.5 : 전체 속도 절반 / 2 : 전체 속도 2배
Curve 설정하면 처음엔 느리다가 후반에 빨라지는 연출 가능</p>
</blockquote>
<p>Limit Velocity over Lifetime
위 탭과 쌍으로 묶어서 봐야 하는 구조로, 아무리 빨라져도 특정 선까지만 끊어주는 브레이크 개념으로 적용, 가속 상한선을 제공하는 느낌</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/0817b0fa-9656-4d85-91f4-f652df2887a5/image.png" alt=""></p>
<blockquote>
<p><strong>Limit : 축별 최대 속도 제한값</strong>
해당 축 속도가 이 값을 넘으면 잘려서 제한됨
특정 방향만 막고 싶을 때 아주 유용함
.
.
<strong>Separate Axes **
켜면 전체 속도 벡터를 총합 크기를 기준으로 제한
끄면 세밀 제한 가능
.
.
*<em>Space *</em>
제한 방향을 월드, 로컬로 제한하는 개념
.
.
**Drag</strong>
속도를 서서히 깎아내리는 감속 계수
Limit : 벽 / Drag : 마찰
Ex) 
Drag : 0 -&gt; 즉시 제한 
Drag : 1~5 -&gt; 점점 느려짐
Drag : 높음 : 공기 저항 느낌
.
.
<strong>Multiply by Size</strong>
파티클 크기에 비례해 Drag, Limit 적용
큰 파티클 : 느리게 / 작은 파티클 : 빠르게
.
.
<strong>Multiply by Lifetime</strong>
생존 시간 비율에 따라 제한하는 강도 변화
Ex) 처음엔 빠르게 튀고 뒤로 갈수록 점점 둔해짐</p>
</blockquote>
<p>Inherit Velocity
파티클이 생성될 때 파티클 시스템을 움직이고 있는 오브젝트의 속도를 가져옴
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/8ebb04c8-4cc7-4705-8339-c012665a0fe5/image.png" alt=""></p>
<blockquote>
<p>Mode
<strong>Initial</strong>
파티클이 생성되는 순간의 속도만 상속됨
이후엔 부모의 움직임과 무관해짐
특징으로는 궤적 안정, 성능 좋음, 번용성 좋음
Ex) 날아가는 미사일의 연기 꼬리, 캐릭터 대쉬 잔상 등에 사용 가능
.
<strong>Current</strong>
파티클이 살아있는 동안 계속 부모의 속도를 따라간다
특징으로는 부모 움직임에 민감하고 이펙트가 붙어있는 느낌을 준다는 점
Ex) 캐릭터에 붙은 오라 등에 사용
.
.
<strong>Mulitplier</strong>
부모의 속도를 얼마나 강하게 받을지 적용
Ex) 1.0 -&gt; 부모 속도 100퍼센트 반영 / 0.5 : 절반만 반영
Curve로 주면 받는 힘 시간별 조정 가능</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[6개월 유니티 게임 개발일지 (2) : 파티클에 대해 3]]></title>
            <link>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-3</link>
            <guid>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-3</guid>
            <pubDate>Sat, 03 Jan 2026 04:09:01 GMT</pubDate>
            <description><![CDATA[<h3 id="box">Box</h3>
<p>네모 모양으로 위로 튀어나오는 개념
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/902fba0c-4ebc-4093-96d6-ca5e52bbee20/image.png" alt=""></p>
<p>다른 모든 건 똑같지만 하나 다른거만 찝어보자면</p>
<blockquote>
</blockquote>
<p><strong>Emit from</strong>: 저번 포스트에서 말했듯이 어디서 나오는지 알려주는 개념
Volume: 몸통에서 방출
Shell: 겉면, 즉 Shell에서 방출
Edge: 끝, Edge에서 방출</p>
<p>.
.
.</p>
<h3 id="mesh">Mesh</h3>
<p>지정한 메시 모양으로 나오는 개념
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/c4154e92-4550-4bfd-9360-c4f58547d4ee/image.png" alt="">
사진의 예를 들자면 3D 기본 메쉬인 capsule을 넣었더니 캡슐 모양으로 파티클이 방출되는 모습을 볼 수 있다</p>
<blockquote>
</blockquote>
<p><strong>Type: 방출 방식</strong>
Vertex(정점): 기본 설정, 점에서 메쉬 모양으로 방출 
    -이 모드에선 Random, Loop, Pingpong 설정 가능 
Edge: 메시 가장자리에서 방출
Triangle: 삼각형 모양으로 방출, 확실한 차이는 모르겠음..
.
.</p>
<blockquote>
<p><strong>Single Material: (유니티 공식문서 인용)</strong>
파티클이 머티리얼 인덱스 번호로 식별되는 특정 서브 메시에서 방출되어야 하는지 결정합니다. 이 옵션을 활성화하면 머티리얼 인덱스 번호를 지정할 수 있는 숫자 필드가 표시됩니다.
.
특정 머터리얼 인덱스 번호가 있는 메쉬에서만 작동하는 개념처럼 보임, 발동 가능한 부분이 있으면 추가 서술해보도록 하겠다
.
.
<strong>Mesh Color: **
색깔이 있는 메쉬라면 쓰거나 쓰지 않거나를 고른다
.
.
**Normal Offset</strong>
나오는 범위를 결정한다, 커지면 바깥에서 방출, 작아지면 가운데서, -가 되면 안쪽으로 발생함</p>
</blockquote>
<h3 id="mesh-renderer">Mesh Renderer</h3>
<p>메쉬와 똑같지만 Mesh Renderer로 쓰는 개념, 탭 차이는 없다
메시 렌더러가 붙은 정적 메시를 Shape로 사용하는 개념</p>
<h3 id="skinned-mesh-renderer">Skinned Mesh Renderer</h3>
<p>마찬가지로 메쉬와 똑같은 탭을 가짐
본으로 변형되는 캐릭터 메시인데, 애니메이션에 따라 변형된 현재 프레임의 메시 기준으로 파티클을 생성하는 개념으로 다가감 
-&gt; 캐릭터 움직이면 파티클 발생 위치도 같이 움직인다</p>
<h3 id="sprite">Sprite</h3>
<p>2D 게임에서 자주 사용하는 파티클 개념으로 쓰인다
Sprite 에셋 자체의 픽셀 영역을 Shape로 사용해 방출하는 개념
-&gt; 그림 안에서 파티클을 뿌리는 개념으로 다가가면 됨
이 스프라이트 자체가 굳이 씬 안에 없어도 됨
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/612457f1-e3a6-4697-8e24-77ffd2b457f0/image.png" alt=""></p>
<blockquote>
<p><strong>Emit From</strong>
Edge : 스프라이트 외곽선에서 생성 -&gt; 테두리 등에서 사용
Surface : 스프라이트 보이는 픽셀 영역 전체
Volume: 2D 기준이기 때문에 Surface와 비슷하지만 Thickness가 있을 때만 의미있음
.
<strong>Sprite : 범위가 될 Sprite</strong>
.
<strong>Texture : 그 스프라이트에 입혀질 텍스쳐</strong></p>
</blockquote>
<h3 id="sprite-renderer">Sprite Renderer</h3>
<p>비슷하지만 씬에 존재하는 Sprite Renderer 컴포넌트를 참조함
이 오브젝트에 붙어있는 스프라이트 모양을 따라 파티클을 생성함
위치, 크기, 회전값 실시간 반영하고, 스프라이트 바뀌면 파티클 Shape도 바뀜
애니메이션 중인 스프라이트도 반영됨</p>
<h3 id="circle">Circle</h3>
<p>원으로 방출, 위 Cone 류의 모양과 차이 없음</p>
<h3 id="edge">Edge</h3>
<p>한 선에서만 방출, 설정도 비슷</p>
<h3 id="rectangle">Rectangle</h3>
<p>네모 모양으로 방출, 설정 비슷함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[6개월 유니티 게임 개발일지 (2) : 파티클에 대해 2]]></title>
            <link>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-2</link>
            <guid>https://velog.io/@hipop1109-dev/6%EA%B0%9C%EC%9B%94-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EA%B2%8C%EC%9E%84-%EA%B0%9C%EB%B0%9C%EC%9D%BC%EC%A7%80-2-%ED%8C%8C%ED%8B%B0%ED%81%B4%EC%97%90-%EB%8C%80%ED%95%B4-2</guid>
            <pubDate>Sat, 03 Jan 2026 03:28:31 GMT</pubDate>
            <description><![CDATA[<p>오늘은 파티클 시스템의 아래 부분의 기능들을 모두 분석해보고 일단 기능 분석을 마무리해보도록 하겠다</p>
<h4 id="emmision">Emmision</h4>
<p>아래 Shape와 함께 많이 다루게 될 기능 중 하나로, 번역하자면 방출이라는 뜻이다, 말 그대로 방출 시의 속성을 다루는 칸이라고 보면 될 듯 하다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/47e096d3-7004-4ffe-b086-7e7bb64da2ca/image.png" alt="">
Rate over Time : 시간에 따라 파티클을 발생시킴, 10으로 설정한다면 1초당 10개 파티클이 생성되는 개념 (Ex) 제자리에서 발생해야 하는 파티클 같은 경우에 사용)</p>
<p>Rate over Distance : 이동 거리에 따라 파티클을 생성함, 따라서 멈춰있는 파티클이라면 생성되지 않음 (Ex) 화살이나 총을 뒤따라가는 연기 파티클 등에서 나올 수 있음)</p>
<p>Brusts : 정해진 시간에 정해진 양의 파티클을 방출함
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/567c1f6b-a1d0-4ca7-93f7-a38acab8fc78/image.png" alt="">
Time : 발생하는 타이밍 조절
Count : 파티클 생성 홧수
Cylces : 반복 횟수
Interval : 반복시간 간격
Probablility : 확률
을 뜻한다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/20c9e57b-e3b7-402d-af52-ca44d21cf842/image.png" alt="">
그래서 이런 식으로
0.3초후 50개의 파티클을 4번, 0.7초 간격으로 발사한다 라는 명령을 입력하면</p>
<p><img src="https://velog.velcdn.com/images/hipop1109-dev/post/3368398c-2f9e-414d-8cf0-ff3b6de8bb7e/image.gif" alt="">
요런 식으로 4번에 걸쳐 발사되는 느낌이 난다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/194ccaba-7541-474b-820f-bd837aef62ba/image.png" alt="">
마찬가지로 이런 식으로 겹쳐서 다중 분출의 효과를 줄 수 있다</p>
<p>Shape
가장 많이 만지게 될 부분으로, 분출되는 모양과 각도, 범위를 정해준다
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/c83dc316-a9ee-4d7c-9252-3f6e548d999f/image.png" alt="">
Shape: 분출되는 모양을 정해준다
하나하나씩 설명을 붙여봐야 할 듯 싶다</p>
<h4 id="sphere-구-모양으로-퍼져나가는-구조">Sphere: 구 모양으로 퍼져나가는 구조</h4>
<p>Radius: 구의 범위, 이 구의 겉면부터 퍼져나가는 개념이기 때문에 넓어지면 그 넓어진 면에서부터 뿌리는 개념으로 적용된다</p>
<p>Radius Thickness : 이 범위의 두께, 즉 파이클을 생성하는 면적의 비율이다, 0으로 설정하면 바깥 표면에서만 생성되고, 1에 가까워질수록 전체 면적에서 골고루 생성되는 개념</p>
<p>Arc : 각도, 360도면 원 전체에서 나오고, 줄일수록 그 원의 각도가 주는 개념이다, 
<img src="https://velog.velcdn.com/images/hipop1109-dev/post/52b534f0-2501-489c-b705-d2b3725b2f1b/image.png" alt="">
이렇게 arc을 바꾸면 나오는 범위가 바뀐다고 볼 수 있다</p>
<p>Arc 아래에도 2가지 탭이 있는데
Mode : 파티클이 생성되는 방법을 정의함
  Random: 원 주위에 파티클 무작위 생성
  Loop : 파티클이 원 주위에 순차적으로 생성, 주기가 끝나면 다시 반복
  Ping Pong : 주기가 끝난 곳에서 이전과 반대 방향으로 반복, 그 외에는 Loop와 같음
  Brust Spread : 파티클이 모양 주위에 고르게 분산되어 생성됨
              (기본 무작위 동작에 비해 더 고른 파티클을 얻을 수 있다)
Spread : 파티클이 생성될 수 있는 범위 조절, 예를 들어 360도로 놓고 Spread를 1로 두면, 파티클이 생성되는 범위가 좁아져 10% 간격으로만 생성된다
Speed: 방출 위치가 나오는 시점 타이밍을 설정함, Loop랑 Ping Pong 상태에서만 나오는데, 0이면 고정 위치에서만 방출하고, 높이면 높일수록 큰 각도를 돌려서 뱉는다</p>
<p>Texture : 방금 만든 윈 자체의 텍스처, 넣으면 원에 텍스쳐가 둘러싸여진다</p>
<p>Position: 위치
Rotation: 회전
Scale: 크기
모두 윈의 크기를 조절해 방출 모양을 결정하는 개념</p>
<p>Align to Direction : 방출 방향대로 파티클을 돌려서 뱉는 개념, 원래는 기존 모양대로 뱉어지지만, 이 탭을 켜면 구체 모양이 나아가는 대로 돌아서 나아간다, 실감나는 연출을 위해 자주 쓰임</p>
<p>Randomize Direction : 0~1까지 값 사용해 파티클 방향이 무작위하게 나아가도록 섞는다, 0이면 영향이 없고 1이면 완전 무작위로 발사됨</p>
<p>Sphereize Direction : 0~1까지의 값 사용해 발사 모양을 구체화 (공 모양으로 만든다) 함, 지금은 Sphere쓰고 있기 때문에 별 영향이 없음</p>
<p>Randomize Position : 파티클을 지정한 값까지 무작위하게 이동시킴, 올려보면 엄청 무작위해지는걸 볼 수 있을 것이다</p>
<p>어우.. 하나 정리해도 이렇게 많은데 다 할 수 있을까?
암튼 계속 나아가자면</p>
<p>Hemisphere랑 Cone, Donut까지 다 똑같은 탭을 쓴다
딱 하나 다른건 Cone 부분의 Length, Volume인데
Emit from이란 탭에서 Volume을 켜면 위 Length까지 조정이 가능하다
Base : 원뿔의 바닥 원판에서만 생성됨 -&gt; 출발점은 항상 바닥이다
Volume : 원뿔 내부 전체 공간에서 랜덤 생성 -&gt; 바닥, 원뿔, 등 전체에서 생성 가능
그래서 Volume일 때 원뿔 자체의 길이를 조정하는 Length가 풀리는 개념</p>
<p>벨로그 이쁘게 쓰는 사람들은 어떻게 쓰는거지..? 탭을 나눠도 이쁘지가 않네</p>
]]></description>
        </item>
    </channel>
</rss>