<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>strange_tiger.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 17 Nov 2025 06:57:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>strange_tiger.log</title>
            <url>https://velog.velcdn.com/cloudflare/strange_tiger/8afc4142-62bc-4ffc-9cdc-a17c51399af3/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. strange_tiger.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/strange_tiger" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[어댑터 패턴]]></title>
            <link>https://velog.io/@strange_tiger/%EC%96%B4%EB%8C%91%ED%84%B0-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@strange_tiger/%EC%96%B4%EB%8C%91%ED%84%B0-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Mon, 17 Nov 2025 06:57:41 GMT</pubDate>
            <description><![CDATA[<h1 id="설명">설명</h1>
<ul>
<li><strong>어댑터 패턴(Adapter pattern)</strong>은 <strong>호환되지 않는</strong> 인터페이스를 가진 객체들을 연결해서 쓸 수 있도록 하는 구조적 디자인 패턴이다.
<img src="https://velog.velcdn.com/images/strange_tiger/post/8d01fd50-8678-4af1-a757-c92cf404fe90/image.png" alt=""></li>
</ul>
<ul>
<li><p>어댑터 패턴은 래퍼(Wrapper)라고도 불리며, 클라이언트와 구현된 인터페이스를 분리해 후에 인터페이스가 바뀌더라도 그 변경 내역은 어댑터에 캡슐화되기 때문에 클라이언트는 바뀔 필요가 없어진다.</p>
</li>
<li><p>어댑터 패턴은</p>
<ul>
<li>클라이언트는 어댑티와 <strong>호환되는 인터페이스</strong>를 받는다.</li>
<li>이 인터페이스를 사용해 클라이언트는 <strong>어댑티의 메소드들을</strong> 안전하게 호출한다.</li>
<li>호출을 수신하면 어댑터는 이 요청을 클라이언트에 그 객체가 예상하는 <strong>형식과 순서대로</strong> 전달한다.</li>
<li>이 과정에서 클라이언트는 어댑터를 모른다.</li>
</ul>
</li>
</ul>
<h1 id="장단점과-사용-시기">장단점과 사용 시기</h1>
<ul>
<li><p>장점</p>
<ol>
<li><p><strong>단일 책임 원칙(SRP)</strong> 준수 : 프로그램의 기본 로직에서 인터페이스 또는 데이터 변환 코드를 분리할 수 있다.</p>
</li>
<li><p><strong>개방 폐쇄 원칙(OCP)</strong> 준수 : 클라이언트가 어댑터와 작동하는 한, 기존 코드를 손상시키지 않고 새로운 어댑터들을 도입할 수 있다.</p>
</li>
</ol>
</li>
<li><p>단점</p>
<ul>
<li>다수의 새로운 인터페이스와 클래스들을 도입해야 하므로 코드의 <strong>전반적인 복잡성</strong>이 증가한다.</li>
</ul>
</li>
<li><p>사용 시기</p>
<ul>
<li><p>기존 클래스를 사용하고 싶지만 그 인터페이스가 나머지 코드와 <strong>호환</strong>되지 않을 때</p>
</li>
<li><p>부모 클래스에 추가할 수 없는 <strong>공통 기능들</strong>이 없는 여러 기존 자식 클래스들을 <strong>재사용</strong>하려고 할 때</p>
<ul>
<li>각 자식 클래스를 확장한 후 누락된 기능들을 새 자식 클래스들에 넣을 수 있다.</li>
<li>하지만 해당 코드를 모든 새 클래스들에 복제해야 한다.</li>
<li>이를 더 깔끔하게 해결하기 위해 위의 <strong>누락된 기능을 어댑터 클래스</strong>에 넣는다.</li>
<li>어댑터 내부에 누락된 기능이 있는 객체들을 래핑하면 필요한 기능들을 동적으로 얻을 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h1 id="구조">구조</h1>
<h2 id="객체-어댑터">객체 어댑터</h2>
<ul>
<li><p>어댑터는 한 객체의 인터페이스를 구현하고 다른 객체는 래핑한다.</p>
</li>
<li><p>합성된 멤버에게 <strong>위임</strong>을 이용한다.</p>
<ul>
<li>위임이란, 자기가 해야 할 일을 클래스 맴버 객체의 메소드에게 <strong>다시 시킴</strong>으로써 목적을 달성하는 것이다.</li>
</ul>
</li>
<li><p>런타임 중에 어댑티가 결정되어 유연하다.</p>
</li>
<li><p>한 방향으로 역할한다.</p>
</li>
<li><p><strong>모든 프로그래밍 언어</strong>로 구현할 수 있다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/ad0c102d-d743-44da-a7b3-58cf79b7dd22/image.png" alt=""></p>
<ul>
<li><p><code>Adaptee(Service)</code> : 어댑터 대상 객체, 호환시키려는 객체</p>
</li>
<li><p><code>Target(Client Interface)</code> : <code>Adapter</code>가 구현하는 인터페이스</p>
</li>
<li><p><code>Adapter</code> : <code>Client</code>와 <code>Adaptee(Service)</code> 중간에서 호환성이 없는 둘을 연결</p>
<ul>
<li><code>Adaptee(Service)</code>를 따로 클래스 멤버로 설정하고 위임을 통해 동작을 매치시킨다.</li>
</ul>
</li>
<li><p><code>Client</code> : 기존 시스템(어댑티)을 어댑터를 통해 이용하려는 쪽. <code>Client Interface</code>를 통하여 <code>Service</code>를 이용할 수 있게 된다.</p>
</li>
</ul>
<h2 id="클래스-어댑터">클래스 어댑터</h2>
<ul>
<li><p>어댑터가 <strong>동시에 두 객체</strong>의 인터페이스를 상속한다.</p>
</li>
<li><p>객체를 래핑할 필요가 없고, 따로 객체 구현없이 바로 코드 재사용이 가능하다.</p>
</li>
<li><p>양방향으로 역할한다.</p>
</li>
<li><p><strong>다중 상속</strong>이 가능한 언어에서만 사용이 가능하며, <strong>죽음의 다이아몬드</strong> 문제 등 다중 상속의 문제 때문에 권장되지 않는 방법이다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/6e5021d7-1d91-4646-b24b-15ecc3d4edf0/image.png" alt=""></p>
<ul>
<li><p><code>Adaptee(Service)</code> : 어댑터 대상 객체, 호환시키려는 객체</p>
</li>
<li><p><code>Target(Cient Interface)</code> : <code>Adapter</code> 가 구현하는 인터페이스.</p>
</li>
<li><p><code>Adapter</code> : <code>Client</code> 와 <code>Adaptee(Service)</code> 중간에서 호환성이 없는 둘을 연결</p>
</li>
<li><p><code>Client</code> : 기존 시스템(어댑티)을 어댑터를 통해 이용하려는 쪽. <code>Existing Class</code>를 통하여 <code>Service</code>를 이용할 수 있게 된다.</p>
</li>
</ul>
<h1 id="구현-및-예시">구현 및 예시</h1>
<h2 id="구현-방법">구현 방법</h2>
<ol>
<li><p>호환되지 않는 인터페이스가 있는 클래스들이 있다.</p>
</li>
<li><p>클라이언트 인터페이스를 선언하고 클라이언트들이 어댑티(서비스)와 통신하는 방법을 기술한다.</p>
</li>
<li><p>어댑터 클래스를 생성하고 클라이언트 인터페이스를 상속한다.</p>
</li>
<li><p>어댑티 객체에 참조를 저장하기 위해 어댑터 클래스에 필드를 추가한다.</p>
</li>
<li><p>클라이언트 인터페이스의 모든 메소드를 어댑터 클래스에서 구현한다.</p>
<ul>
<li>어댑터는 인터페이스 혹은 데이터 형식 변환만 처리해야 하며, 실제 작업의 대부분을 어댑티에 위임해야 한다.</li>
</ul>
</li>
<li><p>클라이언트들은 클라이언트 인터페이스를 통해 어댑터를 사용해야 한다. 이렇게 클라이언트 코드에 영향을 주지 않고도 어댑터들을 변경하고 확장할 수 있다.</p>
</li>
</ol>
<h2 id="예시">예시</h2>
<h3 id="의사코드">의사코드</h3>
<ul>
<li>서로 맞지 않는 정사각형 못과 둥근 구멍이 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/0e17920b-b282-4816-aa65-65758ee7e4d5/image.png" alt=""></p>
<ul>
<li>어댑터는 정사각형 대각선 길이의 절반(즉, 사각형 못을 수용할 수 있는 가장 작은 원의 반지름)을 반지름으로 가진 둥근 못인 척 한다.</li>
</ul>
<pre><code>// RoundHole(둥근 구멍) 및 RoundPeg(둥근 못)라는 호환되는 인터페이스들이 있는
// 두 개의 클래스가 있다고 가정해봅시다.
class RoundHole is
    constructor RoundHole(radius) { ... }

    method getRadius() is
        // 구멍의 반지름을 반환하세요.

    method fits(peg: RoundPeg) is
        return this.getRadius() &gt;= peg.getRadius()

class RoundPeg is
    constructor RoundPeg(radius) { ... }

    method getRadius() is
        // 못의 반지름을 반환하세요.


// 그러나 SquarePeg(직사각형 못)라는 호환되지 않는 클래스가 있습니다.
class SquarePeg is
    constructor SquarePeg(width) { ... }

    method getWidth() is
        // 직사각형 못의 너비를 반환하세요.


// 어댑터 클래스를 사용하면 정사각형 못을 둥근 구멍에 맞출 수 있습니다. 어댑터
// 객체들은 RoundPeg(둥근 못) 클래스를 확장해 둥근 못들처럼 작동하게 해줍니다.
class SquarePegAdapter extends RoundPeg is
    // 실제로 어댑터에는 SquarePeg(정사각형 못) 클래스의 인스턴스가 포함되어
    // 있습니다.
    private field peg: SquarePeg

    constructor SquarePegAdapter(peg: SquarePeg) is
        this.peg = peg

    method getRadius() is
        // 어댑터는 이것이 어댑터가 실제로 감싸는 정사각형 못에 맞는 반지름을
        // 가진 원형 못인 것처럼 가장합니다.
        return peg.getWidth() * Math.sqrt(2) / 2


// 클라이언트 코드 어딘가에…
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // 참

small_sqpeg = new SquarePeg(5)
large_sqpeg = new SquarePeg(10)
hole.fits(small_sqpeg) // 이것은 컴파일되지 않습니다(호환되지 않는 유형)

small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg)
large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_sqpeg_adapter) // 참
hole.fits(large_sqpeg_adapter) // 거짓</code></pre><h3 id="c-예시-코드-1">C# 예시 코드 1</h3>
<pre><code class="language-csharp">using System;

namespace RefactoringGuru.DesignPatterns.Adapter.Conceptual
{
    // The Target defines the domain-specific interface used by the client code.
    public interface ITarget
    {
        string GetRequest();
    }

    // The Adaptee contains some useful behavior, but its interface is
    // incompatible with the existing client code. The Adaptee needs some
    // adaptation before the client code can use it.
    class Adaptee
    {
        public string GetSpecificRequest()
        {
            return &quot;Specific request.&quot;;
        }
    }

    // The Adapter makes the Adaptee&#39;s interface compatible with the Target&#39;s
    // interface.
    class Adapter : ITarget
    {
        private readonly Adaptee _adaptee;

        public Adapter(Adaptee adaptee)
        {
            this._adaptee = adaptee;
        }

        public string GetRequest()
        {
            return $&quot;This is &#39;{this._adaptee.GetSpecificRequest()}&#39;&quot;;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Adaptee adaptee = new Adaptee();
            ITarget target = new Adapter(adaptee);

            Console.WriteLine(&quot;Adaptee interface is incompatible with the client.&quot;);
            Console.WriteLine(&quot;But with adapter client can call it&#39;s method.&quot;);

            Console.WriteLine(target.GetRequest());
        }
    }
}</code></pre>
<ul>
<li>실행 결과<pre><code class="language-plaintext">Adaptee interface is incompatible with the client.
But with adapter client can call it&#39;s method.
This is &#39;Specific request.&#39;</code></pre>
</li>
</ul>
<h3 id="c-예시-코드-2">C# 예시 코드 2</h3>
<pre><code class="language-csharp">namespace Wikipedia.Examples;

using System;

interface ILightningPhone
{
    void ConnectLightning();
    void Recharge();
}

interface IUsbPhone
{
    void ConnectUsb();
    void Recharge();
}

sealed class AndroidPhone : IUsbPhone
{
    private bool isConnected;

    public void ConnectUsb()
    {
        this.isConnected = true;
        Console.WriteLine(&quot;Android phone connected.&quot;);
    }

    public void Recharge()
    {
        if (this.isConnected)
        {
            Console.WriteLine(&quot;Android phone recharging.&quot;);
        }
        else
        {
            Console.WriteLine(&quot;Connect the USB cable first.&quot;);
        }
    }
}

sealed class ApplePhone : ILightningPhone
{
    private bool isConnected;

    public void ConnectLightning()
    {
        this.isConnected = true;
        Console.WriteLine(&quot;Apple phone connected.&quot;);
    }

    public void Recharge()
    {
        if (this.isConnected)
        {
            Console.WriteLine(&quot;Apple phone recharging.&quot;);
        }
        else
        {
            Console.WriteLine(&quot;Connect the Lightning cable first.&quot;);
        }
    }
}

sealed class LightningToUsbAdapter : IUsbPhone
{
    private readonly ILightningPhone lightningPhone;

    private bool isConnected;

    public LightningToUsbAdapter(ILightningPhone lightningPhone)
    {
        this.lightningPhone = lightningPhone;
    }

    public void ConnectUsb()
    {
        this.lightningPhone.ConnectLightning();
    }

    public void Recharge()
    {
        this.lightningPhone.Recharge();
    }
}

public class AdapterDemo
{
    static void Main(string[] args)
    {
        ILightningPhone applePhone = new ApplePhone();
        IUsbPhone adapterCable = new LightningToUsbAdapter(applePhone);
        adapterCable.ConnectUsb();
        adapterCable.Recharge();
    }
}</code></pre>
<ul>
<li>실행 결과<pre><code class="language-plaintext">Apple phone connected.
Apple phone recharging.</code></pre>
<h1 id="참고">참고</h1>
</li>
<li><a href="https://www.yes24.com/Product/Goods/17525598">GoF의 디자인 패턴(개정판) / 에릭 감마, 리처드 헬름, 랄프 존슨, 존 블라시디스 공저
</a></li>
<li><a href="https://en.wikipedia.org/wiki/Adapter_pattern">Adapter pattern | Wikipedia</a></li>
<li><a href="https://refactoring.guru/ko/design-patterns/adapter">어댑터 패턴 | refactoring.guru</a></li>
<li><a href="https://jusungpark.tistory.com/22">디자인패턴 - 어댑터 패턴 (adapter pattern)</a></li>
<li><a href="https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%96%B4%EB%8C%91%ED%84%B0Adaptor-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90#adaptor_pattern">어댑터(Adaptor) 패턴 - 완벽 마스터하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[파인튜닝]]></title>
            <link>https://velog.io/@strange_tiger/%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D</link>
            <guid>https://velog.io/@strange_tiger/%ED%8C%8C%EC%9D%B8%ED%8A%9C%EB%8B%9D</guid>
            <pubDate>Thu, 13 Nov 2025 03:56:49 GMT</pubDate>
            <description><![CDATA[<h1 id="개념">개념</h1>
<ul>
<li><strong>파인튜닝</strong>이란 사전 학습된 모델에 특정 데이터셋을 추가로 학습시켜 모델을 특정 작업에 알맞게 <strong>미세 조정</strong>하는 과정이다.<h1 id="작동-원리">작동 원리</h1>
</li>
</ul>
<ol>
<li>사전 학습된 모델을 준비한다.</li>
<li>마지막 레이어를 교체한다.</li>
<li>특정 데이터로 학습한다.<h1 id="장점">장점</h1>
</li>
</ol>
<ul>
<li><strong>적은 데이터</strong>로 높은 성능을 낼 수 있다.</li>
<li><strong>학습 시간을 단축</strong>할 수 있다.</li>
<li>안정적이고 빠르게 수렴한다.<h1 id="단점">단점</h1>
</li>
<li><strong>높은 연산 비용</strong>이 든다.</li>
<li>학습 이후 <strong>변경된 정보</strong>는 재학습 없이는 반영되지 않는다.</li>
<li><strong>데이터 품질</strong>에 크게 의존한다.</li>
<li>기존에 사전 학습된 모델이 갖고 있던 일반적인 <strong>지식을 잃어버릴 수 있다.</strong> <h1 id="참고">참고</h1>
</li>
<li><a href="https://blog.vessl.ai/ko/posts/fine-tuning-definition-examples-methods">파인튜닝(Fine-tuning)이란? - 뜻, 예시, 데이터셋 준비법</a></li>
<li><a href="https://kr.appen.com/blog/fine-tuning/">파인튜닝(Fine-tuning)이란? – LLM 구축 방법</a></li>
<li><a href="https://tutorials.pytorch.kr/index.html">파이토치(PyTorch) 한국어 튜토리얼</a></li>
<li><a href="https://learn.microsoft.com/ko-kr/windows/ai/windows-ml/tutorials/pytorch-analysis-installation">PyTorch 설치 및 구성</a></li>
<li><a href="https://docs.google.com/presentation/d/1SCv3pjgJX5ldXZh47O4RH4AHUtrLnmGwgCktKPUo05U/edit?usp=sharing">DGX SPARK 교육 정리-이진호</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[RANSAC(Random sample consensus)]]></title>
            <link>https://velog.io/@strange_tiger/RANSACRandom-sample-consensus</link>
            <guid>https://velog.io/@strange_tiger/RANSACRandom-sample-consensus</guid>
            <pubDate>Wed, 28 May 2025 07:29:00 GMT</pubDate>
            <description><![CDATA[<h1 id="설명">설명</h1>
<h2 id="ransac이란">RANSAC이란</h2>
<ul>
<li><p><strong>RANSAC</strong>은 <strong>RAN</strong>dom <strong>SA</strong>mple <strong>C</strong>onsensus의 줄임말로, 데이터를 <strong>랜덤하게 샘플링</strong>하여 사용하고자 하는 <strong>모델을 맞춘(<code>fitting</code>)</strong> 다음 그 결과가 원하는 <strong>목표치(합의점, <code>Consensus</code>)에 도달하였는지 확인</strong>하는 과정을 통해 모델을 데이터에 맞게 최적화하는 과정이다.</p>
<ul>
<li>즉, <strong>목표치가 최대</strong>인, 가장 많은 수의 데이터를 포함하는 모델을 선택하는 방법이다.</li>
</ul>
</li>
<li><p>데이터에서 노이즈를 제거하고 <strong>모델을 예측</strong>하는 알고리즘으로, 특히 컴퓨터 비전 분야에서 광범위하게 사용된다.</p>
</li>
<li><p>선형, 다항 함수, 비선형 함수 등 어떤 모델이든 상관없이 <code>모델 fitting</code> 과정을 적용할 수 있다.</p>
</li>
<li><p>특정 <strong>임계값(<code>threshold</code>)</strong> 이상의 데이터를 완전히 무시하는 특성이 있어 <code>outlier</code>, 즉 정상 분포 밖의 <strong>이상값에 강건한(<code>Robust</code>)</strong> 알고리즘이다.</p>
<ul>
<li><code>Inlier</code> : 데이터의 분포에 포함된 정상적인 관측값.
<code>Outlier</code> : 데이터의 분포에서 현저하게 벗어나 있는 관측값. 이상치와는 개념적으로 다르나, 실용적으로는 구분하기 힘들다.</li>
</ul>
</li>
<li><p>아래 그래프를 통해 <strong>최소자승법</strong>과 <strong>RANSAC</strong>의 강건함을 비교할 수 있다.</p>
<ul>
<li>원본 값과 최소자승법 적용 결과
<img src="https://velog.velcdn.com/images/strange_tiger/post/991dee2f-617f-4a5b-bc14-a7cf57c2d3a6/image.png" alt=""></li>
<li>RANSAC 적용 결과<img src="https://velog.velcdn.com/images/strange_tiger/post/16a41ad7-4aca-4edf-8a1f-93ac5d5c1740/image.png" width="43%" height="43%">

</li>
</ul>
</li>
</ul>
<h2 id="순서">순서</h2>
<ul>
<li><p>RANSAC은 가설 단계와 검증 단계를 반복하는 것으로 최적의 모델을 구한다.</p>
<ul>
<li><p>가설 단계에서 N개의 샘플을 선택하고, 샘플을 바탕으로 모델을 예측한다.</p>
</li>
<li><p>검증 단계에서 전체 데이터에서 예측 모델과 일치하는 데이터 수를 세어 목표치가 더 높다면 새롭게 모델을 저장한다.</p>
</li>
<li><p>이를 N번 반복하여 그 중 최적의 모델을 출력한다.</p>
</li>
</ul>
</li>
<li><p>아래는 그 과정의 흐름도와 그 설명이다.
<img src="https://velog.velcdn.com/images/strange_tiger/post/ff56f4be-1e40-4307-a665-87535de243cd/image.png" alt=""></p>
<ol>
<li><p><code>Pick n random points</code> : <code>inlier</code>와 <code>outlier</code>가 섞여 있는 전체 데이터셋에서 n개의 데이터 (포인트)를 랜덤 샘플링한다.</p>
</li>
<li><p><code>Estimate model parameters</code> : 랜덤 샘플 데이터를 이용하여 사용하고자 하는 모델을 <code>fitting</code>한다. </p>
</li>
<li><p><code>Calculate distance error</code> : ② 과정을 통해 <code>fitting</code>한 모델과 전체 데이터에 대하여 <code>error</code>(차이)를 구한다.</p>
</li>
<li><p><code>Count number of inliers</code> : ③ 과정에서 구한 <code>error</code>를 통해 각 데이터가 <code>inlier</code>인 지 <code>outlier</code>인 지 판단한다. 이 때 판단하는 근거는 <code>threshold</code> 기준을 정하여 판단합니다. <code>threshold</code>가 크면 많은 실제 <code>outlier</code> 또한 <code>inlier</code>가 될 수 있으며, 반대로 작으면 많은 반복이 필요해질 수 있으므로 적당한 수준으로 정해야 한다.</p>
</li>
<li><p><code>Maximum Inliers ?</code> : <code>inlier</code> 목표치 또는 <code>inlier</code> 데이터 비율의 목표치가 있고 현재 <code>fitting</code>한 모델이 이 목표치를 달성하였다면 <code>iteration</code>(반복 과정)을 끝낼 수 있다.(후에 설명할 <code>early stop</code> 전략이다.) 만약 목표치를 달성하지 못하였다면 앞선 과정을 반복한다.</p>
</li>
<li><p><code>N iterations ?</code> : 위 flow-chart를 통해 최대 N번의 <code>iteration</code>을 반복하여 <code>RANSAC</code>을 진행한다. N이 커질수록 시도할 수 있는 횟수가 많아지기 때문에 좋은 모델을 선택할 수 있는 가능성이 커지지만, 그만큼 수행시간이 늘어나게 된다. </p>
</li>
</ol>
</li>
<li><p>아래 그림 예시를 보면, 왼쪽의 원본 데이터셋에 대하여 랜덤으로 데이터를 2개씩 추출한 후, 샘플을 통해 모델을 예측한 그림이다. </p>
<ul>
<li><p>초록색 실선이 모델, 점선이 임계값의 영역이다. 점선 내부가 <code>inlier</code>로 판단되고, 이 <code>inlier</code> 수가 많을수록 목표치가 높다고 판단한다.</p>
</li>
<li><p>이 과정을 반복해 그 중 가장 목표치가 높은 최적의 모델을 찾는 것이다. </p>
<img src="https://velog.velcdn.com/images/strange_tiger/post/6d48d63b-e76d-4bba-bfb1-1144e5598266/image.png" width="45%" height="45%">
</li>
</ul>
</li>
<li><p><a href="https://gaussian37.github.io/vision-concept-ransac/#ransac-%EA%B0%9C%EB%85%90-1">그림을 포함한 RANSAC 진행 예시</a></p>
</li>
</ul>
<h3 id="early-stop-전략">Early Stop 전략</h3>
<ul>
<li><p>RANSAC을 통해 반복적으로 모델을 최적화 할 때, 적합한 모델을 찾았다면 더 이상 모델 fitting 작업을 할 필요가 없다. 따라서 다음과 같이 3가지 파라미터를 정하여 RANSAC을 일찍 끝내는 <code>Early Stop</code> 전략을 사용할 수 있다.</p>
<ul>
<li><p><code>min iteration</code> : 최소 반복 횟수.</p>
</li>
<li><p><code>max iteration</code> : 최대 반복 횟수. 이만큼 반복하면 모델 fitting이 실패했다는 뜻이다.</p>
</li>
<li><p><code>stop inlier ratio</code> : 반복 작업을 끝내기 위한 최소 <code>inlier</code>의 비율이다. 전체 데이터 기준 <code>inlier</code> 비율이 해당 값을 초과하면 RANSAC 작업을 종료한다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="ransac의-파라미터-설정">RANSAC의 파라미터 설정</h2>
<ul>
<li><p>RANSAC에서 데이터를 추출했을 때 현재 추출된 샘플이 모두 <code>inlier</code>인지 아닌지는 판별할 수 없다. 그저 일치하는 데이터가 더 많은가 적은가를 판단할 수 있을 뿐이다.</p>
</li>
<li><p>같은 이유로 RANSAC은 기존보다 더 개선된 모델인지만 판단할 수 있을 뿐 이것이 최고의 모델인지는 아무도 알 수 없다. </p>
</li>
<li><p>하지만 우리는 유한시간 내에서 RANSAC알고리즘을 완료해야하기 때문에 종료 시점, 즉 <strong>반복 횟수</strong>를 정해야 한다. 이를 $$N$$이라고 한다.</p>
<p>$$P = 1 - (1 - α^m)^N$$</p>
</li>
</ul>
<blockquote>
<p>$$P$$  =  <code>inlier</code>로만 이루어진 샘플을 획득할 확률 – 샘플링 성공</p>
<p>$$α$$  =  <code>dataset</code>에서 <code>inlier</code>의 비율</p>
<p>$$m$$ = 회당 추출하는 데이터 수</p>
<p>$$N$$  = 알고리즘 반복 회수</p>
</blockquote>
<ul>
<li><p>데이터를 분석하는 입장에서 <strong>정할 수 있는 값은</strong> $P, m, N$이다. $m$은 작을수록, $N$은 클수록 구하는 샘플의 확률 $P$가 커진다.</p>
</li>
<li><p>$m$이 클수록 샘플링 해야 하는 데이터의 수가 많아지기 때문에 <code>**outlier</code>가 선택될 가능성이 더 커지게 된다.** 즉 <code>inlier</code>로만 이루어진 샘플을 얻을 확률이 낮아지므로 $P$가 작아진다.</p>
<ul>
<li>이러한 이유로 <strong>모델링에 필요한 최소 갯수를 샘플링 하는 방법</strong>을 많이 사용한다. 예를 들면 선형 모델을 모델링할 때에는 2개의 샘플만 있으면 되기 때문에 $m = 2$가 될 수 있다. 2차 함수 모델의 경우 3개의 샘플이 필요하므로 $m = 3$이 된다. 따라서 RANSAC에 사용되는 모델에 따라서  $m$은 자동으로 결정될 수 있다.</li>
</ul>
</li>
<li><p>따라서 바라는 $P$가 있다면 바뀌는 건 $N$뿐이다.</p>
<ul>
<li>예를 들어, 위 포물선 근사 예에서 <code>inlier</code> 비율이 80%라고 했을 때, RANSAC 성공확률을 99.9%로 맞추려면 필요한 반복 횟수는 다음과 같이 계산된다.</li>
</ul>
<p>$$N = \frac{log(1 - P)}{log(1 - α^m)} = \frac{log(1 - 0.999)}{log(1 - 0.8^3)} \fallingdotseq 9.6283$$</p>
<ul>
<li>수학적 확률로 RANSAC을 10번만 돌려도 99.9% 확률로 해(반복 횟수)를 찾을 수 있다는 얘기이다. 생각보다 숫자가 작아서 $$P$$를 99.99%로 놓고 계산해 봐도 $$N = 12.8378$$이 나온다.</li>
</ul>
</li>
<li><p>그리고 $N$만큼 중요한 파라미터가 <code>threshold</code>, 즉 임계값인 $T$이다. 모델을 예측하고, 데이터가 이 모델에 일치하는지 아닌지 판별할 때 모델과 데이터의 차이가 $T$보다 작으면 <code>inlier</code>, 크면 <code>outlier</code>로 구분하게된다.</p>
<ul>
<li><p>$T$를 너무 크게하면 모델간의 변별력이 없어지고 $T$를 너무 작게하면 RANSAC 알고리즘이 불안정해진다.</p>
</li>
<li><p>$T$를 선택하는 가장 좋은(일반적인) 방법은 <code>inlier</code>들의 잔차 분산을 $σ^2$이라 할때, $T = 2σ$ 또는 $T = 3σ$ 정도로 잡는 것이다. </p>
</li>
<li><p>먼저 RANSAC을 적용하고자 하는 실제 문제에 대해서 <code>inlier</code>들로만 구성된 실험 데이터들을 획득하고, <code>inlier</code> 데이터들에 대해서 최소자승법을 적용하여 가장 잘 근사되는 모델을 구한다.</p>
</li>
<li><p>이 모델과 <code>inlier</code>들과의 잔차를 구한 후 분산을 구해, 그 표준편차 $σ$를 구해서 이에 비례하게 $T$를 결정한다. 잔차가 정규분포를 따른다면, $T = 2σ$로 잡으면 97.7%, $T = 3σ$로 잡으면 99.9%의 inlier들을 포함한다.</p>
</li>
</ul>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Random_sample_consensus#Parameters">Wikipedia | Random sample consensus | Parameters</a></p>
</li>
<li><p><a href="https://gaussian37.github.io/vision-concept-ransac/#ransac%EC%9D%98-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%85%8B%ED%8C%85%EB%B2%95-1">RANSAC (RANdom SAmple Consensus) 개념 및 실습 | RANSAC의 파라미터 셋팅법</a></p>
</li>
<li><p><a href="https://darkpgmr.tistory.com/61">RANSAC의 이해와 영상처리 활용</a></p>
</li>
<li><p><a href="https://gnaseel.tistory.com/33#google_vignette">중학생도 이해할 수 있는 RANSAC 알고리즘 원리</a></p>
</li>
</ul>
<h2 id="장단점">장단점</h2>
<h3 id="장점">장점</h3>
<ol>
<li><p><code>outlier</code>에 강건한(<code>robust</code>) 모델을 출력한다.</p>
<ul>
<li><code>outlier</code>을 어느 정도 무시하고 모델링할 수 있다.</li>
</ul>
</li>
<li><p><code>outlier</code>에 강건한(<code>robust</code>) 모델을 설계하는 가장 쉬운 방법이다.</p>
<ul>
<li><code>inlier</code>의 개수만 세면 되어서 구현이 쉽고 어떤 모델이라도 적용이 쉽다.</li>
</ul>
</li>
</ol>
<h3 id="단점-및-한계">단점 및 한계</h3>
<ol>
<li><p>비결정적 알고리즘 <code>Non-deterministic algorithm</code></p>
<ul>
<li>RANSAC은 랜덤으로 샘플링하기에 같은 입력 데이터여도 같은 결과를 보장하지 않는다.
즉, <strong>실행할 때마다 다른 결과를 낼 수 있다.</strong> 이는 모델의 재현성 관점에서 단점이다.</li>
</ul>
</li>
<li><p>불확실성</p>
<ul>
<li>RANSAC은 <code>inlier</code>만으로 샘플링될 확률 <em>p</em>를 위해 <em>N</em>번 반복하는 알고리즘이다.
이는 결국 수학적 확률이므로, 아무리 <strong>반복해도 최적의 모델을 구하지 못할 수도 있다</strong>는 뜻이다.</li>
</ul>
</li>
<li><p>데이터가 밀집되어 있는 경우</p>
<ul>
<li><p>데이터가 모델과 임계값 이하의 차이를 가지고 있다면 그 차이는 전혀 알고리즘에 반영되지 않고 단순히 <code>inlier</code>로 집계된다. 이 때문에 <strong>RANSAC은 밀집된 데이터에 대해서 적합하지 않은 모델을 출력할 가능성이 있다.</strong></p>
<ul>
<li>이를 해결하기 위해 <code>MLESAC</code>라는 알고리즘이 만들어졌다.</li>
</ul>
</li>
<li><p>아래 사진은 몇몇 모델 근사 알고리즘의 <code>loss error</code> 그래프로, RANSAC의 경우 에러가 임계값 이하일 때 <code>loss</code>가 0인 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/strange_tiger/post/c99caecc-785f-48f6-a164-7c880348bc6a/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>임계값 <code>threshold</code> 민감성</p>
<ul>
<li><p>RANSAC은 <code>threshold</code> 파라미터, 즉 <strong>임계값에 크게 영향을 받는다.</strong> 
임계값을 너무 <strong>작게</strong> 설정하면 모델이 안정적으로 <code>fitting</code>되지 않을 수 있고, 데이터의 변화에 너무 민감하게 반응할 수 있다. 또한 기준 이상의 모델을 찾는데 시간이 오래 걸릴 수 있다.
임계값을 너무 <strong>크게</strong> 설정하면 <code>outlier</code>에 해당하는 값도 <code>inlier</code>로 판단할 수 있어 바라는 최적의 모델을 찾을 수 없게 된다.
또한 <code>inlier</code>의 분포가 변한다면 임계값 기준 또한 적응형<code>adaptive</code>으로 변해야 할 수 있다.</p>
</li>
<li><p><strong>이를 모두 고려하여 임계값<code>threshold</code>을 정하는 것이 어렵다.</strong></p>
</li>
</ul>
</li>
<li><p><code>outlier</code>가 특정 모델을 이루고 있을 경우</p>
<ul>
<li><p>만약 <code>outlier</code>가 노이즈 같지 않고 <strong>특정 구조, 분포</strong>를 이루고 있다면, 이러한 <code>outlier</code>들이 샘플링되고 이에 <code>fitting</code>한 잘못된 모델이 결과로서 출력될 수 있다.</p>
</li>
<li><p>따라서 데이터셋을 미리 확인해 <code>outlier</code>가 특정 패턴 및 분포를 가지는 지 사전에 확인하고 제거해야 할 필요가 있다.</p>
</li>
</ul>
</li>
</ol>
<h1 id="관련-항목">관련 항목</h1>
<h2 id="lo-ransac">Lo-RANSAC</h2>
<ul>
<li><a href="https://gaussian37.github.io/vision-concept-ransac/#lo-ransac-%EA%B0%9C%EB%85%90-1">RANSAC (RANdom SAmple Consensus) 개념 및 실습 | Lo-RANSAC</a></li>
</ul>
<h2 id="mlesac">MLESAC</h2>
<ul>
<li><a href="https://velog.io/@greensox284/Regressor-MLESAC">[Regressor] MLESAC</a></li>
<li><a href="https://www.analyticsvidhya.com/blog/2021/02/new-approach-for-regression-analysis-ransac-and-mlesac/">New Approach for Regression Analysis – RANSAC and MLESAC</a></li>
</ul>
<h1 id="예시">예시</h1>
<h2 id="의사코드와-이를-바탕으로-한-python-코드">의사코드와 이를 바탕으로 한 Python 코드</h2>
<ul>
<li>의사코드 <a href="https://en.wikipedia.org/wiki/Random_sample_consensus#Pseudocode">Wikipedia | Random sample consensus | Pseudocode</a><pre><code class="language-pseudo">Given:
  data – A set of observations.
  model – A model to explain the observed data points.
  n – The minimum number of data points required to estimate the model parameters.
  k – The maximum number of iterations allowed in the algorithm.
  t – A threshold value to determine data points that are fit well by the model (inlier).
  d – The number of close data points (inliers) required to assert that the model fits well to the data.
</code></pre>
</li>
</ul>
<p>Return:
    bestFit – The model parameters which may best fit the data (or null if no good model is found).</p>
<p>iterations = 0
bestFit = null
bestErr = something really large // This parameter is used to sharpen the model parameters to the best data fitting as iterations go on.</p>
<p>while iterations &lt; k do
    maybeInliers := n randomly selected values from data
    maybeModel := model parameters fitted to maybeInliers
    confirmedInliers := empty set
    for every point in data do
        if point fits maybeModel with an error smaller than t then
             add point to confirmedInliers
        end if
    end for
    if the number of elements in confirmedInliers is &gt; d then
        // This implies that we may have found a good model.
        // Now test how good it is.
        betterModel := model parameters fitted to all the points in confirmedInliers
        thisErr := a measure of how well betterModel fits these points
        if thisErr &lt; bestErr then
            bestFit := betterModel
            bestErr := thisErr
        end if
    end if
    increment iterations
end while</p>
<p>return bestFit</p>
<pre><code>
- Python [Wikipedia | Random sample consensus | Example code](https://en.wikipedia.org/wiki/Random_sample_consensus#Example_code)
```python
from copy import copy
import numpy as np
from numpy.random import default_rng
rng = default_rng()


class RANSAC:
    def __init__(self, n=10, k=100, t=0.05, d=10, model=None, loss=None, metric=None):
        self.n = n              # `n`: Minimum number of data points to estimate parameters
        self.k = k              # `k`: Maximum iterations allowed
        self.t = t              # `t`: Threshold value to determine if points are fit well
        self.d = d              # `d`: Number of close data points required to assert model fits well
        self.model = model      # `model`: class implementing `fit` and `predict`
        self.loss = loss        # `loss`: function of `y_true` and `y_pred` that returns a vector
        self.metric = metric    # `metric`: function of `y_true` and `y_pred` and returns a float
        self.best_fit = None
        self.best_error = np.inf

    def fit(self, X, y):
        for _ in range(self.k):
            ids = rng.permutation(X.shape[0])

            maybe_inliers = ids[: self.n]
            maybe_model = copy(self.model).fit(X[maybe_inliers], y[maybe_inliers])

            thresholded = (
                self.loss(y[ids][self.n :], maybe_model.predict(X[ids][self.n :]))
                &lt; self.t
            )

            inlier_ids = ids[self.n :][np.flatnonzero(thresholded).flatten()]

            if inlier_ids.size &gt; self.d:
                inlier_points = np.hstack([maybe_inliers, inlier_ids])
                better_model = copy(self.model).fit(X[inlier_points], y[inlier_points])

                this_error = self.metric(
                    y[inlier_points], better_model.predict(X[inlier_points])
                )

                if this_error &lt; self.best_error:
                    self.best_error = this_error
                    self.best_fit = better_model

        return self

    def predict(self, X):
        return self.best_fit.predict(X)

def square_error_loss(y_true, y_pred):
    return (y_true - y_pred) ** 2


def mean_square_error(y_true, y_pred):
    return np.sum(square_error_loss(y_true, y_pred)) / y_true.shape[0]


class LinearRegressor:
    def __init__(self):
        self.params = None

    def fit(self, X: np.ndarray, y: np.ndarray):
        r, _ = X.shape
        X = np.hstack([np.ones((r, 1)), X])
        self.params = np.linalg.inv(X.T @ X) @ X.T @ y
        return self

    def predict(self, X: np.ndarray):
        r, _ = X.shape
        X = np.hstack([np.ones((r, 1)), X])
        return X @ self.params


if __name__ == &quot;__main__&quot;:

    regressor = RANSAC(model=LinearRegressor(), loss=square_error_loss, metric=mean_square_error)

    X = np.array([-0.848,-0.800,-0.704,-0.632,-0.488,-0.472,-0.368,-0.336,-0.280,-0.200,-0.00800,-0.0840,0.0240,0.100,0.124,0.148,0.232,0.236,0.324,0.356,0.368,0.440,0.512,0.548,0.660,0.640,0.712,0.752,0.776,0.880,0.920,0.944,-0.108,-0.168,-0.720,-0.784,-0.224,-0.604,-0.740,-0.0440,0.388,-0.0200,0.752,0.416,-0.0800,-0.348,0.988,0.776,0.680,0.880,-0.816,-0.424,-0.932,0.272,-0.556,-0.568,-0.600,-0.716,-0.796,-0.880,-0.972,-0.916,0.816,0.892,0.956,0.980,0.988,0.992,0.00400]).reshape(-1,1)
    y = np.array([-0.917,-0.833,-0.801,-0.665,-0.605,-0.545,-0.509,-0.433,-0.397,-0.281,-0.205,-0.169,-0.0531,-0.0651,0.0349,0.0829,0.0589,0.175,0.179,0.191,0.259,0.287,0.359,0.395,0.483,0.539,0.543,0.603,0.667,0.679,0.751,0.803,-0.265,-0.341,0.111,-0.113,0.547,0.791,0.551,0.347,0.975,0.943,-0.249,-0.769,-0.625,-0.861,-0.749,-0.945,-0.493,0.163,-0.469,0.0669,0.891,0.623,-0.609,-0.677,-0.721,-0.745,-0.885,-0.897,-0.969,-0.949,0.707,0.783,0.859,0.979,0.811,0.891,-0.137]).reshape(-1,1)

    regressor.fit(X, y)

    import matplotlib.pyplot as plt
    plt.style.use(&quot;seaborn-darkgrid&quot;)
    fig, ax = plt.subplots(1, 1)
    ax.set_box_aspect(1)

    plt.scatter(X, y)

    line = np.linspace(-1, 1, num=100).reshape(-1, 1)
    plt.plot(line, regressor.predict(line), c=&quot;peru&quot;)
    plt.show()</code></pre><ul>
<li>결과
<img src="https://velog.velcdn.com/images/strange_tiger/post/39842ad4-3656-4b17-8485-e8cd2fd1fb4e/image.png" alt=""></li>
</ul>
<h2 id="ransac-lo-ransac---python">RANSAC, Lo-RANSAC - Python</h2>
<ul>
<li><a href="https://gaussian37.github.io/vision-concept-ransac/#ransac-python-code-1">RANSAC Python Code</a></li>
<li><a href="https://gaussian37.github.io/vision-concept-ransac/#lo-ransac-python-code-1">Lo-RANSAC Python Code</a></li>
</ul>
<h2 id="ransac---matlab">RANSAC - Matlab</h2>
<ul>
<li><p><a href="https://darkpgmr.tistory.com/61">RANSAC의 이해와 영상처리 활용</a></p>
</li>
<li><p>포물선 근사 문제를 RANSAC으로 풀어보도록 하자.</p>
</li>
<li><p>관측된 데이터 값들은 (x1, y1), ..., (xn, yn), 포물선의 방정식은 f(x) = ax2 + bx + c, 포물선을 결정하기 위해 뽑아야 할 샘플의 개수는 최소 3개이다.</p>
</li>
</ul>
<ol>
<li><p>c_max = 0 으로 초기화한다.</p>
</li>
<li><p>무작위로 세 점 p1, p2, p3를 뽑는다.</p>
</li>
<li><p>세 점을 지나는 포물선 f(x)를 구한다.</p>
</li>
<li><p>이렇게 구한 f(x)와의 거리 ri = |yi-f(xi)|가 T 이하인 데이터의 개수 c을 구한다.</p>
</li>
<li><p>만일 c가 c_max보다 크다면 현재 f(x)를 저장한다 (과거에 저장된 값은 버린다)</p>
</li>
<li><p>2~5 과정을 N번 반복한 후 최종 저장되어 있는 f(x)를 반환한다.</p>
</li>
<li><p>(선택사항) 최종 f(x)를 지지하는 데이터들에 대해 최소자승법을 적용하여 결과를 refine한다.</p>
</li>
</ol>
<pre><code class="language-matlab">% input data

noise_sigma = 100;

x = [1:100]&#39;;

y = -2*(x-40).^2+30;

oy = 500*abs(x-60)-5000;

y([50:70]) = y([50:70]) + oy([50:70]);

y = y + noise_sigma*randn(length(x),1);



% build matrix

A = [x.^2 x ones(length(x),1)];

B = y;



% RANSAC fitting

n_data = length(x);

N = 100;        % iterations

T = 3*noise_sigma;   % residual threshold

n_sample = 3;

max_cnt = 0;

best_model = [0;0;0];

for itr=1:N,

    % random sampling

    k = floor(n_data*rand(n_sample,1))+1;



   % model estimation

    AA = [x(k).^2 x(k) ones(length(k),1)];

    BB = y(k);

    X = pinv(AA)*BB;  % AA*X = BB



   % evaluation

    residual = abs(B-A*X);

    cnt = length(find(residual&lt;T));

    if(cnt&gt;max_cnt),

        best_model = X;

        max_cnt = cnt;

    end;

end;



% optional LS(Least Square) fitting

residual = abs(A*best_model - B);

in_k = find(residual&lt;T);    % inlier k

A2 = [x(in_k).^2 x(in_k) ones(length(in_k),1)];

B2 = y(in_k);

X = pinv(A2)*B2;    % refined model



% drawing

F = A*X;

figure; plot(x,y,&#39;*b&#39;,x,F,&#39;r&#39;);    </code></pre>
<h2 id="활용-예">활용 예</h2>
<ul>
<li><p><a href="https://gaussian37.github.io/vision-concept-ransac/#computer-vision%EC%97%90%EC%84%9C%EC%9D%98-ransac-%ED%99%9C%EC%9A%A9-1">RANSAC (RANdom SAmple Consensus) 개념 및 실습 | Computer Vision에서의 RANSAC 활용</a></p>
</li>
<li><p><a href="https://www.ipb.uni-bonn.de/html/teaching/msr2-2020/sse2-11-ransac.pdf">RANSAC - Random Sample Consensus | Cyrill Stachniss | RANSAC을 활용한 이미지 스티칭 등 활용 예시</a></p>
</li>
<li><p><a href="https://darkpgmr.tistory.com/61">RANSAC의 이해와 영상처리 활용 | Cyrill Stachniss | RANSAC의 활용예</a></p>
</li>
</ul>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Random_sample_consensus">Wikipedia | Random sample consensus</a></li>
<li><a href="https://gaussian37.github.io/vision-concept-ransac/">RANSAC (RANdom SAmple Consensus) 개념 및 실습</a></li>
<li><a href="https://darkpgmr.tistory.com/61">RANSAC의 이해와 영상처리 활용</a></li>
<li><a href="https://youtu.be/9D5rrtCC_E0?feature=shared">RANSAC - 5 Minutes with Cyrill</a></li>
<li><a href="https://www.ipb.uni-bonn.de/html/teaching/msr2-2020/sse2-11-ransac.pdf">RANSAC - Random Sample Consensus | Cyrill Stachniss</a></li>
<li><a href="https://gnaseel.tistory.com/33">중학생도 이해할 수 있는 RANSAC 알고리즘 원리</a></li>
<li><a href="https://velog.io/@claude_ssim/%EC%8B%9C%EA%B0%81%EC%A7%80%EB%8A%A5-RANSAC">[시각지능] RANSAC</a></li>
<li><a href="https://youtu.be/EkYXjmiolBg?feature=shared">Dealing with Outliers: RANSAC | Image Stitching</a></li>
<li><a href="https://scikit-learn.org/stable/auto_examples/linear_model/plot_ransac.html">Robust linear model estimation using RANSAC</a></li>
<li><a href="https://youtu.be/Q7FqV_bglHo?feature=shared">SLAM Online Study | SLAM DUNK Season 2 | RANSAC</a></li>
<li><a href="https://github.com/anubhavparas/ransac-implementation.git">RANSAC (RANdom SAmple Consensus) Algorithm Implementation</a></li>
<li><a href="https://kipl.tistory.com/11">RANSAC Algorithm</a></li>
<li><a href="https://velog.io/@greensox284/Regressor-MLESAC">[Regressor] MLESAC</a></li>
<li><a href="https://www.analyticsvidhya.com/blog/2021/02/new-approach-for-regression-analysis-ransac-and-mlesac/">New Approach for Regression Analysis – RANSAC and MLESAC</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SVM(서포트 벡터 머신, Support Vector Machine)]]></title>
            <link>https://velog.io/@strange_tiger/%EC%84%9C%ED%8F%AC%ED%8A%B8-%EB%B2%A1%ED%84%B0-%EB%A8%B8%EC%8B%A0Support-Vector-Machine-SVM</link>
            <guid>https://velog.io/@strange_tiger/%EC%84%9C%ED%8F%AC%ED%8A%B8-%EB%B2%A1%ED%84%B0-%EB%A8%B8%EC%8B%A0Support-Vector-Machine-SVM</guid>
            <pubDate>Wed, 28 May 2025 06:53:59 GMT</pubDate>
            <description><![CDATA[<h1 id="설명">설명</h1>
<h2 id="svm이란">SVM이란</h2>
<ul>
<li><p><strong>SVM</strong>은 패턴 인식, 자료 분석을 위한 지도 학습 모델이며, 주로 <strong>분류</strong>와 회귀 분석을 위해 사용한다. </p>
</li>
<li><p>N차원 공간에서 <strong>각 그룹 간의 거리를 최대화</strong>하는 <strong>최적의 선 또는 초평면</strong>을 찾아 데이터를 분류하는 지도형 머신 러닝 알고리즘이다.</p>
</li>
<li><p>두 카테고리 중 어느 하나에 속한 데이터의 집합이 주어졌을 때, SVM 알고리즘은 주어진 데이터 집합을 바탕으로 하여 새로운 데이터가 어느 카테고리에 속할지 판단하는 비확률적 이진 선형 분류 모델, 즉 <strong>둘로 나뉜 그룹과 그 경계</strong>를 만든다. </p>
</li>
</ul>
<img src="https://velog.velcdn.com/images/strange_tiger/post/4dbdbd78-e18d-4313-b058-e0cd86474d56/image.png" width="35%" height="35%">

<h1 id="참고">참고</h1>
<ul>
<li><a href="https://ko.wikipedia.org/wiki/%EC%84%9C%ED%8F%AC%ED%8A%B8_%EB%B2%A1%ED%84%B0_%EB%A8%B8%EC%8B%A0">Wikipedia | 서포트 벡터 머신</a></li>
<li><a href="https://namu.wiki/w/%EC%84%9C%ED%8F%AC%ED%8A%B8%20%EB%B2%A1%ED%84%B0%20%EB%A8%B8%EC%8B%A0">나무위키 | 서포트 벡터 머신</a></li>
<li><a href="https://www.ibm.com/kr-ko/think/topics/support-vector-machine">IBM | 서포트 벡터 머신(SVM)이란 무엇인가요?</a></li>
<li><a href="https://sikmulation.tistory.com/3">[머신러닝]Support Vector Machine(SVM)에 대해 알아보자</a></li>
<li><a href="https://sanghyu.tistory.com/7">SVM(Support Vector Machine) 원리</a></li>
<li><a href="https://m.blog.naver.com/winddori2002/221662413641">[파이썬/머신러닝] SVM(Support Vector Machine) 분류 - 이론</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Transform.lossyScale]]></title>
            <link>https://velog.io/@strange_tiger/Transform.lossyScale</link>
            <guid>https://velog.io/@strange_tiger/Transform.lossyScale</guid>
            <pubDate>Wed, 23 Apr 2025 02:15:04 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li><p>UI 구현 중 카메라 캔버스로 인해 위치 값이 바뀐 경우가 생겼다. 이를 해결하기 위해 <code>lossyScale</code>을 활용했다.</p>
<h1 id="transformlossyscale">Transform.lossyScale</h1>
</li>
<li><p><code>lossyScale</code>은 게임 오브젝트의 절대적 크기, 혹은 <code>Transform</code>의 <code>Scale</code> 변화 값이다.</p>
</li>
<li><p><code>transform</code>의 부모 자식 관계에서, 부모 <code>transform</code>에 크기 조절 속성이 있고 자식 <code>transform</code>에 임의로 회전 속성이 있는 경우, 그 크기가 왜곡된다. 따라서 크기는 3성분 벡터로는 정확하게 표현할 수 없고 3x3 행렬로만 표현할 수 있다. </p>
</li>
<li><p>하지만 행렬은 사용하기 불편하니, <code>transform</code>의 크기를 실제 세계에 최대한 일치시켜 표시하고자 사용하는 속성 값이 <code>lossyScale</code>이다.</p>
<h1 id="참고">참고</h1>
</li>
<li><p><a href="https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Transform-lossyScale.html">Unity Documentation | Transform.lossyScale</a></p>
</li>
<li><p><a href="https://ssabi.tistory.com/25">[Unity] 트랜스폼(Transform) 크기(Scale)</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity 텍스처(Texture)]]></title>
            <link>https://velog.io/@strange_tiger/Unity-%ED%85%8D%EC%8A%A4%EC%B2%98Texture</link>
            <guid>https://velog.io/@strange_tiger/Unity-%ED%85%8D%EC%8A%A4%EC%B2%98Texture</guid>
            <pubDate>Fri, 18 Apr 2025 06:29:51 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li>작업 중 <code>Texture2D.whiteTexture</code>를 사용하다가 에디터가 사진으로 가득차는 참사가 있었다. 이러한 문제가 다시 발생하지 않도록 여기에 공부한 내용을 정리한다.</li>
</ul>
<h1 id="텍스처texture">텍스처(Texture)</h1>
<ul>
<li><p>Unity에서 <code>Texture</code>(텍스처)는 가비지 컬렉터가 관리하지 않는다. 따라서, 수동으로 삭제하는 작업이 필요하다.</p>
</li>
<li><p><code>Texture</code>(텍스처)는 참조 기반이다. 따라서 얕은 복사 등의 문제를 주의해야 한다.</p>
<ul>
<li><code>Texture2D.whiteTexture</code> 또한 참조 기반이다. 따라서 <code>whiteTexture</code>를 바라보고 있는 텍스처를 수정하면 에디터에서 사용하고 있는 흰색 값이 모두 그 사진으로 바뀌어 버리는 등, 문제가 생길 수 있다.</li>
<li>텍스트, 이미지 등 오브젝트에서 사용하는 <code>Color</code> 또한 텍스처이다.</li>
</ul>
</li>
<li><p>텍스처를 수정하면 <code>Apply()</code>로 적용하자.</p>
</li>
</ul>
<h1 id="추가">추가</h1>
<ul>
<li><code>Sprite</code>도 참조 기반이다.</li>
</ul>
<h1 id="참고">참고</h1>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] Virtual(가상), Abstract(추상), Interface(인터페이스)]]></title>
            <link>https://velog.io/@strange_tiger/C-Virtual%EA%B0%80%EC%83%81-Abstract%EC%B6%94%EC%83%81-Interface%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</link>
            <guid>https://velog.io/@strange_tiger/C-Virtual%EA%B0%80%EC%83%81-Abstract%EC%B6%94%EC%83%81-Interface%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4</guid>
            <pubDate>Tue, 25 Feb 2025 03:45:44 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li>상속을 활용하던 중, virtual과 abstract의 사용 개념이 헷갈렸다. 이런 일이 다시 없도록 정리한다.</li>
</ul>
<h1 id="virtual가상-함수">Virtual(가상 함수)</h1>
<ul>
<li><p>메소드, 속성, 인덱서 또는 이벤트 선언을 한정하는데 사용된다.</p>
</li>
<li><p>자식 클래스에서 필요에 따라 오버라이드할 수 있지만 반드시 할 필요는 없다.</p>
</li>
<li><p>자체적으로 구현할 수 있다.</p>
</li>
</ul>
<h1 id="abstract추상-클래스-함수">Abstract(추상 클래스, 함수)</h1>
<ul>
<li><p>자식 클래스에서 구현해야하는 클래스 및 멤버, 메소드를 만들 수 있다.</p>
</li>
<li><p>여러 자식 클래스에서 공유할 기본 클래스의 공통적인 정의를 제공한다. 즉, 반드시 오버라이드 해야 한다.</p>
</li>
<li><p>자체적으로 구현할 수 없다.</p>
</li>
<li><p>추상 클래스는 인스턴스화(객체 생성)할 수 없다.</p>
</li>
</ul>
<blockquote>
<ul>
<li><p>추상 클래스는 구현을 갖되, 인스턴스를 가질 수 없다.</p>
</li>
<li><p>추상 클래스는 추상 메소드를 가질 수 있다.</p>
</li>
<li><p>추상 메소드는 구현을 갖지 못하지만 파생클래스에서 반드시 구현하도록 강제한다.</p>
<ul>
<li>즉, 추상 클래스를 상속하는 클래스들이 반드시 이 메소드를 가질 거라는 약속이다. </li>
</ul>
</li>
</ul>
</blockquote>
<h1 id="interface인터페이스">Interface(인터페이스)</h1>
<blockquote>
<ul>
<li>클래스가 따라야 하는 약속이다.</li>
</ul>
</blockquote>
<ul>
<li>인터페이스로부터 파생될 클래스가 어떤 메소드를 구현해야 할지를 정의한다.</li>
</ul>
<ul>
<li><p>Abstract와 비슷하게 자식 클래스에서 공유할 공통 정의를 제공하지만, 멤버필드를 갖지 않는다. 대신 프로퍼티는 사용이 가능하다.</p>
</li>
<li><p>여러 자식 클래스가 공통적으로 가질 기능을 추가하기 위해 사용한다. 즉, Abatract와 같이 반드시 오버라이드 해야 한다.</p>
</li>
<li><p>자체적으로 구현할 수 없고, 인스턴스화(객체 생성)할 수 없다.</p>
</li>
<li><p>접근제한자는 public만 사용이 가능하다.</p>
</li>
</ul>
<h1 id="표">표</h1>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">Virtual</th>
<th align="center">Abstract</th>
<th align="center">Interface</th>
</tr>
</thead>
<tbody><tr>
<td align="center">구현</td>
<td align="center">기본 구현 가능</td>
<td align="center">구현 불가</td>
<td align="center">구현 불가</td>
</tr>
<tr>
<td align="center">오버라이딩</td>
<td align="center">선택</td>
<td align="center">필수</td>
<td align="center">필수</td>
</tr>
<tr>
<td align="center">목적</td>
<td align="center">기본 기능을 override될 수 있도록 제공</td>
<td align="center">자식 클래스가 공유할 공통 정의를 제공</td>
<td align="center">자식 클래스가 공통적으로 가질 기능을 추가</td>
</tr>
<tr>
<td align="center">호환되지 않는 키워드</td>
<td align="center">static, abstract, private, override</td>
<td align="center">static, virtual</td>
<td align="center">static, abstract, private, protected, virtual</td>
</tr>
</tbody></table>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://www.hanbit.co.kr/store/books/look.php?p_code=B6673972966">이것이 C#이다</a></li>
<li><a href="https://hongjinhyeon.tistory.com/93">[C#] Virtual(가상) vs Abstract(추상) vs Interface(인터페이스)</a></li>
<li><a href="https://kimasill.tistory.com/entry/C-Abstract%EC%B6%94%EC%83%81Virtual%EA%B0%80%EC%83%81Interface%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%B0%A8%EC%9D%B4">[C#] Abstract(추상)/Virtual(가상)/Interface(인터페이스) 차이</a></li>
<li><a href="https://bigexecution.tistory.com/296#google_vignette">C#] Virtual VS Abstract 함수 차이점</a></li>
<li><a href="https://math-development-geometry.tistory.com/40">C# - 추상 클래스, 가상 클래스 - abstract, virtual 사용법</a></li>
<li><a href="https://math-development-geometry.tistory.com/39">C# - interface 인터페이스 사용법과 원리</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[전략 패턴]]></title>
            <link>https://velog.io/@strange_tiger/%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@strange_tiger/%EC%A0%84%EB%9E%B5-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Tue, 18 Feb 2025 03:21:53 GMT</pubDate>
            <description><![CDATA[<h1 id="설명">설명</h1>
<ul>
<li><p><strong>전략 패턴(strategy pattern)</strong>은 실행 중에 <strong>알고리즘을 선택</strong>할 수 있게 하는 행위 소프트웨어 디자인 패턴이다.</p>
</li>
<li><p>전략 패턴은</p>
<ul>
<li><p>특정한 계열의 <strong>알고리즘들을 정의</strong>하고</p>
</li>
<li><p>각 알고리즘을 <strong>캡슐화</strong>하며</p>
</li>
<li><p>이 알고리즘들을 해당 계열 안에서 <strong>상호 교체가 가능</strong>하게 만든다.</p>
</li>
</ul>
</li>
<li><p>객체의 행위를 <strong>동적</strong>으로 바꾸고 싶은 경우 직접 행위를 수정하지 않고 <strong>전략을 바꿔주기</strong>만 함으로써 행위를 유연하게 확장한다.</p>
</li>
</ul>
<h2 id="장단점과-사용-시기">장단점과 사용 시기</h2>
<ul>
<li><p>장점</p>
<ol>
<li><p><strong>개방 폐쇄 원칙(OCP)</strong> 준수 : 콘텍스트(클라이언트)의 기능을 수정하지 않고도 새로운 전략(기능)을 도입할 수 있다.</p>
</li>
<li><p>런타임에 한 객체 내부에서 사용되는 알고리즘들을 <strong>동적으로 교환</strong>할 수 있다.</p>
</li>
<li><p>상속을 <strong>컴포지션(Composition)</strong>으로 대체할 수 있다.</p>
<ul>
<li>컴포지션 : 클래스를 구성하는 부분의 합. 한 클래스가 다른 클래스의 구성요소가 되는 것.</li>
</ul>
</li>
</ol>
</li>
<li><p>단점</p>
<ol>
<li><p>알고리즘이 몇 개밖에 되지 않고 변하지 않는다면, 괜히 새로운 클래스와 인터페이스들로 <strong>코드를 복잡</strong>하게 만들 필요가 없다.</p>
</li>
<li><p>클라이언트들이 적절한 전략을 선택할 수 있도록 <strong>전략 간의 차이점</strong>을 알고 있어야 한다.</p>
</li>
<li><p>현대에는 익명 함수들의 집합 안에서 알고리즘의 다양한 버전을 구현할 수 있는 <strong>함수형 지원</strong>이 있어, 코드의 양을 늘리지 않으면서도 전략 패턴을 사용했을 때와 같이 함수를 구현해 사용할 수 있다.</p>
</li>
</ol>
</li>
<li><p>사용 시기</p>
<ul>
<li><p>객체 안에서 한 <strong>알고리즘의 다양한 변형</strong>을 사용하고 싶을 때</p>
</li>
<li><p><strong>런타임 중에 동적으로</strong> 한 알고리즘에서 다른 알고리즘으로 전환하고 싶을 때</p>
</li>
<li><p>일부 행동(기능)을 실행하는 방식에서 차이가 있는 유사한 클래스들이 많을 때</p>
</li>
<li><p>같은 알고리즘의 다른 변형들 사이를 전환하는 거대한 조건문이 클래스에 있을 때</p>
</li>
</ul>
</li>
</ul>
<h1 id="구조">구조</h1>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/2e279116-e513-43e9-86d4-6b7a17bb443f/image.png" alt=""></p>
<ul>
<li><p><code>Context</code> : <code>Strategy</code>를 이용하는 시스템. 전략 알고리즘을 직접 구현하지 않는다. 대신, <code>strategy.algorithm()</code> 알고리즘을 구현하는 <code>Strategy</code>를 참조한다.</p>
</li>
<li><p><code>Strategy 인터페이스</code> : 전략을 구현하여 시스템에 제공한다. 이로써 <code>Context</code>을 알고리즘의 구현에서 독립시킨다.</p>
</li>
<li><p><code>Strategy1</code>, <code>Strategy2</code> : 구체적인 알고리즘의 구현. 인터페이스를 구현해 만들어진 전략.</p>
</li>
</ul>
<h1 id="구현-및-예시">구현 및 예시</h1>
<h2 id="구현-방법">구현 방법</h2>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/d08696d0-7333-4b94-9d8d-a676af464a9e/image.png" alt=""></p>
<ol>
<li><p><code>Context</code>는 전략 중 하나에 대한 참조를 유지하고 전략 인터페이스를 통해서만 이 객체와 통신한다.</p>
</li>
<li><p><code>Strategy 인터페이스</code>는 모든 전략에 공통이며, <code>Context</code>가 전략을 실행하는 데 사용하는 메소드를 선언한다.</p>
</li>
<li><p><code>ConcreteStrategy</code>들은 <code>Context</code>가 사용하는 알고리즘의 다양한 변형들을 구현한다.</p>
</li>
<li><p><code>Context</code>는 알고리즘을 실행할 때마다 연결된 전략 객체의 메소드를 호출한다. 여기서 <code>Context</code>는 알고리즘이 어떻게 실행되는지, 어떤 유형의 전략을 작동하는지 모른다.</p>
</li>
<li><p><code>Client</code>는 특정 전략 객체를 만들어 <code>Context</code>에 전달한다. 이 때 <code>Context</code>는 <code>Client</code>들이 런타임에 전략을 대체할 수 있도록 하는 세터<code>Setter</code>를 노출한다.</p>
</li>
</ol>
<h3 id="예시와-함께-보는-전략-패턴-구현-방법">예시와 함께 보는 전략 패턴 구현 방법</h3>
<ol>
<li><p>전략을 생성한다.</p>
<ul>
<li><p>운송 수단은 선로를 따라 움직이든지, 도로를 따라 움직이든지 두 가지 방법을 <code>Strategy</code> 클래스를 생성하는 것으로 구현한다.</p>
</li>
<li><p>그리고 두 클래스는 <code>move()</code> 메소드를 구현한다.</p>
</li>
<li><p>두 전략 클래스를 <code>IMovableStrategy</code> 인터페이스로 캡슐화하여 다른 전략이 추가로 확장되는 경우를 고려해 설계하자.</p>
<pre><code class="language-csharp">public interface IMovableStrategy
{
public void move();
}
</code></pre>
</li>
</ul>
</li>
</ol>
<p>public class RailLoadStrategy : IMovableStrategy
{
    public void move()
    {
        Debug.Log(&quot;선로를 통해 이동&quot;);
    }
}</p>
<p>public class LoadStrategy : IMovableStrategy
{
    public void move()
    {
        Debug.Log(&quot;도로를 통해 이동&quot;);
    }
}</p>
<pre><code>
2. 운송 수단에 대한 클래스를 정의한다.

   - 운송 수단은 `move()` 메소드를 통해 움직인다.

   - 이동 방식을 전략을 통해 정한다.

   - 이때 전략을 정할 수 있는 메소드 `setMovableStrategy`를 구현한다.

```csharp
public class Moving
{
    private MovableStrategy movableStrategy;

    public void move()
    {
        movableStrategy.move();
    }

    public void setMovableStrategy(MovableStrategy movableStrategy)
    {
        this.movableStrategy = movableStrategy;
    }
}

public class Bus : Moving
{

}

public class Train : Moving
{

}</code></pre><ol start="3">
<li><p>운송 수단 객체를 사용하는 <code>Client</code> 객체를 구현한다.</p>
<ul>
<li><p><code>Train</code>과 <code>Bus</code> 객체를 생성하고, 각 운송 수단의 이동 방식을 <code>setMovableStrategy()</code> 메소들르 호출하는 것으로 정한다.</p>
</li>
<li><p>여기서 <code>Bus</code>의 이동 방식을 유연하게 수정할 수 있다.</p>
</li>
</ul>
</li>
</ol>
<pre><code class="language-csharp">public class Client
{
    public int main()
    {
        Moving train = new Train();
        Moving bus = new Bus();

        /*
            기존의 기차와 버스의 이동 방식
            1) 기차 - 선로
            2) 버스 - 도로
         */
        train.setMovableStrategy(new RailLoadStrategy());
        bus.setMovableStrategy(new LoadStrategy());

        train.move();    // 선로를 통해 이동
        bus.move();        // 도로를 통해 이동

        /*
            선로를 따라 움직이는 버스가 개발
         */
        bus.setMovableStrategy(new RailLoadStrategy());
        bus.move();        // 선로를 통해 이동
    }
}</code></pre>
<h2 id="의사코드">의사코드</h2>
<ul>
<li>이 예시에서의 Context는 여러 전략들을 사용하여 다양한 산술 연산들을 실행한다.</li>
</ul>
<pre><code class="language-pseudo">// 전략 인터페이스는 어떤 알고리즘의 모든 지원 버전에 공통적인 작업을 선언합니다.
// 콘텍스트는 이 인터페이스를 사용하여 구상 전략들에 의해 정의된 알고리즘을
// 호출합니다.
interface Strategy is
    method execute(a, b)

// 구상 전략들은 기초 전략 인터페이스를 따르면서 알고리즘을 구현합니다. 인터페이스는
// 그들이 콘텍스트에서 상호교환할 수 있게 만듭니다.
class ConcreteStrategyAdd implements Strategy is
    method execute(a, b) is
        return a + b

class ConcreteStrategySubtract implements Strategy is
    method execute(a, b) is
        return a - b

class ConcreteStrategyMultiply implements Strategy is
    method execute(a, b) is
        return a * b

// 콘텍스트는 클라이언트들이 관심을 갖는 인터페이스를 정의합니다.
class Context is
    // 콘텍스트는 전략 객체 중 하나에 대한 참조를 유지합니다. 콘텍스트는 전략의
    // 구상 클래스를 알지 못하며, 전략 인터페이스를 통해 모든 전략과 함께
    // 작동해야 합니다.
    private strategy: Strategy

    // 일반적으로 콘텍스트는 생성자를 통해 전략을 수락하고 런타임에 전략이 전환될
    // 수 있도록 세터도 제공합니다.
    method setStrategy(Strategy strategy) is
        this.strategy = strategy

    // 콘텍스트는 자체적으로 여러 버전의 알고리즘을 구현하는 대신 일부 작업을 전략
    // 객체에 위임합니다.
    method executeStrategy(int a, int b) is
        return strategy.execute(a, b)


// 클라이언트 코드는 구상 전략을 선택하고 콘텍스트에 전달합니다. 클라이언트는 올바른
// 선택을 하기 위해 전략 간의 차이점을 알고 있어야 합니다.
class ExampleApplication is
    method main() is
        Create context object.

        Read first number.
        Read last number.
        Read the desired action from user input.

        if (action == addition) then
            context.setStrategy(new ConcreteStrategyAdd())

        if (action == subtraction) then
            context.setStrategy(new ConcreteStrategySubtract())

        if (action == multiplication) then
            context.setStrategy(new ConcreteStrategyMultiply())

        result = context.executeStrategy(First number, Second number)

        Print result.</code></pre>
<h2 id="c-예시-코드-1">C# 예시 코드 1</h2>
<pre><code class="language-csharp">using System;
using System.Collections.Generic;

namespace RefactoringGuru.DesignPatterns.Strategy.Conceptual
{
    // The Context defines the interface of interest to clients.
    class Context
    {
        // The Context maintains a reference to one of the Strategy objects. The
        // Context does not know the concrete class of a strategy. It should
        // work with all strategies via the Strategy interface.
        private IStrategy _strategy;

        public Context()
        { }

        // Usually, the Context accepts a strategy through the constructor, but
        // also provides a setter to change it at runtime.
        public Context(IStrategy strategy)
        {
            this._strategy = strategy;
        }

        // Usually, the Context allows replacing a Strategy object at runtime.
        public void SetStrategy(IStrategy strategy)
        {
            this._strategy = strategy;
        }

        // The Context delegates some work to the Strategy object instead of
        // implementing multiple versions of the algorithm on its own.
        public void DoSomeBusinessLogic()
        {
            Console.WriteLine(&quot;Context: Sorting data using the strategy (not sure how it&#39;ll do it)&quot;);
            var result = this._strategy.DoAlgorithm(new List&lt;string&gt; { &quot;a&quot;, &quot;b&quot;, &quot;c&quot;, &quot;d&quot;, &quot;e&quot; });

            string resultStr = string.Empty;
            foreach (var element in result as List&lt;string&gt;)
            {
                resultStr += element + &quot;,&quot;;
            }

            Console.WriteLine(resultStr);
        }
    }

    // The Strategy interface declares operations common to all supported
    // versions of some algorithm.
    //
    // The Context uses this interface to call the algorithm defined by Concrete
    // Strategies.
    public interface IStrategy
    {
        object DoAlgorithm(object data);
    }

    // Concrete Strategies implement the algorithm while following the base
    // Strategy interface. The interface makes them interchangeable in the
    // Context.
    class ConcreteStrategyA : IStrategy
    {
        public object DoAlgorithm(object data)
        {
            var list = data as List&lt;string&gt;;
            list.Sort();

            return list;
        }
    }

    class ConcreteStrategyB : IStrategy
    {
        public object DoAlgorithm(object data)
        {
            var list = data as List&lt;string&gt;;
            list.Sort();
            list.Reverse();

            return list;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // The client code picks a concrete strategy and passes it to the
            // context. The client should be aware of the differences between
            // strategies in order to make the right choice.
            var context = new Context();

            Console.WriteLine(&quot;Client: Strategy is set to normal sorting.&quot;);
            context.SetStrategy(new ConcreteStrategyA());
            context.DoSomeBusinessLogic();

            Console.WriteLine();

            Console.WriteLine(&quot;Client: Strategy is set to reverse sorting.&quot;);
            context.SetStrategy(new ConcreteStrategyB());
            context.DoSomeBusinessLogic();
        }
    }
}</code></pre>
<ul>
<li>실행 결과</li>
</ul>
<pre><code class="language-txt">Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it&#39;ll do it)
a,b,c,d,e

Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it&#39;ll do it)
e,d,c,b,a</code></pre>
<h2 id="c-예시-코드-2">C# 예시 코드 2</h2>
<pre><code class="language-csharp">public class StrategyPatternWiki
{
    public static void Main(String[] args)
    {
        Customer firstCustomer = new Customer(new NormalStrategy());

        // Normal billing
        firstCustomer.Add(1.0, 1);

        // Start Happy Hour
        firstCustomer.Strategy = new HappyHourStrategy();
        firstCustomer.Add(1.0, 2);

        // New Customer
        Customer secondCustomer = new Customer(new HappyHourStrategy());
        secondCustomer.Add(0.8, 1);
        // The Customer pays
        firstCustomer.PrintBill();

        // End Happy Hour
        secondCustomer.Strategy = new NormalStrategy();
        secondCustomer.Add(1.3, 2);
        secondCustomer.Add(2.5, 1);
        secondCustomer.PrintBill();
    }
}


class Customer
{
    private IList&lt;double&gt; drinks;

    // Get/Set Strategy
    public IBillingStrategy Strategy { get; set; }

    public Customer(IBillingStrategy strategy)
    {
        this.drinks = new List&lt;double&gt;();
        this.Strategy = strategy;
    }

    public void Add(double price, int quantity)
    {
        drinks.Add(Strategy.GetActPrice(price * quantity));
    }

    // Payment of bill
    public void PrintBill()
    {
        double sum = 0;
        foreach (double i in drinks)
        {
            sum += i;
        }
        Console.WriteLine(&quot;Total due: &quot; + sum);
        drinks.Clear();
    }
}

interface IBillingStrategy
{
    double GetActPrice(double rawPrice);
}

// Normal billing strategy (unchanged price)
class NormalStrategy : IBillingStrategy
{
    public double GetActPrice(double rawPrice)
    {
        return rawPrice;
    }

}

// Strategy for Happy hour (50% discount)
class HappyHourStrategy : IBillingStrategy
{

    public double GetActPrice(double rawPrice)
    {
        return rawPrice * 0.5;
    }
}</code></pre>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://www.yes24.com/Product/Goods/17525598">GoF의 디자인 패턴(개정판) / 에릭 감마, 리처드 헬름, 랄프 존슨, 존 블라시디스 공저</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EC%A0%84%EB%9E%B5_%ED%8C%A8%ED%84%B4">전략 패턴 | Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Strategy_pattern">Strategy pattern | Wikipedia</a></li>
<li><a href="https://refactoring.guru/ko/design-patterns/strategy">전략 패턴 | refactoring.guru</a></li>
<li><a href="https://victorydntmd.tistory.com/292">🙈[디자인패턴] 전략 패턴 ( Strategy Pattern )🐵</a></li>
<li><a href="https://dev-cool.tistory.com/22">상속보다는 컴포지션을 사용하자</a></li>
<li><a href="https://namu.wiki/w/%EB%94%94%EC%9E%90%EC%9D%B8%20%ED%8C%A8%ED%84%B4#s-5.9">디자인 패턴 | 나무위키</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[상태 패턴]]></title>
            <link>https://velog.io/@strange_tiger/%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@strange_tiger/%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Mon, 17 Feb 2025 01:44:20 GMT</pubDate>
            <description><![CDATA[<h1 id="설명">설명</h1>
<ul>
<li><p><strong>상태 패턴(state pattern)</strong>은 <strong>객체 지향 방식</strong>으로 <strong>상태 기계</strong>를 구현하는 디자인 패턴이다.</p>
</li>
<li><p>객체가 <strong>상태</strong>에 따라 행위를 다르게 할 때, 직접 상태를 체크하여 상태에 따른 행위를 호출하는 것이 아니라 <strong>상태를 객체화</strong>하여 필요에 따라 다르게 행동하도록 위임한다.</p>
</li>
<li><p><strong>유한 상태 기계(Finite State Machine, FSM)</strong>는 상태가 추가될 때마다 <strong>조건</strong>이 붙어 상태 전이를 위한 로직이 점점 복잡해진다. 상태 패턴은 각 <strong>상태에 대응하는 클래스</strong>를 만드는 것으로 이 복잡함을 해소할 수 있다. </p>
<ul>
<li>각 상태를 이용하는 호스트 객체를 <strong>컨텍스트(Context)</strong>라고도 부른다.</li>
</ul>
</li>
<li><p>상태 패턴을 사용하는 것으로 상태를 추가하거나 상태 전이 로직을 이해하기 쉽게 만드는 등, <strong>유지보수</strong>에 이득을 얻을 수 있다.</p>
</li>
<li><p>GoF에 따르면 <strong>TCP connection</strong> 구현에 이 패턴이 사용되었다고 한다.</p>
</li>
</ul>
<h2 id="장단점과-사용-시기">장단점과 사용 시기</h2>
<ul>
<li><p>장점</p>
<ol>
<li><p><strong>단일 책임 원칙(SRP)</strong> 준수 : 특정 상태에 대한 코드를 별도 클래스로 구성</p>
</li>
<li><p><strong>개방 폐쇄 원칙(OCP)</strong> 준수 : 기존 State 클래스 혹은 컨텍스트를 수정하지 않고 새로운 상태를 추가 가능</p>
</li>
<li><p>상태에 따른 동작을 각각의 클래스로 분산하여 코드 복잡도 감소</p>
</li>
</ol>
</li>
<li><p>단점</p>
<ol>
<li><p>관리할 클래스 수 증가</p>
</li>
<li><p>객체에 적용할 상태가 몇가지 밖에 없거나 거의 상태 변경이 이루어지지 않는 경우 패턴을 적용하는 것이 과도할 수 있다.</p>
</li>
</ol>
</li>
<li><p>사용 시기</p>
<ul>
<li><p>객체의 기능이 상태에 따라 각기 다를 때</p>
</li>
<li><p>상태 변경에 대한 조건 분기 코드가 많거나 중복될 때</p>
</li>
<li><p>런타임에서 객체의 상태를 변경해야 할 때</p>
</li>
</ul>
</li>
</ul>
<h1 id="구조">구조</h1>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/0f8ddfe6-da13-4c44-80e3-b30f065fdfad/image.png" alt=""></p>
<ul>
<li><p><code>Context</code> : <code>State</code>를 이용하는 시스템. 실제 행위를 실행하는 대신 상태에 위임한다.</p>
</li>
<li><p><code>State 인터페이스</code> : 시스템의 모든 상태에 공통의 인터페이스를 제공한다.</p>
</li>
<li><p><code>State1</code>, <code>State2</code> : 구체적인 각각의 상태 객체. 인터페이스를 구체적으로 구현한다.</p>
</li>
</ul>
<h1 id="구현-및-예시">구현 및 예시</h1>
<h2 id="구현-방법">구현 방법</h2>
<ol>
<li><p><strong>컨텍스트 클래스</strong>를 정한다. 유한 상태 기계일 수도 있고, 조건문으로 상태를 관리하는 클래스일 수도 있고, 아예 새로운 클래스일 수도 있다.</p>
</li>
<li><p><strong>상태 인터페이스</strong>를 선언한다. 상태별 동작을 포함하는 메소드만을 갖도록 한다. </p>
</li>
<li><p>모든 상태에 대해 상태 인터페이스에서 <strong>파생된 상태 클래스</strong>를 만든다. 컨텍스트에서 상태와 관련된 모든 코드를 추출해 넣는다.</p>
</li>
<li><p>컨텍스트 클래스에서 상태 인터페이스 유형의 참조 필드와 필드 값을 오버라이드할 수 있는 <strong>공개된 세터(setter)</strong>를 추가한다.</p>
</li>
<li><p>컨텍스트의 메소드를 보고 조건문을 상태 객체의 <strong>메소드 호출</strong>로 대체한다.</p>
</li>
<li><p><strong>상태 클래스의 인스턴스</strong>를 컨텍스트에 전달하는 것으로 컨텍스트의 상태를 전환할 수 있다.</p>
</li>
</ol>
<h2 id="의사코드">의사코드</h2>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/c2bdde07-d33d-48df-a595-63fee2747405/image.png" alt=""></p>
<pre><code>// AudioPlayer(오디오 플레이어) 클래스는 콘텍스트 역할을 합니다. 이 클래스는 또
// 오디오 플레이어의 현재 상태를 나타내는 상태 클래스 중 하나의 인스턴스에 대한
// 참조를 유지합니다.
class AudioPlayer is
    field state: State
    field UI, volume, playlist, currentSong

    constructor AudioPlayer() is
        this.state = new ReadyState(this)

        // 콘텍스트는 사용자 입력 처리를 상태 객체에 위임합니다. 당연히 결과는
        // 현재 활성화된 상태에 따라 달라집니다. 왜냐하면 각 상태는 입력을
        // 다르게 처리할 수 있기 때문입니다.
        UI = new UserInterface()
        UI.lockButton.onClick(this.clickLock)
        UI.playButton.onClick(this.clickPlay)
        UI.nextButton.onClick(this.clickNext)
        UI.prevButton.onClick(this.clickPrevious)

    // 다른 객체들은 오디오 플레이어의 활성 상태를 전환할 수 있어야 합니다.
    method changeState(state: State) is
        this.state = state

    // 사용자 인터페이스 메서드들은 실행을 활성 상태에 위임합니다.
    method clickLock() is
        state.clickLock()
    method clickPlay() is
        state.clickPlay()
    method clickNext() is
        state.clickNext()
    method clickPrevious() is
        state.clickPrevious()

    // 상태는 콘텍스트에 일부 서비스 메서드들을 호출할 수 있습니다.
    method startPlayback() is
        // …
    method stopPlayback() is
        // …
    method nextSong() is
        // …
    method previousSong() is
        // …
    method fastForward(time) is
        // …
    method rewind(time) is
        // …


// 기초 상태 클래스는 모든 구상 상태들이 구현해야 하는 메서드들을 선언하고 상태와
// 연결된 콘텍스트 객체에 대한 역참조도 제공합니다. 상태는 역참조를 사용하여
// 콘텍스트를 다른 상태로 천이할 수 있습니다.
abstract class State is
    protected field player: AudioPlayer

    // 콘텍스트는 상태 생성자를 통해 자신을 전달합니다. 이는 필요한 경우 상태가
    // 유용한 콘텍스트 데이터를 가져오는 데 도움이 될 수 있습니다.
    constructor State(player) is
        this.player = player

    abstract method clickLock()
    abstract method clickPlay()
    abstract method clickNext()
    abstract method clickPrevious()


// 구상 상태들은 콘텍스트의 상태와 연관된 다양한 행동들을 구현합니다.
class LockedState extends State is

    // 잠긴 플레이어의 잠금을 해제하면 플레이어가 두 가지 상태 중 하나를 택할 수
    // 있습니다.
    method clickLock() is
        if (player.playing)
            player.changeState(new PlayingState(player))
        else
            player.changeState(new ReadyState(player))

    method clickPlay() is
        // 잠금 상태: 아무것도 하지 않는다.

    method clickNext() is
        // 잠금 상태: 아무것도 하지 않는다.

    method clickPrevious() is
        // 잠금 상태: 아무것도 하지 않는다.


// 콘텍스트에서 상태 천이를 실행시킬 수도 있습니다.
class ReadyState extends State is
    method clickLock() is
        player.changeState(new LockedState(player))

    method clickPlay() is
        player.startPlayback()
        player.changeState(new PlayingState(player))

    method clickNext() is
        player.nextSong()

    method clickPrevious() is
        player.previousSong()


class PlayingState extends State is
    method clickLock() is
        player.changeState(new LockedState(player))

    method clickPlay() is
        player.stopPlayback()
        player.changeState(new ReadyState(player))

    method clickNext() is
        if (event.doubleclick)
            player.nextSong()
        else
            player.fastForward(5)

    method clickPrevious() is
        if (event.doubleclick)
            player.previous()
        else
            player.rewind(5)</code></pre><h2 id="c-예시-코드">C# 예시 코드</h2>
<pre><code class="language-csharp">using System;

namespace RefactoringGuru.DesignPatterns.State.Conceptual
{
    // The Context defines the interface of interest to clients. It also
    // maintains a reference to an instance of a State subclass, which
    // represents the current state of the Context.
    class Context
    {
        // A reference to the current state of the Context.
        private State _state = null;

        public Context(State state)
        {
            this.TransitionTo(state);
        }

        // The Context allows changing the State object at runtime.
        public void TransitionTo(State state)
        {
            Console.WriteLine($&quot;Context: Transition to {state.GetType().Name}.&quot;);
            this._state = state;
            this._state.SetContext(this);
        }

        // The Context delegates part of its behavior to the current State
        // object.
        public void Request1()
        {
            this._state.Handle1();
        }

        public void Request2()
        {
            this._state.Handle2();
        }
    }

    // The base State class declares methods that all Concrete State should
    // implement and also provides a backreference to the Context object,
    // associated with the State. This backreference can be used by States to
    // transition the Context to another State.
    abstract class State
    {
        protected Context _context;

        public void SetContext(Context context)
        {
            this._context = context;
        }

        public abstract void Handle1();

        public abstract void Handle2();
    }

    // Concrete States implement various behaviors, associated with a state of
    // the Context.
    class ConcreteStateA : State
    {
        public override void Handle1()
        {
            Console.WriteLine(&quot;ConcreteStateA handles request1.&quot;);
            Console.WriteLine(&quot;ConcreteStateA wants to change the state of the context.&quot;);
            this._context.TransitionTo(new ConcreteStateB());
        }

        public override void Handle2()
        {
            Console.WriteLine(&quot;ConcreteStateA handles request2.&quot;);
        }
    }

    class ConcreteStateB : State
    {
        public override void Handle1()
        {
            Console.Write(&quot;ConcreteStateB handles request1.&quot;);
        }

        public override void Handle2()
        {
            Console.WriteLine(&quot;ConcreteStateB handles request2.&quot;);
            Console.WriteLine(&quot;ConcreteStateB wants to change the state of the context.&quot;);
            this._context.TransitionTo(new ConcreteStateA());
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            // The client code.
            var context = new Context(new ConcreteStateA());
            context.Request1();
            context.Request2();
        }
    }
}</code></pre>
<ul>
<li>실행 결과</li>
</ul>
<pre><code>Context: Transition to ConcreteStateA.
ConcreteStateA handles request1.
ConcreteStateA wants to change the state of the context.
Context: Transition to ConcreteStateB.
ConcreteStateB handles request2.
ConcreteStateB wants to change the state of the context.
Context: Transition to ConcreteStateA.</code></pre><h2 id="예시를-통한-구현-방법-설명">예시를 통한 구현 방법 설명</h2>
<ul>
<li>간단한 티켓 자판기를 만들어 보자. 이 자판기는 두 개의 상태를 가진다.<ul>
<li>동전이 없는 상태<ul>
<li>티켓을 출력하려 해도 상태가 변하지 않는다.</li>
<li>동전을 넣으면 동전이 있는 상태로 변한다.</li>
</ul>
</li>
<li>동전이 있는 상태<ul>
<li>동전을 더 넣어도 상태는 변하지 않는다.</li>
<li>티켓을 뽑으면 동전이 없는 상태로 변한다.</li>
</ul>
</li>
</ul>
</li>
<li>DFA
<img src="https://velog.velcdn.com/images/strange_tiger/post/aa7fda09-07a9-448c-995f-7b4d4edf42ea/image.png" alt=""></li>
</ul>
<ol>
<li><p>컨텍스트 클래스를 정한다.</p>
<pre><code class="language-csharp">public class TicketMachine
{
   private bool isCoin = false;

   private void insertCoin()
   {
       if (isCoin == false)
       {
           isCoin = true;
           Console.WriteLine(&quot;동전을 넣었습니다.&quot;);
       }
       else
           Console.WriteLine(&quot;이미 동전이 들어있습니다.&quot;);
   }

   private void printTicket()
   {
       if (isCoin == true)
       {
           isCoin = false;
           Console.WriteLine(&quot;티켓을 뽑았습니다.&quot;);
       }
       else
           Console.WriteLine(&quot;동전이 없습니다. 동전을 넣어주세요.&quot;);
   }

   public void input()
   {
       if (Console.ReadLine() == &quot;coin&quot;)
           insertCoin();
       else
           printTicket();
   }
}</code></pre>
</li>
<li><p>상태 인터페이스를 선언한다.</p>
<pre><code class="language-csharp">public interface IState
{
   void insertCoin(TicketMachine ticketMachine);
   void printTicket(TicketMachine ticketMachine);
}</code></pre>
</li>
<li><p>모든 상태에 대해 상태 인터페이스에서 파생된 상태 클래스를 만든다. </p>
<pre><code class="language-csharp"> public class CoinState : IState
 {
     public void insertCoin(TicketMachine ticketMachine)
     {
         Console.WriteLine(&quot;이미 동전이 들어있습니다.&quot;);

     }

     public void printTicket(TicketMachine ticketMachine)
     {
         Console.WriteLine(&quot;티켓을 뽑았습니다.&quot;);
         ticketMachine.SetState(new NoCoinState());
     }
 }

 public class NoCoinState : IState
 {
     public void insertCoin(TicketMachine ticketMachine)
     {
         Console.WriteLine(&quot;동전을 넣었습니다.&quot;);
         ticketMachine.SetState(new CoinState());
     }

     public void printTicket(TicketMachine ticketMachine)
     {
         Console.WriteLine(&quot;동전이 없습니다. 동전을 넣어주세요.&quot;);
     }
 }</code></pre>
</li>
<li><p>컨텍스트 클래스에서 상태 인터페이스 유형의 참조 필드와 필드 값을 오버라이드할 수 있는 공개된 세터(setter)를 추가한다.</p>
<pre><code class="language-csharp">public class TicketMachine
{
   private IState currentState = new NoCoinState();

     public void SetState(IState state)
     {
         currentState = state;
     }
      ...</code></pre>
</li>
<li><p>컨텍스트의 메소드를 보고 조건문을 상태 객체의 메소드 호출로 대체한다.</p>
<pre><code class="language-csharp">public class TicketMachine
{
   private IState currentState;

   public void SetState(IState state)
   {
       currentState = state;
   }

   private void insertCoin()
   {
       currentState.insertCoin(this);
   }

   private void printTicket()
   {
       currentState.printTicket(this);
   }

   public void input()
   {
       if (Console.ReadLine() == &quot;coin&quot;)
           insertCoin();
       else
           printTicket();
   }
}</code></pre>
</li>
<li><p>상태 클래스의 인스턴스를 컨텍스트에 전달하는 것으로 컨텍스트의 상태를 전환할 수 있다.</p>
<ul>
<li><p>결과</p>
<pre><code>coin
동전을 넣었습니다.
coin
이미 동전이 들어있습니다.
ticket
티켓을 뽑았습니다.
ticket
동전이 없습니다. 동전을 넣어주세요.
</code></pre><ul>
<li><p>전체 코드</p>
<pre><code class="language-csharp">class main
{
 static void Main(string[] args)
 {
     TicketMachine ticketMachine = new TicketMachine();

     while(true)
         ticketMachine.input();
 }
}
</code></pre>
</li>
</ul>
<p>public interface IState
{
   void insertCoin(TicketMachine ticketMachine);
   void printTicket(TicketMachine ticketMachine);
}</p>
<p>public class CoinState : IState
{
   public void insertCoin(TicketMachine ticketMachine)
   {</p>
<pre><code>   Console.WriteLine(&quot;이미 동전이 들어있습니다.&quot;);</code></pre><p>   }</p>
<p>   public void printTicket(TicketMachine ticketMachine)
   {</p>
<pre><code>   Console.WriteLine(&quot;티켓을 뽑았습니다.&quot;);
   ticketMachine.SetState(new NoCoinState());</code></pre><p>   }
}</p>
<p>public class NoCoinState : IState
{
   public void insertCoin(TicketMachine ticketMachine)
   {</p>
<pre><code>   Console.WriteLine(&quot;동전을 넣었습니다.&quot;);
   ticketMachine.SetState(new CoinState());</code></pre><p>   }</p>
<p>   public void printTicket(TicketMachine ticketMachine)
   {</p>
<pre><code>   Console.WriteLine(&quot;동전이 없습니다. 동전을 넣어주세요.&quot;);</code></pre><p>   }
}</p>
<p>public class TicketMachine
{
   private IState currentState = new NoCoinState();</p>
<p>   public void SetState(IState state)
   {</p>
<pre><code>   currentState = state;</code></pre><p>   }</p>
<p>   private void insertCoin()
   {</p>
<pre><code>   currentState.insertCoin(this);</code></pre><p>   }</p>
<p>   private void printTicket()
   {</p>
<pre><code>   currentState.printTicket(this);</code></pre><p>   }</p>
<p>   public void input()
   {</p>
<pre><code>   if (Console.ReadLine() == &quot;coin&quot;)
       insertCoin();
   else
       printTicket();</code></pre><p>   }
}</p>
<pre><code></code></pre></li>
</ul>
</li>
</ol>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://ko.wikipedia.org/wiki/%EC%83%81%ED%83%9C_%ED%8C%A8%ED%84%B4">상태 패턴 | Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/State_pattern">State pattern | Wikipedia</a></li>
<li><a href="https://velog.io/@jinmin2216/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%EC%8A%A4%ED%85%8C%EC%9D%B4%ED%8A%B8%EC%83%81%ED%83%9C-%ED%8C%A8%ED%84%B4-State-Pattern">[디자인 패턴] 스테이트(상태) 패턴 (State Pattern)</a></li>
<li><a href="https://refactoring.guru/ko/design-patterns/state">상태 패턴 | refactoring.guru</a></li>
<li><a href="https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%83%81%ED%83%9CState-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90#%ED%81%B4%EB%9E%98%EC%8A%A4_%EA%B5%AC%EC%84%B1">상태(State) 패턴 - 완벽 마스터하기</a></li>
<li><a href="https://yummy0102.tistory.com/482">[디자인 패턴] 상태 패턴 (State Pattern)</a></li>
<li><a href="https://johngrib.github.io/wiki/pattern/state/">스테이트 패턴 (State Pattern)</a></li>
<li><a href="https://www.yes24.com/Product/Goods/17525598">GoF의 디자인 패턴(개정판) / 에릭 감마, 리처드 헬름, 랄프 존슨, 존 블라시디스 공저</a></li>
<li><a href="https://namu.wiki/w/%EB%94%94%EC%9E%90%EC%9D%B8%20%ED%8C%A8%ED%84%B4#s-5.8">디자인 패턴 | 나무위키</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[BSP(Binary Space Partitioning)]]></title>
            <link>https://velog.io/@strange_tiger/BSPBinary-Space-Partitioning</link>
            <guid>https://velog.io/@strange_tiger/BSPBinary-Space-Partitioning</guid>
            <pubDate>Fri, 14 Feb 2025 02:01:27 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li><p><strong>BSP</strong>(Binary Space Partitioning, 이진 공간 분할법)이란 <strong>재귀적</strong>으로 유클리드 공간을 초평면 상의 볼록 <strong>집합으로 분할</strong>하는 기법이다. 분할 과정으로 BSP 트리라 불리는 트리 구조가 만들어진다.</p>
</li>
<li><p>3차원(또는 2차원) 공간을 효율적으로 다루기 위해, <strong>하나의 공간을 재귀적으로 둘씩 나누어가는</strong> 기법이라는 뜻이다.</p>
</li>
<li><p>3D 게임 엔진에서 맵을 렌더링할 때, 충돌 감지를 수행할 때, 레이트레이싱을 할 때 등 다양한 분야에서 <strong>연산을 최적화</strong>하려고 할 때 자주 사용된다.</p>
</li>
<li><p>이번에는 로그라이크류 게임에서 맵을 랜덤으로 나눌 때 어떻게 사용할 수 있는지 정리해보고자 한다.</p>
</li>
</ul>
<h1 id="알고리즘의-흐름">알고리즘의 흐름</h1>
<ol>
<li><p><strong>분할할 공간</strong>(씬, 맵, 월드 등)을 하나 정한다.</p>
</li>
<li><p><strong>하나의 분할 평면(또는 분할선)</strong>을 선택하여 현재 공간을 <strong>두 개의 하위 공간</strong>으로 나눈다.</p>
</li>
<li><p>각 하위 공간에 대해 같은 과정을 <strong>재귀적으로 반복</strong>한다.</p>
</li>
<li><p><strong>원하는 기준(최종 목적)</strong>에 도달할 때까지 이 과정을 수행한다.</p>
</li>
</ol>
<ul>
<li><p>이 과정을 거쳐 만들어진 트리를 <strong>BSP 트리</strong>라고 부른다. </p>
</li>
<li><p>트리의 각 노드는 <strong>분할을 위한 평면(또는 선)</strong>에 대한 정보를, 리프(leaf) 노드는 최종적으로 분할이 완료된 <strong>공간 정보</strong>를 담게 된다.</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/78b39805-c412-4998-a495-0bd56642525c/image.png" alt=""></p>
<h2 id="bsp-트리-만들기">BSP 트리 만들기</h2>
<ul>
<li>BSP 트리를 사용하여 <a href="https://ko.wikipedia.org/wiki/%ED%99%94%EA%B0%80_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">페인터 알고리즘</a>을 사용해 다각형을 렌더링할 수 있다. 다각형 목록에서 BSP 트리를 구성하는 재귀 알고리즘은 다음과 같다.</li>
</ul>
<ol>
<li><p>목록에서 다각형(Polygon) <code>P</code>를 하나 선택한다.</p>
</li>
<li><p>BSP 트리에 노드 <code>N</code>을 만들고, <code>P</code>를 해당 노드의 다각형 목록에 추가한다.</p>
</li>
<li><p>목록에 있는 다른 모든 다각형에 대해 다음을 수행한다.</p>
<ol>
<li><p>해당 다각형이 <code>P</code>를 포함하는 평면의 &#39;앞(front)&#39;에 완전히 놓여 있다면, 그 다각형을 <code>P</code> 앞쪽 노드의 다각형 목록으로 옮긴다.</p>
</li>
<li><p>해당 다각형이 <code>P</code>를 포함하는 평면의 &#39;뒤(behind)&#39;에 완전히 놓여 있다면, 그 다각형을 <code>P</code> 뒤쪽 노드의 다각형 목록으로 옮긴다.</p>
</li>
<li><p>해당 다각형이 <code>P</code>를 포함하는 평면과 교차한다면, 두 개의 다각형으로 나누어 앞·뒤 노드 각각에 옮긴다.</p>
</li>
<li><p>해당 다각형이 <code>P</code>를 포함하는 평면 위에 놓여 있다면, 그 다각형을 노드 <code>N</code>의 다각형 목록에 추가한다.</p>
</li>
</ol>
</li>
<li><p><code>P</code> 앞쪽에 있는 다각형 목록에 대해 위 알고리즘을 재귀적으로 적용한다.</p>
</li>
<li><p><code>P</code> 뒤쪽에 있는 다각형 목록에 대해서도 같은 알고리즘을 재귀적으로 적용한다.</p>
</li>
</ol>
<h3 id="예시">예시</h3>
<ul>
<li><p>아래 다이어그램은 위 알고리즘을 사용하여 선분(또는 다각형) 목록을 BSP 트리로 변환하는 과정을 예시로 보여준다. 8단계 각각에서 위 알고리즘이 선분 목록에 적용되어 트리에 새 노드가 추가된다.</p>
</li>
<li><p>먼저 장면(Scene)을 구성하는 선분(2D) 혹은 다각형(3D) 목록을 가지고 시작한다. 트리 도식에서, ‘목록(list)’은 둥근 사각형으로, BSP 트리의 노드는 원으로 표시한다. 공간 상에서 선분의 ‘앞(front)’ 방향은 화살표로 표시되어 있다.    </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/cd11f2c4-bbd8-49b7-bf5d-b999f1123ce9/image.png" alt=""></p>
<ol>
<li><p>위 알고리즘의 단계를 따르면,</p>
<ol>
<li><p>목록에서 선분 <code>A</code>를 하나 선택하고,</p>
</li>
<li><p>이를 노드에 추가한다.</p>
</li>
<li><p>목록에 남아 있는 다른 선분들을 <code>A</code>가 놓인 평면(여기서는 2D이므로 직선)의 앞쪽에 있는 것(<code>B2</code>, <code>C2</code>, <code>D2</code>)과 뒤쪽에 있는 것(<code>B1</code>, <code>C1</code>, <code>D1</code>)으로 분할한다.</p>
</li>
<li><p>먼저 <code>A</code> 앞쪽에 있는 선분들(<code>B2</code>, <code>C2</code>, <code>D2</code>)을 처리한다(단계 2 ~ 5).</p>
</li>
<li><p>그리고 나서 <code>A</code> 뒤쪽에 있는 선분들(<code>B1</code>, <code>C1</code>, <code>D1</code>)을 처리한다(단계 6 ~ 8).</p>
</li>
</ol>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/ecafc0e9-3e9e-449e-b45e-3c5ca8634839/image.png" alt=""></p>
<ol start="2">
<li>이번에는 <code>A</code> 앞쪽에 있는 선분 목록(<code>B2</code>, <code>C2</code>, <code>D2</code>)에 알고리즘을 적용한다. 선분 <code>B2</code>를 선택하여 노드에 추가하고, 남은 선분들을 <code>B2</code>의 앞쪽에 있는 선분(<code>D2</code>)과 뒤쪽에 있는 선분(<code>C2</code>, <code>D3</code>)으로 분할한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/343bb377-8bdc-47ab-a199-3da41629dad7/image.png" alt=""></p>
<ol start="3">
<li><code>B2</code> 및 <code>A</code>의 앞쪽 목록에 있는 선분들 중 <code>D2</code>를 선택한다. 이 목록에는 오직 <code>D2</code>만 존재하므로, 노드에 <code>D2</code>를 추가한 뒤에는 추가로 할 작업이 없다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/975fd28d-d25d-4d5c-9281-8de86059c812/image.png" alt=""></p>
<ol start="4">
<li>이제 <code>B2</code> 앞쪽의 선분 처리를 마쳤으므로, B2 뒤쪽에 있는 선분(<code>C2</code>, <code>D3</code>)을 살펴본다. 이 중에서 <code>C2</code>를 하나 선택하여 노드에 추가하고, 남은 선분(<code>D3</code>)을 <code>C2</code>의 앞쪽 목록에 추가한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/c177cde5-54fa-4dc3-a63a-a05463f684a3/image.png" alt=""></p>
<ol start="5">
<li><code>C2</code> 앞쪽 목록에 있는 선분을 확인한다. 유일한 선분은 <code>D3</code>이므로 이를 노드에 추가하고 계속 진행한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/db38daa1-3fb5-4469-afd4-8e49536d4a20/image.png" alt=""></p>
<ol start="6">
<li>이제 <code>A</code> 앞쪽에 있었던 모든 선분을 BSP 트리에 추가했으므로, 이번에는 <code>A</code> 뒤쪽에 있는 선분 목록을 처리한다. 해당 목록에서 하나의 선분(<code>B1</code>)을 고르고 노드에 추가한 후, 나머지 선분들을 <code>B1</code>의 앞쪽(즉, <code>D1</code>)과 뒤쪽(즉, <code>C1</code>)으로 분할한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/a8d32fc2-69d3-4c90-9b97-8a5b741fa464/image.png" alt=""></p>
<ol start="7">
<li>먼저 <code>B1</code> 앞쪽에 있는 선분 목록을 처리한다. 여기에는 <code>D1</code>만 있으므로, 이를 노드에 추가하고 계속 진행한다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/bf0461d4-b5b6-443e-bedb-6d397de2a08c/image.png" alt=""></p>
<ol start="8">
<li>다음으로 <code>B1</code> 뒤쪽에 있는 선분 목록에는 <code>C1</code>만 있으므로, 이를 노드에 추가하고 BSP 트리 구성을 마친다.</li>
</ol>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/688e6b92-89a9-4888-bda2-5f38c9899793/image.png" alt=""></p>
<ul>
<li>BSP 트리에 포함되는 다각형(혹은 선분)의 최종 개수는 원래 목록보다 많아질 때가 많다. 이는 분할 평면을 가로지르는 다각형(혹은 선분)이 두 개로 나누어지기 때문이다. 이 증가를 최소화하는 것이 바람직하지만, 최종 트리가 적절한 균형을 유지하도록 하는 것도 중요하다. 따라서 효율적인 BSP 트리를 생성하려면 분할 평면(알고리즘의 1단계)으로 사용되는 다각형(혹은 선분)을 선택하는 것이 중요하다.</li>
</ul>
<h1 id="최종-목적">최종 목적</h1>
<ul>
<li><p>BSP 알고리즘에서 최종 목적이란 <strong>언제까지 분할을 계속할 것인가?</strong>이다.</p>
</li>
<li><p>이 최종 목적은 렌더링 최적화, 충돌 검출, 레이 트레이싱, 조명(라이팅) 계산, 렌더링 파이프라인의 클리핑, 그리고 레벨 디자인(게임 맵 생성) 등이 될 수 있다.</p>
</li>
</ul>
<ol>
<li>렌더링 최적화(Visibility Determination)</li>
</ol>
<ul>
<li>예) 3D 게임 엔진에서 시야에 보이는 부분만 효율적으로 그리기 위해 공간을 분할하고, 가시성을 판단하는 용도.</li>
<li>최종 목적: 각 리프 노드가 너무 작게 잘게 분할되기 전에(적정 크기 이하로) 멈추거나, 혹은 조망(시야) 계산이 더 이상 필요 없는 수준으로 쪼개졌을 때 멈춤.</li>
</ul>
<ol start="2">
<li>충돌 검출(Collision Detection)</li>
</ol>
<ul>
<li>예) 물리 엔진에서 객체 충돌을 빠르게 판단하기 위해 공간을 분할.</li>
<li>최종 목적: 분할된 리프 공간 내부에 포함된 오브젝트 수가 일정 기준 이하가 되면 더 이상 분할하지 않음. (예: 리프 노드에 객체가 1~2개만 남거나, 리프 공간의 크기가 임계점보다 작아졌을 때)</li>
</ul>
<ol start="3">
<li>레이 트레이싱(Ray Tracing)</li>
</ol>
<ul>
<li>광선을 쏠 때 충돌할 가능성이 있는 폴리곤/오브젝트만 빠르게 찾기 위해 BSP 트리를 사용할 수 있음.</li>
<li>최종 목적: 광선 검출에 필요한 정확도를 만족할 만큼의 분할(즉, 충분히 세밀하게 오브젝트가 구분된 상태)이 이루어지면 더 이상 분할하지 않음.</li>
</ul>
<ol start="4">
<li>레벨 디자인(게임 맵 생성)</li>
</ol>
<ul>
<li>예) 2D/3D 게임에서 랜덤 던전, 방, 복도 구조 등을 만들기 위해 공간을 재귀적으로 분할.</li>
<li>최종 목적: “방(룸)” 크기나 개수, 또는 연결 경로(복도)의 수가 원하는 수준에 도달할 때까지 분할. 이후 각 리프 노드를 방이나 복도로 활용.</li>
</ul>
<ol start="5">
<li>조명(라이팅) 계산</li>
</ol>
<ul>
<li>예) 라이트맵(Lightmap)을 생성하기 위해서, 또는 GI(Global Illumination)을 계산하기 위해 공간을 분할.</li>
<li>최종 목적: 광원과 표면 간의 상호작용을 계산하기에 충분히 세밀한 분할 상태에 도달하면 더 이상 쪼개지 않음.</li>
</ul>
<ol start="6">
<li>클리핑(Clipping)이나 페인터 알고리즘에 활용</li>
</ol>
<ul>
<li>예) 고전 렌더링 파이프라인에서 배경(뒤에 있는 오브젝트)를 먼저 그리거나, 앞의 물체에 가려지는 뒷부분은 그리지 않도록 공간 분할.</li>
<li>최종 목적: 오브젝트 간 가려짐(Visibility)을 판별하기에 충분할 정도로 나누면 멈춤.</li>
</ul>
<h2 id="분할-종료-조건">분할 종료 조건</h2>
<ul>
<li>최종 목적에 따라 분할 종료 조건은 아래와 같이 정할 수 있다.</li>
</ul>
<ol>
<li><p>오브젝트 개수 기준 : 분할된 공간(리프 노드) 안에 들어 있는 오브젝트(폴리곤 등) 수가 특정 개수 이하가 되면 더 이상 분할하지 않는다.</p>
</li>
<li><p>공간 크기(깊이) 기준 : 분할된 공간의 부피(또는 면적)가 일정 이하로 작아지면 중단한다.</p>
</li>
<li><p>계산 비용과 효율의 기준 : 더 이상 분할을 해봤자 얻을 수 있는 최적화 이점이 작거나, 오히려 분할 관리 비용이 커지면 중단한다.</p>
</li>
<li><p>사용자가 설정한 임의의 기준: 예를 들어 게임 레벨 디자인에서 “방은 최소 3m x 3m 크기, 최대 10개” 같은 조건을 미리 설정해두고, 그 조건을 만족하면 중단한다.</p>
</li>
</ol>
<ul>
<li>이처럼 BSP 알고리즘의 최종 목적에 따라 맞는 분할 종료 조건을 미리 정해 둔 후, 그 조건에 도달할 때까지 공간을 재귀적으로 이등분하는 과정이다.</li>
</ul>
<h1 id="구현">구현</h1>
<h2 id="레벨-디자인---텍스트-게임">레벨 디자인 - 텍스트 게임</h2>
<ul>
<li>로그라이크 게임의 경우, 매번 새로운 맵을 생성하기도 한다. 그 때 BSP 알고리즘을 응용할 수 있다. 이번에는 cpp로 텍스트로 이루어진 맵을 만들 것이다.<ul>
<li>분할 정복을 하고 방을 이어붙이는 BSP 알고리즘을 간단히 만든 예시이다. 엄밀하게는 조금 다르지만, 이진 방식으로 공간을 나눈다는 점에서 비슷하다고 볼 수 있다. 흔히 <strong>BSP 던전 생성 알고리즘</strong>이라고 하는 기법이다.</li>
</ul>
</li>
</ul>
<ol>
<li><p>공간을 둘로 분할한다. 분할 종료 조건을 정하고 실행한다.</p>
<ul>
<li>이번에는 랜덤으로 가로로 할지 세로로 할지, 분할 비율을 어디에 더 크게 할지를 정한다. 너무 한쪽으로만 분할되지 않도록 조건을 추가하였다.<pre><code class="language-cpp">if (rLen / cLen &gt; 1 || (cLen / rLen &lt;= 1 &amp;&amp; rand() % 2))
{
    int divideNum = (r2 - r1) * (rand() % 3 + 4) / 10; // 랜덤비율설정
  ...
}</code></pre>
</li>
</ul>
</li>
<li><p>분할이 끝나면 방을 만든다.</p>
<ul>
<li>분할된 공간은 1을 넣어 방으로 표시한다. 후에 길을 추가하기 위해 전체 직사각형 각 변에서 2씩 빼어 공간을 나눈다.<pre><code class="language-cpp">if (depth == 0 || (r2 - r1 &lt;= 10 || c2 - c1 &lt;= 10))
{
  for (int i = r1 + 2; i &lt; r2 - 2; ++i)
  {
      for (int j = c1 + 2; j &lt; c2 - 2; ++j)
      {
          Dungeon[i][j] = 1;
      }
  }
  return {r1 + 2, c1 + 2, r2 - 3, c2 - 3, r1 + 2, c1 + 2, r2 - 3, c2 - 3 };
}</code></pre>
</li>
</ul>
</li>
<li><p>분할된 방 사이에 길을 만든다.</p>
<ul>
<li>재귀를 통해 방을 연결한다.<pre><code class="language-cpp">...
Dungeon[temp1.r4 + 1][(temp1.c3 + temp1.c4) / 2] = 4;
Dungeon[temp1.r4 + 2][(temp1.c3 + temp1.c4) / 2] = 4;
Dungeon[temp2.r1 - 1][(temp2.c1 + temp2.c2) / 2] = 4;
Dungeon[temp2.r1 - 2][(temp2.c1 + temp2.c2) / 2] = 4;
int rmin = min((temp1.c3 + temp1.c4) / 2,(temp2.c1 + temp2.c2) / 2);
int rmax = max((temp1.c3 + temp1.c4) / 2, (temp2.c1 + temp2.c2) / 2);
for (int i = rmin; i &lt;= rmax; ++i)
{
   Dungeon[temp2.r1 - 2][i] = 4;
}
   ...</code></pre>
</li>
</ul>
</li>
<li><p>전체 코드와 결과</p>
<pre><code class="language-cpp">#include&lt;stdio.h&gt;
#include&lt;iostream&gt;
#include&lt;algorithm&gt;
#include&lt;string&gt;
#include&lt;string.h&gt;
#include&lt;math.h&gt;
using namespace std;

#define DungeonSize 60
int dungeon[DungeonSize][DungeonSize];

//1.분할한다.
//2. 분할이 끝나면 방을 만든다.
//3. 방을 연결한다.
typedef struct DungeonLocation
{
 int r1, c1, r2, c2;
 int r3, c3, r4, c4;
};

DungeonLocation DivideDungeon(int depth, int r1, int c1, int r2, int c2)
{
 DungeonLocation location;
 //2. 방을 만든다. 
 if (depth ==0 || (r2 - r1 &lt;= 10 || c2 - c1 &lt;= 10)) 
 {
     for (int i = r1 + 2; i &lt; r2 - 2; ++i) 
     {
         for (int j = c1 + 2; j &lt; c2 - 2; ++j) 
         {
             dungeon[i][j] = 1;
         }
     }

     return { r1 + 2, c1 + 2, r2 - 3, c2 - 3, r1 + 2, c1 + 2, r2 - 3, c2 - 3 };
 }

 //1. 분할한다
 //3. 방을 합친다.
 int rLen = r2 - r1;
 int cLen = c2 - c1;
 DungeonLocation temp1, temp2;
 if (rLen/cLen &gt; 1 ||(cLen/rLen &lt;= 1 &amp;&amp; rand() % 2))
 { // 세로분할
     int divideNum = (r2 - r1) * (rand() % 3 + 4) / 10;
     //방 분할
     temp1 = DivideDungeon(depth - 1, r1, c1, r1 + divideNum, c2);
     temp2 = DivideDungeon(depth - 1, r1 + divideNum, c1, r2, c2);

     //방합치기.
     dungeon[temp1.r4 + 1][(temp1.c3 + temp1.c4) / 2] = 4;
     dungeon[temp1.r4 + 2][(temp1.c3 + temp1.c4) / 2] = 4;
     dungeon[temp2.r1 - 1][(temp2.c1 + temp2.c2) / 2] = 4;
     dungeon[temp2.r1 - 2][(temp2.c1 + temp2.c2) / 2] = 4;
     int rmin = min((temp1.c3 + temp1.c4) / 2,(temp2.c1 + temp2.c2) / 2);
     int rmax = max((temp1.c3 + temp1.c4) / 2, (temp2.c1 + temp2.c2) / 2);
     for (int i = rmin; i &lt;= rmax; ++i) 
     {
         dungeon[temp2.r1 - 2][i] = 4;
     }
 }
 else 
 {// 가로분할
     int divideNum = (c2 - c1) * (rand() % 3 + 4) / 10;
     //방분할
     temp1 = DivideDungeon(depth - 1, r1, c1, r2, c1 + divideNum);
     temp2 = DivideDungeon(depth - 1, r1, c1 + divideNum, r2, c2);

     //방합치기
     dungeon[(temp1.r3 + temp1.r4) / 2][temp1.c4 + 1] = 3;
     dungeon[(temp1.r3 + temp1.r4) / 2][temp1.c4 + 2] = 3;
     dungeon[(temp2.r1 + temp2.r2) / 2][temp2.c1 - 1] = 3;
     dungeon[(temp2.r1 + temp2.r2) / 2][temp2.c1 - 2] = 3;

     int rmin = min((temp1.r3 + temp1.r4) / 2, (temp2.r1 + temp2.r2) / 2);
     int rmax = max((temp1.r3 + temp1.r4) / 2, (temp2.r1 + temp2.r2) / 2);
     for (int i = rmin; i &lt;= rmax; i++) {
         dungeon[i][temp2.c1-2] = 3;
     }
 }
 location.r1 = temp1.r1;
 location.r2 = temp1.r2;
 location.r3 = temp2.r3;
 location.r4 = temp2.r4;
 location.c1 = temp1.c1;
 location.c2 = temp1.c2;
 location.c3 = temp2.c3;
 location.c4 = temp2.c4;

 return location;
}

void CreateDungeon() 
{
  memset(dungeon, 0, sizeof(dungeon));
  DivideDungeon(5, 0, 0, DungeonSize, DungeonSize);
}

void PrintDungeon() 
{
 for (int i = 0; i &lt; DungeonSize; ++i) 
 {
     for (int j = 0; j &lt; DungeonSize; ++j) 
     {
         printf(&quot;%d&quot;, dungeon[i][j]);
     }
     printf(&quot;\n&quot;);
 }
}

int main(void) 
{
  CreateDungeon();
  PrintDungeon();
}
[출처] BSP(Binary Space Partitioning)알고리즘을 응용해 로그라이크류 게임 방만들기|작성자 jh20s</code></pre>
<p><img src="https://velog.velcdn.com/images/strange_tiger/post/570fc0ce-4c31-44b2-9bc8-7d216d1eec50/image.png" alt=""></p>
</li>
</ol>
<ul>
<li>C#과 유니티로도 이런 식으로 맵을 만들 수 있다. 이또한 BSP 알고리즘을 적용한 예시이다. <a href="https://sharp2studio.tistory.com/44">이 페이지 참고</a> / <a href="https://velog.io/@1217pgy/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%A0%88%EC%B0%A8%EC%A0%81-%EC%83%9D%EC%84%B1%EC%9D%84-%EC%9C%84%ED%95%9C-%EB%8D%98%EC%A0%84-%EC%83%9D%EC%84%B1-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">이 페이지도 참고</a></li>
</ul>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://blog.naver.com/jh20s/222343029141">BSP(Binary Space Partitioning)알고리즘을 응용해 로그라이크류 게임 방만들기</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EC%9D%B4%EC%A7%84_%EA%B3%B5%EA%B0%84_%EB%B6%84%ED%95%A0%EB%B2%95">이진 공간 분할법 | Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Binary_space_partitioning">Binary space partitioning | Wikipedia</a></li>
<li><a href="https://coding-shop.tistory.com/114">Binary Space Partitioning (BSP)란?</a></li>
<li><a href="https://sharp2studio.tistory.com/44">[유니티] BSP 알고리즘을 이용해서 랜덤한 게임 맵 생성하기 [이론]</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%ED%99%94%EA%B0%80_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">화가 알고리즘 | Wikipedia</a></li>
<li><a href="https://velog.io/@1217pgy/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%A0%88%EC%B0%A8%EC%A0%81-%EC%83%9D%EC%84%B1%EC%9D%84-%EC%9C%84%ED%95%9C-%EB%8D%98%EC%A0%84-%EC%83%9D%EC%84%B1-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">[유니티] 절차적 생성을 위한 던전 생성 알고리즘</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Introsort]]></title>
            <link>https://velog.io/@strange_tiger/IntroSort</link>
            <guid>https://velog.io/@strange_tiger/IntroSort</guid>
            <pubDate>Thu, 13 Feb 2025 07:46:45 GMT</pubDate>
            <description><![CDATA[<h1 id="introsort">Introsort</h1>
<h2 id="설명">설명</h2>
<ul>
<li><p><strong>인트로 정렬(introsort)</strong>은 평균적으로 빠른 성능을 내면서 최악의 조건에서도 점진적으로 최적화된 성능을 제공하는 <strong>하이브리드 정렬 알고리즘</strong>이다.</p>
</li>
<li><p>C++ STL에서 기본적으로 제공되는 정렬 함수이다.</p>
</li>
<li><p><strong>퀵 정렬, 힙 정렬, 삽입 정렬</strong>을 합하여 정렬하는 알고리즘이다.</p>
<ul>
<li>3가지 알고리즘의 장점을 합쳐 각 정렬의 단점을 보완하고 최악의 경우를 대비한다.</li>
</ul>
</li>
<li><p>인트로 정렬이 사용하는 이 3가지 알고리즘들은 비교 정렬이기 때문에 인트로 정렬 또한 비교 정렬이다.</p>
<ul>
<li>비교 정렬 : 정렬 알고리즘의 일종으로 두 값을 비교하는 것에 기반한다. </li>
</ul>
</li>
<li><p>최악 시간 복잡도는 <strong>O(n log n)</strong>, 평균 시간 복잡도 또한 O(n log n)이다.</p>
<ul>
<li>최선의 경우에는 퀵 정렬의 최선의 시간 복잡도와 같고, 최악의 경우에는 힙 정렬의 최악의 시간 복잡도와 같다. 즉, 항상 O(n log⁡ n)이다.</li>
</ul>
</li>
</ul>
<h2 id="원리">원리</h2>
<ul>
<li><p>퀵 정렬로 시작한 다음 <strong>재귀 깊이</strong>가 정렬 대상 요소의 수의 레벨(로그)을 초과할 때 힙 정렬로 전환하며 <strong>요소들의 수</strong>가 특정 임계치 미만일 때 삽입 정렬로 전환한다. </p>
</li>
<li><p>의사 코드는 아래와 같다.</p>
<pre><code>procedure sort(A : array):
    maxdepth ← ⌊log2(length(A))⌋ × 2
    introsort(A, maxdepth)

procedure introsort(A, maxdepth):
    n ← length(A)
    if n &lt; 16:
        insertionsort(A)
    else if maxdepth = 0:
        heapsort(A)
    else:
        p ← partition(A)  // assume this function does pivot selection, p is the final position of the pivot
        introsort(A[1:p-1], maxdepth - 1)
        introsort(A[p+1:n], maxdepth - 1)</code></pre></li>
</ul>
<h2 id="구현">구현</h2>
<ul>
<li><p>설명</p>
<ol>
<li>리스트의 크기가 16 이하라면 삽입 정렬을 한다.</li>
<li>전체 리스트에 대해 퀵 정렬을 수행한다.</li>
<li>수행 도중 재귀 호출의 깊이가 <code>2log⁡n</code>을 넘어가게 되면 4번 항목으로 넘어간다.</li>
<li>쪼개진 부분 리스트의 크기가 16 이하라면 그대로 놔둔다.
16보다 크다면 해당 부분 리스트에 대해 힙 정렬을 수행한다.</li>
<li>3, 4번 항목이 모두 완료된 후, 대부분 정렬이 된 전체 리스트에 대해 삽입 정렬을 수행한다.</li>
</ol>
</li>
<li><p>코드 : C++ 기반으로 구현한 코드</p>
<pre><code class="language-cpp">void __swap(int * a, int * b) 
{
    int tmp = * a;
    * a = * b;
    * b = tmp;
}

void __makeHeap(int *arr, int left, int right) 
{
    for(int i=left; i&lt;=right; i++) 
    {
        int now = i;
        while(now &gt; 0) 
        {
            int par = now-1&gt;&gt;1;
            if(arr[par] &lt; arr[now]) __swap(arr+par, arr+now);
            now = par;
        }
    }
}

void __heapSort(int *arr, int left, int right) 
{
    __makeHeap(arr, left, right);
    for(int i=right; i&gt;left; i--) 
    {
        __swap(arr, arr+i);
        int left = 1, right = 2;
        int sel = 0, par = 0;
        while(1) 
        {
            if(left &gt;= i) break;
            if(right &gt;= i) sel = left;
            else 
            {
                if(arr[left] &lt; arr[right]) sel = right;
                else sel = left;
            }
            if(arr[sel] &gt; arr[par]) 
            {
                __swap(arr+sel, arr+par);
                par = sel;
            } 
            else break;

            left = (par&lt;&lt;1) + 1;
            right = left+1;
        }
    }
}

void __insertionSort(int arr[], int left, int right) 
{
    for(int i=left; i&lt;right; i++) 
    {
        int key = arr[i+1];
        int j;
        for(j=i; j&gt;=left; j--) 
        {
            if(arr[j] &gt; key) arr[j+1] = arr[j];
            else break;
        }
        arr[j+1] = key;
    }
}

void __quickSort(int arr[], int left, int right, int depth) 
{
    if(depth == 0) 
    {
        int size = right-left+1;
        if(size &gt; 16) 
        {
            __heapSort(arr, left, right);
        }
        return;
    }

    int i = left, j = right;
    int pivot = arr[(left + right) / 2];
    int temp;
    do 
    {
        while (arr[i] &lt; pivot)
            i++;
        while (arr[j] &gt; pivot)
            j--;
        if (i&lt;= j) 
        {
            __swap(arr+i, arr+j);
            i++;
            j--;
        }
    } 
    while (i&lt;= j);

    if (left &lt; j)
        __quickSort(arr, left, j, depth-1);

    if (i &lt; right)
        __quickSort(arr, i, right, depth-1);

}

void introSort(int arr[], int n) 
{
    int limit = 2*ceil(log2(n));
    if(n &lt;= 16)
    {
        __insertionSort(arr, 0, n-1);
        return;
    }
    __quickSort(arr, 0, n-1, limit);
    __insertionSort(arr, 0, n-1);
}</code></pre>
</li>
<li><p>코드 : C# 기반으로 구현한 코드</p>
<pre><code class="language-csharp">class main
  {
      static void Main(string[] args)
      {
          TestIntroSort sort = new TestIntroSort();
          int[] arr = new int[6] { 3, 4, 5, 2, 1, 10 };

          sort.IntroSort(arr, 6);

          for (int i = 0; i &lt; arr.Length; ++i)
          {
              Console.WriteLine(arr[i]);
          }
          Console.ReadLine();

          /*
          1
          2
          3
          4    
          5
          10
          */
      }
  }

  class TestIntroSort
  {
      void Swap(ref int a, ref int b)
      {
          int tmp = a;
          a = b;
          b = tmp;
      }

      void MakeHeap(int[] arr, int left, int right)
      {
          for (int i = left; i &lt;= right; ++i)
          {
              int now = i;
              while (now &gt; 0)
              {
                  int par = (now - 1) / 2;
                  if (arr[par] &lt; arr[now])
                      Swap(ref arr[par], ref arr[now]);
                  now = par;
              }
          }
      }

      void HeapSort(int[] arr, int left, int right)
      {
          MakeHeap(arr, left, right);

          for (int i = right; i &gt; left; --i)
          {
              Swap(ref arr[0], ref arr[i]);

              int sel = 0, par = 0;

              while (true)
              {
                  if (left &gt;= i)
                      break;
                  if (right &gt;= i)
                      sel = left;
                  else
                  {
                      if (arr[left] &lt; arr[right])
                          sel = right;
                      else
                          sel = left;
                  }
                  if (arr[sel] &gt; arr[par])
                  {
                      Swap(ref arr[sel], ref arr[par]);
                      par = sel;
                  }
                  else break;

                  left = par * 2 + 1;
                  right = left + 1;
              }
          }
      }

      void InsertionSort(int[] arr, int left, int right)
      {
          for (int i = left; i &lt; right; ++i)
          {
              int key = arr[i + 1];
              int j;
              for (j = i; j &gt;= left; --j)
              {
                  if (arr[j] &gt; key)
                      arr[j + 1] = arr[j];
                  else break;
              }
              arr[j + 1] = key;
          }
      }

      void QuickSort(int[] arr, int left, int right, int depth)
      {
          if (depth == 0)
          {
              int size = right - left + 1;
              if (size &gt; 16)
              {
                  HeapSort(arr, left, right);
              }
              return;
          }

          int i = left, j = right;
          int pivot = arr[(left + right) / 2];

          do
          {
              while (arr[i] &lt; pivot)
                  ++i;
              while (arr[j] &gt; pivot)
                  --j;
              if (i &lt;= j)
              {
                  Swap(ref arr[i], ref arr[j]);
                  ++i;
                  --j;
              }
          }
          while (i &lt;= j);

          if (left &lt; j)
              QuickSort(arr, left, j, depth - 1);

          if (i &lt; right)
              QuickSort(arr, i, right, depth - 1);

      }

      public void IntroSort(int[] arr, int n)
      {
          int limit = Convert.ToInt32(2 * Math.Ceiling(Math.Log(n)));
          if (n &lt;= 16)
          {
              InsertionSort(arr, 0, n - 1);
              return;
          }
          QuickSort(arr, 0, n - 1, limit);
          InsertionSort(arr, 0, n - 1);
      }
  }</code></pre>
</li>
</ul>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://justicehui.github.io/medium-algorithm/2019/03/24/IntroSort/">[정렬] 인트로 정렬</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%8A%B8%EB%A1%9C_%EC%A0%95%EB%A0%AC">인트로 정렬 | Wikipedia</a></li>
<li><a href="https://en.wikipedia.org/wiki/Introsort">Introsort | Wikipedia</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EB%B9%84%EA%B5%90_%EC%A0%95%EB%A0%AC">비교 정렬 | Wikipedia</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[AudioSettings.dspTime]]></title>
            <link>https://velog.io/@strange_tiger/AudioSettings.dspTime</link>
            <guid>https://velog.io/@strange_tiger/AudioSettings.dspTime</guid>
            <pubDate>Thu, 13 Feb 2025 06:12:34 GMT</pubDate>
            <description><![CDATA[<h1 id="audiosettingsdsptime">AudioSettings.dspTime</h1>
<ul>
<li><p>Unity의 <code>dspTime</code>은 오디오 시스템에서 처리된 실제 오디오 샘플 수에 기반하여 반환되는 <code>double</code> 값의 초 단위 시간이다.</p>
</li>
<li><p>주로 사용되는 <code>Time.time</code>의 <code>float</code> 값보다 훨씬 정확하고, 실제 시간에 가깝다.</p>
</li>
<li><p><code>AudioSource</code>의 <code>PlayScheduled</code> 메소드에 들어가는 매개변수가 <code>dspTime</code> 값이며, 아래와 같이 활용할 수 있다.</p>
<pre><code class="language-csharp">private AudioSource audioSource;

void Start()
{
    audioSource = GetComponent&lt;AudioSource&gt;();

    double curDspTime = AudioSettings.dspTime;

    audioSource.PlayScheduled(curDspTime + 5d);
    // 5초 뒤에 audioSource를 플레이한다.
}</code></pre>
</li>
<li><p>이로써 <code>FrameRate</code>(프레임 속도)의 영향을 받지않고, 오디오 시스템에 기반하여 5초뒤에 음악을 재생할 수 있다. </p>
</li>
<li><p>아래와 같이 시간을 계산하는 것으로 두 오디오 소스를 연달아 재생할 수도 있다.</p>
<pre><code class="language-csharp">  [SerializeField] private AudioSource audioSource1;
  [SerializeField] private AudioSource audioSource2;

  void Start()
  {
      double curDspTime = AudioSettings.dspTime;
      double audioSource1Duration = (double)audioSource1.clip.samples / audioSource1.clip.frequency;
      // 샘플 수 / 빈도 수로 audioSource1의 오디오 클립 길이를 구한다.

      audioSource1.PlayScheduled(curDspTime);
      audioSource2.PlayScheduled(curDspTime + audioSource1Duration);
      // 현재 시각에서 audioSource1 재생이 끝난 후에 재생한다.
  }</code></pre>
</li>
</ul>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://lefthanddeveloper.tistory.com/46">[Unity] AudioSettings.dspTime 이란?</a></li>
<li><a href="https://docs.unity3d.com/6000.0/Documentation/ScriptReference/AudioSettings-dspTime.html">AudioSettings.dspTime | Unity Documentation</a></li>
<li><a href="https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Time-time.html">Time.time | Unity Documentation</a></li>
<li><a href="https://docs.unity3d.com/6000.0/Documentation/ScriptReference/AudioSource.PlayScheduled.html">AudioSource.PlayScheduled | Unity Documentation</a></li>
<li><a href="https://discussions.unity.com/t/time-deltatime-audio-play-or-audiosettings-dsptime-audio-playscheduled/506096">Time.deltaTime Audio.Play or AudioSettings.dspTime Audio.PlayScheduled</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 메소드 파라미터]]></title>
            <link>https://velog.io/@strange_tiger/C-%EB%A9%94%EC%86%8C%EB%93%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0</link>
            <guid>https://velog.io/@strange_tiger/C-%EB%A9%94%EC%86%8C%EB%93%9C-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0</guid>
            <pubDate>Thu, 13 Feb 2025 05:25:37 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li>코드에서 파라미터에 <code>:</code>가 붙은 것을 처음 봤다. 조사해보니 <strong>Named 파라미터</strong>라더라. 그 내용을 정리하고자 한다.</li>
</ul>
<h1 id="c-메소드-파라미터">C# 메소드 파라미터</h1>
<h2 id="메소드">메소드</h2>
<ul>
<li><p>클래스가 갖는 기능, 혹은 객체와 관련된 함수를 메소드라고 한다.</p>
</li>
<li><p>메소드는 N개의 인수(매개변수, 파라미터)를 가질 수 있으며, 하나의 반환 값을 갖는다. 또한 <code>public</code>이나 <code>private</code> 같은 접근 제한자를 반환 값 앞에 둘 수 있다.</p>
</li>
</ul>
<pre><code class="language-csharp">public int Method(inta, string b, bool c)
{
}</code></pre>
<h2 id="call-by-value직접-참조">Call by Value(직접 참조)</h2>
<ul>
<li><p>C#은 메소드에 파라미터를 전달할 때, 값을 복사하여 전달하는 <code>Call by Value</code> 방식을 사용한다.</p>
</li>
<li><p>즉, 전달 받은 값을 메소드 내에서 변경해도 전달되었던 인수의 값은 변하지 않는다.</p>
</li>
</ul>
<pre><code class="language-csharp">class Calculator
{
    static void Swap(int x, int y)
    {
        int temp = x;
        x = y;
        y = temp;
    }

    static void Main(string[] args)
    {
        int x = 3;
        int y = 5;

        Swap(x, y);

        Console.WriteLine($&quot;x = {x}&quot;);    // x = 3
        Console.WriteLine($&quot;y = {y}&quot;);    // y = 5
    }
}</code></pre>
<h2 id="call-by-reference간접-참조">Call by Reference(간접 참조)</h2>
<ul>
<li><p>메소드에 파라미터를 전달할 때, 참조 형식으로 전달하고자 하면 C#에서는 <code>ref</code> 키워드를 사용한다.</p>
</li>
<li><p>이는 변수의 포인터 값을 전달하는 것으로, 메소드 내에서 값을 변경하면 메소드 외부에도 적용된다.</p>
</li>
<li><p><code>ref</code>를 사용하기 위해서는 <code>ref</code>로 전달되는 변수가 사전에 초기화되어 있어야 한다.</p>
</li>
<li><p>그외에도 <code>Call by Reference</code>를 사용하기 위한 <code>out</code>, <code>in</code> 등의 <strong>매개 변수 한정자</strong>가 있다.</p>
</li>
</ul>
<h3 id="매개-변수-한정자parameter-specifier">매개 변수 한정자(Parameter Specifier)</h3>
<ul>
<li><p>C#에서 <strong>간접 참조</strong>에 좀 더 높은 <strong>가독성</strong>을 주기 위해 제공하는 것.</p>
</li>
<li><p><strong>가독성</strong>을 준다는 것은, 이러한 한정자로 <strong>간접 참조의 목적</strong>을 알기 쉽게 한다는 것을 의미한다.</p>
</li>
<li><p><strong>ref / in / out</strong> 이 있다.</p>
<ul>
<li><p>ref : 
<a href="https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/ref">https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/ref</a></p>
</li>
<li><p>in : 
<a href="https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/in-parameter-modifier">https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/in-parameter-modifier</a></p>
</li>
<li><p>out : 
<a href="https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/out-parameter-modifier">https://docs.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/out-parameter-modifier</a></p>
</li>
</ul>
</li>
<li><p><strong>ref</strong></p>
<ul>
<li><p><strong>참조로 전달</strong>되는 인자를 의미한다.</p>
</li>
<li><p><strong>참조</strong>를 목적으로 한다.</p>
</li>
<li><p>ref 한정자를 쓰면 인자가 반드시 <strong>초기화</strong> 되어 있어야 한다.</p>
</li>
<li><p><strong>호출할 때 꼭 ref 키워드</strong>를 적어야 한다.</p>
</li>
<li><p>예시 코드</p>
<pre><code class="language-csharp">// C++
void Swap(int&amp; a, int&amp; b)
{
  int temp = a;
  a = b;
  b = temp;
}

// C#
// ref 한정자를 쓰면 인자가 반드시 초기화 되어 있어야 한다.
// 초기화를 하지 않으면 컴파일 오류가 난다.
void Swap(ref int a, ref int b)
{
  int temp = a;
  a = b;
  b = temp;
}

int a = 10;
int b = 20;
Swap(ref a, ref b); // 호출할 때 꼭 ref 키워드를 적는다.</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>in</strong></p>
<ul>
<li><p><strong>참조로 전달</strong>되나, <strong>변경이 되지 않는 인자</strong>를 의미한다.</p>
</li>
<li><p><strong>입력</strong>을 목적으로 한다.</p>
</li>
<li><p>in 한정자도 인자가 반드시 <strong>초기화</strong> 되어야 한다.</p>
</li>
<li><p>호출할 때는 적어주지 않아도 된다.</p>
</li>
<li><p>예시 코드</p>
<pre><code class="language-csharp">// C++
void Foo(const int&amp; a, const int&amp; b)
{
  // Do Something...
}

// C#
// in 한정자도 인자가 반드시 초기화 되어야 한다.
// 초기화를 하지 않으면 컴파일 오류가 난다.
void Foo(in int a, in int b)
{
  // Do Something...
}</code></pre>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong>out</strong></p>
<ul>
<li><p><strong>참조로 전달</strong>되는 인자이며, 매개변수를 이용한 <strong>출력</strong>을 의미한다.</p>
</li>
<li><p><strong>출력</strong>을 목적으로 한다.</p>
</li>
<li><p>out 한정자는 <strong>함수가 끝나기 전</strong> 반드시 <strong>어떤 값이 할당</strong>되어야 한다.</p>
</li>
<li><p><strong>호출할 때 꼭 out 키워드</strong>를 적어야 한다.</p>
</li>
<li><p>예시 코드</p>
<pre><code class="language-csharp">// C++
void Foo(int a, int b, int&amp; result)
{
  result = a + b;
}

// C#
// out 한정자는 함수가 끝나기 전 반드시 어떤 값이 할당되어야 한다.
void Foo(int a, int b, out int result)
{
  result = a + b;
}

int r;
Foo(10, 20, out r); // 호출할 땐 꼭 out 키워드를 적는다.</code></pre>
</li>
</ul>
</li>
</ul>
<h1 id="named-파라미터">Named 파라미터</h1>
<ul>
<li><p>메소드에 파라미터를 전달할 때, 일반적으로는 파라미터의 순서에 따라 파라미터가 넘겨졌다.</p>
</li>
<li><p><strong>Named 파라미터</strong>를 사용하면 인수를 매개 변수 목록 내의 해당 위치가 아닌 해당 <strong>이름</strong>과 일치시켜 매개 변수에 대한 인수를 지정할 수 있다.</p>
</li>
</ul>
<pre><code class="language-csharp">Method(name: &quot;John&quot;, age: 10, score: 90);</code></pre>
<pre><code class="language-csharp">class Calculator
{
    static void Swap(in int a, in int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

    static void Main(string[] args)
    {
        int x = 3;
        int y = 5;

        Swap(x, y); // 일반적으로는 이렇게 호출한다.

        Swap(b: x, a: y);
        // 이렇게 파라미터의 이름을 알면 순서 상관없이 전달할 수 있다.

        Console.WriteLine($&quot;x = {x}&quot;);    // x = 3
        Console.WriteLine($&quot;y = {y}&quot;);    // y = 5
    }
}</code></pre>
<h1 id="optional-파라미터">Optional 파라미터</h1>
<ul>
<li><p>메소드의 파라미터가 기본 값을 가지고 있으면, 그 파라미터를 <strong>Optional 파라미터</strong>라고 한다.</p>
<ul>
<li>기본 값은 함수 정의의 일부로, Optional 파라미터에 인수가 전달되지 않으면 사용된다.<pre><code class="language-csharp">int Calc(int a, int b, string calcType = &quot;+&quot;)
{
}</code></pre>
</li>
</ul>
</li>
<li><p>Optional 파라미터를 사용하면 메소드를 호출할 때 해당 인수를 <strong>생략</strong>할 수 있다.</p>
</li>
<li><p>Optional 파라미터는 반드시 파라미터들 중 마지막에 있어야 하며, 여러 개의 Optional 파라미터가 있을 경우에는 Optional이 아닌 파라미터들 뒤에 위치해야 한다.</p>
</li>
</ul>
<pre><code class="language-csharp">class Calculator
{
    int Calc(int a, int b, string calcType = &quot;+&quot;)
    {
        switch (calcType)
        {
            case &quot;+&quot;:
                return a + b;
            case &quot;-&quot;:
                return a - b;
            case &quot;*&quot;:
                return a * b;
            case &quot;/&quot;:
                return a / b;
            default:
                throw new ArithmeticException();
        }
    }

    static void Main(string[] args)
    {
        Calculator c = new Calculator();
        int ret = c.Calc(1, 2);
        Console.WriteLine($&quot;ret = {ret}&quot;);    // ret = 3

        ret = c.Calc(1, 2, &quot;*&quot;);
        Console.WriteLine($&quot;ret = {ret}&quot;);    // ret = 2
    }
}</code></pre>
<h1 id="params">params</h1>
<ul>
<li><p>일반적으로 메소드의 파라미터 개수는 정의와 함께 고정된다.</p>
</li>
<li><p>다만 파라미터의 개수를 미리 알 수 없는 경우, <code>params</code> 키워드를 사용해 가변 배열을 인수로 갖게 할 수 있다.</p>
</li>
<li><p>반드시 파라미터 중 하나만 존재해야 하며, 파라미터 중 마지막에 위치해야 한다.</p>
</li>
</ul>
<pre><code class="language-csharp">int Calc(params int[] values){};

int s = Calc(1, 2, 3, 4);
s = Calc(6, 7, 8, 9, 10);</code></pre>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://jini00.tistory.com/276">[예제로 배우는 C# 프로그래밍] 메서드 파라미터</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EB%A9%94%EC%84%9C%EB%93%9C_(%EC%BB%B4%ED%93%A8%ED%84%B0_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D)">메서드 (컴퓨터 프로그래밍) | Wikipedia</a></li>
<li><a href="https://velog.io/@strange_tiger/%EA%B2%BD%EC%9D%BC%EA%B2%8C%EC%9E%84%EC%95%84%EC%B9%B4%EB%8D%B0%EB%AF%B8-%EB%A9%80%ED%8B%B0-%EB%94%94%EB%B0%94%EC%9D%B4%EC%8A%A4-%EB%A9%94%ED%83%80%EB%B2%84%EC%8A%A4-%ED%94%8C%EB%9E%AB%ED%8F%BC-%EA%B0%9C%EB%B0%9C%EC%9E%90-%EC%96%91%EC%84%B1%EA%B3%BC%EC%A0%95-20220708-2022040420221213">경일게임아카데미 수업 내용 중</a></li>
<li><a href="https://daekyoulibrary.tistory.com/entry/C-%EC%B0%B8%EC%A1%B0%EC%97%90-%EC%9D%98%ED%95%9C-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98-%EC%A0%84%EB%8B%AC-ref-out">[C#] 참조에 의한 매개변수 전달 (ref, out)</a></li>
<li><a href="https://product.kyobobook.co.kr/detail/S000201856223">이것이 C#이다</a></li>
<li><a href="https://www.csharpstudy.com/">예제로 배우는 C# 프로그래밍</a></li>
<li><a href="https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments">명명된 인수와 선택적 인수(C# 프로그래밍 가이드) | MSDN</a></li>
<li><a href="https://todamfather.tistory.com/104">Part2. C# 기초 다지기(14. params 키워드)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity와 Visual Studio Code 연동]]></title>
            <link>https://velog.io/@strange_tiger/Unity%EC%99%80-Visual-Studio-Code-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@strange_tiger/Unity%EC%99%80-Visual-Studio-Code-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Mon, 10 Feb 2025 10:00:03 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li>CLine을 활용하기로 결정하면서 Unity와 VS Code를 연동할 필요가 생겼다. 그 과정을 아래에 정리한다.<h1 id="unity">Unity</h1>
<h2 id="1-visual-studio-code-다운로드">1. Visual Studio Code 다운로드</h2>
</li>
</ul>
<ol>
<li><p>Visual Studio Code를 다운로드하기 위해, Visual Studio Code를 다운로드할 수 있는 <a href="https://code.visualstudio.com/Download">홈페이지</a>에 접속 </p>
</li>
<li><p>자신의 OS에 해당하는 버전을 다운로드</p>
</li>
<li><p>자신이 설치하려는 경로에 <code>VSCodeUserSetup</code>을 설치</p>
</li>
<li><p><code>VSCodeUserSetup</code>을 실행</p>
</li>
<li><p>사용권 계약에 동의합니다를 선택, <code>다음</code> 버튼을 클릭</p>
</li>
<li><p>설치 경로를 설정하고 <code>다음</code> 버튼을 클릭</p>
</li>
<li><p>시작 메뉴에 폴더를 만들지 설정하고 <code>다음</code> 버튼을 클릭</p>
</li>
<li><p>설치 준비 완료창에서 <code>설치</code> 버튼을 클릭</p>
</li>
<li><p>설치가 끝났다면 <code>종료</code> 버튼을 클릭해 VS Code를 실행</p>
</li>
</ol>
<h2 id="2-visual-studio-code-기본-설정">2. Visual Studio Code 기본 설정</h2>
<h3 id="visual-studio-code-extensions-설정">Visual Studio Code Extensions 설정</h3>
<ol>
<li><p><code>Extensions UI</code>를 클릭 (혹은 <code>Ctrl + Shift + X</code>)</p>
</li>
<li><p>Search Extensions in Marketplace에 설치할 Extension을 검색</p>
</li>
<li><p><code>Install</code> 버튼을 눌러 설치</p>
</li>
</ol>
<ul>
<li><p>설치할 Extension</p>
<ul>
<li><code>C#</code> / <code>C# Extensions</code> / <code>Debugger for Unity</code> / <code>Unity Code Snippets</code> / <code>Unity Tools</code></li>
</ul>
</li>
</ul>
<h3 id="visual-studio-code-manage-설정">Visual Studio Code Manage 설정</h3>
<ul>
<li>개발의 편리를 위한 설정으로, 넘겨도 된다.</li>
</ul>
<ol>
<li><p><code>Manage</code>(톱니바퀴) &gt; <code>Settings</code></p>
</li>
<li><p><a href="https://learn.microsoft.com/ko-kr/visualstudio/ide/find-code-changes-and-other-history-with-codelens?view=vs-2019">Code Lens</a> 검색 후 <code>Editor: Code Lens</code> 기능을 끈다.</p>
</li>
<li><p><code>Format on Paste</code>를 검색한 후, <code>Editor: Format on Paste</code> 기능을 킨다.</p>
</li>
</ol>
<h2 id="3-net-framework-sdk-설치">3. .NET Framework SDK 설치</h2>
<ul>
<li>Visual Studio가 이미 설치되어 있다면 이미 .NET Framework SDK가 설치되어 있을 수 있다. &gt; 최신 버전으로 다시 설치할 것을 추천</li>
</ul>
<ol>
<li><p><a href="https://dotnet.microsoft.com/ko-kr/download">.NET Framework SDK Download</a> 홈페이지에 접속</p>
</li>
<li><p>자신의 OS 버전에 맞는 버전을 다운로드</p>
<ul>
<li>주의 : <code>Runtime</code>이 아닌 <code>SDK</code>를 설치할 것</li>
</ul>
</li>
<li><p>SDK Setup 다운로드 후 실행</p>
</li>
<li><p><code>설치</code> 버튼을 눌러 SDK 설치</p>
</li>
<li><p>설치가 완료되면 <code>닫기</code> 버튼을 눌러 종료</p>
</li>
<li><p>.NET Framework SDK를 안전하게 적용하기 위해 컴퓨터를 다시 시작</p>
</li>
</ol>
<h2 id="4-unity에서-script-editor-변경">4. Unity에서 Script Editor 변경</h2>
<ol>
<li><p>Unity Project 실행</p>
</li>
<li><p>Unity Editor Main Menu에서 <code>Edit</code> &gt; <code>Preferences</code> 버튼을 클릭</p>
</li>
<li><p><code>External Tools</code>를 클릭 후 <code>External Script Editor</code>에서 Visual Studio Code를 선택</p>
<ul>
<li>드롭다운에서 Visual Studio Code가 보이지 않을 경우 <code>Browse</code> 버튼을 클릭해 설치 경로를 따라가 선택할 것</li>
</ul>
</li>
</ol>
<h1 id="추가--wpf">추가 : WPF</h1>
<ul>
<li>VS Code, VS Code Extensions 설정, .NET Framework SDK 다운로드는 위 글을 보고 할 것.</li>
</ul>
<ol>
<li><p>작업위치 생성</p>
<ul>
<li>cmd 창을 열어 작업 위치를 생성<pre><code>예시
mkdir c:\vscodeworks
cd c:\vscodeworks</code></pre></li>
<li>GUI를 사용해도 무관</li>
</ul>
</li>
<li><p>템플릿 생성</p>
<ul>
<li><p>WPF 템플릿(프로젝트)를 만든다.</p>
</li>
<li><p>cmd 또는 terminal 창에서 아래와 같이 dotnet 명령어를 사용</p>
<pre><code>dotnet new wpf --name 템플릿명 --framework net버전
예시
dotnet new wpf --name TestApp --framework net7.0</code></pre></li>
<li><p><code>--name</code> 옵션을 생략한다면 상위 폴더의 이름이 기본 값으로 대체된다.</p>
</li>
</ul>
</li>
<li><p>프로젝트 열기</p>
<ul>
<li>생성된 템플릿(프로젝트)는 VS Code에서 열 수 있다.</li>
</ul>
</li>
</ol>
<h1 id="참고">참고</h1>
<ul>
<li>Unity<ul>
<li><a href="https://gongdolhoon.tistory.com/entry/UnityTutorial-1-Unity%EC%99%80-Visual-Studio-Code-%EC%97%B0%EB%8F%99">[Unity/Tutorial] 1. Unity와 Visual Studio Code 연동</a></li>
<li><a href="https://unity.com/releases/2019-2/programmer-tools#ide-support-moving-packages">UNITY 2019.2 RELEASE, Nex features and updates for programmer tools</a></li>
<li><a href="https://code.visualstudio.com/Download">Download Visual Studio Code</a></li>
<li><a href="https://docs.microsoft.com/ko-kr/visualstudio/ide/find-code-changes-and-other-history-with-codelens?view=vs-2019">Code Lens | MSDN</a></li>
<li><a href="https://dotnet.microsoft.com/download">.NET Framework download</a></li>
</ul>
</li>
<li>WPF<ul>
<li><a href="https://forum.dotnetdev.kr/t/vscode-wpf/5512/2">VSCode에서 WPF 시작하기</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[C# AES 파일 암호화/복호화]]></title>
            <link>https://velog.io/@strange_tiger/C-AES-%ED%8C%8C%EC%9D%BC-%EC%95%94%ED%98%B8%ED%99%94%EB%B3%B5%ED%98%B8%ED%99%94</link>
            <guid>https://velog.io/@strange_tiger/C-AES-%ED%8C%8C%EC%9D%BC-%EC%95%94%ED%98%B8%ED%99%94%EB%B3%B5%ED%98%B8%ED%99%94</guid>
            <pubDate>Fri, 07 Feb 2025 09:06:53 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li>외부 API의 인증 토큰을 암호화해서 저장할 필요가 생겼다. 그 암호화에 AES를 적용해보고자 한다.</li>
</ul>
<h1 id="aes-advanced-encryption-standard">AES (Advanced Encryption Standard)</h1>
<ul>
<li><p>대칭키 암호화 알고리즘으로, 키 값을 가지고 암호화/복호화를 하므로 키 값을 모른다면 데이터를 확인 할 수 없다.</p>
</li>
<li><p>암호화 키는 128, 192, 256 세 가지 중 하나가 될 수 있으며, 각각 AES-128, AES-192, AES-256라고 불린다.</p>
</li>
<li><p>AES 암호화 알고리즘을 사용하기 위해서는 <strong>Key 값</strong>과 <strong>IV 값</strong>을 가지고 있어야 한다.</p>
<ul>
<li><p>Key : 데이터를 암호화/복호화를 하기 위해 필요한 값 즉, 키 값을 모른다면 데이터를 저장하거나 불러올 수 없다.</p>
</li>
<li><p>IV : 초기화 벡터로, 암호화 과정에서 입력 데이터의 패턴을 깨뜨려 동일한 텍스트가 동일하게 암호화되지 않도록 한다.</p>
</li>
</ul>
</li>
<li><p>C#에서 C# 에서 AES 암호화 알고리즘을 사용하기 위해서는 아래 라이브러리를 사용한다고 선언해야한다.</p>
<pre><code class="language-csharp">using System.Security.Cryptography;</code></pre>
<ul>
<li>System.Security.Cryptography의 객체들은 네이티브 리소스를 사용하여 가비지 컬렉터(GC)가 자동으로 리소스를 해제하지 않기 때문에 수동으로 리소스를 해제해줘야 한다.</li>
</ul>
</li>
</ul>
<h1 id="예제-코드">예제 코드</h1>
<pre><code class="language-csharp">using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Moti_PhysioLicenseManager
{
    /// &lt;summary&gt;
    /// AES-256 대칭 암·복호화 헬퍼.
    /// &lt;para&gt;
    /// ⚠️ &lt;strong&gt;주의&lt;/strong&gt;&lt;br/&gt;
    /// 아래 &lt;see cref=&quot;ExampleKey&quot;/&gt; / &lt;see cref=&quot;ExampleIV&quot;/&gt; 는
    /// &quot;예제&quot;용 임시 값입니다. 실제 서비스에선 &lt;c&gt;RandomNumberGenerator&lt;/c&gt; 등으로
    /// 안전하게 생성한 값을 사용하세요.
    /// &lt;/para&gt;
    /// &lt;example&gt;
    /// &lt;code&gt;
    /// // ────────────────────────── 예제 사용법 ──────────────────────────
    /// using var cryptor = new DataCryptor();
    ///
    /// string plain = &quot;Hello Moti-Physio!&quot;;
    ///
    /// // ① 암호화 (문자열 → 바이트)
    /// byte[] cipher = cryptor.Encrypt(
    ///     Encoding.UTF8.GetBytes(plain),   // 평문
    ///     DataCryptor.ExampleKey,          // ★ 예제 Key
    ///     DataCryptor.ExampleIV);          // ★ 예제 IV
    ///
    /// // ② (선택) 전송/저장을 위해 Base64 문자열화
    /// string cipherText = Convert.ToBase64String(cipher);
    ///
    /// // ③ 복호화
    /// byte[] cipherBytes = Convert.FromBase64String(cipherText);
    /// string decrypted = Encoding.UTF8.GetString(
    ///     cryptor.Decrypt(cipherBytes,
    ///                     DataCryptor.ExampleKey,
    ///                     DataCryptor.ExampleIV));
    ///
    /// Console.WriteLine(decrypted); // Hello Moti-Physio!
    /// // ────────────────────────────────────────────────────────────────
    /// &lt;/code&gt;
    /// &lt;/example&gt;
    /// &lt;/summary&gt;
    public sealed class DataCryptor : IDisposable
    {
        #region 🔑  예제 Key / IV (절대로 실서비스에 쓰지 마세요!)

        /// &lt;summary&gt;32 바이트 AES-256 키 (0x00~0x1F 순차값)&lt;/summary&gt;
        public static readonly byte[] ExampleKey =
        {
            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
            0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
            0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
            0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
        };

        /// &lt;summary&gt;16 바이트 IV (0xA0~0xAF 순차값)&lt;/summary&gt;
        public static readonly byte[] ExampleIV =
        {
            0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
            0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF
        };

        #endregion

        private readonly Aes _aes = Aes.Create();
        private bool _disposed;

        #region Encrypt

        /// &lt;summary&gt;
        /// UTF-8 문자열을 받아 암호 바이트 배열을 반환합니다.
        /// &lt;/summary&gt;
        public byte[] Encrypt(string plainText, byte[] key, byte[] iv) =&gt;
            Encrypt(Encoding.UTF8.GetBytes(plainText), key, iv);

        /// &lt;summary&gt;
        /// 바이트 배열을 암호화합니다.
        /// &lt;/summary&gt;
        public byte[] Encrypt(byte[] plain, byte[] key, byte[] iv)
        {
            if (plain is null || plain.Length == 0)
                throw new ArgumentException(nameof(plain));

            InitKeyIv(key, iv);

            using MemoryStream ms = new();
            using CryptoStream cs = new(ms, _aes.CreateEncryptor(), CryptoStreamMode.Write);
            cs.Write(plain, 0, plain.Length);
            cs.FlushFinalBlock();
            return ms.ToArray();
        }

        #endregion
        #region Decrypt

        /// &lt;summary&gt;
        /// Base64 문자열을 복호화해 UTF-8 문자열로 돌려줍니다.
        /// &lt;/summary&gt;
        public string Decrypt(string base64CipherText, byte[] key, byte[] iv)
        {
            byte[] cipher = Convert.FromBase64String(base64CipherText);
            return Encoding.UTF8.GetString(Decrypt(cipher, key, iv));
        }

        /// &lt;summary&gt;
        /// 암호 바이트 배열을 복호화해 평문 바이트를 반환합니다.
        /// &lt;/summary&gt;
        public byte[] Decrypt(byte[] cipher, byte[] key, byte[] iv)
        {
            if (cipher is null || cipher.Length == 0)
                throw new ArgumentException(nameof(cipher));

            InitKeyIv(key, iv);

            using MemoryStream ms = new(cipher);
            using CryptoStream cs = new(ms, _aes.CreateDecryptor(), CryptoStreamMode.Read);
            using MemoryStream plain = new();
            cs.CopyTo(plain);
            return plain.ToArray();
        }

        #endregion
        #region Helpers / Dispose

        private void InitKeyIv(byte[] key, byte[] iv)
        {
            _aes.Key = (key is { Length: &gt; 0 }) ? key : ExampleKey;
            _aes.IV  = (iv  is { Length: &gt; 0 }) ? iv  : ExampleIV;
        }

        /// &lt;inheritdoc/&gt;
        public void Dispose()
        {
            if (_disposed) return;
            _aes.Dispose();
            _disposed = true;
        }

        #endregion
    }
}</code></pre>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://velog.io/@dev_benedictus/C-AES-%ED%8C%8C%EC%9D%BC-%EC%95%94%ED%98%B8%ED%99%94%EB%B3%B5%ED%98%B8%ED%99%94">C# AES 파일 암호화/복호화</a></li>
<li><a href="https://deff-dev.tistory.com/145">[Unity/C#] AES 암호화 알고리즘</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[윈도우 문제 파악하기]]></title>
            <link>https://velog.io/@strange_tiger/%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%AC%B8%EC%A0%9C-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@strange_tiger/%EC%9C%88%EB%8F%84%EC%9A%B0-%EB%AC%B8%EC%A0%9C-%ED%8C%8C%EC%95%85%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 06 Feb 2025 01:39:38 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li>WPF로 만든 런처 프로그램이 실행되지 않는 오류가 발생했다. 윈도우 오류인지 검사하는 방법을 정리한다.<h1 id="이벤트-뷰어">이벤트 뷰어</h1>
</li>
<li>Windows OS는 컴퓨터의 사용 기록을 이벤트 뷰어(Event Viewer)에 항상 기록한다. 그 중 비정상적인 오류 로그를 확인하는 것으로 오류 원인을 찾을 수 있다.</li>
<li>오류 원인을 모르겠을 때 가장 먼저 확인하고 참고하는 중요한 도구이다.</li>
<li>서비스 오류, 설치 오류, 하드웨어 장치 및 드라이버 오류 등의 모든 오류에 대한 기록을 남긴다.<h2 id="사용법">사용법</h2>
</li>
</ul>
<ol>
<li><p><code>실행</code>을 검색하거나 <code>Window + R</code>로 연 실행 창에서 <code>eventvwr.msc</code>을 입력하고 실행한다.</p>
</li>
<li><p>내보낼 로그의 유형을 선택한다. <code>ex) Windows Logs &gt; Application</code></p>
</li>
<li><p><code>작업(Action)</code> -&gt; <code>모든 이벤트를 다른 이름으로 저장</code>을 클릭해 로그를 저장한다.</p>
</li>
<li><p>로그 내용을 확인해 에러 사항을 확인한다.
 <code>ex) Application 로그 내용에서 Application Error 사항을 확인 - event id 1000</code></p>
</li>
<li><p>에러 내용에 따라 문제를 파악하고 해결할 수 있다.</p>
<ul>
<li><a href="https://germmen.tistory.com/entry/%EC%9C%88%EB%8F%84%EC%9A%B0-10-%EC%9D%B4%EB%B2%A4%ED%8A%B8">간략한 해결책</a></li>
</ul>
</li>
</ol>
<h1 id="windows-안정성-모니터">Windows 안정성 모니터</h1>
<ul>
<li><p>시스템 성능 및 안정성에 영향을 미치는 Windows OS에서 발생하는 소프트웨어 문제를 식별해주는 도구이다.</p>
</li>
<li><p>시작과 시스템 종료 사이에 나타나는 운영 체제의 시스템 상태를 지속적으로 모니터링한다.</p>
</li>
<li><p>시스템에 발생한 각종 문제의 원인과 해결책을 파악하는데 큰 도움이 된다.</p>
<h2 id="사용법-1">사용법</h2>
</li>
<li><p>제어판의 <code>시스템 및 보안/보안 및 유지 관리</code>에 들어가 <code>안정성 기록 보기</code>를 클릭한다.</p>
</li>
<li><p>혹은 검색으로 <code>안정성 기록 보기</code>를 검색하는 것으로 실행할 수 있다.</p>
</li>
<li><p>안정성 모니터는 아래 5가지 정보를 중점적으로 보인다.</p>
<ul>
<li><p><code>애플리케이션 실패</code>: 애플리케이션 오류나 에러를 추적해 보여준다(예: ‘MS 아웃룩의 실행이 멈췄습니다’).</p>
</li>
<li><p><code>윈도우 실패</code>: OS 오류나 에러를 보여준다(예: ‘윈도우 하드웨어 에러’).</p>
</li>
<li><p><code>기타 실패</code>: 발생한 여타 다른 오류나 에러들을 보여준다(예: ‘디스크 오류’)</p>
</li>
<li><p><code>경고</code>: 시스템 행동에 딱히 영향을 미치지 않는 오류나 에러를 보여준다(예: ‘드라이버 설치 실패’).</p>
</li>
<li><p><code>정보</code>: 시스템의 변화나 업데이트를 알려준다(예: ‘윈도우 업데이트가 성공적으로 설치되었습니다’라거나 ‘드라이버 설치를 성공적으로 완료했습니다’ 등). </p>
</li>
</ul>
</li>
<li><p>각 열은 보기 기준 항목을 토대로 날짜를 표시하며, 그 아래에 심각도(중요, 경고, 정보)에 따라 이벤트를 시간순대로 나열한다.</p>
</li>
<li><p>각 이벤트를 선택해 자세한 정보를 알 수 있다.</p>
</li>
<li><p>모니터 창 좌측 하단의 <code>안정성 기록 저장</code> 버튼을 클릭해 <code>XML</code> 포맷으로 데이터의 스냅샷을 저장할 수 있다.</p>
<h1 id="참고">참고</h1>
</li>
<li><p>이벤트 뷰어</p>
<ul>
<li><a href="https://www.dell.com/support/kbdoc/ko-kr/000124382/windows-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EB%82%B4%EB%B3%B4%EB%82%B4%EB%8A%94-%EB%B0%A9%EB%B2%95">Windows 이벤트 로그를 내보내는 방법</a></li>
<li><a href="https://prolite.tistory.com/959">이벤트 뷰어, 윈도우 시스템 사용 기록 각종 로그 정보 보기</a></li>
<li><a href="https://answers.microsoft.com/ko-kr/windows/forum/windows_7-windows_programs/event-id-1000-application-%EC%97%90%EB%9F%AC/08049d33-6108-46d6-b74c-3785d8102765">event id 1000 application 에러 관련</a></li>
<li><a href="https://burning-dba.tistory.com/20">EventID: 1000 / Application Error / Error</a></li>
<li><a href="https://learn.microsoft.com/ko-kr/troubleshoot/windows-server/performance/troubleshoot-application-service-crashing-behavior">MSDN|애플리케이션 또는 서비스 충돌 동작 문제 해결 지침</a></li>
</ul>
</li>
<li><p>Windows 안정성 모니터</p>
<ul>
<li><a href="https://www.dell.com/support/kbdoc/ko-kr/000178177/windows-%EC%95%88%EC%A0%95%EC%84%B1-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%EC%8B%9D%EB%B3%84%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95">Windows 안정성 모니터를 사용하여 소프트웨어 문제를 식별하는 방법</a></li>
<li><a href="https://germmen.tistory.com/entry/%EC%9C%88%EB%8F%84%EC%9A%B0-10-%EC%9D%B4%EB%B2%A4%ED%8A%B8">윈도우 10 이벤트 뷰어 에러코드 완벽 가이드: 원인과 해결 방법</a></li>
<li><a href="https://dolpali.tistory.com/325">PC 사용 중 오류가 났을 때 오류가 난 프로그램 찾기, 안정성 모니터 활용하기</a></li>
<li><a href="https://www.cio.com/article/3531636/%EC%9C%88%EB%8F%84%EC%9A%B0-10%EC%97%90%EC%84%9C-%EC%95%88%EC%A0%95%EC%84%B1-%EB%AA%A8%EB%8B%88%ED%84%B0-%EA%B8%B0%EB%8A%A5-%EC%82%AC%EC%9A%A9%EB%B2%95.html">윈도우 10에서 ‘안정성 모니터’ 기능 사용법</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[C#] 비동기와 코루틴 - async, await ]]></title>
            <link>https://velog.io/@strange_tiger/C-async-await</link>
            <guid>https://velog.io/@strange_tiger/C-async-await</guid>
            <pubDate>Thu, 02 Jan 2025 08:05:50 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li><p>유니티 환경에서, Monobehaviour를 상속 받지 않는 클래스에서 비동기적 혹은 코루틴과 유사하게 실행되는 함수를 만드는 방법을 찾는 과정에서 배운 내용을 기술한다.</p>
<h1 id="비동기-프로그래밍이란">비동기 프로그래밍이란?</h1>
</li>
<li><p>프로그램의 실행을 <strong>비동기적</strong>으로 처리하는, 즉 하나의 작업이 완료될 때까지 <strong>기다리지 않고</strong> 다른 작업을 수행할 수 있도록 하는 프로그래밍 방식이다.</p>
</li>
<li><p>아침 식사로 예를 들자면,</p>
<pre><code> 1. 커피 한 잔을 따릅니다.
  2. 팬을 가열한 다음 계란 두 개를 볶습니다.
  3. 베이컨 세 조각을 튀깁니다.
  4. 빵 두 조각을 굽습니다.
  5. 토스트에 버터와 잼을 바릅니다.
  6. 오렌지 주스 한잔을 따릅니다.</code></pre><p>라는 6단계를, 시간이 많이 걸리는 가열 작업들을 동시에 처리하는 것으로</p>
<pre><code> 1. 커피 한 잔을 따릅니다.
  2. 팬을 가열한 다음 계란 두 개를 볶습니다. &amp;&amp; 베이컨 세 조각을 같이 튀깁니다. &amp;&amp; 빵 두 조각을 같이 굽습니다.
  3. 토스트에 버터와 잼을 바릅니다.
  4. 오렌지 주스 한잔을 따릅니다.</code></pre><p>와 같이 단축할 수 있다.</p>
</li>
<li><p>이처럼 시간이 오래 걸리는 작업 때문에 프로그램이 멈추는 <strong>병목 현상</strong>을 피하고 작업 시간을 줄이기 위해 비동기 프로그래밍은 사용된다.</p>
</li>
<li><p>특히, 작업이 언제 마무리될지 <strong>시간을 가늠하기 어렵고,</strong> 그 시간 동안 <strong>프로세스가 멈추어선 안되는</strong> 작업에 많이 사용된다.</p>
</li>
<li><p>즉 다음과 같은 장점이 있어 비동기는 활용된다.</p>
<ol>
<li><p><strong>자원</strong>을 효율적으로 활용할 수 있다.</p>
</li>
<li><p><strong>메인 스레드를 막지 않고</strong> 긴 작업을 수행할 수 있다.</p>
</li>
</ol>
</li>
</ul>
<h1 id="c-비동기-프로그래밍">C# 비동기 프로그래밍</h1>
<h2 id="asyncawait">Async/Await</h2>
<ul>
<li><p>C# 비동기 프로그래밍의 대표적인 구조이다.</p>
</li>
<li><p><code>async</code> 키워드를 붙인 메소드가 <code>await</code>으로 비동기 작업의 완료 시점까지 <strong>대기</strong>한다.</p>
</li>
<li><p><code>Task</code> 또는 <code>Task&lt;T&gt;</code>를 반환형으로 사용하며, 내부에서 <code>await</code> 키워드를 통해 실제로 비동기 메서드를 호출한다.</p>
</li>
</ul>
<h3 id="예시">예시</h3>
<ul>
<li><p>동기 코드</p>
<pre><code class="language-csharp">static void Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine(&quot;coffee is ready&quot;);

    Egg eggs = FryEggs(2);
    Console.WriteLine(&quot;eggs are ready&quot;);

    Bacon bacon = FryBacon(3);
    Console.WriteLine(&quot;bacon is ready&quot;);

    Toast toast = ToastBread(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine(&quot;toast is ready&quot;);

    Juice oj = PourOJ();
    Console.WriteLine(&quot;oj is ready&quot;);
    Console.WriteLine(&quot;Breakfast is ready!&quot;);
}</code></pre>
</li>
<li><p>비동기 코드</p>
<pre><code class="language-csharp">static async Task Main(string[] args)
{
    Coffee cup = PourCoffee();
    Console.WriteLine(&quot;coffee is ready&quot;);

    Egg eggs = await FryEggsAsync(2);
    Console.WriteLine(&quot;eggs are ready&quot;);

    Bacon bacon = await FryBaconAsync(3);
    Console.WriteLine(&quot;bacon is ready&quot;);

    Toast toast = await ToastBreadAsync(2);
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine(&quot;toast is ready&quot;);

    Juice oj = PourOJ();
    Console.WriteLine(&quot;oj is ready&quot;);
    Console.WriteLine(&quot;Breakfast is ready!&quot;);
}</code></pre>
<ul>
<li><code>async</code>가 붙은 각 작업(FryEggsAsync 등)이 실행되는 동안 스레드가 차단되지 않고, 작업이 완료되면 이어서 계속하게 된다.</li>
</ul>
</li>
</ul>
<h2 id="task와-task-based-asynchronous-patterntap">Task와 Task-based Asynchronous Pattern(TAP)</h2>
<ul>
<li><p>.NET에서 권장하는 비동기 설계 방식으로, <code>Task</code> 객체를 통해 비동기 작업을 표현한다.</p>
</li>
<li><p><code>TaskCompletionSource</code>나 <code>CancellationToken</code> 등을 함께 사용해 작업 완료, 취소, 예외 처리를 체계적으로 관리할 수 있다.</p>
</li>
<li><p>아래 내용을 정리해보자.</p>
<ul>
<li><a href="https://learn.microsoft.com/ko-kr/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap">MSDN | .NET의 TAP(작업 기반 비동기 패턴): 소개 및 개요</a></li>
<li><a href="https://sejoung.github.io/2021/01/2021-01-22-csharp_task/">.NET 테스크 기반 비동기 패턴(Task-based async model)의 사용</a></li>
<li><a href="https://jettstream.tistory.com/574#google_vignette">C# 비동기 프로그래밍 TAP이란 무엇인가?</a></li>
</ul>
</li>
</ul>
<h2 id="비동기-함수-구현의-흐름">비동기 함수 구현의 흐름</h2>
<h3 id="1-async와-비동기-메소드-정의">1. async와 비동기 메소드 정의</h3>
<ul>
<li><p><code>async</code> 키워드를 사용해 메소드를 정의하는 것으로 해당 메소드가 비동기적으로 동작함을 선언한다.</p>
</li>
<li><p>반환형은 주로 <code>Task</code> 혹은 <code>Task&lt;T&gt;</code>를 사용하며, 경우에 따라 <code>ValueTask</code> 또는 <code>void</code>가 될 수 있으나, <code>void</code>는 이벤트 핸들러 등 특수한 경우에만 권장된다.</p>
</li>
<li><p>일반적인 형태</p>
<pre><code class="language-csharp">public async Task MyAsyncMethod()
{
    // 비동기로 처리할 로직
}</code></pre>
</li>
</ul>
<h3 id="2-await와-실제-비동기-작업">2. await와 실제 비동기 작업</h3>
<ul>
<li><p><code>await</code> 키워드로 비동기 작업을 <strong>호출</strong>하고, 그 완료 시점을 자연스럽게 기다릴 수 있다.</p>
</li>
<li><p><code>await</code> 키워드가 붙은 작업은 호출할 때 스레드를 블로킹하지 않고 별개 스레드에 비동기적으로 진행된다. 작업이 끝날 때까지 다른 스레드가 정상적으로 동작한다. </p>
</li>
<li><p>일반적인 형태</p>
<pre><code class="language-csharp">public async Task&lt;string&gt; DownloadDataAsync(string url)
{
    using (var client = new HttpClient())
    {
        // 실제 비동기 메서드 (GetStringAsync)는 Task&lt;string&gt;을 반환
        // await를 붙여서 비동기 완료 시점을 기다린다
        string data = await client.GetStringAsync(url);
        return data; // 작업 완료 후 결과 반환
    }
}</code></pre>
</li>
</ul>
<h3 id="3-메소드의-동작-방식--상태-기계state-machine">3. 메소드의 동작 방식 : 상태 기계(State Machine)</h3>
<ul>
<li><p><code>async/await</code> 패턴을 사용하면, 컴파일러가 자동으로 메서드를 <strong>“상태 기계(State Machine)”</strong> 형태로 변환한다.</p>
<ul>
<li><p>이는 <code>await</code> 키워드로 실행 흐름이 일시 정지된 경우, 함수 내부에 여러 개의 중단점을 설정하고 <strong>어떤 단계에서 멈췄는지</strong>를 기억해야 한다.</p>
</li>
<li><p>C# 컴파일러는 이러한 처리를 위해 메소드를 &quot;상태 기계&quot; 형태로 변환하여, 각 <code>await</code> 지점마다 메소드의 실행 상태(현재 위치, 지역 변수, 예외 처리 상태 등)를 보관한다.</p>
</li>
<li><p><code>async/await</code> 뿐만 아니라 C#의 코루틴 같은 <code>yield return</code>(이터레이터) 문법도 유사한 방식으로 구현된다.</p>
</li>
</ul>
</li>
<li><p>호출 측에서는 메서드가 즉시 <code>Task</code>(또는 <code>Task&lt;T&gt;</code>)를 반환하므로, 스레드가 블로킹되지 않고 비동기 실행이 진행된다.</p>
</li>
<li><p><code>await</code> 키워드 기준으로 코드가 분할되어, 비동기 작업이 완료되면 메서드가 중단된 지점부터 다시 실행을 이어간다.</p>
</li>
</ul>
<h3 id="4-호출-측에서의-사용-예시">4. 호출 측에서의 사용 예시</h3>
<ul>
<li><p>예시 코드</p>
<pre><code class="language-csharp">public async Task ProcessDataAsync()
{
    // 비동기 메서드 호출
    string result = await DownloadDataAsync(&quot;http://example.com&quot;);
    Console.WriteLine(result);

    // 다운로드 후, 추가 로직 수행
    // (다운로드가 끝날 때까지 메서드의 나머지 부분이 자동으로 대기)
}</code></pre>
</li>
<li><p>위 코드에서 <code>ProcessDataAsync</code>는 <code>DownloadDataAsync</code>가 끝날 때까지 프로그램이 멈추지 않고 다른 작업을 수행할 수 있다.</p>
</li>
<li><p><code>await</code> 이후 로직(출력 등)은 다운로드가 완료된 후에 실행된다.</p>
</li>
</ul>
<h3 id="5-예외-처리와-흐름-제어">5. 예외 처리와 흐름 제어</h3>
<ul>
<li><p>예외 처리</p>
<ul>
<li><p><code>await</code> 구문 내부에서 발생하는 예외는 <code>try/catch</code> 구문으로 일반적으로 잡을 수 있다.</p>
</li>
<li><p>예시</p>
<pre><code class="language-csharp">public async Task SafeDownloadAsync()
{
    try
    {
        string data = await DownloadDataAsync(&quot;http://example.com&quot;);
        // 성공 시 처리
    }
    catch (Exception ex)
    {
        // 예외 처리
    }
}</code></pre>
</li>
</ul>
</li>
<li><p>취소(CancellationToken)</p>
<ul>
<li>긴 작업 중의 취소를 위해 메소드에서 <code>Cancellation</code> 파라미터를 받고, 비동기 작업 도중에 <code>token.ThrowIfCancellationRequested()</code> 등을 적절히 호출해줄 수 있다.</li>
</ul>
</li>
</ul>
<h3 id="6-반환형-선택-가이드">6. 반환형 선택 가이드</h3>
<ol>
<li><p><code>Task</code> : 결과값이 없는 단순 비동기 작업</p>
</li>
<li><p><code>Task&lt;T&gt;</code> : 비동기 처리 후 결과값이 있는 작업</p>
</li>
<li><p><code>ValueTask</code> / <code>ValueTask&lt;T&gt;</code> : .NET Core 2.1 이상부터 지원하는 반환형으로, 오버헤드가 큰 경우 성능 최적화를 위해 사용할 수 있으나, 사용 시 주의사항이 많다.</p>
</li>
<li><p><code>void</code> : 이벤트 핸들러 등의 특수 상황에서만 사용하도록 한다. <strong>예외 전파</strong>가 어렵고 호출 측에서 작업 완료나 예외를 추적하기 불편하므로 일반 메소드 구현에는 지양하자.</p>
<ul>
<li>예외 전파 : 상위 계층으로 예외가 전달될때마다 새로운 예외에 포함시켜 다시 던지는 과정.
예외 체이닝, 예외 래핑이라고 불리기도 한다</li>
</ul>
</li>
</ol>
<h3 id="7-실제-사용-예시">7. 실제 사용 예시</h3>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// [250102 Jinho]
/// 해당 기기가 갖는 기능 권한 내용을 설정
/// 비동기적으로, 0.5초씩 딜레이를 넣어 권한을 매핑
/// &lt;/summary&gt;
private async void SetPermission()
{
    if (permissions == null)
    {
        permissions = new Dictionary&lt;PermissionType, bool&gt;();
    }

    foreach (PermissionType p in System.Enum.GetValues(typeof(PermissionType)))
    {
        await Task.Delay(500);

        permissions[p] = GetPermissionQuery(p);
        UnityEngine.Debug.Log(&quot;permission:&quot; + p);
    }
}</code></pre>
<pre><code class="language-csharp">/// &lt;summary&gt;
/// 프레임 큐에서 프레임을 가져와 FFmpeg에 인코딩하도록 전달하는 메서드입니다.
/// &lt;/summary&gt;
/// &lt;param name=&quot;frameQueue&quot;&gt;인코딩할 프레임 큐입니다.&lt;/param&gt;
/// &lt;param name=&quot;ffmpegStream&quot;&gt;FFmpeg의 표준 입력 스트림입니다.&lt;/param&gt;
/// &lt;param name=&quot;queueLock&quot;&gt;프레임 큐에 대한 동기화 잠금 객체입니다.&lt;/param&gt;
/// &lt;remarks&gt;
/// 이 메서드는 큐에서 프레임을 가져와 FFmpeg로 전달하며, 녹화가 종료될 때까지 계속 실행됩니다. 
/// 녹화가 종료되면 FFmpeg 스트림을 닫습니다.
/// &lt;/remarks&gt;
private async void EncodeFrames(Queue&lt;byte[]&gt; frameQueue, Stream ffmpegStream, object queueLock)
{
    while (isRecording || frameQueue.Count &gt; 0)
    {
        byte[] frame = null;
        lock (queueLock)
        {
            if (frameQueue.Count &gt; 0)
            frame = frameQueue.Dequeue();
        }

        if (frame != null)
        {
            await ffmpegStream.WriteAsync(frame, 0, frame.Length);
            await ffmpegStream.FlushAsync();
        }
        else
        {
            await Task.Delay(1);
        }
    }
    ffmpegStream.Close();
}</code></pre>
<h2 id="비동기-지연delay-구현">비동기 지연(Delay) 구현</h2>
<ul>
<li><p><code>Thread.Sleep(밀리세컨드)</code>
호출한 스레드를 지정된 시간만큼 <strong>정지시킨다.</strong> 
UI 스레드에서 호출하면 화면과 이벤트 처리가 멈춘다. 
일반적인 UI 스레드에서 권장되지 않는다.</p>
</li>
<li><p><code>Task.Delay(밀리세컨드)</code>
지정된 시간 동안 비동기로 <strong>대기</strong>하고, 그동안 스레드를 <strong>정지하지 않는다.</strong>
<code>async/await</code> 키워드와 같이 사용하면 UI 프리즈 없이 자연스럽게 지연된다.</p>
</li>
<li><p><code>DispatcherTimer</code>, <code>System.TimeSpan</code>, <code>System.Timers.Timer</code> 등 주기적 호출을 사용한 지연 및 시간 재기도 가능하다.</p>
</li>
</ul>
<h3 id="예시-1">예시</h3>
<pre><code class="language-csharp">///// timeDelay01 - All Stop 멈춤
private void btn01_Click(object sender, RoutedEventArgs e)
{
    lbl01.Content = DateTime.Now.ToString(&quot;G&quot;) + &quot; StartTime \r\n&quot;;
    Thread.Sleep(3000);   // 1000은 1초
    lbl01.Content += DateTime.Now.ToString(&quot;G&quot;) + &quot; EndTime \r\n&quot;;
}


///// timeDelay02 - 창은 멈추지 않으나 마우스로 창 이동중에는 시간이 흐르지 않음
void timeDelay02(int tDelaySecond)
{
    DateTime dtStart = DateTime.Now;
    TimeSpan firstTime = new TimeSpan(DateTime.Now.Ticks);


    while (firstTime.Ticks + (tDelaySecond * 10000000) &gt;= DateTime.Now.Ticks)
    {
        TimeSpan elapsedSpan = new TimeSpan(DateTime.Now.Ticks - firstTime.Ticks);
        lbl02.Content =
            dtStart + &quot; StartTime \r\n&quot;
            + dtStart.AddSeconds(tDelaySecond) + &quot; EndTime \r\n&quot;
            + elapsedSpan.Seconds.ToString();

        this.Dispatcher.Invoke((ThreadStart)(() =&gt; { }), DispatcherPriority.Input);
    }
}

private void btn02_Click(object sender, RoutedEventArgs e)
{
    timeDelay02(3);    // Second
    MessageBox.Show(&quot;The End&quot;);
}

///// timeDelay03 - 백그라운드에서 시간을 재는 가장 단순하고 좋은 방법
async private void timeDelay03(int tDelaySecond)
{
    await Task.Delay(tDelaySecond);

    lbl03.Content += DateTime.Now.ToString(&quot;G&quot;) + &quot; EndTime \r\n&quot;;
    MessageBox.Show(&quot;Completed&quot;);
}

private void btn03_Click(object sender, RoutedEventArgs e)
{
    lbl03.Content = DateTime.Now.ToString(&quot;G&quot;) + &quot; StartTime \r\n&quot;;
    timeDelay03(1000 * 3);    // 1000 = 1 Second

    MessageBox.Show(&quot;The End&quot;);
}

///// timeDelay04 - 백그라운드에서 시간을 재는 또다른 방법
private void btn04_Click(object sender, RoutedEventArgs e)
{
    lbl04.Content = DateTime.Now.ToString(&quot;G&quot;) + &quot; StartTime \r\n&quot;;
    DispatcherTimer timer = new DispatcherTimer();
    timer.Interval = TimeSpan.FromSeconds(3);
    timer.Tick += (s, a) =&gt;
    {
        lbl04.Content += DateTime.Now.ToString(&quot;G&quot;) + &quot; EndTime \r\n&quot;;
        MessageBox.Show(&quot;Completed&quot;);
        timer.Stop();
    };
    timer.Start();
    MessageBox.Show(&quot;The End&quot;);
}</code></pre>
<h1 id="비동기와-코루틴coroutine">비동기와 코루틴(Coroutine)</h1>
<h2 id="코루틴">코루틴</h2>
<ul>
<li><p>유니티에서의 로직은 라이프 사이클처럼 보통 동기 방식으로 돌아간다. 그 사이에 흐름이나 시간을 조절하는 등 비동기적인 작업은 <strong>코루틴</strong>을 사용해 구현된다.</p>
</li>
<li><p>코루틴은 메소드의 호출과 반환이 같은 프레임에 완료되어야 하는 일반적인 메소드들과 달리, 작업을 <strong>여러 프레임에 분산</strong>시킬 수 있는 Unity에서 제공하는 메소드이다.</p>
</li>
<li><p>Unity에서 코루틴은 실행을 <strong>일시 중지(<code>yield</code>)</strong>하고 Unity에 <strong>제어권을 반환</strong>한 다음, 다음 프레임에서 <strong>중단된 부분(<code>yield</code>)부터</strong> 계속할 수 있는 메소드이다.</p>
<ul>
<li><code>yield</code> :  <code>yield</code>를 사용하여 메소드의 호출자에게 제어권을 양보할 수 있다.</li>
</ul>
</li>
<li><p>다음과 같은 경우에 코루틴은 사용된다.</p>
<ol>
<li><p><strong>여러 프레임</strong> 단위에 걸쳐 처리해야하는 작업을 해야하는 경우</p>
</li>
<li><p>한 프레임 단위에서 처리하기에는 작업이 <strong>너무 오래 걸리는 경우</strong></p>
</li>
</ol>
</li>
</ul>
<h2 id="코루틴과-비동기asyncawait">코루틴과 비동기(Async/Await)</h2>
<ul>
<li><p><strong>코루틴</strong>은 함수의 실행 흐름 자체를 제어하며, 작업을 멈췄다가 계속하는 방식으로, 결국 <strong>Main Thread</strong>에서 모든 작업이 이루어지기에, 비동기적인 방식이지만 <strong>멀티 스레드는 아니다.</strong></p>
</li>
<li><p><strong>Async/Await</strong>은 <code>await</code> 키워드로 표시되는 비동기 작업 완료 시점에 그 스레드로 돌아와 코드를 이어 실행하는 방식으로, <strong>복수의 스레드</strong>를 사용하는 비동기적 방식이다.</p>
</li>
</ul>
<h1 id="비동기-프로그래밍의-주의사항">비동기 프로그래밍의 주의사항</h1>
<h2 id="데드락deadlock과-레이스-컨디션race-condition">데드락(Deadlock)과 레이스 컨디션(Race Condition)</h2>
<ul>
<li><p>두 개 이상의 스레드가 공유 데이터에 접근하는 과정에서 <code>데드락</code>, <code>레이스 컨디션</code> 같은 동기화 문제가 발생할 수 있다.</p>
</li>
<li><p>이를 <code>락(Lock)</code>, <code>뮤텍스(Mutex)</code>나 <code>세마포어(Semaphore)</code> 등의 동기화 수단을 사용하거나, 애초에 불변하도록 구조를 짜는 것으로 방지해야 한다.</p>
</li>
</ul>
<h2 id="예외-처리">예외 처리</h2>
<ul>
<li><p>비동기 메소드의 예외 흐름</p>
<ul>
<li><p><code>try/catch</code> 구문 안에서 <code>await</code> 키워드를 사용하면, 해당 비동기 호출에서 발생하는 예외도 <code>catch</code> 블록에서 잡을 수 있다.</p>
</li>
<li><p>비동기 메소드 내에서 <code>throw</code>된 예외가 최상위까지 전파될 경우, <code>Task.Exception</code> 또는 <code>AggregateException</code> 형태로 포착된다.</p>
</li>
</ul>
</li>
<li><p>비동기 이벤트 핸들러</p>
<ul>
<li><p>WPF/WinForms 등의 이벤트 핸들러에서 <code>async void</code> 형태로 사용할 때, 예외 처리가 누락되기 쉬우므로 주의한다.</p>
</li>
<li><p>가급적이면 이벤트 핸들러를 <code>async Task</code> 형태로 사용하거나, <code>DispatcherUnhandledException</code> 등의 글로벌 핸들러로 감싼다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="취소와-타임아웃">취소와 타임아웃</h2>
<ul>
<li><p><code>CancellationToken</code></p>
<ul>
<li><p>긴 시간 소요 작업(예: 네트워크 요청, 대용량 파일 처리 등)에서는 취소 기능이 중요하다.</p>
</li>
<li><p>메소드를 <code>async</code>로 만들 때 <code>CancellationToken</code> 매개변수를 받아, 도중에 작업을 중단할 수 있도록 구현한다.</p>
</li>
</ul>
</li>
<li><p>타임아웃</p>
<ul>
<li><p><code>HttpClient</code> 등 외부 I/O 호출 시 타임아웃을 설정해 무한 대기를 방지한다.</p>
</li>
<li><p>필요에 따라 <code>Task.WhenAny</code> 등을 사용해 일정 시간 내 작업 미완료 시 다른 로직으로 진행하는 패턴을 사용할 수 있다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="성능-최적화와-모니터링">성능 최적화와 모니터링</h2>
<ul>
<li><p>초당 수천 건 이상으로 호출 빈도가 매우 많으면 오히려 오버헤드가 커져, 자원 관리에 손해를 볼 수 있다.</p>
</li>
<li><p>비동기 디버깅은 StackTrace가 복잡해질 수 있다. 이에 대비해 성능 모니터링 도구(Profiling)와 로깅 체계를 잘 갖춰야 문제점을 빨리 파악할 수 있다.</p>
</li>
</ul>
<h1 id="코루틴비동기-프로그래밍-베스트-프랙티스">코루틴/비동기 프로그래밍 베스트 프랙티스</h1>
<h2 id="비동기-api-설계-원칙">비동기 API 설계 원칙</h2>
<h3 id="async-메서드-네이밍-가이드">async 메서드 네이밍 가이드</h3>
<ul>
<li><p><code>async</code> 메소드 이름에는 &quot;Async&quot; 접미사를 붙이는 것이 관례이다. 반환형은 보통 <code>Task</code>나 <code>Task&lt;T&gt;</code>로 한다.</p>
</li>
<li><p>여러 단계의 비동기 호출이 연쇄적으로 이어질 경우, <strong>작업 단위를 명확히</strong> 구분하여 재활용 가능하게 구성한다.</p>
</li>
</ul>
<h3 id="cancellation-token-활용">Cancellation Token 활용</h3>
<ol>
<li><p>간단한 작업에서의 CancellationToken 사용 예시</p>
<pre><code class="language-csharp"> using System;
 using System.Threading;
 using System.Threading.Tasks;

 public class CancellationExample
 {
     public async Task DoWorkAsync(CancellationToken cancellationToken)
     {
         Console.WriteLine(&quot;작업 시작...&quot;);

         for (int i = 0; i &lt; 10; i++)
         {
             // 토큰이 취소 상태인지 확인
             if (cancellationToken.IsCancellationRequested)
             {
                 Console.WriteLine(&quot;작업 취소 요청됨.&quot;);
                 // 필요한 정리 로직이 있다면 추가
                 return; 
             }

             // 시뮬레이션용 비동기 대기
             await Task.Delay(500); 
             Console.WriteLine($&quot;작업 진행 중... {i + 1}/10&quot;);
         }

         Console.WriteLine(&quot;작업 완료!&quot;);
     }
 }</code></pre>
<ul>
<li>위 예시 코드에서는 for 루프를 돌며 취소 요청을 계속 확인(<code>cancellationToken.IsCancellationRequested</code>)하고, 취소되면 즉시 메서드를 종료한다.</li>
</ul>
</li>
<li><p><code>HttpClient</code>에서 취소 토큰 사용 예시</p>
<pre><code class="language-csharp"> using System;
 using System.Net.Http;
 using System.Threading;
 using System.Threading.Tasks;

 public class HttpDownloadExample
 {
     private static readonly HttpClient _client = new HttpClient();

     public async Task&lt;string&gt; DownloadStringWithCancellationAsync(string url, CancellationToken cancellationToken)
     {
         // HttpClient의 GetAsync, GetStringAsync 등은 CancellationToken을 받는 오버로드가 있음.
         HttpResponseMessage response = await _client.GetAsync(url, cancellationToken);

         // 응답 상태코드가 성공이 아니면 예외 발생
         response.EnsureSuccessStatusCode();

         // 응답 본문을 문자열로 읽음
         string content = await response.Content.ReadAsStringAsync(cancellationToken);
         return content;
     }
 }</code></pre>
<ul>
<li><p><code>HttpClient</code>의 <code>GetAsync</code>, <code>ReadAsStringAsync</code> 메소드는 취소 토큰을 인자로 받아서, 네트워크 작업 도중에 토큰이 취소 신호를 감지하면 <code>OperationCanceledException</code> 예외를 던진다.</p>
<ul>
<li>호출 측에서는 이 예외를 잡아서 필요한 처리(로그, 정리 등)를 수행할 수 있다.</li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>호출부에서의 사용 예시</p>
<pre><code class="language-csharp">using System;
using System.Threading;
using System.Threading.Tasks;

public class Caller
{
    public async Task RunAsync()
    {
        using var cts = new CancellationTokenSource();

        // 지정된 시간 후 자동 취소: 3초 뒤 취소
        cts.CancelAfter(TimeSpan.FromSeconds(3)); 

        var worker = new CancellationExample();
        try
        {
            // 비동기 작업에 취소 토큰 전달
            await worker.DoWorkAsync(cts.Token); 
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine(&quot;비동기 작업이 취소되었습니다.&quot;);
        }
    }
}</code></pre>
<ul>
<li><p><code>CancellationTokenSource</code> : 취소 요청을 발생시키기 위한 객체</p>
<ul>
<li><code>CancelAfter(TimeSpan)</code>으로 일정 시간 이후 자동으로 <code>Cancel()</code>을 호출해줄 수 있다.</li>
</ul>
</li>
<li><p><code>cts.Token</code> : 실제 작업에 넘길 취소 토큰(<code>CancellationToken</code>)으로, 작업 내부에서 <code>IsCancellationRequested</code> 확인 혹은 API에서 직접 지원하는 방식으로 사용한다.</p>
</li>
<li><p>취소가 발생하면 <code>OperationCanceledException</code>이 발생하고, 이를 <code>try/catch</code>로 처리할 수 있다.</p>
</li>
</ul>
</li>
</ol>
<h2 id="코루틴-사용-시-주의사항">코루틴 사용 시 주의사항</h2>
<ul>
<li><p>Unity에서의 코루틴</p>
<ul>
<li><p><code>IEnumerator</code> 형태의 메소드에 <code>yield return</code>으로 프레임 단위나 특정 조건에서 실행을 제어한다.</p>
</li>
<li><p>코루틴은 Unity 메인 스레드를 기반으로 동작하므로, 무거운 작업(네트워크, 대용량 처리 등)은 코루틴에서 직접 돌기보단 <code>async/await</code> 또는 별도 쓰레드를 사용하는 것이 낫다.</p>
</li>
</ul>
</li>
</ul>
<ul>
<li><p>상태 제어</p>
<ul>
<li>코루틴은 <code>Pause/Resume</code> 제어가 쉽지만, 중단되는 지점이 많으면 프로그램 흐름이 복잡해진다. 각 코루틴의 목적과 중단 조건을 명확히 설계한다.</li>
</ul>
</li>
<li><p>코루틴 정리</p>
<ul>
<li>스크립트나 오브젝트가 파괴(Destroy)되면, 해당 오브젝트의 코루틴도 중단해야 메모리 누수나 예기치 않은 로직 실행을 막을 수 있다.</li>
</ul>
</li>
</ul>
<h2 id="비동기와-코루틴-혼용-전략">비동기와 코루틴 혼용 전략</h2>
<ul>
<li><p>코루틴은 스케줄링, 비동기는 I/O</p>
<ul>
<li><p>코루틴은 게임 루프나 특정 타이밍 제어(프레임에서 매번 조금씩 작업)를 다루기 좋다.</p>
</li>
<li><p>비동기는 I/O 작업(HTTP 요청, DB 쿼리, 파일 I/O 등)을 다루기 좋다.</p>
</li>
<li><p>두 기법을 필요에 따라 적절히 사용하되, 각각의 책임 범위를 분명히 나누어야 한다.</p>
</li>
</ul>
</li>
<li><p>에러 핸들링 일관성</p>
<ul>
<li><p>Unity 코루틴에서 예외가 발생하면 별도 로깅이 없으면 알기 어려울 수 있다.</p>
</li>
<li><p><code>async</code> 메소드와 결합된 경우, 예외 처리를 코루틴 내부와 비동기 메소드 내부 어디서 할지 미리 정해둔다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="코드-품질-개선">코드 품질 개선</h2>
<ul>
<li><p>LINQ, Rx, UniTask 등 활용</p>
<ul>
<li><p>Unity 환경에서 코루틴과 async/await을 쉽게 혼합할 수 있도록 도와주는 UniTask와 같은 라이브러리를 사용하면 코드를 단순화할 수 있다.</p>
</li>
<li><p>Reactive Extensions(Rx)를 통해 이벤트 기반으로 비동기 흐름을 선언형으로 작성할 수도 있다.</p>
</li>
</ul>
</li>
</ul>
<h2 id="유지보수-전략">유지보수 전략</h2>
<ul>
<li><p>단일 책임 원칙(SRP)</p>
<ul>
<li>하나의 비동기 메서드가 너무 많은 일을 하지 않도록 분리한다. 유지보수성과 재사용성을 높이는 핵심.</li>
</ul>
</li>
<li><p>추상화(Abstraction)와 테스트</p>
<ul>
<li>외부 종속(네트워크, 파일 시스템 등)을 인터페이스로 분리하고, 테스트 시 Mock/Stubs 등을 활용하면 비동기 로직도 단위 테스트가 가능하다.</li>
</ul>
</li>
<li><p>리팩토링 주기</p>
<ul>
<li>비동기 메서드가 계속 늘어나면, 내부에서 호출 흐름이 복잡해질 수 있다. 일정 주기로 코드를 검토하고, 로직 분산 또는 통합을 해 준다.</li>
</ul>
</li>
</ul>
<h1 id="정리">정리</h1>
<ul>
<li><p>비동기 프로그래밍은 긴 시간 소요 작업으로 인해 UI나 다른 작업이 멈추지 않도록 하는 핵심 기술로, C#에서는 <code>async/await</code>와 <code>Task</code>가 있다.</p>
</li>
<li><p>코루틴은 <strong>중단과 재개</strong> 매커니즘으로, 게임 엔진 또는 특정 시점 제어가 필요한 환경에서 유용하지만, 일반적인 비동기 프로그래밍과는 설계가 다르다.</p>
</li>
<li><p>비동기를 실제로 적용할 때는 UI 스레드를 정지시키지 않는지와 예외 처리, 동기화 문제(데드락, 레이스 컨디션), 성능 모니터링 등에 각별히 주의해야 한다.</p>
</li>
<li><p><a href="https://chatgpt.com/share/67ac5777-9274-8007-9a33-1ed038162650">비동기 프로그래밍 요약</a></p>
<h1 id="참고">참고</h1>
</li>
<li><p><a href="https://tistory.jeon.sh/59">Unity: 비동기와 코루틴을 혼동하지 않기</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/ko-kr/dotnet/csharp/asynchronous-programming/">MSDN | async 및 await를 사용한 비동기 프로그래밍</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.tasks.task.delay?view=net-8.0">MSDN | Task.Delay 메서드</a></p>
</li>
<li><p><a href="https://insurang.tistory.com/entry/WPF-C-%EC%8B%9C%EA%B0%84-%EB%94%9C%EB%A0%88%EC%9D%B4-%EC%A3%BC%EA%B8%B0-4%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95-Time-Delay-async-await-Task-ThreadSleep">WPF &amp; C# - 시간 딜레이 주기 4가지 방법 ( Time Delay / async await Task / Thread.Sleep )</a></p>
</li>
<li><p><a href="https://kimyc1223.github.io/blog/2024/06/23/TechPost.html">Unity Coroutine과 Task의 차이 비교</a></p>
</li>
<li><p><a href="https://chatgpt.com/share/6776448b-eb40-8007-9a63-17f55fafaebb">ChatGPT o1 | 유니티 환경에서, Monobehaviour를 상속 받지 않는 클래스에서 비동기적 혹은 코루틴과 유사하게 실행되는 함수</a></p>
</li>
<li><p><a href="https://wjunsea.tistory.com/133">[C#] CancellationToken 이해</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.threading.cancellationtoken?view=netframework-4.8">CancellationToken 구조체 | MSDN</a></p>
</li>
<li><p><a href="http://www.simpleisbest.net/post/2013/02/06/About_Async_Await_Keyword_Part_1.aspx">간편한 비동기 프로그래밍:async/await (1)</a></p>
</li>
<li><p><a href="https://velog.io/@sangmin7648/Java-%EC%98%88%EC%99%B8-%EC%A0%84%ED%8C%8C">Java 예외 전파</a></p>
</li>
<li><p><a href="https://learn.microsoft.com/ko-kr/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap">MSDN | .NET의 TAP(작업 기반 비동기 패턴): 소개 및 개요</a></p>
</li>
<li><p><a href="https://sejoung.github.io/2021/01/2021-01-22-csharp_task/">.NET 테스크 기반 비동기 패턴(Task-based async model)의 사용</a></p>
</li>
<li><p><a href="https://jettstream.tistory.com/574#google_vignette">C# 비동기 프로그래밍 TAP이란 무엇인가?</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[코루틴 안의 지연을 특정 이벤트 호출까지로]]></title>
            <link>https://velog.io/@strange_tiger/%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%95%88%EC%9D%98-%EC%A7%80%EC%97%B0%EC%9D%84-%ED%8A%B9%EC%A0%95-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%98%B8%EC%B6%9C%EA%B9%8C%EC%A7%80%EB%A1%9C</link>
            <guid>https://velog.io/@strange_tiger/%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%95%88%EC%9D%98-%EC%A7%80%EC%97%B0%EC%9D%84-%ED%8A%B9%EC%A0%95-%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%98%B8%EC%B6%9C%EA%B9%8C%EC%A7%80%EB%A1%9C</guid>
            <pubDate>Fri, 29 Nov 2024 03:49:42 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li>코루틴 안의 지연을 특정 이벤트 호출까지로 하고 싶다.</li>
<li>Unity에서 코루틴 안의 지연을 특정 이벤트 호출까지로 설정하려면, 이벤트가 발생할 때까지 기다리는 로직을 추가하면 됩니다. 이를 위해 yield return과 이벤트를 결합한 방식을 사용할 수 있습니다. 아래는 이를 구현하는 방법을 단계별로 설명합니다.</li>
</ul>
<h1 id="방법">방법</h1>
<h2 id="1-unityevent와-코루틴-결합하기">1. UnityEvent와 코루틴 결합하기</h2>
<p>Unity의 UnityEvent 또는 C#의 <strong>Action</strong>을 사용하여 이벤트를 호출하고, 코루틴에서 해당 이벤트가 발생할 때까지 기다립니다.</p>
<ul>
<li>코드 예제<pre><code class="language-csharp">using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
</code></pre>
</li>
</ul>
<p>public class EventBasedCoroutine : MonoBehaviour
{
    public UnityEvent onCustomEvent;</p>
<pre><code>private void Start()
{
    StartCoroutine(WaitForEventCoroutine());
}

private IEnumerator WaitForEventCoroutine()
{
    Debug.Log(&quot;Waiting for event...&quot;);

    // 이벤트 발생까지 대기
    yield return WaitForEvent(() =&gt; onCustomEvent);

    Debug.Log(&quot;Event received! Continuing coroutine...&quot;);
}

private IEnumerator WaitForEvent(Func&lt;UnityEvent&gt; eventGetter)
{
    bool eventTriggered = false;

    UnityAction onEventAction = () =&gt; { eventTriggered = true; };

    // 이벤트에 리스너 등록
    eventGetter().AddListener(onEventAction);

    // 이벤트가 발생할 때까지 대기
    yield return new WaitUntil(() =&gt; eventTriggered);

    // 리스너 제거
    eventGetter().RemoveListener(onEventAction);
}

// 예제: 이벤트 호출
private void Update()
{
    if (Input.GetKeyDown(KeyCode.Space)) // 스페이스바를 누르면 이벤트 발생
    {
        Debug.Log(&quot;Event triggered!&quot;);
        onCustomEvent?.Invoke();
    }
}</code></pre><p>}</p>
<pre><code>### 주요 동작
1. 이벤트 리스너 등록:
이벤트가 발생했는지 확인하는 플래그를 변경합니다.
2. WaitUntil 사용:
특정 조건(여기서는 이벤트 플래그)이 참이 될 때까지 코루틴을 멈춥니다.
3. 리스너 제거:
이벤트 리스너를 제거하여 메모리 누수를 방지합니다.
## 2. C#의 Action 사용
UnityEvent 대신 C#의 Action 델리게이트를 사용해도 동일한 방식으로 구현할 수 있습니다.

- 코드 예제
```csharp
using System;
using System.Collections;
using UnityEngine;

public class ActionBasedCoroutine : MonoBehaviour
{
    public Action onCustomAction;

    private void Start()
    {
        StartCoroutine(WaitForActionCoroutine());
    }

    private IEnumerator WaitForActionCoroutine()
    {
        Debug.Log(&quot;Waiting for action...&quot;);

        // Action 발생까지 대기
        yield return WaitForAction(() =&gt; onCustomAction);

        Debug.Log(&quot;Action received! Continuing coroutine...&quot;);
    }

    private IEnumerator WaitForAction(Func&lt;Action&gt; actionGetter)
    {
        bool actionTriggered = false;

        Action onAction = () =&gt; { actionTriggered = true; };

        // Action에 리스너 등록
        actionGetter() += onAction;

        // Action이 발생할 때까지 대기
        yield return new WaitUntil(() =&gt; actionTriggered);

        // 리스너 제거
        actionGetter() -= onAction;
    }

    // 예제: Action 호출
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space)) // 스페이스바를 누르면 Action 호출
        {
            Debug.Log(&quot;Action triggered!&quot;);
            onCustomAction?.Invoke();
        }
    }
}</code></pre><h2 id="3-커스텀-조건을-사용한-지연">3. 커스텀 조건을 사용한 지연</h2>
<p>이벤트 외에 특정 상태나 조건에 따라 코루틴을 지연하려면, 조건을 확인하는 yield return 구문을 사용합니다.</p>
<ul>
<li>코드 예제<pre><code class="language-csharp">private bool isEventTriggered = false;
</code></pre>
</li>
</ul>
<p>private void Start()
{
    StartCoroutine(WaitForConditionCoroutine());
}</p>
<p>private IEnumerator WaitForConditionCoroutine()
{
    Debug.Log(&quot;Waiting for custom condition...&quot;);</p>
<pre><code>// 특정 조건이 참이 될 때까지 대기
yield return new WaitUntil(() =&gt; isEventTriggered);

Debug.Log(&quot;Condition met! Continuing coroutine...&quot;);</code></pre><p>}</p>
<p>// 조건을 변경하여 코루틴 진행
private void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        Debug.Log(&quot;Condition set!&quot;);
        isEventTriggered = true;
    }
}</p>
<pre><code>## 4. 응용: 이벤트와 조건을 결합한 방식
이벤트와 조건을 결합하여 특정 이벤트가 발생하거나, 조건이 만족될 때 코루틴을 재개할 수 있습니다.

- 코드 예제
```csharp
private bool isConditionMet = false;
public UnityEvent onCustomEvent;

private void Start()
{
    StartCoroutine(WaitForEventOrConditionCoroutine());
}

private IEnumerator WaitForEventOrConditionCoroutine()
{
    Debug.Log(&quot;Waiting for event or condition...&quot;);

    yield return new WaitUntil(() =&gt; isConditionMet || IsEventTriggered());

    Debug.Log(&quot;Event or condition met! Continuing coroutine...&quot;);
}

private bool IsEventTriggered()
{
    bool eventTriggered = false;

    UnityAction onEventAction = () =&gt; { eventTriggered = true; };
    onCustomEvent.AddListener(onEventAction);

    // 한 번이라도 실행되면 true 반환
    if (eventTriggered)
    {
        onCustomEvent.RemoveListener(onEventAction);
    }

    return eventTriggered;
}

// 조건을 변경하거나 이벤트를 호출
private void Update()
{
    if (Input.GetKeyDown(KeyCode.Space))
    {
        Debug.Log(&quot;Event triggered!&quot;);
        onCustomEvent?.Invoke();
    }

    if (Input.GetKeyDown(KeyCode.C))
    {
        Debug.Log(&quot;Condition met!&quot;);
        isConditionMet = true;
    }
}</code></pre><h1 id="선택-방법">선택 방법</h1>
<ol>
<li>UnityEvent 방식: Unity의 직관적 이벤트 시스템을 활용하고 싶을 때.</li>
<li>Action 방식: 가볍고 유연한 이벤트 처리가 필요할 때.</li>
<li>조건 기반: 특정 상태나 조건을 직접 처리해야 할 때.</li>
<li>이벤트 + 조건 결합: 이벤트와 조건 중 하나만 만족해도 코루틴을 진행해야 할 때.</li>
</ol>
<ul>
<li>이 방식을 통해 Unity 코루틴의 흐름을 더 유연하게 제어할 수 있습니다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[이벤트 호출마다 다른 함수 호출]]></title>
            <link>https://velog.io/@strange_tiger/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%98%B8%EC%B6%9C%EB%A7%88%EB%8B%A4-%EB%8B%A4%EB%A5%B8-%ED%95%A8%EC%88%98-%ED%98%B8%EC%B6%9C</link>
            <guid>https://velog.io/@strange_tiger/%EC%9D%B4%EB%B2%A4%ED%8A%B8-%ED%98%B8%EC%B6%9C%EB%A7%88%EB%8B%A4-%EB%8B%A4%EB%A5%B8-%ED%95%A8%EC%88%98-%ED%98%B8%EC%B6%9C</guid>
            <pubDate>Fri, 29 Nov 2024 03:45:34 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li><p>이벤트가 <code>Invoke</code> 될때마다 다른 함수가 호출되도록 하고 싶다. 어떤 방법이 가능할까?</p>
</li>
<li><p>Unity에서 이벤트가 <code>Invoke</code>될 때마다 다른 함수가 호출되도록 하려면, 이벤트 핸들러의 동작을 유동적으로 변경할 수 있는 다양한 방법이 있다. 아래에서 각각의 방법을 설명합니다.</p>
</li>
</ul>
<h1 id="방법">방법</h1>
<h2 id="1-다이나믹-리스너-관리">1. 다이나믹 리스너 관리</h2>
<p><code>UnityEvent.RemoveListener</code>와 <code>UnityEvent.AddListener</code>를 사용하여 이벤트를 <code>Invoke</code>하기 전에 다른 리스너를 등록하거나 제거하는 방법입니다.</p>
<ul>
<li>코드 예제<pre><code class="language-csharp">using UnityEngine;
using UnityEngine.Events;
</code></pre>
</li>
</ul>
<p>public class DynamicEventExample : MonoBehaviour
{
    public UnityEvent myEvent = new UnityEvent();
    private int callCount = 0;</p>
<pre><code>private void Start()
{
    // 첫 리스너 등록
    AddNextListener();

    // 여러 번 Invoke 호출
    myEvent.Invoke();
    myEvent.Invoke();
    myEvent.Invoke();
}

private void AddNextListener()
{
    myEvent.RemoveAllListeners(); // 이전 리스너 제거

    if (callCount == 0)
    {
        myEvent.AddListener(Function1);
    }
    else if (callCount == 1)
    {
        myEvent.AddListener(Function2);
    }
    else if (callCount == 2)
    {
        myEvent.AddListener(Function3);
    }

    callCount++; // 다음 호출을 위한 증가
}

private void Function1()
{
    Debug.Log(&quot;Function 1 Called&quot;);
    AddNextListener(); // 다음 리스너로 교체
}

private void Function2()
{
    Debug.Log(&quot;Function 2 Called&quot;);
    AddNextListener(); // 다음 리스너로 교체
}

private void Function3()
{
    Debug.Log(&quot;Function 3 Called&quot;);
    AddNextListener(); // 다음 리스너로 교체
}</code></pre><p>}</p>
<pre><code>## 2. 큐(Queue)를 사용한 함수 호출
함수 호출 순서를 큐로 관리하여 이벤트 호출 시마다 큐의 다음 함수가 실행되도록 합니다.

- 코드 예제
```csharp
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class QueueEventExample : MonoBehaviour
{
    public UnityEvent myEvent = new UnityEvent();
    private Queue&lt;UnityAction&gt; functionQueue = new Queue&lt;UnityAction&gt;();

    private void Start()
    {
        // 함수 등록
        functionQueue.Enqueue(Function1);
        functionQueue.Enqueue(Function2);
        functionQueue.Enqueue(Function3);

        // 이벤트에 처리 함수 등록
        myEvent.AddListener(InvokeNextFunction);

        // 여러 번 Invoke 호출
        myEvent.Invoke();
        myEvent.Invoke();
        myEvent.Invoke();
    }

    private void InvokeNextFunction()
    {
        if (functionQueue.Count &gt; 0)
        {
            UnityAction nextFunction = functionQueue.Dequeue();
            nextFunction.Invoke(); // 다음 함수 실행
        }
        else
        {
            Debug.Log(&quot;No more functions in the queue.&quot;);
        }
    }

    private void Function1()
    {
        Debug.Log(&quot;Function 1 Called&quot;);
    }

    private void Function2()
    {
        Debug.Log(&quot;Function 2 Called&quot;);
    }

    private void Function3()
    {
        Debug.Log(&quot;Function 3 Called&quot;);
    }
}</code></pre><h2 id="3-이벤트-핸들러-동적으로-생성">3. 이벤트 핸들러 동적으로 생성</h2>
<p>이벤트 핸들러를 동적으로 변경하여 매 호출 시 새로운 동작을 지정합니다.</p>
<ul>
<li>코드 예제<pre><code class="language-csharp">using UnityEngine;
using UnityEngine.Events;
</code></pre>
</li>
</ul>
<p>public class DynamicHandlerExample : MonoBehaviour
{
    public UnityEvent myEvent = new UnityEvent();
    private int callCount = 0;</p>
<pre><code>private void Start()
{
    myEvent.AddListener(() =&gt; OnEventCalled(callCount));

    // 여러 번 Invoke 호출
    myEvent.Invoke();
    callCount++;
    myEvent.Invoke();
    callCount++;
    myEvent.Invoke();
}

private void OnEventCalled(int callIndex)
{
    switch (callIndex)
    {
        case 0:
            Debug.Log(&quot;First Function Called&quot;);
            break;
        case 1:
            Debug.Log(&quot;Second Function Called&quot;);
            break;
        case 2:
            Debug.Log(&quot;Third Function Called&quot;);
            break;
        default:
            Debug.Log(&quot;No more functions.&quot;);
            break;
    }
}</code></pre><p>}</p>
<pre><code>## 4. 리스트로 함수 관리
리스트에 여러 함수를 저장하고, 호출될 때마다 리스트에서 순차적으로 함수를 호출합니다.

- 코드 예제
```csharp
using System.Collections.Generic;
using UnityEngine;

public class ListEventExample : MonoBehaviour
{
    public UnityEvent myEvent = new UnityEvent();
    private List&lt;System.Action&gt; functionList = new List&lt;System.Action&gt;();
    private int currentIndex = 0;

    private void Start()
    {
        // 함수 등록
        functionList.Add(Function1);
        functionList.Add(Function2);
        functionList.Add(Function3);

        // 이벤트 등록
        myEvent.AddListener(InvokeNextFunction);

        // 여러 번 Invoke 호출
        myEvent.Invoke();
        myEvent.Invoke();
        myEvent.Invoke();
    }

    private void InvokeNextFunction()
    {
        if (currentIndex &lt; functionList.Count)
        {
            functionList[currentIndex]?.Invoke();
            currentIndex++;
        }
        else
        {
            Debug.Log(&quot;No more functions in the list.&quot;);
        }
    }

    private void Function1()
    {
        Debug.Log(&quot;Function 1 Called&quot;);
    }

    private void Function2()
    {
        Debug.Log(&quot;Function 2 Called&quot;);
    }

    private void Function3()
    {
        Debug.Log(&quot;Function 3 Called&quot;);
    }
}</code></pre><h2 id="5-상태-기반-함수-호출">5. 상태 기반 함수 호출</h2>
<p>이벤트를 호출할 때마다 상태를 바꾸고, 상태에 따라 호출할 함수를 결정합니다.</p>
<ul>
<li>코드 예제<pre><code class="language-csharp">using UnityEngine;
</code></pre>
</li>
</ul>
<p>public class StateEventExample : MonoBehaviour
{
    public UnityEvent myEvent = new UnityEvent();
    private int state = 0;</p>
<pre><code>private void Start()
{
    myEvent.AddListener(CallBasedOnState);

    // 여러 번 Invoke 호출
    myEvent.Invoke();
    myEvent.Invoke();
    myEvent.Invoke();
}

private void CallBasedOnState()
{
    switch (state)
    {
        case 0:
            Function1();
            break;
        case 1:
            Function2();
            break;
        case 2:
            Function3();
            break;
        default:
            Debug.Log(&quot;No more states.&quot;);
            break;
    }

    state++; // 다음 상태로 전환
}

private void Function1()
{
    Debug.Log(&quot;Function 1 Called&quot;);
}

private void Function2()
{
    Debug.Log(&quot;Function 2 Called&quot;);
}

private void Function3()
{
    Debug.Log(&quot;Function 3 Called&quot;);
}</code></pre><p>}</p>
<p>```</p>
<h1 id="어떤-방법을-선택해야-할까">어떤 방법을 선택해야 할까?</h1>
<ol>
<li><code>RemoveListener</code>와 <code>AddListener</code>:
간단한 리스너 교체가 필요할 때.</li>
<li><code>Queue</code> 사용:
함수 호출 순서를 명확히 관리해야 할 때.</li>
<li>다이나믹 핸들러:
상태 기반의 다이나믹한 함수 호출이 필요할 때.</li>
<li>리스트 기반 관리:
여러 함수를 유연하게 추가/제거하거나 순서를 변경해야 할 때.</li>
<li>상태 기반 호출:
상태에 따라 분기 로직이 자연스러운 경우.</li>
</ol>
<ul>
<li>위 방법 중 작업의 복잡도와 요구사항에 따라 적합한 방법을 선택합니다. </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[은행가의 반올림]]></title>
            <link>https://velog.io/@strange_tiger/%EC%9D%80%ED%96%89%EA%B0%80%EC%9D%98-%EB%B0%98%EC%98%AC%EB%A6%BC</link>
            <guid>https://velog.io/@strange_tiger/%EC%9D%80%ED%96%89%EA%B0%80%EC%9D%98-%EB%B0%98%EC%98%AC%EB%A6%BC</guid>
            <pubDate>Mon, 04 Nov 2024 03:01:35 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<ul>
<li><p><code>Math.Round</code> 함수를 사용 중에 예상하는 값과 다른 결과가 나오는 경우가 생겼다.</p>
</li>
<li><p>내가 예상한 결과는 <strong>사사오입</strong>이었다.</p>
</li>
<li><p>결과는 예를 들어, <code>0.5</code>를 반올림 했을 때 <code>1</code>이 아닌 <code>0</code>이 나오는 경우가 생겼다.</p>
</li>
<li><p>그 원인은 <code>Math.Round</code>의 기본 설정이 <strong>은행가의 반올림</strong>이기 때문이었다.</p>
</li>
<li><p>후에 같은 문제가 생기지 않도록 <strong>은행가의 반올림</strong>과 <strong>Math.Round</strong> 등의 반올림 기법을 정리한다.</p>
<h1 id="은행가의-반올림">은행가의 반올림</h1>
</li>
<li><p>은행가의 반올림이란, 0.5이하는 버리고 0.5이상이면 올리는데, 정확하게 0.5이면 <strong>가장 가까운 짝수</strong>로 올리거나 내리는 것이다.</p>
</li>
<li><p>가령 12.5에서 0.5는 버려지고 12로 만들지만 13.5는 0.5를 더하여 14가 된다.</p>
</li>
<li><p>일반적으로 사용하는 사사오입과 달라 비합리적으로 보이지만, <strong>통계적으로 더 정확한 방법</strong>이라고 한다. 아래 설명을 보자.</p>
<blockquote>
<p>가령 12.0부 터 13.0사이를 0.1씩의 간격으로 나누면 9개의 값이 들어갑니다.</p>
</blockquote>
</li>
</ul>
<p>12.1, 12.2, 12.3, 12.4, 12.5, 12.6, 12.7, 12.8, 12.9 그리고 이 값들은 반올림의 대상이 됩니다.
상식적인 반올림이라면 9개의 숫자 중 5개는 올리고 4개는 버리게 됩니다.
그러나 이 방법은 공평하지 않다.
1/9만큼 한쪽은 더 가지고 한쪽은 부족하게 됩니다.
그러나 0.5에서 가장 가까운 짝수로 옮기도록 하게 되면 어떻게 될까요?
12.0 부터 14.0까지 18개의 반올림 대상이 생기고 버리는 쪽이나 올리는 쪽 모두 9개의 숫자를 나누어 갖게 됩니다.
따라서 한쪽에 치우치지 않는 공평한 셈이 됩니다.</p>
<ul>
<li>경제학은 물론, 프로그래밍에서도 많이 사용된다. 특히, 앞으로 설명할 C#의 반올림은 기본적으로 <strong>은행가의 반올림</strong>을 적용한다.</li>
</ul>
<h1 id="mathround">Math.Round</h1>
<ul>
<li><p>값을 가장 가까운 정수나 지정된 소수 자릿수로 반올림한다.</p>
</li>
<li><p>C#의 <code>System</code> 네임스페이스로 사용이 가능하다.</p>
</li>
<li><p><code>double</code> 인수를 <code>double</code> 결과값으로 반환한다.</p>
</li>
<li><p>두 가지 반올림 규칙을 지원한다.</p>
<ul>
<li><p><code>MidpointRounding.AwayFromZero</code> : 일반적인 사사오입 규칙이다.</p>
</li>
<li><p><code>MidpointRounding.ToEven</code> : 은행가의 반올림을 적용한다. 기본적으로 적용된다.</p>
</li>
</ul>
</li>
<li><p>소수점 아래 반올림하는 자릿수를 정할 수 있다.</p>
</li>
</ul>
<h1 id="mathfround">Mathf.Round</h1>
<ul>
<li><p>값을 가장 가까운 정수로 반올림한다.</p>
</li>
<li><p>Unity에서 기본적으로 지원한다. </p>
</li>
<li><p><code>float</code> 인수를 <code>float</code> 결과값으로 반환한다.</p>
</li>
<li><p>기본적으로 은행가의 반올림을 적용한다.</p>
</li>
<li><p>소수점 아래 반올림하는 자릿수를 정할 수 없어, 아래 방법을 사용할 수 있다.</p>
<pre><code class="language-csharp">float a = Mathf.Round(3.56f); // 4.0f
float a = Mathf.Round(3.56f * 10) * 0.1f; // 3.6f</code></pre>
</li>
</ul>
<h1 id="참고">참고</h1>
<ul>
<li><a href="https://youtu.be/D87AE7QtenU?feature=shared">[코딩표준] 반올림도 마음대로 못한다고? Round() 함수 사용 금지 이유!</a></li>
<li><a href="https://learn.microsoft.com/ko-kr/dotnet/api/system.math.round?view=net-8.0">Math.Round 메서드 | MSDN</a></li>
<li><a href="https://blog.naver.com/ociramma13159/222658621444">Math.Round(2.5)가 3 대신 2를 반환하는 이유는 무엇입니까?</a></li>
<li><a href="https://shinjguk.com/archives/621">c# round와 반올림</a></li>
<li><a href="https://www.freeism.co.kr/wp/archives/1792">은행가의 반올림 (Banker’s rounding)</a></li>
<li><a href="https://www.officetutor.co.kr/board/faq_lib/frm_vba_content.asp?page=1&amp;idx=20">은행원의 반올림은 무죄인가?</a></li>
<li><a href="https://blog.nullbus.net/98">Math.Round의 함정?</a></li>
<li><a href="https://iygames.tistory.com/3">[Unity3D] 유니티 C#에서 반올림,올림,내림 사용하기 + 원하는 ...</a></li>
<li><a href="https://docs.unity3d.com/6000.0/Documentation/ScriptReference/Mathf.Round.html">Mathf.Round | Unity Documentation</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>