<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>b_h_b.log</title>
        <link>https://velog.io/</link>
        <description>게임 개발 꿈나무</description>
        <lastBuildDate>Fri, 14 Jun 2024 13:17:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>b_h_b.log</title>
            <url>https://velog.velcdn.com/images/b_h_b/profile/db6fbbb8-3c6b-4e3f-9373-b2134288a7fe/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. b_h_b.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/b_h_b" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[내일배움캠프 9주차 5일차 TIL - 유니티 JSON]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-9%EC%A3%BC%EC%B0%A8-5%EC%9D%BC%EC%B0%A8-TIL-%EC%9C%A0%EB%8B%88%ED%8B%B0-JSON</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-9%EC%A3%BC%EC%B0%A8-5%EC%9D%BC%EC%B0%A8-TIL-%EC%9C%A0%EB%8B%88%ED%8B%B0-JSON</guid>
            <pubDate>Fri, 14 Jun 2024 13:17:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>유니티 심화 주차 강의 진도 나가기</li>
<li>동적 생성 특강 듣기</li>
<li>JSON 복습하기</li>
</ul>
<hr>
<hr>
<h1 id="유니티-json">유니티 JSON</h1>
<h2 id="개념">개념</h2>
<blockquote>
<p>JSON은 JavaScript 객체 문법으로 구조화된 데이터를 표현하기 위한 문자 기반 표준 포맷으로 웹이나 네트워크에서 서버와 클라이언트 사이에 데이터를 주고 받을 때 주로 사용된다.</p>
</blockquote>
<hr>
<hr>
<h2 id="특징">특징</h2>
<blockquote>
</blockquote>
<ul>
<li>JSON의 데이터는 Key와 Value의 쌍으로 이루어진 데이터로 저장된다.<pre><code class="language-c">{
  &quot;이름&quot;: &quot;홍길동&quot;,
  &quot;나이&quot;: 25,
  &quot;성별&quot;: &quot;남성&quot;,
  &quot;직업&quot;: &quot;개발자&quot;,
  &quot;주소&quot;: {
      &quot;도시&quot;: &quot;서울&quot;,
      &quot;우편번호&quot;: &quot;12345&quot;
  },
  &quot;취미&quot;: [&quot;독서&quot;, &quot;음악감상&quot;, &quot;여행&quot;]
}</code></pre>
</li>
<li>다른 언어에서도 JSON을 읽고 쓸 수 있는 기능이 제공된다.
(JSON이 언어로부터 독립적인 데이터 포맷이기 때문)<pre><code class="language-c">// 사용 가능한 언어
JavaScript, Python, Java, C#, PHP, Ruby, Swift ... 그외 등등</code></pre>
</li>
<li>주석을 넣을 수 없다</li>
</ul>
<hr>
<hr>
<h2 id="장단점">장단점</h2>
<blockquote>
</blockquote>
<h3 id="장점">장점</h3>
<ul>
<li>텍스트를 사용하기 때문에 사람이 이해하기 쉽다.</li>
<li>직렬화와 비직렬화 함수를 통해서 변환이 편하다.</li>
</ul>
<hr>
<h3 id="단점">단점</h3>
<ul>
<li>작은 문법 오류에도 민감하게 반응한다.</li>
</ul>
<hr>
<hr>
<h2 id="사용-방법">사용 방법</h2>
<h3 id="기본적으로-실험해볼-데이터들">기본적으로 실험해볼 데이터들</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">// 기본적인 데이터 구조와 리스트, Dictionary
public class JsonTestClass
{
    public string name;
    public int health;
    public float speed;
    public List&lt;string&gt; itemList = new List&lt;string&gt;();
    public Dictionary&lt;string, float&gt; potionList = new Dictionary&lt;string, float&gt;();
&gt;
    public JsonTestClass()
    {
        name = &quot;르탄&quot;;
        health = 100;
        speed = 5f;
        itemList.Add(&quot;스파르타의 창&quot;);
        itemList.Add(&quot;스파르타의 방패&quot;);
        potionList.Add(&quot;회복 물약&quot;, 4);
        potionList.Add(&quot;마나 물약&quot;, 3);
    }
&gt;
    public void Print()
    {
        Debug.Log($&quot;이름: {name}&quot;);
        Debug.Log($&quot;체력: {health}&quot;);
        Debug.Log($&quot;속도: {speed}&quot;);
        foreach (string item in itemList)
        {
            Debug.Log($&quot;소지한 아이템 {item}&quot;);
        }
        foreach (var data in potionList)
        {
            Debug.Log($&quot;포션 목록 - {data.Key} : {data.Value}&quot;);
        }
    }
}
&gt;
// Vector3
public class JsonVector
{
    public Vector3 vector3 = new Vector3(1f, 1f, 1f);
}
&gt;
// MonoBehaviour
public class TestMono : MonoBehaviour
{
    public int i = 10;
    public Vector3 v3 = new Vector3(2f, 2f, 2f);
}</code></pre>
<hr>
<hr>
<h3 id="newtonsoft-json으로-테스트">Newtonsoft JSON으로 테스트</h3>
<blockquote>
</blockquote>
<h4 id="코드">코드</h4>
<pre><code class="language-c">// 기본 데이터 구조 직렬화
JsonTestClass jTest1 = new JsonTestClass();
string jsonData = JsonConvert.SerializeObject(jTest1); // 오브젝트를 매개변수로 전달
Debug.Log(jsonData);
&gt;
// 기본 데이터 구조 역직렬화
JsonTestClass jTest2 = JsonConvert.DeserializeObject&lt;JsonTestClass&gt;(jsonData);
jTest2.Print();
&gt;
// MonoBehaviour 직렬화
GameObject obj = new GameObject();
obj.AddComponent&lt;TestMono&gt;();
Debug.Log(JsonConvert.SerializeObject(obj));
&gt;
// Vector3 직렬화
JsonVector jVector = new JsonVector();
Debug.Log(JsonConvert.SerializeObject(jVector));</code></pre>
<hr>
<h4 id="결과">결과</h4>
<p>-
<img src="https://velog.velcdn.com/images/b_h_b/post/3c743826-8b62-45ce-be67-24f29c2cc7bb/image.png" alt=""></p>
<ul>
<li>기본 데이터 구조 직렬화<blockquote>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/f0bfbb6b-a216-4191-ab23-74e613b2ee7d/image.png" alt=""></li>
<li>기본 데이터 구조 역직렬화<blockquote>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/0e887b65-506d-46c3-9edc-7db82a20f546/image.png" alt=""></li>
<li>MonoBehaviour 클래스 자기 참조 루프<ul>
<li>MonoBehaviour를 상속받는 클래스를 직렬화할 때에 순환 구조를 해결해도 다른 예외는 해결책이 없다. 그래서 상속받지 않는 클래스가 대신 값을 받아오거나 다른 매개 변수를 통해서 직렬화해줘야한다.<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/47a790db-34c9-43e1-8d46-b442dd028bcf/image.png" alt=""></li>
<li>Vector3 normalized 루프<blockquote>
</blockquote>
<ul>
<li>Vector3를 어거지로 정상 직렬화하고 싶다면..<pre><code class="language-c">JsonVector jVector = new JsonVector();
JsonSerializerSettings setting = new JsonSerializerSettings(); // setting을 건들여준다.
setting.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
Debug.Log(JsonConvert.SerializeObject(jVector));</code></pre>
<img src="https://velog.velcdn.com/images/b_h_b/post/ce126cb4-edb4-43e5-aa05-ad09a11fde97/image.png" alt=""></li>
</ul>
</li>
<li>위와 같이 Loop 설정을 바꿔서 직렬화할 수 있지만, 문제는 좌표가 아닌 다른 것들도 다 딸려서 저장되기 때문에 사용하지 않는 편이다.</li>
<li>그래서 MonoBehaviour를 상속받는 클래스처럼 따로 특정값만 받아서 직렬화해주는 방식을 사용한다.</li>
</ul>
<hr>
<hr>
<h3 id="유니티-기본-제공-json-utility로-테스트">유니티 기본 제공 Json Utility로 테스트</h3>
<blockquote>
<h4 id="코드-1">코드</h4>
</blockquote>
<pre><code class="language-c">// 기본적인 데이터 구조 직렬화
 JsonTestClass jTest1 = new JsonTestClass();
 string jsonData = JsonUtility.ToJson(jTest1);
 Debug.Log(jsonData);
&gt;
// 기본적인 데이터 구조 역직렬화
 JsonTestClass jTest2 = JsonUtility.FromJson&lt;JsonTestClass&gt;(jsonData);
 jTest2.Print();
&gt;
// Vector3 직렬화
 JsonVector jVector = new JsonVector();
 string jsonVector = JsonUtility.ToJson(jVector);
 Debug.Log(jsonVector);
&gt;
// Mono 직렬화
 GameObject obj = new GameObject();
 var test1 = obj.AddComponent&lt;TestMono&gt;();
 test1.i = 100;
 test1.v3 = new Vector3(3f, 3f, 3f);
 /// 직렬화 할 때는 게임 오브젝트가 아니라 해당 클래스 컴포넌트를 직접적으로 가져와줘야한다.
 string jsonData2 = JsonUtility.ToJson(obj.GetComponent&lt;TestMono&gt;());
 Debug.Log(jsonData2);
&gt;
// Mono 역직렬화
 GameObject obj2 = new GameObject();
 // 기존 역직렬화와 다르게 Overwrite를 사용한다
 JsonUtility.FromJsonOverwrite(jsonData, obj2.AddComponent&lt;TestMono&gt;());
 // FromJsonOverwrite - 새로운 오브젝트를 만들지 않고, 기존에 있는 오브젝트에 클래스의 변수값을 덮어씌우는 처리를 한다.</code></pre>
<hr>
<h4 id="결과-1">결과</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/ed6264fd-4dee-4d5d-8251-77cc16b77354/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li>보면은 Dictionary가 Serialize 되지 않은 모습을 볼 수 있다. 
(Json Utility는 Dictionary와 클래스 Serialize를 지원하지 않는다.)</li>
<li><blockquote>
<p>클래스는 [System.Serializable]로 해결 가능하지만, </p>
</blockquote>
</li>
<li><blockquote>
<p>Dictionary는 아예 지원하지 않는다. (외부 라이브러리를 사용해야된다;)</p>
</blockquote>
</li>
<li>Vector3나 MonoBehaviour를 상속받는 클래스의 오브젝트를 직렬화할 때 정상적으로 직렬화된다.</li>
</ul>
<hr>
<hr>
<h3 id="테스트로-알아낸-것">테스트로 알아낸 것</h3>
<blockquote>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/d16c671e-65ed-46cb-9b6f-9caa74cf461e/image.png" alt=""></p>
</blockquote>
<hr>
<hr>
<h3 id="파일을-만들어서-저장하고-읽어오기">파일을 만들어서 저장하고 읽어오기</h3>
<blockquote>
</blockquote>
<h4 id="파일을-만들어서-저장하기">파일을 만들어서 저장하기</h4>
<pre><code class="language-c">// 파일 경로 지정 (File 모드 읽거나 만들거나)
 FileStream saveStream = new FileStream(Application.dataPath + &quot;/test.json&quot;, FileMode.OpenOrCreate);
 // 데이터를 만들어서 직렬화
 JsonTestClass jTest1 = new JsonTestClass();
 string jsonSaveData = JsonConvert.SerializeObject(jTest1);
 // 문자열인 Json 데이터를 인코딩 UTF8의 GetBytes 함수로 Byte 배열로 만들어준다. (파일 저장 때문에)
 byte[] saveData = Encoding.UTF8.GetBytes(jsonSaveData);
 // 쓰고 닫기
 saveStream.Write(saveData, 0, saveData.Length);
 saveStream.Close();</code></pre>
<h4 id="저장된-결과">저장된 결과</h4>
<p> <img src="https://velog.velcdn.com/images/b_h_b/post/8019ce18-3746-45d2-8e47-a174481aa0b3/image.png" alt=""></p>
<ul>
<li>잘 저장된 모습을 볼 수 있다. (Application.dataPath면 Assets 파일에다 저장될 것이다.)</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/066d8943-09dd-42ac-8df8-66cd7e952ad5/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="만든-파일을-읽어오기">만든 파일을 읽어오기</h4>
<pre><code class="language-c">// 지정된 파일 열기 (FileMode.Open)
FileStream loadStream = new FileStream(Application.dataPath + &quot;/test.json&quot;, FileMode.Open);
// 생성된 파일 stream에서 Read로 데이터를 읽어드린 다음 읽은 데이터를 반대로 String으로 인코딩해준다.
byte[] loadData = new byte[loadStream.Length];
loadStream.Read(loadData, 0, loadData.Length);
loadStream.Close();
string jsonLoadData = Encoding.UTF8.GetString(loadData);
// 그 다음에 이 문자열을 DeSerialize해준다.
JsonTestClass jTest2 = JsonConvert.DeserializeObject&lt;JsonTestClass&gt;(jsonLoadData);
jTest2.Print();</code></pre>
<h4 id="불러서-읽은-결과">불러서 읽은 결과</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/0352262b-b2f1-4445-9098-97033075d038/image.png" alt=""></li>
<li>기본적인 데이터 구조들이 불러와진 걸 볼 수 있다.<blockquote>
</blockquote>
이와 같이 게임 데이터를 로컬에 저장하고 불러오는 세이브 기능을 구현해볼 수 있다.</li>
</ul>
<hr>
<hr>
<h3 id="추가적으로">추가적으로</h3>
<blockquote>
<p>Json 데이터를 클래스로 만드는 방법이 있는데
Json 2 C#를 검색하면 Json 데이터를 읽고 자동으로 클래스로 만들어주는 사이트들이 있다.</p>
</blockquote>
<hr>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
<h3 id="베르의-게임-개발-유튜브"><a href="https://www.youtube.com/watch?v=N9r2ju3xJGk">베르의 게임 개발 유튜브</a></h3>
</blockquote>
<h3 id="json으로-작업하기"><a href="https://developer.mozilla.org/ko/docs/Learn/JavaScript/Objects/JSON">JSON으로 작업하기</a></h3>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>저번에 Visual Studio로만 하는 JSON 활용법과 Unity에서 하는 JSON 활용법이 달라서 신기했다. 추후에 가능하다면 JSON을 통한 멀티 플레이어 기능 구현을 하는 방법을 알아둬야겠다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 8주차 2일차 TIL - 도주 AI]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-8%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-TIL-%EB%8F%84%EC%A3%BC-AI</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-8%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-TIL-%EB%8F%84%EC%A3%BC-AI</guid>
            <pubDate>Wed, 05 Jun 2024 08:53:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>팀프로젝트 진행하기 ( 토끼 만들기 )</li>
<li>AI 내비게이터를 좀 더 깊게 파보기</li>
</ul>
<hr>
<p>오늘은 AI 내비게이터를 활용한 도주 AI를 만들어볼 생각이다.</p>
<hr>
<h1 id="도주-ai">도주 AI</h1>
<h2 id="목표">목표</h2>
<blockquote>
<ul>
<li>기본적으로는 플레이어로 부터 잘 멀어져야한다.</li>
</ul>
</blockquote>
<ul>
<li>모서리에 붙었을 경우에 대한 이동 처리도 생각해줘야한다.</li>
<li>도주에 어느 정도 랜덤성을 부여해줘야한다.</li>
</ul>
<hr>
<hr>
<h2 id="기초적인-프레임워크">기초적인 프레임워크</h2>
<blockquote>
</blockquote>
<pre><code class="language-c"> // 필요한 컴포넌트
 protected Animator animator;
 protected NavMeshAgent agent;
&gt;
 // 플레이어 거리 구하기
 protected float playerDistance;
&gt;
 // 상태
 protected AIState aiState;
&gt;
 // SO
 public AnimalSO statSO;
&gt;
 void Awake()
 {
     agent = GetComponent&lt;NavMeshAgent&gt;();
     animator = GetComponent&lt;Animator&gt;();
 }
&gt;
 void Start()
 {
     ChangeState(AIState.Idle);
 }
&gt;
 protected virtual void Update()
 {
     if (aiState == AIState.Dead) { return; }
 &gt;    
     // 플레이어 거리 감지
     playerDistance = (transform.position - CharacterManager.Instance.Player.transform.position).sqrMagnitude; // sqr을 활용했으니 제곱을 하는 걸 잊지 맙시다.
&gt;
     animator.SetBool(&quot;Moving&quot;, aiState != AIState.Idle);
&gt;
     switch (aiState)
     {
         case AIState.Idle:
             // IdleState();
             break;
         case AIState.Flee:
             FleeState();
             break;
     }
&gt;
     animator.speed = agent.speed / statSO.walkSpeed;
 }
&gt;
 // 상태 변환
 protected virtual void ChangeState(AIState state)
 {
     aiState = state;
&gt;
     switch (aiState)
     {
         case AIState.Idle:
             agent.isStopped = true;
             agent.speed = statSO.walkSpeed;
             break;
         case AIState.Flee:
             agent.isStopped = false;
             agent.speed = statSO.runSpeed;
             break;
         case AIState.Dead: // 초기화 작업
             agent.isStopped = true;
             agent.speed = 0f;
             animator.speed = 1f;
             agent.ResetPath();
             animator.SetBool(&quot;Dead&quot;, true);
             _collider.isTrigger = true; // 통과 시키게 하기 위해서
             Invoke(&quot;Destroy&quot;, 30f);
             break;
     }
 }
 &gt;
 ... IdleState 부분 생략...
 &gt;
 // 도주 상태 시
 void FleeState()
 {
     if (agent.remainingDistance &lt; 1f)
     {
         agent.SetDestination(NewFleePoint());
     }
     else
     {
         ChangeState(AIState.Idle);
     }
 }
&gt;
// 도주 목적지 설정
 Vector3 NewFleePoint()
 {
 &gt; 목적지 설정 코드
 }
&gt;
// 술래잡기 테스트용
 private void OnCollisionEnter(Collision collision)
 {
     if (collision.gameObject.CompareTag(&quot;Player&quot;))
     {
         if (aiState == AIState.Dead) { return; }
         ChangeState(AIState.Dead);
     }
 }
&gt;
 protected virtual void Destroy()
 {
     Destroy(this.gameObject);
 }</code></pre>
<ul>
<li>스파르타 코딩클럽에서 제공된코드를 살짝 고쳐줬다.</li>
</ul>
<hr>
<hr>
<h2 id="구현하는-과정">구현하는 과정</h2>
<blockquote>
</blockquote>
<h3 id="반대-방향으로만-도망">반대 방향으로만 도망</h3>
<h4 id="코드">코드</h4>
<pre><code class="language-c">    void FleeState()
    {
        if (agent.remainingDistance &lt; 1f)
        {
            agent.SetDestination(NewFleePoint());
        }
        else
        {
            ChangeState(AIState.Wandering);
        }
    }
&gt;
    Vector3 NewFleePoint()
    {
        Vector3 dir = transform.position - CharacterManager.Instance.Player.transform.position.normalized;
        return transform.position + dir;
    }</code></pre>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/07b93db9-7ef9-4190-9d11-1d8bd0899d60/image.gif" alt=""></p>
<ul>
<li>모서리를 만나면 너무 약하다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="랜덤으로-돌리지만-특정-부분만-거르기">랜덤으로 돌리지만 특정 부분만 거르기</h3>
<h4 id="코드-1">코드</h4>
<pre><code class="language-c">    void FleeState()
    {
        if (agent.remainingDistance &lt; 1f)
        {
            agent.SetDestination(NewFleePoint());
        }
        else
        {
            ChangeState(AIState.Wandering);
        }
    }
&gt;
    Vector3 NewFleePoint()
    {
        NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * statSO.safeDistance), out NavMeshHit hit, statSO.safeDistance, NavMesh.AllAreas);
        int i = 0;
        while (GetDestinationAngle(hit.position) &gt; 90 || playerDistance &lt; statSO.safeDistance)
        {
            NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * statSO.safeDistance), out hit, statSO.safeDistance, NavMesh.AllAreas);
            i++;
            if (i == 30)
                break;
        }
        return hit.position;
    }
&gt;
    float GetDestinationAngle(Vector3 targetPos)
    {
        return Vector3.Angle(transform.position - CharacterManager.Instance.Player.transform.position, transform.position + targetPos);
    }</code></pre>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/d853da04-e61d-4264-b233-8d1f11be5550/image.gif" alt=""></p>
<ul>
<li>전보다 낫지만 뭔가 보충이 좀 더 필요해보인다. 
플레이어에게 꼴아박는 경우가 있다;<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="최종-개선">최종 개선</h3>
<ul>
<li>FleeState에 조건을 추가하여 좀 더 능동적인 도주 패턴을 덧붙여줬다.<h4 id="코드-2">코드</h4>
<pre><code class="language-c">  void FleeState()
 {
     float destinationDistance = (agent.destination - CharacterManager.Instance.Player.transform.position).sqrMagnitude;
// 조건에다가 목적지가 플레이랑 가깝지 않은 지 그리고 당장 플레이어와 너무 가깝지 않은지에 따른 조건을 추가해뒀다.
     if (agent.remainingDistance &lt; 1f || destinationDistance &lt; statSO.safeDistance * statSO.safeDistance || playerDistance &lt; 1f)
     {
         agent.SetDestination(NewFleePoint());
     }
     else
     {
         ChangeState(AIState.Wandering);
     }
 }
... 아랫 부분은 랜덤 방향 도주와 동일하다.</code></pre>
</li>
</ul>
<hr>
<hr>
<h2 id="개선-후-결과">개선 후 결과</h2>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/c60a8d7c-08ef-498a-b976-9bea5a45a1e0/image.gif" alt=""></p>
<ul>
<li>빨간색이 랜덤 도주, 파란색이 추가적인 조건을 덧붙여준 토끼다</li>
<li>체감상 느끼기로는 그냥 뭉쳐있으면 도주 능력이 어느 정도 약해질 수 밖에 없는 것 같다.</li>
<li>실제 적용으로는 다를 수도 있으니, 되도록이면 코드는 간결하게 만들수록 좋을 것이다.</li>
</ul>
<hr>
<hr>
<h2 id="오늘-알게된-코드">오늘 알게된 코드</h2>
<blockquote>
</blockquote>
<h3 id="vector">Vector</h3>
<h4 id="sqrmagnitude">.sqrMagnitude</h4>
<ul>
<li>루트를 씌우지 않은 벡터의 스칼라값으로 기존 magnitude보다 연산은 빠르지만 조건 덧붙여줄 때에는 조건을 제곱해줘야한다.<h3 id="navmeshagent">NavMeshAgent</h3>
<h4 id="findclosetedgeout-navmeshhit-edgehit">.FindClosetEdge(out NavMeshHit edgeHit)</h4>
</li>
<li>현재 위치에서 가장 가까운 NavMesh의 바깥 테두리 위치를 가져온다.</li>
</ul>
<hr>
<hr>
<hr>
<h1 id="느낀점">느낀점</h1>
<blockquote>
<p>확실히 한 사람이 여러 역할을 분담받는 것보다는 파트를 나눠 한 사람씩 역할을 맡으니까. 좀 더 세부적인 사항을 신경 쓸 수 있어서 좋은 것 같다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 8주차 1일차 TIL - AI 내비게이터]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-8%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-AI-%EB%82%B4%EB%B9%84%EA%B2%8C%EC%9D%B4%ED%84%B0</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-8%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-AI-%EB%82%B4%EB%B9%84%EA%B2%8C%EC%9D%B4%ED%84%B0</guid>
            <pubDate>Tue, 04 Jun 2024 00:31:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>팀 프로젝트 기획 후 진행</li>
<li>AI 내비게이터 배우기</li>
</ul>
<hr>
<hr>
<h1 id="ai-내비게이터">AI 내비게이터</h1>
<h2 id="개념">개념</h2>
<blockquote>
<p>유니티의 기능 AI 길찾기 기능 중 하나로 런타임 이전에 이동 가능한 영역을 지정하고, Nav Mesh Agent 컴포넌트를 활용하여 해당 AI에게 길찾기 능력을 부여해주는 기능이다.</p>
</blockquote>
<h2 id="특징">특징</h2>
<blockquote>
</blockquote>
<h3 id="navigation-mesh-네비게이션-매쉬">Navigation Mesh (네비게이션 매쉬)</h3>
<ul>
<li>3D 공간을 그리드로 나누어 이동 가능한 영역과 불가능한 영역을 구분하는 매쉬입니다.
캐릭터는 이 영역을 기반으로 경로를 계산해서 이동합니다.<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/11a4c687-a535-47f7-8503-5f159d701da1/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/207a6c2f-18a9-4d9b-b6fd-0a4415309b63/image.png" alt=""></li>
<li>하나는 움직이지 않는 지형을 위한 영역 지정이고 또 다른 하나는 움직이는 오브젝트를 위한 영역 지정이다.<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="pathfinding경로-탐색">Pathfinding(경로 탐색)</h3>
<ul>
<li>캐릭터의 현재 위치에서 목표 지점까지 가장 적절한 경로를 찾는 알고리즘입니다.</li>
<li>주로 A* 알고리즘 등이 사용되며, 지정된 목표 위치까지 최단 경로를 탐색합니다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="steering-behavior-스티어링-동작">Steering Behavior (스티어링 동작)</h3>
<ul>
<li>캐릭터나 NPC가 경로를 따라 이동할 때보다 자연스러운 동작을 구현하는데 사용됩니다.</li>
<li>동적으로 캐릭터의 이동 방향과 속력을 조정하여 부드럽고 현실적인 이동을 시뮬레이션 합니다.<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/a321486d-1fc6-4b70-bdd6-ee906dccd1af/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="obstacle-avoidance-장애물-피하기">Obstacle Avoidance (장애물 피하기)</h3>
<ul>
<li>캐릭터가 이동 중에 장애물과 충돌하지 않도록 하는 기술입니다.
각종 센서나 알고리즘을 사용하여 장애물을 감지하고 피하는 동작을 수행합니다.<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/73de2bac-8cbb-413e-b7ac-f1b8fc608fa8/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="local-avoidance-근접-회피">Local Avoidance (근접 회피)</h3>
<ul>
<li>여러 캐릭터나 NPC가 서로 충돌하지 않도록 하는 기술입니다.
캐릭터들 사이의 거리를 유지하거나 회피 동작을 수행하여 서로 부딪히지 않도록 합니다.</li>
</ul>
<h2 id="사용하는-방법">사용하는 방법</h2>
<blockquote>
<h2 id="navmesh-영역-정하기">NavMesh 영역 정하기</h2>
</blockquote>
<h3 id="1-내비게이션-obsolete">1. 내비게이션 (Obsolete)</h3>
<h4 id="들어가기-전에-기초-설명">들어가기 전에 기초 설명</h4>
<h4 id="agents">Agents</h4>
<ul>
<li>길 찾기를 수행할 Agent들의 Obstacle Avoidance를 설정하고 모으는 리스트이다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/eb345ed4-f1d8-4c35-b3ee-bc17a56a5b25/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="areas">Areas</h4>
<ul>
<li>각 영역의 가중치 값을 정하는데 사용하는 항목이다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/2535e610-05a0-4eae-9a3b-b589aee4d58c/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="bake">Bake</h4>
<ul>
<li>모든 기초 설정이 완료되었다면, Bake를 하여 Agents들의 이동 영역을 산출해준다.
<img src="https://velog.velcdn.com/images/b_h_b/post/03b9d29e-47fa-4b1c-8271-8e8eb2f8e6c7/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="object">Object</h4>
<ul>
<li>선택한 오브젝트들에게 Area를 부여해주는 곳이다</li>
<li>Navigation Static을 선택해주지 않으면, 해당 오브젝트에게서 이동 영역을 산출해줄 수 없다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/63a0a5aa-c401-43e5-9528-3ce8e0c1da07/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<h4 id="이동-가능-영역-bake하기">이동 가능 영역 Bake하기</h4>
<ul>
<li>내비게이션(Obsolete) 창 열기<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/5f15729f-bbec-4e83-afea-698ed92436ff/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>Object를 선택하고 Inspector창에 있는 Navigation (Obsolete) 창을 선택한다.
<img src="https://velog.velcdn.com/images/b_h_b/post/54e686d5-2bab-4c65-8f20-5b0563240b02/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li>Bake를 하기 전에 Static을 활성화하고, 원하는 Area를 설정한다.
<img src="https://velog.velcdn.com/images/b_h_b/post/63a0a5aa-c401-43e5-9528-3ce8e0c1da07/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li>산출하고자 하는 오브젝트들에게 Object 설정을 해줬다면 Bake를 해주자
<img src="https://velog.velcdn.com/images/b_h_b/post/64f28345-8a9c-45b5-8727-3b2d3a09dbe7/image.png" alt=""></li>
<li>필자는 이동영역이 붕뜨는 것을 방지하기 위해서 Radius를 0.1로 설정하고 Bake한다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li>아래와 같이 보이긴한데 흐릿하게 보인다면 잘 산출된 것이다.
<img src="https://velog.velcdn.com/images/b_h_b/post/5f6b3e5c-586f-4bd6-9cb8-cba71f59c67f/image.png" alt=""></li>
</ul>
<hr>
<hr>
<blockquote>
<h2 id="navmeshagent로-영역-통한-이동-구현하기">NavMeshAgent로 영역 통한 이동 구현하기</h2>
<p>이제 이동 영역을 정했으니 이동 영역을 사용하는 동물을 만들어보자</p>
</blockquote>
<hr>
<h3 id="navmeshagent-컴포넌트">NavMeshAgent 컴포넌트</h3>
<ul>
<li>AI Navigation을 활용한 캐릭터의 이동을 위해서는 들어가야하는 컴포넌트로 오브젝트의 자연스러운 이동이나 장애물 피하기 경로 찾기 설정들을 할 수 있다.
<img src="https://velog.velcdn.com/images/b_h_b/post/f9244e23-fe74-44e1-a597-d72cd32a5904/image.png" alt=""></li>
<li>원하는 오브젝트에다가 위 컴포넌트를 추가시키자!<h4 id="각종-설정">각종 설정</h4>
<h4 id="steering">Steering</h4>
</li>
<li><strong>Speed</strong>                최대 이동 속도(초당 월드 단위로)</li>
<li><strong>Angular Speed</strong>        최대 회전 속도(초당 각도)</li>
<li><strong>Acceleration</strong>        최대 가속(제곱 초당 월드 단위로)</li>
<li><strong>Stopping distance</strong>    에이전트는 목표 위치에 가까워졌을 시 정지합니다.</li>
<li><strong>Auto Braking</strong> 활성화 시 에이전트는 목적지에 다다를 때 속도를 줄입니다. 
에이전트가 멀티플 포인트 사이에서 부드럽게 움직여야 하는 순찰과 같은 동작을 할 때에는 반드시 비활성화 시켜야 합니다.<h4 id="obstacle-avoidance">Obstacle Avoidance</h4>
</li>
<li><em>Radius*</em>            에이전트의 반경은 장애물과 다른 에이전트 간의 충돌 계산하기 위해 사용됩니다.</li>
<li><em>Height*</em>            에이전트가 장애물 밑으로 지나갈 수 있는 높이 간격입니다.</li>
<li><em>Quality*</em>            장애물 회피 품질입니다. 에이전트의 수가 많다면 장애물 회피 품질을 줄임으로써 CPU 시간을 절약할 수 있습니다. 회피를 없음으로 설정할 경우 충돌만 해결할 수 있을 뿐 다른 에이전트와 장애물에 대한 적극적인 회피는 하지 않습니다.</li>
<li><em>Priority*</em>        낮은 우선 순위의 에이전트는 이 에이전트의 회피 대상에서 제외됩니다. <pre><code>   값은 0에서 99사이에서 설정되어야 하며 낮은 숫자가 높은 우선 순위임을 의미합니다.</code></pre><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="필요한-기초-코드">필요한 기초 코드</h3>
<pre><code class="language-c">// 네임스페이스
using UnityEngine.AI; 
&gt;
// 컴포넌트 가져오기
NavMeshAgent agent;
void Awake()
{
    agent = GetComponent&lt;NavMeshAgent&gt;();
}</code></pre>
<blockquote>
</blockquote>
<hr>
<h3 id="기초적으로-사용하게될-각종-navmeshagent의-멤버들"><a href="https://docs.unity3d.com/ScriptReference/AI.NavMeshAgent.html">기초적으로 사용하게될 각종 NavMeshAgent의 멤버들</a></h3>
<h4 id="프로퍼티">프로퍼티</h4>
<ul>
<li><strong>isStopped</strong>:                 NavMesh 에이전트가 현재 경로를 따라 이동을 중지하거나 계속하는지를 설정하거나 가져옵니다.
( 해당 설정을 활성화 시에는 NavMeshAgent를 이용한 움직임이 정지됩니다.)</li>
<li><strong>remainingDistance</strong>:         에이전트의 위치와 현재 경로의 목적지 사이의 거리를 나타냅니다. <h4 id="메서드">메서드</h4>
</li>
<li><strong>bool SamplePosition(Vector3 sourcePosition, out AI.NavMeshHit hit, float maxDistance, int areaMask)</strong>
<strong><em>(NavMesh)</em></strong> sourcePosition으로부터 지정된 maxDistance 내에서 가장 가까운 NavMesh의 지점을 찾습니다.</li>
<li><strong>bool SetDestination(Vector3 target)</strong>
목적지를 설정하거나 업데이트하여 새 경로에 대한 계산을 트리거합니다.<h4 id="추가적인-설명">추가적인 설명</h4>
</li>
<li><a href="https://docs.unity3d.com/ScriptReference/AI.NavMeshHit.html"><strong>NavMeshHit</strong></a></li>
<li><blockquote>
<p>NavMesh 쿼리에 대한 결과 정보가 담겨져있다.
프로퍼티 : distance, hit, mask, normal, <strong>position</strong></p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="배회하는-코드">배회하는 코드</h3>
<pre><code class="language-c">    void Update()
    {
        // 플레이어 감지
        playerDistance = (transform.position - CharacterManager.Instance.Player.transform.position).sqrMagnitude; // sqr을 활용했으니 제곱하는 걸 잊지 맙시다.
&gt;
        WanderingState();
    }
&gt;
    void WanderingState()
    {
        if (agent.remainingDistance &lt; 0.1f)
        {
            NewWanderingPoint();
        }
    }
&gt;
    private void NewWanderingPoint()
    {
        NavMesh.SamplePosition(transform.position + (Random.onUnitSphere * detectDistance), out NavMeshHit hit, detectDistance, NavMesh.AllAreas);
        agent.SetDestination(hit.position);
    }</code></pre>
<ul>
<li>위 코드는 목표에 도착하면 주기적으로 목적지를 계속 바꾸는 코드이다.<h3 id="적용-결과">적용 결과</h3>
<img src="https://velog.velcdn.com/images/b_h_b/post/5e55927c-d5d6-4e85-be85-aefbe2bc1208/image.gif" alt=""></li>
<li>자신의 주위로 계속해서 배회한다. (detectDistance를 1로 설정해두었다.)<blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<h3 id="각종-코드-주의-사항">각종 코드 주의 사항</h3>
<ul>
<li><strong>SetDestination을 사용할 때 곧바로 Vector3를 넣으면 의도된대로 작동이 안될 수도 있다.</strong></li>
<li><blockquote>
<p>Navmesh를 벗어난 목적지로 설정해버릴 경우 그대로 자리에서 멈춰버린다.</p>
</blockquote>
<h4 id="---sampleposition은-랜덤으로-정하는-게-아니라-받은-sourceposition값에서-가장-가까운-navmesh를-찾는-역할을-하는-것이다">-  SamplePosition은 랜덤으로 정하는 게 아니라 받은 sourceposition값에서 가장 가까운 NavMesh를 찾는 역할을 하는 것이다.</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/fd2f2792-9262-4477-b8aa-e1ac5d6c6a71/image.png" alt=""></li>
<li>SamplePosition은 위와 같은 과정으로 포지션 값을 뽑는 것이고, 만약에 적절한 NavMesh 값을 찾지 못하면 false를 반환한다.</li>
</ul>
<hr>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
<h3 id="유니티">유니티</h3>
</blockquote>
<h4 id="navmeshagent"><a href="https://docs.unity3d.com/kr/2022.3/Manual/class-NavMeshAgent.html">NavMeshAgent</a></h4>
<h4 id="navmeshagent의-멤버"><a href="https://docs.unity3d.com/ScriptReference/AI.NavMeshAgent.html">NavMeshAgent의 멤버</a></h4>
<h3 id="영상">영상</h3>
<h4 id="유니티-클릭-이동-구현하기--navigationnavmesh---오늘코딩"><a href="https://www.youtube.com/watch?v=ILefNAZGVFY">[유니티] 클릭 이동 구현하기 | Navigation(NavMesh) - 오늘코딩</a></h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 7주차 5일차 TIL - 3차원의 시점과 이동]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-5%EC%9D%BC%EC%B0%A8-TIL</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-5%EC%9D%BC%EC%B0%A8-TIL</guid>
            <pubDate>Fri, 31 May 2024 14:56:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>배운 내용 정리하기</li>
<li>밍글데이</li>
<li>팀원 간 개인 프로젝트 서로 피드백하기</li>
</ul>
<hr>
<hr>
<h1 id="3차원의-시점">3차원의 시점</h1>
<h2 id="2차원과-다른점">2차원과 다른점</h2>
<blockquote>
</blockquote>
<ul>
<li>2차원은 아무런 설정 없이 캐릭터가 움직일 때 카메라를 따라가게 만들면 되지만,
3차원은 마우스에 따라서 시야를 바꿔줘야할 필요가 있다. (3차원의 각도가 요구된다)
<img src="https://velog.velcdn.com/images/b_h_b/post/50c90b29-09cd-4574-9638-cd09713c3f22/image.png" alt=""></li>
<li>3차원은 2차원과 달리 카메라를 상하좌우로 틀어줘야할 필요가 있다.</li>
</ul>
<hr>
<hr>
<h2 id="3차원-시야-구현-방법">3차원 시야 구현 방법</h2>
<blockquote>
<h3 id="카메라의-3차원-회전">카메라의 3차원 회전</h3>
</blockquote>
<ul>
<li>x-는 카메라가 위를 보고, x+는 카메라가 아래를 본다.</li>
<li>y-는 카메라가 좌를 보고, y-는 카메라가 우를 본다.
<img src="https://velog.velcdn.com/images/b_h_b/post/8a6f8115-b335-41af-ab89-175896b14076/image.png" alt=""></li>
<li>물론 추후에 이동 때문에 y rotation을 돌 때에는 캐릭터 오브젝트를 회전시킬 예정이다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<blockquote>
<h3 id="delta"><a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.4/api/UnityEngine.InputSystem.Pointer.html#UnityEngine_InputSystem_Pointer_delta">Delta</a></h3>
</blockquote>
<ul>
<li>마우스 포인터의 이동값을 받아낸다. (포인터가 한 프레임에서 다음 프레임으로 이동할 때의 변화량) 즉 델타를 나타냅니다. (DeltaTime이랑 비슷한 느낌)
(게임 화면 좌표 기준으로 포인터 위치 변화량을 뽑아낸다.)
<img src="https://velog.velcdn.com/images/b_h_b/post/4eee24da-359b-4824-8709-dd84970a7874/image.png" alt=""></li>
<li>상하가 -x, +x의 값이고</li>
<li>좌우가 -y, +y의 값이다.<blockquote>
</blockquote>
위를 보면 알겠지만 
Delta.y의 값은 카메라 각도의 상하 움직임에 사용되어야하고 (-로 반영)
Delta.x의 값은 캐릭터 좌우의 움직임을 구현해야한다. (캐릭터의 yRot을 구현)</li>
</ul>
<hr>
<hr>
<h2 id="구현하기">구현하기</h2>
<blockquote>
<h3 id="hierarchy-설정">Hierarchy 설정</h3>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/081e3eb1-e403-488e-b858-d9332ceda02f/image.png" alt=""><blockquote>
<h3 id="코드">코드</h3>
</blockquote>
<pre><code class="language-c"> void Start()
 {
     ...
     CharacterManager.Instance.Player.OnLook += Look;
     Cursor.lockState = CursorLockMode.Locked; // 이걸 해두지 않으면 마우스가 화면에서 벗어난다.
 }
&gt;
  private void Look(Vector2 direction)
  {
      camCurXRot += direction.y * lookSensitive; // 민감도 배수
      camCurXRot = Mathf.Clamp(camCurXRot, minXLook, maxXLook);
      cameraContainer.localEulerAngles = new Vector3(-camCurXRot, 0, 0); // 카메라의 x축 회전
&gt;
      transform.eulerAngles += new Vector3(0, direction.x * lookSensitive, 0); // 캐릭터의 y축 회전
  }</code></pre>
<blockquote>
<h3 id="구현된-모습">구현된 모습</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/b1699ceb-e1b1-4ab9-a6e8-eb2bf963314d/image.gif" alt=""></p>
</blockquote>
</li>
<li>의도된대로 잘 움직여진다.</li>
</ul>
<hr>
<hr>
<hr>
<h1 id="3차원의-이동">3차원의 이동</h1>
<hr>
<hr>
<h2 id="2차원과-다른점-1">2차원과 다른점</h2>
<blockquote>
<h3 id="2차원">2차원</h3>
</blockquote>
<ul>
<li>2차원은 x축과 y축이 존재하고, 3차원은 x축, y축, z축이 존재한다.</li>
<li>2차원은 Vector2, 3차원은 Vector3를 사용해서 움직인다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/5df81283-6444-4a53-80a0-f4f84829b8d2/image.png" alt=""></li>
<li>x - 좌우 <strong>|</strong> y - 상하<blockquote>
</blockquote>
</li>
</ul>
<hr>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/b45cfe9a-a1d7-4e0b-90ab-4faf1f783121/image.png" alt=""></p>
<ul>
<li>x - 좌우 <strong>|</strong> y - 높이 <strong>|</strong> z - 앞뒤<blockquote>
</blockquote>
</li>
</ul>
<hr>
<p>이러한 특징을 가졌기 때문에 상하좌우를 움직일 때는 3차원에서는 y부분이 사용되지 않게 조심하자</p>
<hr>
<hr>
<h2 id="3차원-걷기-구현-방법">3차원 걷기 구현 방법</h2>
<blockquote>
<ul>
<li>2차원과 달리 <strong>3차원은 x, z를 활용해서 이동</strong>해야한다.
(Vector2를 그대로 적용해버리면 높이가 올라가는 현상이 발생한다;)</li>
</ul>
</blockquote>
<ul>
<li>바라보는 시점에 따라서도 줘야하는 값이 달라진다.
그럴 때 사용하는 코드는 <code>transform.forward(오브젝트 앞뒤)</code>, <code>transform.right(오브젝트 우좌)</code>
// 안하면 그냥 말 그대로 x와 z의 방향대로 움직이게 된다. <em>(인형뽑기의 팔이나 다름 없다)</em>
<img src="https://velog.velcdn.com/images/b_h_b/post/a88a1fb2-bafe-4d13-a84b-273c77a11f34/image.png" alt=""></li>
<li>해당 사진을 보면 </li>
<li><em>transform.forward는 저 파란색 화살표 방향의 단위 벡터*</em>를 반환해주고</li>
<li><em>transform.right는 빨간색 화살표의 단위 벡터*</em>를 반환해준다는 의미가 된다.</li>
</ul>
<hr>
<hr>
<h2 id="구현하기-1">구현하기</h2>
<blockquote>
<h3 id="코드-1">코드</h3>
</blockquote>
<ul>
<li>이동 부여 방식은 취향껏 AddForce의 Acceleration과 ClampMagnitude를 걸어주었다.<ul>
<li><code>Vector3.ClampMagnitude(대상값, 최대값)</code> 대상 벡터의 스칼라값이 최대값을 넘지 못하게 막는다.<pre><code class="language-c">void FixedUpdate()
{
  Move();
}
&gt;
private void Move()
{
  if (currentMove == Vector3.zero) { return; } // 움직임이 없을 시 즉시 반환
&gt;
  Vector3 dir = transform.forward * currentMove.y + transform.right * currentMove.x; // 이게 3차원의 포인트이다.
  dir = dir.normalized;
&gt;
  _rigidbody.AddForce(dir * moveSpeed, ForceMode.Acceleration);
  _rigidbody.velocity = Vector3.ClampMagnitude(_rigidbody.velocity, maxVelocity); // 스칼라 Clamp
}
&gt;
private void MoveInput(Vector2 direction)
{
  currentMove = direction;
}
&gt;    </code></pre>
<h3 id="구현된-모습-1">구현된 모습</h3>
<img src="https://velog.velcdn.com/images/b_h_b/post/a4ece639-ae70-40be-a2a0-f5f4ec7dfe7f/image.gif" alt=""></li>
</ul>
</li>
<li>보이는 걸로는 잘 모르겠지만, 의도된대로 잘 움직인다.</li>
</ul>
<hr>
<hr>
<h2 id="3차원-이동에서-알게된-코드">3차원 이동에서 알게된 코드</h2>
<blockquote>
</blockquote>
<p><strong><code>transform.forward</code></strong>    - 파란색 화살표의 단위 벡터
<strong><code>transform.right</code></strong>     - 빨간색 화살표의 단위 벡터
<strong><code>Vector3.ClampMagnitude(대상값, 최대값)</code></strong> - 대상 벡터의 스칼라값이 최대값을 넘지 못하게 제한</p>
<hr>
<hr>
<hr>
<h1 id="쓰면서-느낀점">쓰면서 느낀점</h1>
<blockquote>
<p>확실히 복습을 해보면서 부족한 부분을 채우니 해당 코드나 값들에 대한 이해도가 높아졌다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 7주차 4일차 TIL - 기능 구상]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%EC%83%81</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%EC%83%81</guid>
            <pubDate>Thu, 30 May 2024 14:54:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이</li>
<li>개인 프로젝트 진행 후 제출</li>
</ul>
<hr>
<p>_ 작성 중... _
이번 TIL은 필자의 생각이 많이 들어있습니다.</p>
<hr>
<h1 id="기능-구상">기능 구상</h1>
<h2 id="기능-구상에-관하여">기능 구상에 관하여</h2>
<blockquote>
<p>이 과정은 게임의 기획보다는 좀 더 세부적으로 들어가보는 과정이 담겨져있다.</p>
</blockquote>
<h2 id="기능을-구상하게-되는-과정">기능을 구상하게 되는 과정</h2>
<blockquote>
</blockquote>
<h3 id="구상-단계">구상 단계</h3>
<h4 id="1-필요성">1. 필요성</h4>
<h4 id="2-흐름-파악">2. 흐름 파악</h4>
<h4 id="3-명시화">3. 명시화</h4>
<blockquote>
</blockquote>
<hr>
<h3 id="적용-단계">적용 단계</h3>
<h4 id="4-적용">4. 적용</h4>
<h4 id="5-개선-및-프레임워크-반영">5. 개선 및 프레임워크 반영</h4>
<hr>
<hr>
<h3 id="1-필요성-1">1. 필요성</h3>
<blockquote>
<p>기능을 구상하기 앞서 사실 그 기능이 필요해야 구상을 하게되지 않을까 생각한다, 설령 지금 당장이 아니더라도 추후에 쓰이게될 것 같은 경우에도 마찬가지다. 물론 필요성이라는 것도 개인의 주관에 따라서 달라지니 팀 프로젝트를 하고 있다면 회의를 통해 필요성을 도출하고 결정해줘야할 필요가 있다.</p>
</blockquote>
<hr>
<h3 id="필요성에서-따져야할-3가지">필요성에서 따져야할 3가지</h3>
<ul>
<li><strong>방향성</strong> -&gt; <em>지금 개발하는 게임의 방향성에 맞는가?</em> </li>
<li><strong>범용성</strong> -&gt; <em>여러 게임에 전반적으로 자주 쓰이는 기능인가?</em></li>
<li><strong>독자성</strong> -&gt; <em>타 게임들과는 차별성을 가지는가?</em><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="방향성">방향성</h3>
<blockquote>
<p>지금 개발하는 게임의 방향성에 맞는가?</p>
</blockquote>
<h4 id="마인크래프트">마인크래프트</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/00aa604f-dece-48c2-8bb9-f148b46271e7/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="슬레이더-스파이어">슬레이더 스파이어</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/2582b9ad-2b29-496a-a10d-6636331753cd/image.png" alt=""><blockquote>
</blockquote>
두 가지 게임의 아이템 보관법에 대해서 가지고 와보았다. 하나는 필요한 템을 자기가 원하는 대로 넣고, 필요 없으면 템을 버릴 수 있는 <strong>(개방적인 인벤토리)</strong>이고, 다른 하나는 원하는 아이템만 가지고 오거나 버릴 수 없다. (<strong>폐쇄적인 인벤토리</strong>) 이렇게 둘이 <strong>똑같은 인벤토리임에도 불구하고 서로 다른 기능을 가지게 되는 이유는 게임의 방향성</strong>에 있을 것이다. 게임의 방향성에 따라 자유도에 맞춰서 인벤토리를 자유롭게 건들 수 있게 하느냐. 아니면 게임의 난이도 조절과 컨셉에 맞게 인벤토리를 폐쇄적으로 만드느냐는 우리가 개발하고자 하는 게임의 방향성에 따라서 달라지게 될 것이다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<h3 id="범용성">범용성</h3>
<blockquote>
</blockquote>
<p>여러 게임에서 전반적으로 자주 쓰이는 기능인가?</p>
<h4 id="돈스타브">돈스타브</h4>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/73a7318a-b0c4-4c2a-a6ed-c3431ef33e75/image.png" alt=""></p>
<blockquote>
</blockquote>
<hr>
<h4 id="스타듀밸리">스타듀밸리</h4>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/2b05ec44-a75d-4fbc-b822-6d387e7cc076/image.png" alt=""></p>
<blockquote>
</blockquote>
<hr>
<h4 id="타르코프">타르코프</h4>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/7382059f-0cf8-49ab-8409-3f5d2fdbe1a0/image.png" alt=""></p>
<blockquote>
</blockquote>
<hr>
<p>인벤토리라는 기능이 주로 사용되는 게임들은 아이템이 있고 그 아이템들을 통해서 살아가거나 수익을 내거나와 같이 <strong>생존과 자원 관리의 특징을 가진 게임에서 자주 쓰일 것</strong>이다. 물론 인벤토리를 생략해도 해당 게임의 특징이 사리지지는 않지만 없으면 많이 아쉬운 느낌이 들 수도 있다.</p>
<blockquote>
</blockquote>
<hr>
<hr>
<h3 id="독자성">독자성</h3>
<blockquote>
</blockquote>
<p>기존에 있던 게임들과는 다른 특별한 특징을 가지는가?</p>
<blockquote>
</blockquote>
<h4 id="백팩-히어로">백팩 히어로</h4>
<p> <img src="https://velog.velcdn.com/images/b_h_b/post/b24a0136-18d3-4c49-995e-afabf95fb296/image.png" alt=""></p>
<blockquote>
</blockquote>
<p>위 이미지에 대해 설명해주자면 위의 인벤토리가 특별한 이유가 아이템을 단순히 한 칸씩 담는 게 아니라 <strong>아이템의 모양에 따라서 차지하는 공간이 다르다</strong>. 이 뿐만 아니라 <strong>배치된 위치에 따라서 아이템들끼리 서로 버프나 디버프롤 주고받게 되어서</strong> 인벤토리 정리에 매우 많은 신경을 써줘야한다. (인벤토리의 공간을 기존과 다르게 더욱 능동적으로 활용했다.)</p>
<hr>
<hr>
<h3 id="2-흐름-파악-1">2. 흐름 파악</h3>
<blockquote>
<p>그렇다면 그 기능이 필요하다는 게 확실시 된다면, 이제 그 기능의 대략적인 구상을 해볼 차례이다.</p>
</blockquote>
<hr>
<h3 id="데이터-흐름-파악하기">데이터 흐름 파악하기</h3>
<p>데이터 흐름을 파악하는 것이 제일 먼저 중요하다.
예시로 인벤토리의 데이터 흐름을 생각해보자</p>
<blockquote>
</blockquote>
<hr>
<h3 id="기능의-기초적인-흐름-파악">기능의 기초적인 흐름 파악</h3>
<ol>
<li>템을 줍는다 -&gt; 2. 주운 템을 보관한다 -&gt; 3. 보관한 템을 사용한다 -&gt; 4. 사용한 템을 처리한다.<blockquote>
</blockquote>
사실 그냥 단순하게 내가 가방을 가지고 있다고 생각하고 내가 그걸 어떻게 흘러가는지를 생각해보면 된다. (설령 가방이 아니더라도 뭔가를 보관하고 들고 다닐 수 있는 거면 죄다 인벤토리라고 파악해도 된다. <strong>추상화</strong>)
이렇게 생각하는 과정이 단순해 보일 순 있지만, 사실 이렇게 과정을 잘 나눠줘야 그 다음 과정을 넘어갈 수 있다.<blockquote>
</blockquote>
</li>
</ol>
<hr>
<h3 id="흐름의-구체화-1-육하원칙">흐름의 구체화 1 (육하원칙)</h3>
<p><strong>1. 템을 줍는다</strong>
누가: 플레이어 | 언제: 원할 때 | 어디서: 템이 있는 자리
무엇을: 떨어진 템 | 어떻게: RayCast로 감지해서 특정 입력으로 | 왜: 필요/호기심
<strong>2. 주운 템을 보관하다</strong>
누가: 인벤토리 | 언제: 플레이어가 템을 주울 때 | 어디서: 인벤토리 역할 오브젝트 or 스크립트에
무엇을: 주운 템을 | 어떻게: 배열, 리스트, Prefab들을 활용해서  | 왜: 나중에 사용하기 위해
<strong>3. 보관한 템을 사용하다</strong>
누가: 플레이어 | 언제: 원할 때 | 어디서: 필요한 장소에서
무엇을: 필요한 템  | 어떻게: 템을 선택 후 키 또는 마우스 입력 등  | 왜: 필요/호기심
<strong>4. 사용한 템을 처리한다</strong>
누가: 인벤토리/템 | 언제: 템이 사용됐을 때 | 어디서: 인벤토리에서
무엇을: 사용된 템 | 어떻게: 템의 특징에 따라서 처리(장착, 소모 등) | 왜: 게임의 방향성</p>
<blockquote>
</blockquote>
<p>아니면 아래와 같은 구상도 가능하다.</p>
<blockquote>
</blockquote>
<hr>
<h3 id="흐름의-구체화-2-필요성">흐름의 구체화 2 (필요성)</h3>
<p>*<em>1. 템을 줍는다 *</em>
-&gt; 템의 정보
-&gt; 템을 감지하고 가져오는 기능
-&gt; 주워진 템은 사라져야 한다.</p>
<blockquote>
</blockquote>
<p><strong>2. 주운 템을 보관한다</strong>
-&gt; 템을 담을 공간
-&gt; 템을 담는 방식(단일, 스택 및 아이템 배치 위치, 순서 등등)
-&gt; 가지고 있는 템을 출력</p>
<blockquote>
</blockquote>
<p><strong>3. 보관한 템을 사용한다</strong>
-&gt; 사용하고자하는 템의 참조값(주소값), 
-&gt; 사용을 위한 입력 방법 (클릭, 키 입력, 버튼 등)</p>
<blockquote>
</blockquote>
<p><strong>4. 사용한 템을 처리한다</strong>
-&gt; 사용한 템의 타입 (장착, 소모품)
-&gt; 사용한 아이템의 타입에 따라 처리해줄 메서드
-&gt; 사용 후 갯수를 감소하거나 템 칸 정리</p>
<blockquote>
</blockquote>
<hr>
<p>심지어 위와 같은 구상 과정도 필수적인 부분이라 추가적인 기능을 생각해본다면..
<strong>아이템 정리</strong> - 정렬, 버리기, 유저의 직접적인 정리(드래그 앤 드롭)
<strong>핫 키</strong> - 버튼 하나만으로 들고 있는 템을 바꾸거나, 소모품을 바로 소비</p>
<blockquote>
</blockquote>
<p>그리고 추가적으로 게임의 방향성에 따라서 인벤토리의 UI와 가지게 되는 기능도 다양하게 달라질 수 있다.</p>
<hr>
<hr>
<h3 id="3-명시화-1">3. 명시화</h3>
<blockquote>
<p>데이터의 흐름이 어느 정도 구체화되었다면 이제는 시각화해볼 차례이다.</p>
</blockquote>
<hr>
<h3 id="시각화를-위한-다양한-방법">시각화를 위한 다양한 방법</h3>
<ul>
<li>FlowChart</li>
<li>UML</li>
<li>ERD</li>
<li>그 외의 여러가지 방법<blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<h3 id="flowchart">FlowChart</h3>
<ul>
<li>플로우 차트는 순서도 또는 흐름도라고도 불리며 ,어떠한 일을 처리하는 과정을 순서대로 간단한 기호와 도형으로 도식화한 것을 의미한다.<blockquote>
</blockquote>
<h4 id="간단한-플로우차트">간단한 플로우차트</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/296c3e80-2da9-438a-ada0-e5e2a79a49fd/image.png" alt=""><blockquote>
</blockquote>
<h4 id="플로우차트-사용-규칙">플로우차트 사용 규칙</h4>
플로우 차트를 작성할 때 사용 규칙은 이와 같다.</li>
</ul>
</li>
<li><em>1*</em> 기호의 내부에는 처리할 내용이 들어가야한다.</li>
<li><em>2*</em> 순서는 위에서 아래로, 왼쪽에서 오른쪽을 원칙으로 하며 그 외의 경우는 화살표로 사용해야 한다.</li>
<li><em>3*</em> 흐름선은 서로 교차해도 무관하며 서로 영향을 주지 않는다.</li>
<li><em>4*</em> 흐름선 여러 개가 모여 하나로 합칠 수 있다.</li>
<li><em>5*</em> 기호의 모형은 가로, 세로의 비율은 정하지 않으나 잘 구분할 수 있어야한다.<h4 id="플로우차트-일반적-기호">플로우차트 일반적 기호</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/95ce0715-ab45-4e03-b8d6-119873b6589a/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="인벤토리-기능을-플로우-차트로-나타내보기">인벤토리 기능을 플로우 차트로 나타내보기</h4>
<p>_... 추후에 작성 바람... _</p>
<blockquote>
</blockquote>
<hr>
<hr>
<h3 id="uml">UML</h3>
<p>UML 다이어그램은 Unified Modeling Language(통합 모델링 언어)를 사용하여 시스템과 소프트웨어를 시각화하는 방법입니다.
복잡한 소프트웨어 시스템의 설계, 아키텍처 등을 이해하기 위해 UML 다이어그램을 작성합니다.</p>
<blockquote>
</blockquote>
<h4 id="uml-예시">UML 예시</h4>
<h4 id="1-클래스만-표현">1. 클래스만 표현</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/4311e680-b656-48d8-b686-4761e78f7276/image.png" alt=""></li>
<li>스파르타 코딩 클럽 자료입니다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="2-필드와-메서드까지-표현">2. 필드와 메서드까지 표현</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/78372776-1a18-48b2-8a71-cd283ec5c2a9/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<ul>
<li>화살표의 방향은 주로 해당 클래스가 대상의 데이터에 접근하게 될 때 표시해준다.</li>
<li>필드와 메서드를 명시하게 될 경우 쓰일 것 같은 필드와 메서드를 잘 구상해줘야한다.</li>
<li>ERD도 UML과 비슷한 계열이지만, 데이터의 상호작용을 좀 더 세세하게 표현한 다이어그램이다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="uml을-활용하여-인벤토리-기능-구상해보기">UML을 활용하여 인벤토리 기능 구상해보기</h4>
<p><em>... 추후에 작성 바람 ...</em></p>
<hr>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
</blockquote>
<h3 id="flowchart-1">FlowChart</h3>
<ul>
<li><h3 id="플로우-차트란-무엇인가---미니맘바"><a href="https://blog.naver.com/ycpiglet/222113989214">플로우 차트란 무엇인가? - 미니맘바</a></h3>
</li>
<li><h3 id="위키백과"><a href="https://ko.wikipedia.org/wiki/%EC%88%9C%EC%84%9C%EB%8F%84">위키백과</a></h3>
<h3 id="uml-1">UML</h3>
</li>
<li><h3 id="uml이란"><a href="https://miro.com/ko/diagramming/what-is-a-uml-diagram/">UML이란?</a></h3>
</li>
</ul>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>역시 뭔가 뚜렷한 기능이나 디자인 패턴에 관한 TIL 작성은 쉬운 편에 속하지만 추상적인 개념들(구상이나, 정의되어있지 않은 지식)에 대해서는 확실히 TIL 작성에 길을 헤매이게 되는 느낌이 많이 든다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 7주차 3일차 TIL - Rigidbody ]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-Rigidbody</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-Rigidbody</guid>
            <pubDate>Wed, 29 May 2024 14:55:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이</li>
<li>프로젝트 진행하기 (~ 선택 구현 사항 3)</li>
</ul>
<hr>
<hr>
<h1 id="rigidbody">Rigidbody</h1>
<h2 id="개념">개념</h2>
<blockquote>
<p><strong>Rigidbody는 게임 오브젝트가 물리 제어로 동작하게 합니다.</strong> Rigidbody는 Force와 Torque를 받아 오브젝트가 사실적으로 움직이도록 해주고, Rigidbody가 포함된 모든 게임 오브젝트는 중력의 영향을 받을 수 있게되며, 스크립팅을 통해 가해진 힘으로 움직이거나 NVDIA PhysX 물리 엔진을 통해 다른 오브젝트와 상호작용하게 됩니다.</p>
</blockquote>
<h2 id="컴포넌트의-각종-프로퍼티">컴포넌트의 각종 프로퍼티</h2>
<h3 id="mass">Mass</h3>
<blockquote>
</blockquote>
<ul>
<li>오브젝트의 질량 (디폴트 값을 킬로그램)</li>
<li>코드
<code>_rigidbody.mass = 10;</code><blockquote>
<blockquote>
<h4 id="테스트-영상">테스트 영상</h4>
<hr>
</blockquote>
</blockquote>
<h3 id="각종-이동-처리">각종 이동 처리</h3>
</li>
<li>각 물체의 mass는 흰색은 1 회색은 100으로 설정해주었다.<blockquote>
<blockquote>
<h4 id="_rigidbodyvelocity--direction--speed--timedeltatime"><code>_rigidbody.velocity = direction * speed * Time.deltaTime;</code></h4>
<p>흰색과 회색에서 각각 250으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/b0298488-f725-4c6b-ad74-e01e94a8c615/image.gif" alt=""></p>
</blockquote>
</blockquote>
</li>
<li>둘이 속도가 균일하게 나온다. <blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<blockquote>
<blockquote>
<h4 id="_rigidbodyaddforcedirection--speed--timedeltatime-forcemodeforce"><code>_rigidbody.AddForce(direction * speed * Time.deltaTime, ForceMode.Force);</code></h4>
<p>흰색과 회색에게 각각 50으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/ef0acd32-3dfb-45a8-87c3-670ccb8591d5/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>가속이 생기는데 mass에 따라서 차이가 발생한다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<blockquote>
<blockquote>
<p>Force 모드에서 <strong>흰색은 50 회색은 5000으로 설정</strong>
<img src="https://velog.velcdn.com/images/b_h_b/post/ac5077b1-2da7-49b1-8c72-504e0b73576a/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>서로 mass 차이가 나는 상태에서 Force로 동일한 속도를 내고자 한다면 <strong>a(mass) : b(mass) = a(Force) : b(Force)</strong>를 통해서 계산해줘야할 것 같다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<blockquote>
<blockquote>
<h4 id="_rigidbodyaddforcedirection--speed--timedeltatime-forcemodeacceleration"><code>_rigidbody.AddForce(direction * speed * Time.deltaTime, ForceMode.Acceleration);</code></h4>
<p>흰색과 회색에게 각각 50으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/7afb6e2b-4af2-4bda-b0eb-210d7b16cca5/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>mass 상관 없이 둘이 동일한 속도로 가속이 발생한다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<blockquote>
<blockquote>
<h4 id="_rigidbodyaddforcedirection--speed--timedeltatime-forcemodeimpulse"><code>_rigidbody.AddForce(direction * speed * Time.deltaTime, ForceMode.Impulse);</code></h4>
<p>흰색과 회색에게 각각 10으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/f2076489-1424-4ab1-89ef-06c2cc1bb693/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>가속이 폭발적으로 증가하고 mass에 따라서 차이가 발생한다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<blockquote>
<blockquote>
<h4 id="_rigidbodyaddforcedirection--speed--timedeltatime-forcemodevelocitychange"><code>_rigidbody.AddForce(direction * speed * Time.deltaTime, ForceMode.VelocityChange);</code></h4>
<p>흰색과 회색에게 각각 10으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/586f0fc0-5fd4-4526-80a2-bf40a8c06551/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>mass 상관없는 impulse mode인 것 같다.</li>
</ul>
<hr>
<hr>
<hr>
<h3 id="drag">Drag</h3>
<blockquote>
</blockquote>
<ul>
<li>위치 이동의 저항 (중력과 움직임에 저항이 생긴다)</li>
<li>코드
<code>_rigidbody.drag = 10;</code></li>
</ul>
<hr>
<blockquote>
<blockquote>
<h4 id="테스트-영상-1">테스트 영상</h4>
<hr>
</blockquote>
</blockquote>
<h3 id="중력-저항">중력 저항</h3>
<blockquote>
<blockquote>
<h4 id="초기-설정">초기 설정</h4>
<ul>
<li><strong>Drag 설정</strong></li>
</ul>
</blockquote>
</blockquote>
<ul>
<li>파란색은 <strong>기본값(0)</strong></li>
<li>빨간색은 <strong>20</strong><blockquote>
<blockquote>
<ul>
<li>각자에게 중력을 추가해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/9079fc35-76a9-4cbc-8b82-e60dc3a8952f/image.gif" alt=""></li>
</ul>
</blockquote>
</blockquote>
<ul>
<li>Drag가 없는 파란색 직육면체는 바로 떨어지고 Drag가 20인 빨간색은 천천히 떨어진다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="이동-저항">이동 저항</h3>
<blockquote>
<blockquote>
<h4 id="초기-설정-1">초기 설정</h4>
<ul>
<li><strong>Drag 설정</strong></li>
</ul>
</blockquote>
</blockquote>
<ul>
<li>파란색은 <strong>기본값(0)</strong></li>
<li>빨간색은 <strong>20</strong><h4 id="사용된-이동-코드">사용된 이동 코드</h4>
<pre><code class="language-c">IEnumerator MovePlatform()
{
   Vector3 direction = (directions[curDirectionIdx] - transform.position).normalized; // 방향 단위 벡터
   while (true) // 목표 지점에 도착할 때까지 반복합니다.
   {
       if (Vector3.Distance(transform.position, directions[curDirectionIdx]) &lt; 0.2f) { break; }
       _rigidbody.velocity = direction * speed * Time.deltaTime; // 초를 따라 추가
       yield return null;
   }
   _rigidbody.velocity = Vector3.zero; // velocity 0으로 만들기 (이게 없으면 날라가니 주의)
   yield return new WaitForSeconds(waitTime); // 멈추는 시간 설정
   curDirectionIdx++;    // 다음 목적지로
   if (curDirectionIdx &gt;= directions.Count) // Count를 넘는다면
   {
       curDirectionIdx = 0; // 다시 0으로
   }
   StartMove(); // 움직임 재시작
}</code></pre>
<ul>
<li>speed는 500으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/09c75280-df08-404d-af5b-a474c88af4ed/image.gif" alt=""></li>
<li>해당 영상에서 보이듯이 Drag가 없는 파란색은 빠르게 움직이고 Drag가 있는 빨간색은 천천히 움직인다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h4 id="-정지-코드-제외">+ 정지 코드 제외</h4>
<p>speed를 250으로 바꿔주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/a904c87d-408f-48e5-971b-ba1f10d2fee7/image.gif" alt=""></p>
<ul>
<li>빨간색은 Velocity를 주는 것을 멈추면 잘 멈추는 반면 파란색은 그대로 나아가버린다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<h3 id="각종-이동-처리-1">각종 이동 처리</h3>
<blockquote>
<blockquote>
<h4 id="_rigidbodyvelocity--direction--speed--timedeltatime-1"><code>_rigidbody.velocity = direction * speed * Time.deltaTime;</code></h4>
<p>파란색과 빨간색에게 각각 250으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/ecb83190-2ff6-4808-8f26-eafa14a43689/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>빨간색이 저항을 받아서 그런지 속도가 감속되는 현상이 생긴다. (프레임 단위로 velocity = 처리이다.) <blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<blockquote>
<blockquote>
<h4 id="_rigidbodyaddforcedirection--speed--timedeltatime-forcemodeforce-1"><code>_rigidbody.AddForce(direction * speed * Time.deltaTime, ForceMode.Force);</code></h4>
<p>파란색과 빨간색에게 각각 50으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/3336c49a-52b1-4db5-a6f9-1908acc48db9/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>파란색은 가속이 잘 전달되는 반면 빨간색은 이상하게 속도 제한이 걸리는 것 같다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<blockquote>
<blockquote>
<h4 id="_rigidbodyaddforcedirection--speed--timedeltatime-forcemodeimpulse-1"><code>_rigidbody.AddForce(direction * speed * Time.deltaTime, ForceMode.Impulse);</code></h4>
<p>파란색과 빨간색에게 각각 10으로 설정해주었다.
<img src="https://velog.velcdn.com/images/b_h_b/post/edbebec1-ac3e-46e5-8acd-a17c36ba07ee/image.gif" alt=""></p>
</blockquote>
</blockquote>
<ul>
<li>위와 마찬가지로 빨간색에게 속도 제한이 걸린다.<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="impulse-모드-빨간색만-200으로-설정">Impulse 모드 빨간색만 200으로 설정</h4>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/ba6af0a4-ba21-48ef-92ab-c1e428b62f44/image.gif" alt=""></p>
<ul>
<li>아무리 힘을 줘도 저항 때문에 속도가 중첩이 되질 않는다..</li>
</ul>
<hr>
<hr>
<hr>
<h3 id="angular-drag">Angular Drag</h3>
<blockquote>
</blockquote>
<ul>
<li>회전의 저항</li>
<li>코드
<code>_rigidbody.angularDrag = 10;</code></li>
</ul>
<hr>
<blockquote>
<blockquote>
<h4 id="테스트-영상-2">테스트 영상</h4>
<hr>
<h4 id="_rigidbodyangularvelocity--vector3up--rotatespeed"><code>_rigidbody.angularVelocity = Vector3.up * rotateSpeed;</code></h4>
<ul>
<li><strong>AngularDrag 설정</strong></li>
</ul>
</blockquote>
</blockquote>
<ul>
<li>파란색은 <strong>기본값(0.05)</strong></li>
<li>빨간색은 <strong>20</strong> </li>
<li>rotateSpeed를 20으로 설정했다.<blockquote>
<blockquote>
<ul>
<li>멈추는 버튼을 추가해서 멈추는 실험
<img src="https://velog.velcdn.com/images/b_h_b/post/2c9a36f3-0bf6-4165-a6b7-6221d28a6df6/image.gif" alt="">
해당 영상을 보면 <strong>파란색 직육면체가 좀 더 빨리 회전</strong>하는 것을 볼 수 있고 <strong>움직임을 멈췄을 경우 파란색은 천천히 멈추고 빨간색은 바로 멈추는 것</strong>을 알 수 있다. </li>
</ul>
</blockquote>
</blockquote>
</li>
</ul>
<hr>
<blockquote>
<blockquote>
<h4 id="_rigidbodyaddtorquevector3up--rotatespeed"><code>_rigidbody.AddTorque(Vector3.up * rotateSpeed);</code></h4>
</blockquote>
</blockquote>
<ul>
<li>AddTorque는 각도 버전의 Addforce라 생각해주면 된다. (기본 Forcemode는 Force이다.)</li>
<li>속도는 각각 20으로 설정해뒀다.
<img src="https://velog.velcdn.com/images/b_h_b/post/a81f22fa-4a7b-4002-be43-7c14fe087524/image.gif" alt=""></li>
<li>해당 영상을 보면 알 듯이 빨간색은 저항을 받아서 속도의 제한을 받고 파란색은 잘 회전되는 모습을 볼 수 있다.</li>
<li>그리고 멈췄을 경우 저항이 있는 빨간색(20)은 바로 멈추고 파란색(0.05)은 천천히 멈추고 있는 것을 볼 수 있다.</li>
</ul>
<hr>
<hr>
<hr>
<h3 id="usegravity">UseGravity</h3>
<blockquote>
</blockquote>
<ul>
<li>중력 유무<blockquote>
</blockquote>
앞선 Drag 영상에서 봤듯이 물체에 중력을 부여해줄 수 있다. 중력을 사용하지 않겠다면 체크란을 비워주면 된다.</li>
<li>코드
<code>Rigidbody(의 인스턴스).useGravity = true;</code></li>
</ul>
<hr>
<hr>
<hr>
<h3 id="is-kinematic">Is Kinematic</h3>
<blockquote>
</blockquote>
<ul>
<li>오브젝트가 물리 엔진으로 제어되지 않고 오로지 Trasnform으로만 조작된다. 플랫폼을 옮기는 경우나 HingeJoint가 추가된 리지드바디를 애니메이션화하는 경우에 유용하다</li>
<li>코드
<code>Rigidbody(의 인스턴스).isKinematic = true;</code>
<img src="https://velog.velcdn.com/images/b_h_b/post/34aa9861-dc08-4eac-9e77-206cff6f720c/image.png" alt=""></li>
<li>당연하겠지만 velocity에 변화를 주려하면 이러한 에러가 발생한다.</li>
</ul>
<hr>
<hr>
<hr>
<h3 id="interpolate">Interpolate</h3>
<blockquote>
</blockquote>
<ul>
<li>리지드바디의 움직임이 어색해 보일 경우에 사용한다.<ul>
<li><strong>None</strong>         - 보간 없음</li>
<li><strong>Interpolate</strong>     - 이전 프레임의 트랜스폼에 맞게 움직임을 부드럽게 처리합니다.</li>
<li><strong>Extrapolate</strong>     - 다음 프레임의 트랜스폼을 추정해 움직임을 부드럽게 처리합니다.</li>
</ul>
</li>
<li>코드
<code>Rigidbody(의 인스턴스).interpolation = RigidbodyInterpolation.Interpolate;</code></li>
</ul>
<hr>
<hr>
<hr>
<h3 id="collision-detection">Collision Detection</h3>
<blockquote>
</blockquote>
<ul>
<li>빠르게 움직이는 오브젝트가 충돌의 감지 없이 다른 오브젝트를 지나쳐가는 것을 방지합니다.<h4 id="discrete">Discrete</h4>
</li>
<li>물리 시스템이 불연속 충돌 검사를 사용하여 이 리지드바디의 콜라이더에 대한 충돌을 계산합니다. 이 리지드바디가 빠르게 움직이는 충돌에 관여하지 않는 경우 Discrete를 선택합니다.Discrete 충돌 검사는 컴퓨터 리소스를 많이 사용하지 않습니다.<h4 id="continous">Continous</h4>
</li>
<li>물리 시스템은 스위핑 기반 CCD를 사용하여 이 리지드바디의 콜라이더와 정적 콜라이더(연관된 리지드바디가 없는 콜라이더) 간의 충돌을 계산합니다. 이 리지드바디가 정적 콜라이더와의 빠르게 움직이는 충돌에 관여하는 경우 Continuous를 선택합니다. 스위핑 기반 CCD는 Discrete 또는 Continuous Speculative보다 컴퓨터 리소스를 많이 사용합니다.<h4 id="continuous-dynamic">Continuous Dynamic</h4>
</li>
<li>물리 시스템은 스위핑 기반 CCD를 사용하여 이 리지드바디의 콜라이더와 Discrete 충돌 검사로 설정된 콜라이더를 제외한 다른 모든 콜라이더 간의 충돌을 계산합니다. 이 리지드바디가 임의의 콜라이더와의 빠르게 움직이는 충돌에 관여하는 경우 Continuous Dynamic을 선택합니다. 스위핑 기반 CCD는 Discrete 또는 Continuous Speculative보다 컴퓨터 리소스를 많이 사용합니다.<h4 id="continuous-speculative">Continuous Speculative</h4>
</li>
<li>리지드바디와 콜라이더에 추측성 연속 충돌 검사를 사용합니다. 키네마틱 바디를 설정할 수 있는 유일한 CCD 모드입니다. 이 메서드는 스위핑 기반 연속 충돌 검사보다 리소스를 덜 소모합니다.</li>
</ul>
<hr>
<hr>
<hr>
<h3 id="constraints">Constraints</h3>
<blockquote>
</blockquote>
<ul>
<li>리지드 바디의 움직임에 대한 제약사항<ul>
<li><strong>Freeze Position</strong> - 월드 좌표계의 X, Y, Z 축에서 이동하는 리지드 바디의 움직임 제한 유무</li>
<li><strong>Freeze Rotation</strong> - 로컬 좌표계의 X, Y, Z 축에서 회전하는 리지드 바디의 움직임 제한 유무</li>
</ul>
</li>
<li>코드<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/23aeb26c-44ec-440f-8251-d3d8fe6aa84a/image.png" alt=""><blockquote>
</blockquote>
혹여나 Rigidbody에 힘을 가했는데 의도된 대로 움직여지지 않으면 이 체크란을 확인해주자.
Transform.position을 이용한 이동의 제한은 막힌 게 아니니 그것을 사용해도 된다.</li>
</ul>
</li>
</ul>
<hr>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
<h3 id="유니티-공식-문서"><a href="https://docs.unity3d.com/kr/2022.3/Manual/class-Rigidbody.html">유니티 공식 문서</a></h3>
</blockquote>
<h3 id="유니티-공식-문서-스크립트-레퍼런스"><a href="https://docs.unity3d.com/ScriptReference/Rigidbody.html">유니티 공식 문서 스크립트 레퍼런스</a></h3>
<h3 id="유니티-입문-강좌-part-3---리지드-바디---케이디"><a href="https://www.youtube.com/watch?v=V1ZcL55h3h4">유니티 입문 강좌 part 3 - 리지드 바디 - 케이디</a></h3>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>유니티라는 엔진을 알면 알수록 생각보다 자기가 모르는 것에 대한 실험을 하기가 좋은 환경이라고 생각이 든다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 7주차 2일차 - 명령 패턴(Command Pattern)]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-%EB%AA%85%EB%A0%B9-%ED%8C%A8%ED%84%B4Command-Pattern</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-%EB%AA%85%EB%A0%B9-%ED%8C%A8%ED%84%B4Command-Pattern</guid>
            <pubDate>Tue, 28 May 2024 11:58:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>스파르타 코딩클럽 강의 수강 (~ 1-15)</li>
<li>챌린지반 특강 수강하기 (디자인 패턴 상편)</li>
<li>명령 패턴에 대해서 자세히 파고 들기</li>
</ul>
<hr>
<hr>
<h1 id="명령-패턴command-pattern">명령 패턴(Command Pattern)</h1>
<hr>
<h2 id="개념">개념</h2>
<blockquote>
<p>발송자에서 수신자로 곧바로 명령을 전달하는 것이 아닌 중간 과정에서 새로운 명령 클래스를 생성하여 명령에 대한 처리를 독자적으로 수행하는 객체를 만드는 패턴이다. (<strong>명령의 객체화</strong>)</p>
</blockquote>
<hr>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/aab12a3a-85ac-43ec-a692-bb10b36fae19/image.png" alt=""></p>
<hr>
<hr>
<h2 id="구조와-특징">구조와 특징</h2>
<blockquote>
<h2 id="구조">구조</h2>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/227b374b-49b2-4132-822e-e74987caa578/image.png" alt=""></p>
</blockquote>
<hr>
<h3 id="발송자-클래스invoker">발송자 클래스(Invoker)</h3>
<ul>
<li>요청(명령)의 시작을 관리하는 역할<ul>
<li>해당 클래스에는 명령 객체에 대한 참조를 저장하기 위한 필드가 있어야한다. (참조 변수든 리스트 형태든 뭐든간에)</li>
<li>발송자는 요청을 수신자에게 직접 보내는 대신 해당 명령을 작동시킨다.</li>
<li>발송자는 명령 객체를 생성할 책임은 없고 일반적으로 생성자를 통해 클라이언트로부터 미리 생성된 명령을 받는다.<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="_명령command-인터페이스-_">_명령(command) 인터페이스 _</h3>
<ul>
<li>일반적으로 명령을 실행하기 위한 단일 메서드만을 선언한다.<h3 id="구체적_명령">구체적_명령</h3>
</li>
<li>다양한 유형의 요청(명령)을 구현<ul>
<li>구체적_명령은 자체적으로 작업을 수행해서는 안되며, 대신 수신자 객체 중 하나에다가 호출을 전달해야한다.
(그러나 코드의 단순화를 위해서 작업 수행 로직이 병합될 수 있다.)</li>
<li>수신자 객체에서 메서드를 실행하는 데 필요한 매개변수들은 구체적_명령 필드들로 선언할 수 있다. 생성자를 통해서만 이러한 필드의 초기화를 허용함으로써 명령 객체들을 불변으로 만들 수 있다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="수신자-클래스receiver">수신자 클래스(Receiver)</h3>
<ul>
<li>일부 작업 수행이 포함된 클래스<ul>
<li>거의 모든 객체는 수신자 역할을 할 수 있다.</li>
<li>대부분의 명령 클래스들은 요청이 수신자에게 전달되는 방법에 대한 세부 정보만 처리하는 반면 수신자 자체는 실제 작업을 수행한다.</li>
</ul>
</li>
</ul>
<hr>
<h3 id="클라이언트">클라이언트</h3>
<ul>
<li>구체적_명령 인스턴스를 만들고 설정한다.<ul>
<li>클라이언트는 수신자 인스턴스를 포함한 모든 요청 매개변수들을 명령의 생성자로 전달해야하며, 그렇게 만들어진 명령은 하나 또는 여러 발송자에게 전달될 수 있다.</li>
</ul>
</li>
</ul>
<hr>
<hr>
<h2 id="특징">특징</h2>
<h3 id="명령의-캡슐화">명령의 캡슐화</h3>
<ul>
<li>특정 작업을 수행하는데 필요한 정보를 객체로 만드는 것
이 객체는 작업을 수행하는 메서드와 그 메서드를 호출할 때 필요한 매개변수 등의 정보를 포함한다. 이렇게하면 명령 객체는 작업을 수행하는데 필요한 모든 정보를 가지고 있으므로, 이 객체를 저장하거나 전달하거나 나중에 실행하는 등의 작업을 할 수 있다. <blockquote>
</blockquote>
! 캡슐화를 통해서 <strong>명령의 저장 및 재사용</strong>이나 <strong>명령의 실행 및 취소</strong>가 가능하다.</li>
</ul>
<hr>
<hr>
<h2 id="예시">예시</h2>
<blockquote>
<p>코드 출처 : <a href="https://refactoring.guru/ko/design-patterns/command/csharp/example"><strong>리팩토링 구루</strong></a></p>
</blockquote>
<pre><code class="language-c">namespace command_Pattern
{
   // Command 인터페이스는 명령을 실행하는 메서드를 선언한다
    public interface ICommand
    {
        void Execute();
    }
&gt;
    // 일부 명령은 자체적으로 간단한 작업을 구현할 수 있다
    class SimpleCommand : ICommand
    {
        private string _payload = string.Empty;
&gt;
        public SimpleCommand(string payload) // 생성자
        {
            this._payload = payload;
        }
&gt;
        public void Execute() // 단순 작업
        {
            Console.WriteLine($&quot;단순명령: 저는 출력과 같이 단순한 일을 해낼 수 있습니다.({this._payload})&quot;);
        }
    }
&gt;
    // 그러나, 일부 명령들은 더 복잡한 작업을 수신자(Receiver)라고 불리는 객체에게 위임할 수 있다
    class ComplexCommand : ICommand
    {
        // 내 명령을 수행해줄 수신자
        private Receiver _receiver;
&gt;
        // 수신자의 메서드를 실행하는 데 필요한 컨텍스트 데이터
        private string _a;
&gt;
        private string _b;
&gt;
        // 복합 명령은 생성자를 통해 하나 이상의 수신자 객체와 함께 모든 컨텍스트 데이터를 받아들일 수 있다
        public ComplexCommand(Receiver receiver, string a, string b)
        {
            this._receiver = receiver;
            this._a = a;
            this._b = b;
        }
&gt;
        // 명령은 수신자의 어떤 메서드에게든 위임할 수 있다
        public void Execute()
        {
            Console.WriteLine(&quot;복합 명령: 복잡한 작업은 수신자 객체들에 의해 수행되어야합니다.&quot;);
            this._receiver.DoSomething(this._a);
            this._receiver.DoSomethingElse(this._b);
        }
    }
&gt;
    // 수신자 클래스는 어떤 중요한 작업 로직을 포함하고 있다
    // 그들은 요청을 수행하는 데 관련된 모든 종류의 작업을 수행하는 방법을 알고 있다
    // 사실, 어떤 클래스든지 수신자로써 역할을 할 수 있다
    class Receiver
    {
        // 작업 처리 메서드들
        public void DoSomething(string a)
        {
            Console.WriteLine($&quot;수신자: ({a})에서 작업.&quot;);
        }
&gt;
        public void DoSomethingElse(string b)
        {
            Console.WriteLine($&quot;수신자: ({b})에서도 작업&quot;);
        }
    }
&gt;
    // 발송자는 하나 또는 여러 명령어와 연결되어 있고, 명령에 요청을 보내는 역할을 한다.
    class Invoker
    {
        private ICommand _onStart; // 자신의 일을 시작하기 전에 호출할 명령
&gt;
        private ICommand _onFinish; // 자신의 일이 끝나고 나서 호출할 명령
&gt;
        // 명령 초기화 (명령 할당)
        public void SetOnStart(ICommand command)
        {
            this._onStart = command;
        }
&gt;
        public void SetOnFinish(ICommand command)
        {
            this._onFinish = command;
        }
&gt;
        // 호출자는 구체적인 명령이나 수신자 클래스에 의존하지 않는다
        // 명령을 실행함으로써 간접적으로 수신자에게 요청을 전달한다
        public void DoSomethingImportant()
        {
            Console.WriteLine(&quot;발송자: 제가 시작하기 전에 작업을 수행하시길 원하시는 분 있나요?&quot;);
            if (this._onStart is ICommand)
            {
                this._onStart.Execute();
            }
&gt;
            Console.WriteLine(&quot;발송자: ..중요한 일을 수행하는 중입니다...&quot;);
&gt;
            Console.WriteLine(&quot;발송자: 제가 끝나고 나서 작업을 수행하시길 원하시는 분 있나요?&quot;);
            if (this._onFinish is ICommand)
            {
                this._onFinish.Execute();
            }
        }
    }
&gt;
    class Program
    {
        static void Main(string[] args)
        {
            // 클라이언트 코드는 호출자를 어떤 명령어로든 매개변수화할 수 있습니다
            Invoker invoker = new Invoker(); // 발송자 인스턴스
            invoker.SetOnStart(new SimpleCommand(&quot;안녕!&quot;)); // 시작 부분에 단순 명령 할당
            Receiver receiver = new Receiver(); // 수신자 인스턴스 (복합 명령을 위한 것)
            invoker.SetOnFinish(new ComplexCommand(receiver, &quot;이메일 보내기&quot;, &quot;보고서 작성&quot;)); // 끝나는 부분에 복합 명령 할당
&gt;
            invoker.DoSomethingImportant(); // 설정된 명령 실행
        }
    }
}</code></pre>
<hr>
<h3 id="실행-결과">실행 결과</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/0b2ffeef-0cce-4ed7-ad66-d08696f38871/image.png" alt=""></li>
</ul>
<hr>
<h3 id="해당-구조-분석">해당 구조 분석</h3>
<ul>
<li>클라이언트에서 명령을 생성하여 발송자 인스턴스에게 전달하는 방식임을 알 수 있다.</li>
<li>발송자는 명령을 호출하는 순서를 정할 수 있는 것을 볼 수 있다.</li>
<li>명령 클래스들은 작업이 단순하다면 스스로 수행이 가능하고 복잡한 작업일 경우 수신자를 통해서 수신자의 메서드를 활용한다.</li>
</ul>
<hr>
<hr>
<h2 id="장단점">장단점</h2>
<blockquote>
<h3 id="장점">장점</h3>
</blockquote>
<h4 id="1-단일-책임-원칙">1. 단일 책임 원칙</h4>
<ul>
<li>작업을 호출하는 클래스들은 이러한 작업을 수행하는 클래스들로부터 분리할 수 있다.<h4 id="2-개방폐쇄-원칙">2. 개방/폐쇄 원칙</h4>
</li>
<li>기존 코드를 수정하지 않고 새 명령들을 도입할 수 있다.<h4 id="3-실행-취소다시-실행을-구현">3. 실행 취소/다시 실행을 구현</h4>
<h4 id="4-작업들의-지연된실행을-구현">4. 작업들의 지연된실행을 구현</h4>
<h4 id="5-간단한-명령들의-집합을-복잡한-명령으로-조합">5. 간단한 명령들의 집합을 복잡한 명령으로 조합</h4>
</li>
</ul>
<hr>
<h3 id="단점">단점</h3>
<h4 id="1-발송자와-수신자-사이에-완전히-새로운-레이어를-도입하기-때문에-코드가-복잡해질-수-있다">1. 발송자와 수신자 사이에 완전히 새로운 레이어를 도입하기 때문에 코드가 복잡해질 수 있다.</h4>
<hr>
<hr>
<h2 id="주요-용도">주요 용도</h2>
<blockquote>
</blockquote>
<h3 id="주요-기능">주요 기능</h3>
<h4 id="시간을-되돌리는-기능-되돌리기-시간-역행-등">시간을 되돌리는 기능 (되돌리기, 시간 역행 등)</h4>
<h4 id="로그를-기록하고-복구하는-기능">로그를 기록하고 복구하는 기능</h4>
<h3 id="주로-사용하는-게임">주로 사용하는 게임</h3>
<h4 id="rts-멀티-플레이-환경에서-플레이어들의-입력을-동기화-및-통일화결정론적-락스텝">RTS 멀티 플레이 환경에서 플레이어들의 입력을 동기화 및 통일화(결정론적 락스텝)</h4>
<h4 id="체스나-바둑-같은-게임에서-기보를-기록-및-수-되돌리기-기능">체스나 바둑 같은 게임에서 기보를 기록 및 수 되돌리기 기능</h4>
<hr>
<hr>
<h2 id="직접-만들어보기">직접 만들어보기</h2>
<blockquote>
<p><strong>... coming soon ...</strong></p>
</blockquote>
<hr>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
<h3 id="리팩토링-구루---커맨드-패턴"><a href="https://refactoring.guru/ko/design-patterns/command">리팩토링 구루 - 커맨드 패턴</a></h3>
</blockquote>
<hr>
<hr>
<hr>
<h1 id="til을-작성하면서-느낀점">TIL을 작성하면서 느낀점</h1>
<blockquote>
<p>디자인 패턴이라는 것은 사실은 뭔가 흐릿흐릿했던 코드 구조들을 정형화하게 된 일종의 도구라고 생각한다. 물론 이 패턴을 알지 않아도 점차 코딩을 하다보면 디자인 패턴에 가까워지긴 하지만, 디자인 패턴을 대략적으로 아는 것과 뚜렷하게 아는 거랑은 큰 차이가 있다고 생각한다. 앞으로 코딩할 때 다양한 디자인 패턴을 배워서 내 개발 실력을 한층 끌어올리기 위해 노력해야겠다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 7주차 1일차 - 레이캐스팅 ]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-%EB%A0%88%EC%9D%B4%EC%BA%90%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-7%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-%EB%A0%88%EC%9D%B4%EC%BA%90%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Mon, 27 May 2024 14:45:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이</li>
<li>스파르타 코딩클럽 진도 나가기 (~ 1-10)</li>
</ul>
<hr>
<hr>
<h1 id="raycast">Raycast</h1>
<h2 id="개념">개념</h2>
<blockquote>
<p>유니티의 기능 중 하나로 보이지 않는 선(Ray)을 출력하여 해당 선에 닿인 오브젝트들의 정보를 가져오는 유니티 기능 중 하나이다.</p>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/ab971d7c-9e93-4001-9530-218811a21c27/image.png" alt=""></li>
</ul>
<hr>
<hr>
<h2 id="사용-방법">사용 방법</h2>
<blockquote>
</blockquote>
<h3 id="ray-만들기">Ray 만들기</h3>
<pre><code class="language-c">Ray ray = new Ray(transform.position, transform.forward); // 오브젝트
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); // 카메라 중심
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition)); // 마우스</code></pre>
<blockquote>
</blockquote>
<h3 id="raycast를-활용해서-검출하기">Raycast를 활용해서 검출하기</h3>
<pre><code class="language-c">void Update()
{
    IsPlayer();
}
&gt;
void IsPlayer()
{
   Ray ray = new Ray(lazer.position, Vector3.forward); // (위치, 방향)
   if (Physics.Raycast(ray, 10f, playerLayerMask))
   {
       text.text = &quot;정면 감지됨&quot;;
   }
}</code></pre>
<hr>
<hr>
<h2 id="raycast-1">Raycast()</h2>
<blockquote>
</blockquote>
<h3 id="매개-변수에-대한-설명">매개 변수에 대한 설명</h3>
<h4 id="origin">origin</h4>
<ul>
<li>Ray 시작점<h4 id="direction">direction</h4>
</li>
<li>Ray 방향<blockquote>
</blockquote>
</li>
<li><em>! 위 둘을 퉁쳐서 ray를 넣기도 한다.*</em><h4 id="maxdistance">maxDistance</h4>
</li>
<li>Ray의 최대 검출 거리 (기본값은 Infinite이다)<h4 id="layermask">layerMask</h4>
</li>
<li>Ray에 적중된 오브젝트가 건네준 레이어와 동일한 지 검사<h4 id="raycasthit">RaycastHit</h4>
</li>
<li>가장 가까운 오브젝트의 정보를 가져옴</li>
<li><blockquote>
<p><strong>RaycastHit</strong> - 객체의 정보</p>
</blockquote>
</li>
<li><blockquote>
<p><strong>RaycastHit.point</strong> - 레이캐스팅이 감지된 위치 </p>
</blockquote>
</li>
<li><blockquote>
<p><strong>RaycastHit.distance</strong> - Ray의 원점에서 충돌 지점까지의 거리</p>
</blockquote>
</li>
<li><blockquote>
<p><strong>RaycastHit.transform</strong> - 충돌 객체의 transform 정보</p>
</blockquote>
<h4 id="querytriggerinteraction">queryTriggerInteraction</h4>
</li>
<li>적중된 오브젝트들을 쿼리를 통해서 감지할 지 무시할 지 정한다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<h3 id="각종-오버로딩">각종 오버로딩</h3>
<h3 id="--physicsraycast">-&gt; <a href="https://docs.unity3d.com/ScriptReference/Physics.Raycast.html">Physics.Raycast</a></h3>
<hr>
<hr>
<h2 id="사용-시-알아야할-것">사용 시 알아야할 것</h2>
<blockquote>
<p>Raycast를 제대로 활용하려면 오브젝트의 방향이 어디인지 알아야한다.</p>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/fb42d6be-a3fd-4fd4-9e4f-452273d51280/image.png" alt=""></li>
<li>해당 화살표들은 보면, 빨간색은 X, 녹색은 Y, 파란색은 Z 방향이다.</li>
</ul>
<hr>
<hr>
<h3 id="실험-코드">실험 코드</h3>
<pre><code class="language-c"> void IsPlayer()
 {
     Ray rayF = new Ray(lazer.position, Vector3.forward); // (위치, 방향)
     Ray rayB = new Ray(lazer.position, Vector3.back);
     Ray rayL = new Ray(lazer.position, Vector3.left);
     Ray rayR = new Ray(lazer.position, Vector3.right);
     Ray rayU = new Ray(lazer.position, Vector3.up);
&gt;
     if (Physics.Raycast(rayF, 10f, playerLayerMask))
     {
         text.text = &quot;정면 감지됨&quot;;
     }
     else if (Physics.Raycast(rayB, 10f, playerLayerMask))
     {
         text.text = &quot;후면 감지됨&quot;;
     }
     else if (Physics.Raycast(rayL, 10f, playerLayerMask))
     {
         text.text = &quot;좌측 감지됨&quot;;
     }
     else if (Physics.Raycast(rayR, 10f, playerLayerMask))
     {
         text.text = &quot;우측 감지됨&quot;;
     }
     else if (Physics.Raycast(rayU, 10f, playerLayerMask))
     {
         text.text = &quot;상측 감지됨&quot;;
     }
     else
     {
         text.text = &quot;아무것도 감지되지 않음&quot;;
     }
 }</code></pre>
<hr>
<hr>
<h3 id="영상-결과">영상 결과</h3>
<ul>
<li>기본적인 시험</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/935581ae-50d2-44d3-9a0c-5f757219a045/image.gif" alt=""></li>
<li>해당 영상을 보면 아래와 같은 결과가 도출될 수 있음을 알 수 있다.
<img src="https://velog.velcdn.com/images/b_h_b/post/3db2114b-98bd-4027-8275-9a1ffb9cd59e/image.png" alt="">
<img src="https://velog.velcdn.com/images/b_h_b/post/b0077c9d-5ef8-4e6f-8ab4-b9e07d5bacc8/image.png" alt=""></li>
<li>오브젝트들을 관리할 때 이 화살표를 유의하면서 Raycast를 활용해주자</li>
</ul>
<hr>
<ul>
<li>rotation을 돌려서 시험
<img src="https://velog.velcdn.com/images/b_h_b/post/a744390b-18aa-4a12-847f-d82d53f92022/image.gif" alt=""></li>
<li>rotation이 바뀐거랑 Ray의 방향이랑은 전혀 관계가 없는 것 같다.</li>
</ul>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>새로운 강의 자료가 나오니 실력을 더 쌓을 수 있게 될 수 있어서 좋은 반면 해당 강의 내용을 제대로 소화해내려면은 TIL을 좀 더 간결하고 알차게 써줘야할 필요성이 느껴진다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 6주차 4일차 TIL - SOLID 원칙]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL-SOLID-%EC%9B%90%EC%B9%99</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL-SOLID-%EC%9B%90%EC%B9%99</guid>
            <pubDate>Thu, 23 May 2024 14:45:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이 (오브젝트 풀링)</li>
<li>프로젝트 제출 및 발표</li>
<li>객체 지향 특강 2부</li>
</ul>
<hr>
<p>오늘은 객체 지향 특강에서 들었던 SOLID 원칙에 대해서 알아보고자 한다.</p>
<hr>
<h1 id="solid-원칙">SOLID 원칙</h1>
<blockquote>
<ul>
<li><strong>객체 지향 프로그래밍에서 소프트웨어 디자인의 다섯 가지 기본원칙</strong>
이 원칙은 소프트웨어의 유지보수성, 확장성, 재사용성 등을 향상시키는 데 중요한 역할을 한다.</li>
</ul>
</blockquote>
<ol>
<li><strong>S - Single Responsibility Principle (단일 책임 원칙)</strong><ul>
<li>클래스는 하나의 책임만 가져야 하며, 해당 클래스의 변경 사유는 하나여야 합니다.</li>
</ul>
</li>
<li><strong>O - Open/Closed Principle (개방/폐쇄 원칙)</strong><ul>
<li>소프트웨어 엔티티(클래스, 모듈, 함수 등)는 확장에 대해서는 열려 있어야 하지만 변경에 대해서는 닫혀 있어야 합니다.</li>
</ul>
</li>
<li><strong>L - Liskov Substitution Principle (리스코프 치환 원칙)</strong><ul>
<li>하위 클래스는 상위 클래스에서 가능한 모든 동작을 수행할 수 있어야 합니다.</li>
</ul>
</li>
<li><strong>I - Interface Segregation Principle (인터페이스 분리 원칙)</strong><ul>
<li>클라이언트는 사용하지 않는 메서드에 의존하도록 강요받아서는 안 됩니다. (단일 책임 원칙의 인터페이스 버전)</li>
</ul>
</li>
<li><strong>D - Dependency Inversion Principle (의존성 역전 원칙)</strong><ul>
<li>상위 모듈은 하위 모듈에 의존해서는 안 되며, 둘 모두 추상화에 의존해야 합니다. (또한 상위 모듈을 상속받은 자기와 같은 단계의 모듈로부터 상속 받아서는 안된다.)<blockquote>
</blockquote>
이 원칙을 준수하면 유지보수성이 높아지고 확장이 용이하며, 더욱 견고하고 효율적인 소프트웨어를 만들 수 있게 됩니다</li>
</ul>
</li>
</ol>
<hr>
<hr>
<hr>
<h2 id="단일-책임-원칙">단일 책임 원칙</h2>
<blockquote>
</blockquote>
<p><strong>단일 책임 원칙(Single Responsibility Principle, SRP)</strong>은 소프트웨어 개발에서 가장 기본적인 원칙 중 하나로, 각 클래스는 단 하나의 책임만을 가져야 한다는 원칙입니다. 이것은 클래스가 변경되어야 하는 이유가 단 하나여야 한다는 것을 의미합니다.</p>
<blockquote>
</blockquote>
<ol>
<li><strong>클래스는 단일 기능을 수행합니다</strong>: 각 클래스는 한 가지 명확한 목적이나 기능을 수행합니다. 이는 클래스가 변경되어야 하는 이유를 명확하게 파악할 수 있도록 합니다.</li>
<li><strong>모듈성과 재사용성을 높입니다</strong>: 단일 책임을 가진 클래스는 다른 클래스와의 결합도가 낮아지므로 모듈화가 용이해집니다. 이로써 코드를 재사용하고 수정하기 쉬워집니다.</li>
<li><strong>유지보수가 용이합니다</strong>: 각 클래스가 단일 책임을 가지므로 코드의 수정이 필요할 때 해당 클래스만 수정하면 됩니다. 이는 코드의 복잡성을 줄이고 유지보수 비용을 낮출 수 있습니다.<blockquote>
</blockquote>
소프트웨어에서 데이터베이스 접속, 데이터 처리, 사용자 인터페이스 표시 등 각각의 작업은 서로 다른 책임을 가지고 있다. 따라서 각각의 작업을 담당하는 클래스를 따로 구성하는 것이 SRP를 준수하는 좋은 방법이다.<blockquote>
</blockquote>
SRP를 지키지 않는 코드는 한 클래스가 너무 많은 책임을 가지거나, 여러 기능을 포함하고 있는 형태이다. 이러한 형태는 가독성이 떨어지고 유지 보수의 어려움을 증가시킨다.</li>
</ol>
<hr>
<hr>
<h3 id="단일-책임-원칙을-준수한-코드-예시">단일 책임 원칙을 준수한 코드 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">using System;
&gt;
// 사용자 정보를 관리하는 클래스
public class UserManager {
    private Database database;
&gt;
    public UserManager(Database database) {
        this.database = database;
    }
&gt;
    // 사용자를 데이터베이스에 저장하는 메서드
    public void SaveUser(User user) {
        // 데이터베이스에 저장하는 로직
    }
&gt;
    // 사용자를 데이터베이스에서 불러오는 메서드
    public User LoadUser(int userId) {
        // 데이터베이스에서 사용자 정보를 불러오는 로직
        return null;
    }
}
&gt;
// 데이터베이스 관리 클래스
public class Database {
    // 데이터베이스 연결 및 관리를 위한 메서드
}
&gt;
// 사용자 정보 클래스
public class User {
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}</code></pre>
<p>이 코드에서는 <code>UserManager</code> 클래스는 사용자 정보의 저장 및 로드와 같은 사용자 관리 기능만을 수행합니다. 따라서 클래스의 책임이 명확하게 구분되어 있습니다.</p>
<hr>
<hr>
<h3 id="단일-책임-원칙을-준수하지-않은-코드-예시">단일 책임 원칙을 준수하지 않은 코드 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">using System;
&gt;
// 사용자 정보를 관리하고 동시에 UI를 업데이트하는 클래스
public class UserManager {
    private Database database;
    private UIUpdater uiUpdater;
&gt;
    public UserManager(Database database, UIUpdater uiUpdater) {
        this.database = database;
        this.uiUpdater = uiUpdater;
    }
&gt;
    // 사용자를 데이터베이스에 저장하고 동시에 UI를 업데이트하는 메서드
    public void SaveUserAndUpdateUI(User user) {
        // 데이터베이스에 저장하는 로직
        // UI를 업데이트하는 로직
    }
&gt;
    // 사용자를 데이터베이스에서 불러오고 동시에 UI를 업데이트하는 메서드
    public User LoadUserAndUpdateUI(int userId) {
        // 데이터베이스에서 사용자 정보를 불러오는 로직
        // UI를 업데이트하는 로직
        return null;
    }
}
&gt;
// 데이터베이스 관리 클래스
public class Database {
    // 데이터베이스 연결 및 관리를 위한 메서드
}
&gt;
// UI를 업데이트하는 클래스
public class UIUpdater {
    // UI 업데이트를 위한 메서드
}</code></pre>
<p>이 코드에서는 <code>UserManager</code> 클래스가 사용자 정보의 저장과 동시에 UI 업데이트도 수행합니다. 이는 클래스가 여러 가지 책임을 가지고 있어서 단일 책임 원칙을 위반하는 것입니다. 이러한 경우 클래스가 변경되어야 하는 이유가 둘 이상이 될 수 있으며, 코드의 유지보수가 어려워질 수 있습니다.</p>
<hr>
<hr>
<hr>
<h2 id="개방폐쇄-원칙">개방/폐쇄 원칙</h2>
<blockquote>
</blockquote>
<p><strong>개방/폐쇄 원칙(Open/Closed Principle, OCP)</strong>는 소프트웨어 설계 원칙 중 하나로, &quot;소프트웨어 개체(클래스, 모듈, 함수 등)는 확장에는 열려 있어야 하고 수정에는 닫혀 있어야 한다&quot;는 원칙을 의미합니다. 즉, 새로운 기능을 추가할 때는 기존 코드를 수정하지 않고 확장을 통해 가능하도록 해야 합니다.</p>
<blockquote>
</blockquote>
<ol>
<li><strong>추상화와 다형성 활용</strong>: 추상화를 통해 인터페이스나 추상 클래스를 정의하고, 다형성을 활용하여 이를 구현하는 클래스들을 만듭니다. 새로운 기능이 추가되면 새로운 구현 클래스를 만들어 기존 인터페이스를 구현하면 됩니다.<blockquote>
</blockquote>
</li>
<li><strong>확장 포인트 제공</strong>: 기존 코드에 새로운 기능을 추가하기 위한 확장 포인트를 제공합니다. 이를 통해 새로운 기능을 추가하는데 필요한 수정을 최소화하고, 기존 코드의 안정성을 유지할 수 있습니다.<blockquote>
</blockquote>
</li>
<li><strong>디자인 패턴 활용</strong>: 디자인 패턴 중 전략 패턴, 팩토리 패턴 등을 활용하여 OCP를 지키는 방법이 있습니다. 이러한 패턴은 새로운 기능을 추가할 때 기존 코드 수정을 최소화하고 확장성을 높일 수 있도록 도와줍니다.<blockquote>
</blockquote>
OCP를 준수하면 코드의 재사용성과 유연성을 높일 수 있으며, 새로운 기능 추가 시 코드 변경으로 인한 부작용을 최소화할 수 있습니다.</li>
</ol>
<hr>
<hr>
<h3 id="ocp를-지킨-코드-예시">OCP를 지킨 코드 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">// 추상화를 통한 확장 포인트 제공
public interface IShape
{
    void Draw();
}
&gt;
// 기존 코드 수정 없이 새로운 기능 추가
public class Circle : IShape
{
    public void Draw()
    {
        Console.WriteLine(&quot;Drawing a circle&quot;);
    }
}
&gt;
public class Square : IShape
{
    public void Draw()
    {
        Console.WriteLine(&quot;Drawing a square&quot;);
    }
}
&gt;
// 새로운 기능을 추가하기 위해 확장
public class Triangle : IShape
{
    public void Draw()
    {
        Console.WriteLine(&quot;Drawing a triangle&quot;);
    }
}</code></pre>
<p>위 코드에서는 <code>IShape</code> 인터페이스를 통해 도형을 추상화하고, 이를 구현하는 클래스들을 만듭니다. 새로운 기능을 추가할 때는 기존 코드를 수정하지 않고 <code>IShape</code> 인터페이스를 구현하는 새로운 클래스를 추가함으로써 확장합니다.</p>
<hr>
<hr>
<h3 id="ocp를-지키지-않은-코드-예시">OCP를 지키지 않은 코드 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">public class DrawingTool
{
    public void DrawCircle()
    {
        Console.WriteLine(&quot;Drawing a circle&quot;);
    }
&gt;
    public void DrawSquare()
    {
        Console.WriteLine(&quot;Drawing a square&quot;);
    }
}
&gt;
// 새로운 기능 추가 시 기존 코드를 수정해야 함
public class DrawingToolExtended : DrawingTool
{
    public void DrawTriangle()
    {
        Console.WriteLine(&quot;Drawing a triangle&quot;);
    }
}</code></pre>
<blockquote>
</blockquote>
<p>위 코드에서는 새로운 기능을 추가할 때 기존 클래스를 상속받아 새로운 메서드를 추가하는 방식을 사용합니다. 이 경우 새로운 기능을 추가할 때마다 기존 코드를 수정해야 하므로 OCP를 위반하게 됩니다. 또한 이러한 방식은 클래스 간의 결합도가 높아져 유지보수가 어려워질 수 있습니다.</p>
<hr>
<hr>
<hr>
<h2 id="리스코프-치환-원칙">리스코프 치환 원칙</h2>
<blockquote>
</blockquote>
<p><strong>리스코프 치환 원칙(Liskov Substitution Principle, LSP)</strong>은 객체 지향 프로그래밍에서 중요한 원칙 중 하나입니다. 이 원칙은 “하위 타입은 그것의 기반(상위) 타입으로 대체될 수 있어야 한다”는 원칙을 나타냅니다. 즉, 어떤 클래스를 사용하는 코드는 그 클래스의 하위 클래스를 사용하더라도 동일하게 작동해야합니다.</p>
<ul>
<li>하위 클래스는 상위 클래스에서 가능한 모든 동작을 수행할 수 있어야 한다.</li>
<li>이 원칙은 상속 관계에서 하위 클래스가 상위 클래스와 동일하게 행동하도록 보장하여 프로그램의 논리적 일관성을 유지해야 합니다.</li>
</ul>
<hr>
<hr>
<h3 id="리스코프-치환-원칙의-예시">리스코프 치환 원칙의 예시</h3>
<blockquote>
</blockquote>
<p>Animal이라는 상위 클래스가 있고, Dog과 Cat이라는 두 개의 하위 클래스가 있다고 가정해봅시다. 이 경우, Animal 클래스를 사용하는 코드는 Dog 또는 Cat 클래스를 사용하더라도 동일하게 작동해야 합니다.</p>
<pre><code class="language-c">public class Animal
{
    public virtual void Speak()
    {
        Debug.Log(&quot;The animal speaks.&quot;);
    }
}
&gt;
public class Dog : Animal
{
    public override void Speak()
    {
        Debug.Log(&quot;The dog barks.&quot;);
    }
}
&gt;
public class Cat : Animal
{
    public override void Speak()
    {
        Debug.Log(&quot;The cat meows.&quot;);
    }
}
&gt;
public class AnimalTest
{
    public void LetAnimalSpeak(Animal animal)
    {
        animal.Speak();
    }
}</code></pre>
<p>위 코드에서는 LetAnimalSpeak메서드는 Animal 클래스의 인스턴스를 매개변수로 받습니다. 그러나 이 메서드는 Dog 또는 Cat 클래스의 인스턴스를 받아도 동일하게 작동합니다. 이것이 리스코프 치환 원칙을 따르는 예입니다. 이 원칙을 따르면 코드의 유연성과 재사용성이 향상 되며, 클래스 계층 구조의 설계가 개선됩니다.</p>
<hr>
<hr>
<h3 id="리스코프-치환-원칙을-위반의-예시">리스코프 치환 원칙을 위반의 예시</h3>
<blockquote>
</blockquote>
<p>직사각형과 정사각형의 관계를 생각해봅시다. 정사각형은 직사각형의 특별한 경우이므로, 직사각형 클래스를 상속하여 정사각형 클래스를 만들 수 있을 것 같습니다. 그러나 이 경우 문제가 발생할 수 있습니다. 직사각형 클래스에서 너비와 높이는 독립적으로 설정할 수 있지만, 정사각형 클래스에서는 너비와 높이가 항상 같아야합니다. 따라서 정사각형 클래스는 직사각형 클래스의 모든 기능을 제대로 대체할 수 없으므로, 이 경우는 리스코프 치환 원칙을 어기는 경우이다.</p>
<blockquote>
</blockquote>
<p><strong>대체 가능한 비유</strong> - 고래는 물에 살지만 아기를 낳기 때문에 어류 클래스가 아니라 포유류 클래스에 속하는 거랑 비슷하다. 
해당 경우에는 어류 클래스의 알을 낳는 기능을 상속받지 못했기 때문에 리스코프 치환 원칙을 어기게 되는 것이다.</p>
<blockquote>
</blockquote>
<p>그래서 이와 같이 상속하기 애매한 부분에서는 <strong>클래스의 상속 대신에 인터페이스의 상속을 이용해줘야한다.</strong></p>
<pre><code class="language-c">public interface Size
{
    public void GetShape();
}
&gt;
public enum Habitat // 열거형으로도 속성을 나눠줄 수 있다.
{
    Land,
    Marine
}</code></pre>
<hr>
<hr>
<hr>
<h2 id="인터페이스-분리-원칙">인터페이스 분리 원칙</h2>
<blockquote>
</blockquote>
<p><strong>&#39;인터페이스 분리 원칙(Interface Segregation Principle, ISP)&#39;</strong>을 나타냅니다. 
이 원칙은 클라이언트가 자신이 이용하지 않는 메서드에 의존하지 않아야 한다는 원칙을 의미합니다. 즉, 클라이언트에게 필요한 메서드만을 제공해야 하며, 불필요한 메서드는 제거하거나 다른 인터페이스로 분리해야 합니다.</p>
<blockquote>
</blockquote>
<p>예를 들어, ISmartPhone이라는 인터페이스가 있고, 이 인터페이스에는 여러 가지 스마트폰 기능을 정의하는 여러 메서드가 있다고 가정해 봅시다. 그런데 이 중 일부 기능은 특정 스마트폰 모델에서만 지원되는 기능일 수 있습니다. 이런 경우, 모든 스마트폰 모델이 ISmartPhone 인터페이스의 모든 메서드를 구현해야 하는 것은 비효율적입니다.</p>
<blockquote>
</blockquote>
<p>해당 원칙을 지킨다면</p>
<blockquote>
</blockquote>
<p><strong>1. 코드의 복잡성 감소 **
**2. 유지 관리가 용이</strong>
<strong>3. 코드의 재사용성이 향상</strong>
<strong>4. 클래스와 인터페이스의 설계가 개선</strong></p>
<hr>
<hr>
<h3 id="인터페이스-분리-원칙을-지킨-예시">인터페이스 분리 원칙을 지킨 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">public interface IBasicPhone
{
    void Call(string number);
    void SendText(string number, string text);
}
&gt;
public interface IAdvancedPhone : IBasicPhone
{
    void UseWirelessCharging();
    void UseBiometrics();
}
&gt;
public class BasicPhoneModel : IBasicPhone
{
    public void Call(string number) { /* 구현 */ }
    public void SendText(string number, string text) { /* 구현 */ }
}
&gt;
public class AdvancedPhoneModel : IAdvancedPhone
{
    public void Call(string number) { /* 구현 */ }
    public void SendText(string number, string text) { /* 구현 */ }
    public void UseWirelessCharging() { /* 구현 */ }
    public void UseBiometrics() { /* 구현 */ }
}</code></pre>
<p>이 코드에서 BasicPhoneModel은 기본적인 전화와 문자 메시지 기능만을 지원하므로 IBasicPhone 인터페이스만을 구현하고, AdvancedPhoneModel은 추가적인 무선 충전과 생체 인식 기능을 지원하므로 IAdvancedPhone 인터페이스를 구현합니다. 이렇게 하면 각 스마트폰 모델은 자신이 지원하는 기능을 정의하는 인터페이스만을 구현하게 되므로, 인터페이스 분리 원칙을 준수하게 됩니다</p>
<hr>
<hr>
<hr>
<h2 id="의존-역전-원칙">의존 역전 원칙</h2>
<blockquote>
</blockquote>
<p><strong>의존 역전 원칙(Dependency Inversion Principle, DIP)</strong></p>
<blockquote>
</blockquote>
<ul>
<li>상위 모듈은 하위 모듈에 의존해서는 안됩니다. 둘 다 추상화에 의존해야합니다.</li>
<li>추상화는 세부 사항에 의해 의존해서는 안됩니다. 세부 사항이 추상화에 의존해야합니다.<blockquote>
</blockquote>
이 원칙은 소프트웨어 구조의 결합도를 줄이는데 중요한 역할을 합니다. 이를 통해 소프트웨어의 유지 보수성과 확장성이 향상됩니다. 이 원칙을 따르면, 시스템의 각 부분을 독립적으로 개발하고 변경할 수 있으므로, 전체 시스템에 대한 영향을 최소화할 수 있습니다.</li>
</ul>
<hr>
<hr>
<h3 id="의존-역전-원칙을-지킨-예시">의존 역전 원칙을 지킨 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">public interface IWeapon
{
    void Use();
}
&gt;
public class Player
{
    private IWeapon _weapon;
&gt;
    public Player(IWeapon weapon)
    {
        _weapon = weapon;
    }
&gt;
    public void Attack()
    {
        _weapon.Use();
    }
}
&gt;
public class Sword : IWeapon
{
    public void Use()
    {
        // 공격 로직
    }
}</code></pre>
<blockquote>
</blockquote>
<p>이 예시에서는 Player 클래스가 IWeapon이라는 인터페이스에 의존하고 있습니다. 이로 인해 Player는 Sword 뿐만 아니라 IWeapon 인터페이스를 구현하는 어떤 무기와도 작동할 수 있습니다. 이렇게 하면 Player 클래스를 변경하지 않고도 다른 무기를 사용할 수 있습니다. 이것이 의존 역전 원칙을 지키는 방법입니다. 이 원칙을 따르면 코드의 유연성과 재사용성이 향상됩니다.</p>
<hr>
<hr>
<h3 id="의존-역전-원칙을-지키지-않은-예시">의존 역전 원칙을 지키지 않은 예시</h3>
<blockquote>
</blockquote>
<pre><code class="language-c">public class Player
{
    private Sword _sword;
&gt;
    public Player()
    {
        _sword = new Sword();
    }
&gt;
    public void Attack()
    {
        _sword.Swing();
    }
}
&gt;
public class Sword
{
    public void Swing()
    {
        // 공격 로직
    }
}</code></pre>
<p>위의 코드에서 Player 클래스는 Sword 클래스에 직접적으로 의존하고 있습니다. 이는 Player가 다른 무기를 사용하려면 Player 클래스를 변경해야함을 의미합니다.</p>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>객체 지향 원칙을 얼핏 들어보기만 했지 제대로 이해했다는 생각은 많이 들지 않았다. 오늘 특강을 듣고 다시 이해해보는 과정을 거치니까. 전보다 좀 더 잘 이해하게 되었다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 6주차 3일차 TIL - 객체 지향의 중요성]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</guid>
            <pubDate>Wed, 22 May 2024 14:17:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이 (제너릭 싱글턴 구현하기)</li>
<li>팀 프로젝트 진행 (파워업 추가(보호막), 프로젝트 마무리, 발표 준비)</li>
<li>게임 프로젝트 개발하면서 아쉬웠던 점 회고하기</li>
</ul>
<hr>
<p>...작성 중...
오늘은 객체 지향의 4가지 특징에 대해서 회고하고, 객체 지향 프로그래밍의 특징을 활용하여 프로젝트에서 구현한 기능 중 하나인 떨어지는 오브젝트들을 리팩토링해보기로 했다.</p>
<p>기초적인 부분들은 어느 정도 넘어갈 수도 있으니 주의하자</p>
<hr>
<h1 id="객체-지향-프로그래밍">객체 지향 프로그래밍</h1>
<h2 id="개념">개념</h2>
<blockquote>
<p>객체 지향 프로그래밍은 컴퓨터 프로그램을 명령어의 목록으로 보는 시각에서 벗어나 여러 개의 독립된 단위, 즉 &quot;객체&quot;들의 모음으로 파악하고자 하는 것이다. 각각의 객체는 메시지를 주고받고, 데이터를 처리할 수 있다. <a href="https://ko.wikipedia.org/wiki/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D"><strong>위키백과</strong></a></p>
</blockquote>
<hr>
<hr>
<hr>
<h2 id="4가지-특징">4가지 특징</h2>
<h3 id="추상화">추상화</h3>
<blockquote>
</blockquote>
<ul>
<li>구체적인 여러 물건들의 공통적인 특징이나 기능을 추출하는 것</li>
<li>불필요한 세부 사항들을 제거하고 가장 본질적이고 공통적인 부분만을 추출하여 표현하는 것 <blockquote>
</blockquote>
객체 지향 프로그래밍 중 하나인 추상화는 여러 객체의 공통적인 부분을 묶어서 동일한 데이터와 기능들을 클래스로 묶어서 상속해주기 위해 사용된다. (또는 인스턴스를 생성할 때 인스턴스마다의 공통된 특징을 묶어서 클래스 하나만으로도 다양한 종류의 인스턴스를 만드는 코드를 기획할 때 쓰인다. [클래스를 붕어빵틀이라고 설명하는 이유]<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="추상화-예시">추상화 예시</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/72449732-d5a0-46df-a955-c66a91927547/image.png" alt=""></li>
<li>위 이미지를 보면 마인크래프트의 도구들을 하나로 묶어주는건데, 마인크래프트를 해보면 알겠지만 <strong>각 도구들에게는 특정 블럭을 캐면 다른 걸 들고있는 것보다 빨리 캐는 특성</strong>을 가지고 있다. (<strong>DigSpeed</strong>) 추상화를 이용한다면 공통된 특징을 찾아내고 이것을 통해서 객체들이 상속받을 부모 클래스를 설계할 수 있다.</li>
</ul>
<hr>
<h4 id="조금-더-추상화해보자">조금 더 추상화해보자</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/609ef181-c29f-4ced-aee9-b28e940665d3/image.png" alt=""></li>
<li>위와 같이 <strong>도구, 음식, 블럭으로 추상화</strong>해봤고 이러한 <strong>모든 애들이 플레이어의 인벤토리에 들어가기 때문에 Item이라는 추상화</strong>까지 해낼 수 있다. 마인크래프트에서 아이템들은 전부 인벤토리 칸에 들어갈 수 있다. (<strong>Pick()</strong>)</li>
</ul>
<hr>
<p>이렇게 게임 속 객체들에 대한 추론 뿐만 아니라 실제 사물에서도 이러한 추론을 통해 공통된 특징을 유추해볼 수 있다. (자동차, 오토바이 -&gt; 이동수단) 이러한 <em>추상화를 잘한다면 구체화된 객체들에게 상속해줄 부모 클래스의 설계를 잘해낼 수 있다.</em>
그렇기 때문에 <strong>게임 기획에 있어서 추상화라는 개념은 매우 중요한 요소</strong>이다. </p>
<hr>
<hr>
<h3 id="상속">상속</h3>
<blockquote>
</blockquote>
<ul>
<li>특정 클래스의 데이터와 기능들을 하위 클래스에게 물려주는 것<blockquote>
</blockquote>
상속을 통해서 각 객체들마다 중복되는 코드를 계속해서 써내는 것보다는 공통된 특징들을 부모 클래스를 하나 만들어내고 그러한 특징을 가진 객체들에게 상속시켜주자. 앞서했던 추상화를 통해 오브젝트끼리의 공통된 데이터와 기능을 추출하고 그걸 바탕으로 상속된 부모 클래스를 만들어주면 더욱더 효율적으로 상속시켜줄 수 있다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="기본적인-상속">기본적인 상속</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/0a5f5fe2-be1b-468d-92ef-90af465f507b/image.png" alt=""></li>
<li>위 이미지를 보면 Tool 클래스는 Sword의 부모 클래스로 Sword 클래스에게 필드와 기능들을 전달해주고 있다. 이 상속을 이용한다면 공통된 특징들은 부모 클래스로 통해서 전해주고 자신의 구체적인 특징들은 자식 클래스에서 구현해주면 된다.</li>
</ul>
<hr>
<h4 id="상속-관계">상속 관계</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/847b9b7d-a920-42d1-a5e7-dbba828b38e0/image.png" alt=""></li>
<li><strong>상속의 길이에는 제한이 없다</strong> (위에서부터 상속된 클래스의 모든 멤버들을 가져갈 수 있는 것이다.) 하지만 <strong>*클래스의 다중 상속은 불가능</strong>하니 그 점을 유의하자 (<em>*하나의 클래스가 두 개의 클래스를 상속 받는 것</em>)</li>
</ul>
<hr>
<h3 id="인터페이스">인터페이스</h3>
<ul>
<li>객체 지향 프로그래밍의 기능 중 하나로 기능의 틀을 만드는 역할을 한다. 인터페이스는 클래스처럼 상속 받을 수 있지만 인터페이스 내에 있는 메서드들을 반드시 표현해줘야한다.<h4 id="인터페이스-사용">인터페이스 사용</h4>
</li>
<li>필드 구현이 불가능하다. (상수는 가능하다.)</li>
<li>인터페이스를 상속 받는다면 해당 인터페이스의 멤버들을 모두 구현해줘야한다.</li>
<li>클래스와 달리 <strong>다중 상속</strong>이 가능하다. <h4 id="인테페이스-예시">인테페이스 예시</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/33c68df5-7102-41a5-915d-ac153f100eac/image.png" alt=""></li>
<li>인터페이스는 주로 자주 사용되는 기능에 대한 부분을 구현해줄 때 사용된다. (분류로 나누기에는 애매할 때)<blockquote>
</blockquote>
</li>
</ul>
<hr>
<p>상속은 <strong>공통된 데이터와 기능을 부모 클래스를 통해 상속하여 코드의 중복을 줄이기 위해 사용</strong>된다.
좀 더 디테일하게 들어가주면 클래스는 주로 객체들의 명사적인 부분, 정체성에 대해 설정할 때 기획해주는 것이 좋고, 인터페이스는 주로 객체들의 동사적, 기능적인 부분에 대해 설정할 때 기획해주는 것이 올바른 방향이다.
<strong>class -&gt; A is ~</strong> ||| <strong>Interface -&gt; A can ~</strong></p>
<hr>
<hr>
<h3 id="다형성">다형성</h3>
<blockquote>
</blockquote>
<ul>
<li>어떤 객체가 상황에 따라서 다양한 기능이나 속성을 가질 수 있는 것을 말한다.<blockquote>
</blockquote>
예를 들어서 검이라는 무기는 적을 공격할 수 있지만, 거미줄을 쉽게 깨는 역할도 있고, 아이템 액자에 걸어줄 수 있는 등 단순히 한 가지의 특징이나 기능 뿐만 아니라 다양한 것들을 수행해낼 수 있다는 것을 말한다. 다형성의 특징을 가진 기능은 주로 <strong>overriding</strong>과 <strong>overloading</strong>이나 <strong>상위 클래스의 변수를 참조</strong>하는 기능들이 있다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="상위-클래스인터페이스-참조">상위 클래스/인터페이스 참조</h4>
<p>사실 다형성의 가장 큰  부분을 생각한다면 아무래도 상위 존재 참조에 대한 부분이 있을 것이다.
우리가 특정 기능에 대한 액세스를 시도하기 위해서는 해당 클래스 타입 인스턴스를 받아오고 그 클래스 내에 있는 메서드를 실행시켜줘야한다.</p>
<h5 id="코드-예시">코드 예시</h5>
<pre><code class="language-c">// 인스턴스를 받아와서 일을 수행하는 코드
public class Something
{
    public void Method1(Class1 instance)
    {
        instance.method1;
    }
}</code></pre>
<p>만약에 개발을 수행하면서 비슷한 기능을 가진 클래스가 추가적으로 생기게 된다면 그 많은 클래스들에 대한 추가적인 처리도 해줘야한다.</p>
<h5 id="코드-예시-1">코드 예시</h5>
<pre><code class="language-c">// 추가적인 클래스들의 액세스를 위해서 추가적으로 overloading을 해줘야한다.
    public void Method1(Class1 instance)
    {
        instance.method1;
    }
    public void Method1(Class2 instance)
    {
        instance.method1;
    }
    public void Method1(Class3 instance)
    {
        instance.method1;
    }
    public void Method1(Class4 instance)
    {
        instance.method1;
    }
// 이렇게 되면 쓸데 없이 코드가 길어지고 난잡해진다.</code></pre>
<p>이 현상이 계속 반복된다면 그만큼 일이 가중될 것이니 전혀 효율적인 방법이 아니다. 
하지만 그 클래스들의 상위 클래스/인터페이스를 참조한다면 메서드의 액세스가 필요한 클래스가 계속 추가된다하더라도 추가적인 일이 발생되지는 않게 되어 확장성을 용이하게 만들어준다.</p>
<pre><code class="language-c">// 상위 클래스 타입으로 가져와주자
public void Method1(ParentClass instance)
{
    instance.method1; 
}</code></pre>
<blockquote>
</blockquote>
<hr>
<p>이와 같이 <strong>다형성을 잘 이용한다면 적은 코드만으르도 다양한 걸 표현해낼 수 있고 액세스의 편의성을 가져다준다.</strong></p>
<hr>
<h3 id="캡슐화">캡슐화</h3>
<blockquote>
</blockquote>
<ul>
<li>서로 연관있는 속성과 기능들을 하나의 캡슐로 만들어 데이터를 외부로부터 보호하는 것<blockquote>
</blockquote>
사실 보호하는 측면도 있지만, 일처리적인 부분으로 보면 굳이 밖에서 내부 필드들을 건들지 않아도 독립적으로 처리할 수 있게 만들어주는 측면도 존재한다. 자신의 필드를 독자적으로 해결해낼 수 있다면 클래스 간의 결합도를 효과적으로 줄여줄 수 있다.<blockquote>
</blockquote>
캡슐화에 밀접하는 기능들은 주로 <strong>접근 제한자</strong>, <strong>프로퍼티</strong>에 해당한다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<hr>
<hr>
<hr>
<h1 id="리팩토링-해보기">리팩토링 해보기</h1>
<blockquote>
<ul>
<li>OOP의 특징들을 토대로 내가 프로젝트에서 구현했던 떨어지는 오브젝트들을 리팩토링해볼 생각이다.</li>
</ul>
</blockquote>
<h2 id="리팩토링-전">리팩토링 전</h2>
<blockquote>
</blockquote>
<h4 id="foodcs">Food.cs</h4>
<pre><code class="language-c">public class Food : MonoBehaviour
{
    [SerializeField] private LayerMask playerCollisionLayer;
    [SerializeField] private LayerMask shieldCollisionLayer;
&gt;
    private FoodAnimator animator;
    private void Awake()
    {
        animator = GetComponent&lt;FoodAnimator&gt;();
    }
&gt;
    private int scorePoint = 100; 
&gt;
    void Update()
    {
        transform.position += Vector3.down * Time.deltaTime * 2 * SpawnManager.instance.speedScaling;
    }
&gt;
    private void OnTriggerEnter2D(Collider2D other)
    {
        if(IsLayerMatched(shieldCollisionLayer, other.gameObject.layer)) { return; }
&gt;
        if (IsLayerMatched(playerCollisionLayer.value, other.gameObject.layer))
        { 
            GameManager.instance.ScoreEarn(scorePoint);
            SoundManager.instance.PlayClickSound();
            animator.IsHit(true);
        }
        Invoke(&quot;Disabled&quot;, 3f);     
    }
&gt;
    private bool IsLayerMatched(int layerMask, int objectLayer)
    {
        return layerMask == (layerMask | (1 &lt;&lt; objectLayer));
    }
&gt;
    private void Disabled()
    {
        gameObject.SetActive(false);
        animator.IsHit(false);
    }
}</code></pre>
<h4 id="avoidcs">Avoid.cs</h4>
<pre><code class="language-c">public class AvoidFood : MonoBehaviour
{
    public IObjectPool&lt;GameObject&gt; pool { get; set; }
&gt;
    [SerializeField] private LayerMask playerCollisionLayer;
&gt;
    void Update()
    {
        transform.position += Vector3.down * Time.deltaTime * 2 * SpawnManager.instance.speedScaling; // 속도 스케일링
    }
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (IsLayerMatched(playerCollisionLayer.value, other.gameObject.layer))
        { 
            PlayerController controller = other.gameObject.GetComponent&lt;PlayerController&gt;();
            controller.CallDieEvent();
&gt;
            Invoke(&quot;GameOverEvent&quot;,0.5f);            
        }        
        gameObject.SetActive(false);
    }
&gt;
    private bool IsLayerMatched(int layerMask, int objectLayer)
    {
        return layerMask == (layerMask | (1 &lt;&lt; objectLayer));
    }
&gt;
    private void GameOverEvent()
    {
        SystemManager.instance.CallGameOver();
    }
}</code></pre>
<h4 id="powerupcs">PowerUp.cs</h4>
<pre><code class="language-c">public class PowerUp : MonoBehaviour
{
    [SerializeField] private LayerMask playerCollisionLayer;
    [SerializeField] private LayerMask shieldCollisionLayer;
&gt;
    private FoodAnimator animator;
&gt;
    private void Awake()
    {
        animator = GetComponent&lt;FoodAnimator&gt;();
    }
&gt;
    void Update()
    {
        transform.position += Vector3.down * Time.deltaTime * 2.5f * SpawnManager.instance.speedScaling; // 속도 스케일링
    }
&gt;
    private void OnTriggerEnter2D(Collider2D other)
    {
        if (IsLayerMatched(shieldCollisionLayer, other.gameObject.layer)) { return; }
&gt;
        if (IsLayerMatched(playerCollisionLayer.value, other.gameObject.layer))
        {
            SoundManager.instance.PlayItemSound();
            animator.IsHit(true);
            Movement playerCoroutine = other.gameObject.GetComponent&lt;Movement&gt;();
            playerCoroutine.StartSpeedUP();
        }
        Invoke(&quot;Disabled&quot;, 3f);
    }
&gt;
    private bool IsLayerMatched(int layerMask, int objectLayer)
    {
        return layerMask == (layerMask | (1 &lt;&lt; objectLayer));
    }
&gt;
    private void Disabled()
    {
        gameObject.SetActive(false);
        animator.IsHit(false);
    }
}</code></pre>
<p>심지어 파워업은 사이즈 증가, 사이즈 감소, 보호막까지 3개 더 있다</p>
<blockquote>
</blockquote>
<hr>
<hr>
<h3 id="문제점-짚기">문제점 짚기</h3>
<blockquote>
</blockquote>
<ul>
<li>중복되는 코드들이 많다.<ul>
<li>Animator, LayerMask</li>
<li>아래로 이동</li>
<li>트리거 감지, 레이어 검사</li>
<li>비활성화</li>
</ul>
</li>
<li>다 따로 나눠져 있어서 코드도 완전 제각각이다.</li>
</ul>
<hr>
<hr>
<h2 id="리팩토링-이후">리팩토링 이후</h2>
<blockquote>
</blockquote>
<h3 id="어떻게-리팩토링을-할까">어떻게 리팩토링을 할까?</h3>
<ul>
<li>코드를 하나로 통일시키고 생성하는 과정에서만 다르게 하자! (SO의 정보 -&gt; 오브젝트의 정보를 담는 스크립트)<ul>
<li>플레이어와 부딪혔을 때 발생하는 상호작용을 다른 부분으로 갈아끼우는 방식으로 들어가면 좋을 것 같다.
(상호작용 관련 enum과 enum을 받아서 switch로 상호작용을 처리하는 클래스를 만들자)</li>
<li>혹여나 플레이어 레이어 뿐만 아니라 다른 레이어에 부딪혔을 때도 상호작용 부분도 만들어 주는 것도 좋다.
(Layer를 단일로 받지 않고 List<LayerMask>로 바꾸고 이걸 SO로 전해받는 식으로 해주면 될 것 같다)<blockquote>
</blockquote>
일단 꾸미는 부분은 잠시 예외시키고 상호작용 부분에만 신경써보자.<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="리팩토링-코드">리팩토링 코드</h3>
<h4 id="fallingobjectcs">FallingObject.cs</h4>
<pre><code class="language-c">public class FallingObject : MonoBehaviour
{
    public FallingObjectSO objectSO; // SO를 넣어주는 것 (또는 스폰 매니저에서 줘도 된다.)
&gt;
    void Update()
    {
        transform.position += Vector3.down * Time.deltaTime * 2;
    }
&gt;
    private void OnTriggerEnter2D(Collider2D collider)
    {
        foreach (LayerMask layer in objectSO.InteractionWhenEnter) // foreach를 통해 모두 검사 (||와 비슷)
        {
            if (IsLayerMatched(layer, collider.gameObject.layer))
            {
                ItemInteractionManager.instance.InteractionActive(objectSO.type, objectSO.Score);
                  break;
            }
        }
&gt;
        foreach (LayerMask layer in objectSO.DestroyWhenEnter)
        {
            if (IsLayerMatched(layer, collider.gameObject.layer))
            {
                Disabled();
                  break;
            }
        }
    }
&gt;
    private bool IsLayerMatched(int layerMask, int objectLayer)
    {
        return layerMask == (layerMask | (1 &lt;&lt; objectLayer));
    }
&gt;
    private void Disalbed()
    {
        gameObject.SetActive(false);
    }
}
&gt;</code></pre>
<hr>
<h4 id="fallingobjectsocs">FallingObjectSO.cs</h4>
<pre><code class="language-c">[CreateAssetMenu(fileName = &quot;FallingObjectSO&quot;, menuName = &quot;FallingObject&quot;, order = 0)]
public class FallingObjectSO : ScriptableObject
{
    public List&lt;LayerMask&gt; InteractionWhenEnter;    // 이 레이어에 부딪히면 상호작용을 일으킨다
    public List&lt;LayerMask&gt; DestroyWhenEnter;        // 이 레이어에 부딪히면 사라지게 한다.
&gt;
    public int Score;                // 점수 ( 점수 획득 상호작용에 쓰임 )
    public InteractionType type;    // 상호작용 타입
}    </code></pre>
<blockquote>
</blockquote>
<hr>
<h4 id="iteminteractionmanagercs">ItemInteractionManager.cs</h4>
<blockquote>
</blockquote>
<pre><code class="language-c">public enum InteractionType
{
    Score,
    Big,
    Small,
    Shield,
    GameOver = 100
}
&gt;
public class ItemInteractionManager
{
    public static ItemInteractionManager instance = new ItemInteractionManager();
    public void InteractionActive(InteractionType type, int score)
    {
        switch((int)type)
        {
            case 0:
                // 점수 증가 - GameManager.instance.EarnScore(score);
                break;
            case 1:
                // 크기 증가 - SpawnManager.instance.SizeChange = 2.0f;
                break;
            case 2:
                // 크기 감소 - SpawnManager.instance.SizeChange = 0.5f;
                break;
            case 3:
                // 쉴드 활성화 - GameManager.instance.player.ShieldActive();
                break;
            case 100:
                // 게임 오버 - SystemManager.instance.
                break;
        }
    }
}</code></pre>
<hr>
<p>  위와 같이 객체 지향의 특징인 추상화와 다형성을 활용해서 코드를 좀 더 간결하고 깔끔하게 리팩토링하고 새로운 것이 추가되어도 코드의 통일성을 해치지 않게 되어 서로 다르게 생성되는 경우가 발생하지 않는다.</p>
<hr>
<hr>
<h1 id="참고-자료">참고 자료</h1>
<blockquote>
<h3 id="사진---마인크래프트">사진 - 마인크래프트</h3>
<h3 id="동영상">동영상</h3>
<p><strong>1. <a href="https://www.youtube.com/watch?v=vrhIxBWSJ04">객체지향 프로그래밍이 뭔가요? - 얄팍한 코딩 사전</a></strong>
<strong>2. <a href="https://www.youtube.com/watch?v=cg1xvFy1JQQ">객체지향 프로그래밍? 문과도 이해쌉가능. 10분컷 - 노마드 코더</a></strong></p>
</blockquote>
<h3 id="위키-백과">위키 백과</h3>
<ol>
<li><a href="https://ko.wikipedia.org/wiki/%EA%B0%9D%EC%B2%B4_%EC%A7%80%ED%96%A5_%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D"><strong>객체 지향 프로그래밍 - 위키백과</strong></a><h3 id="블로그">블로그</h3>
</li>
</ol>
<p>*<em>1. <a href="https://www.codestates.com/blog/content/%EA%B0%9D%EC%B2%B4-%EC%A7%80%ED%96%A5-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-%ED%8A%B9%EC%A7%95">객체 지향 프로그래밍의 4가지 원칙 - 나태웅</a> *</em></p>
<hr>
<hr>
<hr>
<h1 id="til을-쓰면서-느낀점">TIL을 쓰면서 느낀점</h1>
<blockquote>
<p>객체 지향이라는 것을 뭔가 정확히 알기보다는 겉으로만 알고 있는 느낌이 강했지만, 다시 한번 복습하고 알아보니 어떤 원리를 작용하게 되는지 자세히 알게 되었다. 그리고 코드를 짤 때는 이상하게도 뭔가 뚜렷하게 코드를 떠올리는 것보다는 추상적인 것에서부터 시작해서 마치 그림을 그리듯이 구도를 짠 다음에 자기가 알고 있는 코드를 동원하게 되는 느낌이다. 디자인 패턴이에 대해서 배울 때 그게 강하게 느껴진다. 아무래도 코드를 잘 짜려면 다양한 디자인 패턴을 겪어봐야할 필요도 느껴졌다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 6주차 2일차 TIL - 싱글턴]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-TIL-%EC%8B%B1%EA%B8%80%ED%84%B4</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-TIL-%EC%8B%B1%EA%B8%80%ED%84%B4</guid>
            <pubDate>Tue, 21 May 2024 14:54:44 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이 (제너릭 싱글턴 구현하기)</li>
<li>팀 프로젝트 진행 (오브젝트 풀링, 파워업 추가)</li>
<li>챌린지반 특강 듣기 (Delegate, event, UnityAction)</li>
<li>여태까지 배운 내용 시험 풀기</li>
</ul>
<hr>
<p>오늘은 선언해두면 언제 어디서든 접근이 가능한 싱글턴 패턴에 대해서 알아보자</p>
<hr>
<h1 id="싱글턴">싱글턴</h1>
<h2 id="기초-개념">기초 개념</h2>
<blockquote>
<p>싱글턴은 클래스에 인스턴스가 하나만 있도록 하면서 이 인스턴스에 대한 전역 접근 지점을 제공하는 생성 디자인 패턴입니다. <a href="https://refactoring.guru/ko/design-patterns/singleton"><strong>리팩토링 구루</strong></a>
<img src="https://velog.velcdn.com/images/b_h_b/post/ad21c195-2152-44ac-ae49-af676b63885a/image.png" alt=""></p>
</blockquote>
<ul>
<li>모든 인스턴스들이 싱글턴 패턴에게 접근이 가능하다.</li>
</ul>
<hr>
<hr>
<hr>
<h2 id="기초-구현">기초 구현</h2>
<blockquote>
<p>싱글턴을 선언할 때 가장 중요한 키워드에 대해서 설명해주겠다.</p>
</blockquote>
<h3 id="static-정적-선언"><a href="https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/static">static</a> (정적 선언)</h3>
<blockquote>
</blockquote>
<ul>
<li>인스턴스가 아닌 클래스에 속하는 정적 멤버를 선언할 때 사용하는 키워드입니다.</li>
<li>클래스, 인터페이스 및 구조체에서 필드, 메서드, 속성, 연산자, 이벤트 및 생성자에 static 키워드를 추가할 수 있습니다</li>
<li>static 한정자는 인덱서 또는 종료자와 함께 사용할 수 없습니다.<pre><code class="language-c">public class Class1
{
  public static int staticField = 2;
&gt;    
  public static void staticMethod()
  {
      Console.WriteLine(&quot;저는 정적 메서드입니다. 바로 외부에서 호출이 가능합니다.&quot;);
  }
}
&gt;
// 호출 해보기
static void Main(string[] args)
{
  Console.WriteLine(Class1.staticField);
  Class1.staticMethod();
}</code></pre>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/1dbe56ef-d66b-48cc-a0b5-c81fae2bb97e/image.png" alt=""></li>
<li>정적 선언한 필드와 메서드들은 인스턴스 선언 없이 바로 외부로 호출이 가능하다.</li>
</ul>
<hr>
<hr>
<h3 id="-static-사용-시-주의사항-">% static 사용 시 주의사항 %</h3>
<h4 id="1-static이-아닌-멤버에서는-static-멤버를-사용-가능하지만-static인-멤버에서-static이-아닌-멤버를-사용할-수-없다">1. static이 아닌 멤버에서는 static 멤버를 사용 가능하지만 static인 멤버에서 static이 아닌 멤버를 사용할 수 없다</h4>
<p><strong>(사용하려면 인스턴스 생성을 해줘야한다)</strong></p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/909ee45b-12d2-4810-8cd6-3ce726047473/image.png" alt=""></li>
<li>그래서 위와 동일한 이유로 Progam 클래스에 선언했다고 해서 Main 함수에서 사용이 불가능한 이유와 동일하다.</li>
</ul>
<hr>
<h4 id="2-static-멤버는-인스턴스-단위로-참조가-불가능하다">2. static 멤버는 인스턴스 단위로 참조가 불가능하다.</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/5d20b0fd-ae42-4b6a-8350-7d373fa6b01d/image.png" alt=""></li>
<li>static은 아예 클래스 소속이기 때문에 클래스 자체로 불러줘야한다.</li>
</ul>
<hr>
<p>static은 아예 클래스 자체에 속하기 때문에 이 점을 이용해서 싱글턴을 만들어내는 것이다.</p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/e11407de-34ca-404c-a2ba-81bbe9a6f3b9/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<hr>
<h3 id="static을-활용해-싱글턴-선언하기">static을 활용해 싱글턴 선언하기</h3>
<blockquote>
</blockquote>
<h4 id="유니티에서-가장-기초적인-싱글턴">유니티에서 가장 기초적인 싱글턴</h4>
<pre><code class="language-c">public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    void Awake()
    {
        instance = this; // this는 클래스의 현재 인스턴스를 가리킨다.
    }
}</code></pre>
<h4 id="visualstudio에서-기초적인-싱글턴">VisualStudio에서 기초적인 싱글턴</h4>
<pre><code class="language-c">public class GameManager
{
    public static GameManager instance = new GameManager(); // Awake 같은 건 없으니 이로 대체해주자.
&gt;
    public int field = 1;
}
&gt;
// 사용해보기
static void Main(string[] args)
{
    GameManager.instance.field = 4;
    Console.WriteLine(GameManager.instance.field);
}</code></pre>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/cc5a4a46-7cb4-4eb9-ab83-cbccf736d3b3/image.png" alt=""></li>
<li>숫자가 잘 변경되고 호출된 모습을 볼 수 있다.</li>
</ul>
<hr>
<ul>
<li>해당 코드들을 잘 보면 static을 이용해 클래스 소속 클래스타입 멤버를 선언해서 거기에 자기 자신을 할당하는 방식이다.</li>
</ul>
<hr>
<h4 id="이것도-싱글턴-패턴인가요">이것도 싱글턴 패턴인가요?</h4>
<pre><code class="language-c">    static class staticClass
    {
        public static int staticField = 4;
&gt;
        public static void staticMethod()
        {
            Console.WriteLine(&quot;정적 클래스의 정적 메서드입니다.&quot;);
        }
    }</code></pre>
<ul>
<li>물론 싱글톤과 비슷하게 static 필드를 바꿀 수 있고 메서드도 호출 가능하지만, 싱글턴에 비해서 제한적인 사항이 많다.</li>
</ul>
<ol>
<li>정적 멤버만 선언 가능</li>
<li>인터페이스 구현 불능</li>
<li>상속과 재정의 불능</li>
</ol>
<ul>
<li>정적 클래스는 단순히 서로 연관된 메서드를 묶어두는 것에만 가깝기 때문에 절차적 프로그래밍에 가깝다고 본다. </li>
<li>해당 코드의 사용처는 주로 유틸리티 클래스, 수학 함수 등에서 사용된다.</li>
</ul>
<h2 id="싱글턴-사용-시-주의할-점">싱글턴 사용 시 주의할 점</h2>
<blockquote>
<p>싱글턴의 편의성 때문에 사용에 주의해야 한다.</p>
</blockquote>
<h4 id="1-싱글턴-클래스가-많은-책임과-기능-짊어질-수도-있다">1. 싱글턴 클래스가 많은 책임과 기능 짊어질 수도 있다.</h4>
<ul>
<li>가능하다면 최대한 여러 클래스로 분담해주는 것이 좋다.
(의존도와 결합도에 유의해줘야한다.)<h4 id="2-객체-생성-시기를-제어할-수-없다">2. 객체 생성 시기를 제어할 수 없다.</h4>
</li>
<li>그래서 주로 항상 상주해야만 하는 GameManager나 AudioManager과 같은 곳에 싱글톤을 사용하는 편이다.<h4 id="3-유니티에서는-겹쳐서-생성되는-경우를-주의해야한다">3. 유니티에서는 겹쳐서 생성되는 경우를 주의해야한다.</h4>
</li>
<li>if (instance == null)을 통해 클래스의 instance가 겹치지 않게 유의해줘야한다.</li>
</ul>
<h2 id="유니티에서-싱글턴-패턴-구현하기">유니티에서 싱글턴 패턴 구현하기</h2>
<blockquote>
<h3 id="현재-사용하는-싱글턴">현재 사용하는 싱글턴</h3>
</blockquote>
<pre><code class="language-c">public class SystemManager : MonoBehaviour
{
    public static SystemManager instance;
&gt;
    void Awake()
    {
        if (instance == null)
        {
            instance = this;
            DontDestroyOnLoad(instance); // 씬이 넘어가도 파괴되지 않게하기
        }
        else
        {
            Destroy(gameObject); // 겹치는 건 파괴
        }</code></pre>
<hr>
<blockquote>
<h3 id="제너릭-싱글턴오브젝트-탐색">제너릭 싱글턴(오브젝트 탐색)</h3>
</blockquote>
<h4 id="코드-출처---실리의-프로그램-사이트">코드 출처 - <a href="https://sillyknight.tistory.com/30">실리의 프로그램 사이트</a></h4>
<pre><code class="language-c">public class Singleton&lt;T&gt; : MonoBehaviour where T : MonoBehaviour
{
    private static T instance;
&gt;
    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                GameObject obj;
                obj = GameObject.Find(typeof(T).Name);
                if (obj == null)
                {
                    obj = new GameObject(typeof(T).Name);
                    instance = obj.AddComponent&lt;T&gt;();
                }
                else
                {
                    instance = obj.GetComponent&lt;T&gt;();
                }
            }
            return instance;
        }
    }
&gt;
    public void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}</code></pre>
<ul>
<li>해당 제너릭 싱글턴은 오브젝트를 기준으로 찾아내기 때문에 해당 싱글턴을 상속받은 클래스와 오브젝트의 이름이 동일해야한다. </li>
</ul>
<hr>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
</blockquote>
<h4 id="리팩토링-구루---싱글톤-패턴"><a href="https://refactoring.guru/ko/design-patterns/singleton">리팩토링 구루 - 싱글톤 패턴</a></h4>
<h4 id="learnmicrosoft---static-한정자"><a href="https://learn.microsoft.com/ko-kr/dotnet/csharp/language-reference/keywords/static">Learn.Microsoft - static 한정자</a></h4>
<h4 id="실리의-프로그램-사이트---unity-singleton을-제네릭으로-만들어놓고-써봅시다"><a href="https://sillyknight.tistory.com/30">실리의 프로그램 사이트 - Unity Singleton을 제네릭으로 만들어놓고 써봅시다</a></h4>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>기존에 알고 있음에도 불구하고 다시 써보고 정리해보니 전보다 이해가 깊이 되고 좀 더 발전된 방식의 싱글톤 사용이 가능해졌다. 기존 지식을 회고하는 것만으로도 코드 기획에 많은 도움이 되는 것 같다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 6주차 1일차 TIL - Coroutine]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-Coroutine</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-6%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-Coroutine</guid>
            <pubDate>Mon, 20 May 2024 15:09:46 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이 (InputSystem에서 키 바인딩 덮어씌우기)</li>
<li>팀 프로젝트 진행 (UI 상호작용, 게임 기본 로직, 파워업 추가)</li>
<li>특강 듣기 (유니티의 다양한 직군)</li>
</ul>
<hr>
<hr>
<h1 id="coroutine">Coroutine</h1>
<h2 id="개념">개념</h2>
<blockquote>
<p>작업을 다수의 프레임에 분산하는 메서드로 Unity에서 코루틴은 실행을 일시정지하고 제어를유니티에 반환하지만, 중단된 부분에서 다음 프레임을 계속할 수 있는 메서드입니다. (비동기처럼 작동하는 것처럼 보이지만, 엄연히 메인 스레드에서 실행되는 동기 작업입니다.)</p>
</blockquote>
<h3 id="함수와-코루틴의-차이">함수와 코루틴의 차이</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/68957280-0062-49b1-be2d-4704c2964524/image.png" alt=""></p>
<ul>
<li>함수는 return을 만날 때까지 계속 제어권을 가지게되는 반면에 코루틴은 yield return을 통해서 중간 지점에서 제어권을 반환했다가 다시 조건을 충족하면 제어권을 가져오는 방식으로 작업을 처리한다. </li>
</ul>
<hr>
<hr>
<h2 id="사용방법">사용방법</h2>
<blockquote>
<h3 id="코루틴-메서드-선언하기">코루틴 메서드 선언하기</h3>
</blockquote>
<pre><code class="language-c">IEnumerator Coroutine1()    //IEnumerable랑 헷갈리지 않게 조심해야한다.
{
    // 처리하고자 하는 코드
    yield return null; //yield return을 반드시 넣어줘야한다.
    // yield return null 이후 실행해줄 코드
} // 여기까지 와야 코루틴 끝</code></pre>
<hr>
<h3 id="코루틴-사용하기">코루틴 사용하기</h3>
<pre><code class="language-c">StartCoroutine(Coroutine1());
StartCoroutine(&quot;Coroutine1&quot;); // 문자열로도 가능하다.</code></pre>
<ul>
<li>성능상 문자열보다는 메서드 형태로 전달해주는 것이 좋다.<h4 id="사용할-때-주의사항">사용할 때 주의사항</h4>
</li>
<li>Awake에서 실행하면 잘 작동이 안될 수도 있다. 
(Awake의 호출 순서 때문이다(오브젝트 활성화 전에 호출))</li>
<li>비활성화 상태에서 코루틴을 동작시키려하면 작동이 안된다.</li>
</ul>
<hr>
<h3 id="코루틴의-다양한-제어점-반환">코루틴의 다양한 제어점 반환</h3>
<h4 id="yield-return-null">yield return null</h4>
<ul>
<li>다음 프레임까지 대기 후 실행<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/951a3429-0a08-458a-bb64-7115434903ee/image.png" alt=""><h4 id="yield-return-new-waitforfixedupdate">yield return new WaitForFixedUpdate()</h4>
</li>
</ul>
</li>
<li>다음 FixedUpdate가 호출될 때까지 대기 후 실행<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/e9a4a6d7-8388-44df-873d-63be3cad750f/image.png" alt=""><h4 id="yield-return-new-waitforendofframe">yield return new WaitForEndOfFrame()</h4>
</li>
</ul>
</li>
<li>현재 프레임이 종료될 때까지 대기 후 실행<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/b6efb6a0-39ae-45b8-8708-4418f1c9ac46/image.png" alt=""><h4 id="yield-return-new-waitforsecondsfloat">yield return new WaitForSeconds(float)</h4>
</li>
</ul>
</li>
<li>지정한 초만큼 대기 후 실행 
Update와 LateUpdate 사이에서 실행<h4 id="yield-return-new-waitforsecondsrealtimefloat">yield return new WaitForSecondsRealtime(float)</h4>
</li>
<li>괄호 안의 시간(초)이 지날 때까지 대기 후 실행 
(timeScale의 영향을 안 받음)
Update와 LateUpdate 사이에서 실행<h4 id="yield-return-new-waituntil">yield return new WaitUntil()</h4>
</li>
<li>괄호 안의 조건이 만족할 때까지 대기 후 실행
Update와 LateUpdate 사이에서 실행<h4 id="yield-return-new-waitwhile">yield return new WaitWhile()</h4>
</li>
<li>괄호 안의 조건이 만족하지 않을 때까지 대기 후 실행
Update와 LateUpdate 사이에서 실행<h4 id="yield-return-startcoroutine">yield return StartCoroutine()</h4>
</li>
<li>다른 코루틴이 끝날 때까지 대기 후 실행</li>
</ul>
<hr>
<h3 id="코루틴-멈추기">코루틴 멈추기</h3>
<h4 id="yield-break">yield break</h4>
<ul>
<li>이 코드가 적힌 시점에서 코루틴을 멈춘다</li>
</ul>
<hr>
<h3 id="코루틴에게-매개변수-전달">코루틴에게 매개변수 전달</h3>
<pre><code class="language-c">StartCoroutine(Coroutine1(매개변수1, 매개변수2, 매개변수3));
StartCoroutine(&quot;Coroutine1&quot;, 매개변수_단일);
// 메서드로 보내면 여러 개를 보낼 수 있고 문자열로 보내면 하나 밖에 못 보낸다</code></pre>
<hr>
<h3 id="코루틴-외부-상호작용">코루틴 외부 상호작용</h3>
<h4 id="외부에서-멈추기">외부에서 멈추기</h4>
<pre><code class="language-c">StopCoroutine(&quot;Coroutine1&quot;) // 문자열로 시작했을 때에만 가능하다.
&gt;
// 다른 방법
Coroutine myCoroutine1;
&gt;
{
    myCoroutine1 = StartCoroutine(Coroutine1());
    ...
    StopCoroutine(myCoroutine1); // 접근만 된다면 다른 클래스에서도 사용 가능하다.
}</code></pre>
<h4 id="스크립트-내-코루틴-모두-멈추기">스크립트 내 코루틴 모두 멈추기</h4>
<pre><code class="language-c">StopAllCoroutine(); // 스크립트 내 코루틴 모두 멈추기</code></pre>
<hr>
<hr>
<h2 id="주요-사용-용도">주요 사용 용도</h2>
<blockquote>
<ul>
<li>주로 일시적인 효과 처리나 시간차 호출이 필요할 경우에 사용한다. </li>
</ul>
</blockquote>
<ul>
<li>Update보다 보다도 유연한 시간 처리가 가능하다 (다양한 제어점 반환 옵션이 존재하기 때문)<h3 id="아이템의-지속-시간-처리">아이템의 지속 시간 처리</h3>
<pre><code class="language-c"> public void StartSpeedUP() // 외부 접근용 메서드
 {
         // 지속된 효과가 없다면 효과를 시작하고, 존재한다면 계속 쌓아준다.
     if (speedUPDuration &lt;= 0) { StartCoroutine(SpeedUP()); }
     else { speedUPDuration += 10; }
 }
&gt;
 IEnumerator SpeedUP() // 스피드 코루틴
 {
     speedUPDuration += 10;     // 초기 지속 시간 갱신
     speedUP = 2f;            // 속도 배수
     while (speedUPDuration &gt; 0) // 시간이 0보다 작아질 때까지 계속
     {
         yield return new WaitForSeconds(1f); // 1초 대기
         speedUPDuration -= 1; // 1초 감소
     }
     speedUP = 1f; // 지속 시간이 끝나면 원래대로
 }</code></pre>
<img src="https://velog.velcdn.com/images/b_h_b/post/b02e95ac-406d-41f8-83c5-5c847d2ee426/image.gif" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="다양한-아이템을-시간-차를-다르게해서-생성하기">다양한 아이템을 시간 차를 다르게해서 생성하기</h3>
<pre><code class="language-c">    void Start()
    {
        StartCoroutine(SpawnFood());
        StartCoroutine(SpawnAvoidFood());
        StartCoroutine(SpawnPowerUPs());
    }
&gt;
    IEnumerator SpawnFood()
    {
        while (true)
        {
            yield return new WaitForSeconds(foodSpawnSpeed);
            Instantiate(food);
        }
    }
&gt;
    IEnumerator SpawnAvoidFood()
    {
        while (true)
        {
            yield return new WaitForSeconds(AvoidfoodSpawnSpeed);
            Instantiate(avoidFood);
        }
    }
&gt;
    IEnumerator SpawnPowerUPs()
    {
        while (true)
        {
            yield return new WaitForSeconds(PowerUPSpawnSpeed);
            Instantiate(powerUP);
        }
    }</code></pre>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/15936cd8-9009-48c6-a2ff-f855aab8c039/image.gif" alt=""></p>
<hr>
<hr>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
</blockquote>
<ul>
<li><h3 id="유니티-docs"><a href="https://docs.unity3d.com/kr/2021.3/Manual/Coroutines.html">유니티 Docs</a></h3>
<blockquote>
</blockquote>
<h3 id="베르의-게임-개발-유튜브">베르의 게임 개발 유튜브</h3>
<blockquote>
</blockquote>
</li>
<li><h4 id="코루틴-다루기-1---코루틴-기초--유니티"><a href="https://www.youtube.com/watch?v=ahji9F5hJJ4">코루틴 다루기 (1) - 코루틴 기초 | 유니티</a></h4>
</li>
<li><h4 id="코루틴-다루기-2---코루틴-중단하기--코루틴-매개변수--yield-break--유니티"><a href="https://www.youtube.com/watch?v=iK7zDp5TEks&amp;t=22s">코루틴 다루기 (2) - 코루틴 중단하기 + 코루틴 매개변수 + yield break | 유니티</a></h4>
<blockquote>
</blockquote>
<h3 id="케이디">케이디</h3>
</li>
<li><h4 id="유니티로-배우는-c-강좌-part-完---코루틴"><a href="https://www.youtube.com/watch?v=ePl2C4LTKGQ">유니티로 배우는 C# 강좌 Part 完 - 코루틴</a></h4>
<blockquote>
</blockquote>
<h3 id="kkimssi">KKIMSSI</h3>
<blockquote>
</blockquote>
</li>
<li><h4 id="unity-yield-return-종류"><a href="https://yeobi27.tistory.com/entry/Unity-yield-return-%EC%A2%85%EB%A5%98">[Unity] yield return 종류</a></h4>
</li>
</ul>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>왠지 이 코루틴을 굉장히 많이 사용하게 될 것 같다는 느낌이 든다;</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 5주차 4일차 TIL - Input System]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL-Input-System</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL-Input-System</guid>
            <pubDate>Fri, 17 May 2024 13:46:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 풀이</li>
<li>팀 프로젝트 진행하기 ( 볼륨 슬라이더 구현, 더블탭 대쉬 구현 )</li>
<li>Input System에 대해서 자세히 파고 들기</li>
</ul>
<hr>
<p>오늘은 입력의 처리를 받는 Input System에 대해서 알아보자.</p>
<hr>
<h1 id="unityengineinputsystem">UnityEngine.InputSystem</h1>
<h2 id="기초-설명">기초 설명</h2>
<blockquote>
</blockquote>
<p>유니티에서 기존 Input Manager의 단점을 해소하고 확장성 있게 개량한 입력 처리 기능이다.</p>
<blockquote>
</blockquote>
<hr>
<blockquote>
<h3 id="장점">장점</h3>
</blockquote>
<ul>
<li><strong>Cross-Platform Compatibility</strong>
New Input System은 다양한 플랫폼과 입력 장치에 대해 일관된 방식으로 작동합니다.</li>
<li><strong>Rebinding</strong>
플레이어가 게임 내에서 자신의 입력 설정을 변경할 수 있도록 지원합니다.</li>
<li><strong>Multiplayer Support</strong>
여러 플레이어가 동일한 장치에서 게임을 플레이하거나, 각각의 장치에서 게임을 플레이할 때 입력을 쉽게 처리할수 있습니다.</li>
</ul>
<h2 id="사용-방법">사용 방법</h2>
<h3 id="1-input-system-설치">1. Input System 설치</h3>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/d18264e0-cf8d-436e-93bd-f11bb44faa85/image.png" alt=""></p>
<ul>
<li>설치 후 프로젝트를 재시작해줘야한다.</li>
</ul>
<h3 id="2-input-action-asset-추가">2. Input Action Asset 추가</h3>
<blockquote>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/6d41b2e6-d625-4a05-b79a-1d0e333f9939/image.png" alt=""></li>
<li>생성하고자 하는 곳에 오른쪽 클릭 후 Create -&gt; Input Actions를눌러 추가한다. </li>
</ul>
<h3 id="3-input-action-창-설정">3. Input Action 창 설정</h3>
<blockquote>
</blockquote>
<h3 id="3-1-control-shceme">3-1. Control Shceme</h3>
<ul>
<li>각종 입력 장치에 대한 종류를 구분하는 것으로 추가해두면 Action을 설정에서 사용한다.</li>
<li>필수적인 과정은 아니지만, 혹시나 컨트롤러에 대한 구분이 필요할 경우에는 해두는 것이 좋다</li>
<li>해당 컨트롤러 스킴을 설정해두면 Player Input에서 설정해줄 수 있다.
<img src="https://velog.velcdn.com/images/b_h_b/post/ae5887df-b2b6-40bf-a2b0-637abee55ed4/image.png" alt=""><ul>
<li>Any로 설정해두면 컨트롤 스킴 상관없이 처리한다는 의미이다. 혹여나 다양한 컨트롤러를 지원하고 싶다면 Any로 설정해두고 Auto-Switch를 켜두는 게 좋다.</li>
</ul>
</li>
</ul>
<hr>
<blockquote>
</blockquote>
<h3 id="3-2-action-map">3-2. Action Map</h3>
<ul>
<li>특정 개체(Player 등)나 기능(UI)단위로 구별해둔 Action 리스트로 주로 관련된 것끼리 묶어줘서 컴포넌트에 전달할 때 사용된다.<ul>
<li>아래 이미지를 보면 +를 통해서 Map을 추가해줄 수 있다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/03ea1074-a477-4fda-9cb7-ae5efb7594b4/image.png" alt=""></li>
</ul>
</li>
<li>InputAction 컴포넌트로 가보면 구별해놓은 Action Map 설정들을 고를 수 있다.<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/cb02b5cf-bc4d-45f2-a5a1-c02cb454df64/image.png" alt=""></li>
<li>여기서 None으로 설정해버리면 아무 액션도 받지 않겠다는 의미이다; 
(근데 유니티에서 필요한 것을 알아서 찾아가는 것 같다.) <em>//굳이 None으로 설정해두지는 말자</em><blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
<hr>
<h3 id="3-3-action">3-3. Action</h3>
<ul>
<li>특정 행동의 입력 처리를 설정하는 걸로 주로 처리받을 값과 입력 처리를 관리하는 곳이다.
<img src="https://velog.velcdn.com/images/b_h_b/post/6f80fdd9-f4e6-4b05-bf07-99f27f7932ea/image.png" alt=""><ul>
<li>Move를 선택한 상태로 오른쪽을 보면 ActionType과 Control Type을 설정할 수 있다.
<img src="https://velog.velcdn.com/images/b_h_b/post/4f925124-3484-4016-b6ab-f1189d80560d/image.png" alt=""><h4 id="action-type"><a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Actions.html#action-types">Action Type</a></h4>
입력에 따른 이벤트 트리거를 설정하는 곳으로 기능의 요구 사항에 맞게 설정해주면 좋다.</li>
<li>** Value ** - 기본적인 액션 타입으로 컨트롤의 상태가 지속적으로 변하는 모든 입력에 대해서는 이를 사용한다.</li>
<li>** Button ** - 주로 누를 때마다 한번씩 액션을 트리거하는 입력에 사용하는 것이 좋습니다.</li>
<li>** Pass-Through ** - 바인딩된 모든 컨트롤의 변경 사항이 해당 컨트롤의 값을 가진 콜백을 트리거합니다. 이는 컨트롤 세트로부터 모든 입력을 처리하려는 경우에 유용합니다.<h4 id="control-type">Control Type</h4>
트리거한 이벤트에 전달할 매개변수 종류를 지정하는 곳이다.</li>
</ul>
</li>
<li>아래 이미지가 해당하는 매개변수들의 종류이다.
<img src="https://velog.velcdn.com/images/b_h_b/post/b41e771b-17e7-4ef2-a69d-79928b71cf65/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="3-4-binding">3-4. Binding</h3>
<ul>
<li>해당 액션에게 키입력을 넣어주는 과정이다.</li>
<li><strong>3-4-1.</strong> 해당 액션에 오른쪽 클릭을 눌러 추가하거나, +를 통해 추가해줄 수 있다.
<img src="https://velog.velcdn.com/images/b_h_b/post/72d98d76-173a-43da-8fca-63c7264776d2/image.png" alt=""><ul>
<li>Value Any가 아니고서야는 오른쪽 클릭으로 추가해주는 게 편할 것이다. </li>
</ul>
</li>
<li><strong>3-4-2.</strong> 해당 Binding을 눌러서 Path에서 키를 추가해주면 된다.
<img src="https://velog.velcdn.com/images/b_h_b/post/0e2a44d1-6b71-4a6f-b17c-3c5318e1e8bf/image.png" alt="">
<img src="https://velog.velcdn.com/images/b_h_b/post/6ee20458-6b84-484e-934d-5cb53b6329e7/image.png" alt=""><ul>
<li>왼쪽에 매우 작은 상단바를 눌러주면 입력을 감지해주는데 넣고 싶은 키를 입력하면 감지해줘서 선택 가능할 수 있다.</li>
</ul>
</li>
</ul>
<hr>
<hr>
<h3 id="4-player-input-컴포넌트">4. Player Input 컴포넌트</h3>
<blockquote>
</blockquote>
<ul>
<li>Add Component에서 Player Input를 찾아 추가해주자<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/7cb13e5e-03ca-4798-8099-959109586607/image.png" alt=""></li>
</ul>
</li>
<li>Player Input에 만들어두었던 Input Action Asset을 넣어주자<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/f82f8a37-428c-41a4-a6e6-82678e0ba23c/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<h3 id="behavior-설정">Behavior 설정</h3>
<ul>
<li>주로 메시지의 범위나 메시지를 전달하는 방식을 정해주는 곳이다.
<img src="https://velog.velcdn.com/images/b_h_b/post/f1be2e11-ff25-4088-b901-f71f0b2b2532/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li><h4 id="inputvalue-value">InputValue value</h4>
<ul>
<li><strong>Send Messages</strong>         : 동일한 게임 오브젝트의 컴포넌트에 메시지를 날린다.</li>
<li><strong>BroadCast Messages</strong>     : 같은 게임 오브젝트와 그 자식 게임 오브젝트의 컴포넌트에 메시지를 날린다. 
(성능 문제 때문에 추천하지 않는다)</li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><h4 id="inputactioncallbackcontext-context-더-자세한-이벤트-처리-가능">InputAction.CallbackContext context (더 자세한 이벤트 처리 가능)</h4>
<ul>
<li><strong>Invoke Unity Event</strong> : OnClick처럼 Unity Event형태로 관리된다. (매우 편리하다)<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/46e2bcc7-1422-491c-91ae-3d0643b3a5d3/image.png" alt=""></li>
</ul>
</li>
<li><strong>Invoke C Sharp Events</strong> : PlayerInput.OnActionTriggered에 호출하고 싶은 이벤트 등록해서 어떤 이벤트인지 확인해서 쓸 수 있다.</li>
</ul>
</li>
</ul>
<hr>
<hr>
<h3 id="5-스크립트에서-이벤트-처리하기">5. 스크립트에서 이벤트 처리하기</h3>
<blockquote>
</blockquote>
<ul>
<li><strong>요구되는 네임스페이스</strong><pre><code class="language-c">using UnityEngine.InputSystem</code></pre>
<h4 id="sendmessages-또는-broadcast-messages일-경우">SendMessages 또는 BroadCast Messages일 경우</h4>
</li>
<li>아래 이미지에 적힌 함수를 적어서 호출해줘야한다.
<img src="https://velog.velcdn.com/images/b_h_b/post/119c9703-a3ee-483d-8c05-f0a79640d3ac/image.png" alt=""><pre><code class="language-c">private void OnMove(InputValue value) // 접근 제한자 제한이 없다.
{
  Vector2 direction = value.Get&lt;Vector2&gt;();    // 값을 가지고 올 때는 .Get&lt;&gt;();을 활용한다.
  CallMoveEvent(direction);
}
&gt;
// Value Any일 경우에는 (주로 bool값 매개변수 전달에 사용된다.)
value.isPressed;    // interaction이 없는 상태에서 키를 눌렀을 때 true 뗐을 때false를 반환한다.</code></pre>
</li>
</ul>
<hr>
<h4 id="invoke-unity-event일-경우">Invoke Unity Event일 경우</h4>
<ul>
<li>Events/ActionMap을 열어 거기에 해당하는 메서드를 추가해줘야한다.
<img src="https://velog.velcdn.com/images/b_h_b/post/2f756c7c-2925-4905-96f1-d96e9e7d8dae/image.png" alt=""><pre><code class="language-c">public void OnDash(InputAction.CallbackContext context) // 사용을 위해서는 반드시 public 이상이어야 한다.
{
  if (context.started)    { // 입력 시작 시 (주로 키입력이 시작됐을 때 발동) }
  if (context.performed)    { // 조건 충족 시 (인터랙션의 조건이 충족되었을 경우) }
  if (context.canceled)    { // 조건 미충족 시 또는 취소 시 (조건 충족 후 키를 떼거나 조건이 충족되지 못했을 시) }
} // 위와 같은 자세한 설정이 가능한데 해당 조건에 따라서 필요한 기능을 넣어주면 된다.</code></pre>
<blockquote>
</blockquote>
<em>Invoke C Sharp Events는 사용한 적이 없기에 넘어간다..</em></li>
</ul>
<hr>
<hr>
<h2 id="interaction"><a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Interactions.html#multitap">Interaction</a></h2>
<blockquote>
<p>키에 추가적인 조건을 걸어줘서 키를 누르고 일정 시간이 충족되고 나서 떼야 호출되는 경우나 연속으로 키를 눌러야 호출되는 등의 다양한 조건들을 걸어줄 수 있다.</p>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/c8532962-58fc-4aa0-8725-6934eaf77533/image.png" alt=""><h3 id="공통된-설정">공통된 설정</h3>
</li>
<li><h4 id="press-point---이벤트가-호출되기-위한-임계값으로-사실-키보드-같은-경우에는-키-입력이-들어오기만-하면-1로-판정이-되서하나만-누르든-동시에-누르든-간에-키보드에서-쓰이기보다는-조이스틱이나-게임-패드에-주로-사용된다">Press Point - 이벤트가 호출되기 위한 임계값으로 사실 키보드 같은 경우에는 키 입력이 들어오기만 하면 1로 판정이 되서(하나만 누르든 동시에 누르든 간에) 키보드에서 쓰이기보다는 <strong>조이스틱</strong>이나 <strong>게임 패드</strong>에 주로 사용된다.</h4>
<h3 id="hold--꾸욱-눌러줘야-호출하는-상호작용">Hold | 꾸욱 눌러줘야 호출하는 상호작용</h3>
</li>
<li><h4 id="hold-time--첫-활성화부터-얼마-이상의-활성화를-유지해야-호출되는-지를-설정한다">Hold Time : 첫 활성화부터 얼마 이상의 활성화를 유지해야 호출되는 지를 설정한다.</h4>
<h3 id="multi-tap--여러번-눌러줘야-호출하는-상호작용">Multi Tap | 여러번 눌러줘야 호출하는 상호작용</h3>
</li>
<li><h4 id="tap-count--몇-번까지-눌러줘야-호출되는-지를-설정">Tap Count : 몇 번까지 눌러줘야 호출되는 지를 설정</h4>
</li>
<li><h4 id="max-tap-spacing--키-입력이-시작되고-나서-비활성화-상태에서의-허용-시간">Max Tap Spacing : 키 입력이 시작되고 나서 비활성화 상태에서의 허용 시간</h4>
</li>
<li><h4 id="max-tap-duration--활성화-상태에서의-키-유지를-허락하는-시간-넘어가면-취소된다">Max Tap Duration : 활성화 상태에서의 키 유지를 허락하는 시간 (넘어가면 취소된다.)</h4>
<h3 id="press--눌렀을-때-뗐을-때-호출할-지의-상호작용">Press | 눌렀을 때, 뗐을 때 호출할 지의 상호작용</h3>
</li>
<li><h4 id="press-only--눌렀을-때-호출">Press Only : 눌렀을 때 호출</h4>
</li>
<li><h4 id="release-only--뗐을-때-호출">Release Only : 뗐을 때 호출</h4>
</li>
<li><h4 id="press--release--press--release를-합친-것이다">Press &amp; Release : Press + Release를 합친 것이다.</h4>
<h3 id="slow-tap">Slow Tap</h3>
</li>
<li><h4 id="min-tap-duration--키를-누르고-최소-유지-시간-이후-뗐을-때-호출되는-시간을-정한다">Min Tap Duration : 키를 누르고 최소 유지 시간 이후 뗐을 때 호출되는 시간을 정한다.</h4>
<h3 id="tap">Tap</h3>
</li>
<li><h4 id="max-tap-duration--키를-누르고-최대-유지-시간-이전에-뗐을-때-호출되는-시간을-정한다">Max Tap Duration : 키를 누르고 최대 유지 시간 이전에 뗐을 때 호출되는 시간을 정한다.</h4>
</li>
</ul>
<h3 id="각종-상호작용에-대한-실험-정리">각종 상호작용에 대한 실험 정리</h3>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/df693d90-9c31-43a6-af56-ae41a736e310/image.png" alt=""></p>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
</blockquote>
<ul>
<li><h4 id="유니티-tips-다양한-입력-장치를-손쉽게-지원하는-input-system-패키지-공략"><a href="https://www.youtube.com/watch?v=dsLBzrbo-Vs">[유니티 TIPS] 다양한 입력 장치를 손쉽게 지원하는 Input System 패키지 공략</a></h4>
</li>
<li><h4 id="docsunity3dcominput-action-assets"><a href="https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/ActionAssets.html">docs.unity3d.com/Input Action Assets</a></h4>
</li>
</ul>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>사실 오늘 작성하는 것보다 Input Action을 활용한 더블탭 대쉬를 넣으려고 하다가 MultiTap의 performed 조건이 Tap Count 충족에서 눌렀을 때가 아닌 누르고 뗐을 때라는 점에서 많이 난감했지만, 오히려 왜 굳이 InputAction에 의존해야하는 생각을 해서 코드로 하면 좀 더 쉽고 간단히 짤 수 있을 거라는 생각을 하게 되었다. 그래서 InputAction을 활용하지 않고 스크립트 내에서 그대로 처리했더니 도리어 InputAction을 활용했던 것보다 코드가 좀 더 간결해지고 빈틈 없이 작성할 수 있게 되었다. 이런 점을 생각해보면 이러한 기능들 이전에 결국 코드를 설계하고 짜는 그러한 기본 바탕들이 제일 중요하다는 걸 알게 되었다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 5주차 3일차 TIL - 오브젝트 추적 1]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%B6%94%EC%A0%81-1</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%EC%B6%94%EC%A0%81-1</guid>
            <pubDate>Thu, 16 May 2024 14:56:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 진행</li>
<li>팀 프로젝트 계획 수립 및 진행</li>
<li>객체 지향 특강 듣기</li>
<li>기초적인 오브젝트 추적 구현해보기</li>
</ul>
<hr>
<p>오늘은 적들이 플레이어를 찾고 추적하는 방법의 기초적인 구현을 해보려고 한다.</p>
<hr>
<h1 id="오브젝트-추적">오브젝트 추적</h1>
<h2 id="적이-플레이어-오브젝트를-찾아서-쫓아가기까지-필요한-과정">적이 플레이어 오브젝트를 찾아서 쫓아가기까지 필요한 과정</h2>
<blockquote>
<h3 id="1-플레이어-오브젝트를-식별한다">1. 플레이어 오브젝트를 식별한다</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/6d4c9139-1e79-4791-907e-3b7ecbdde5a4/image.png" alt=""></p>
</blockquote>
<ul>
<li><h4 id="적-오브젝트는-플레이어를-감지하는-기능-또는-플레이어의-정보를-어디선가-받아와야-된다">적 오브젝트는 플레이어를 감지하는 기능 또는 플레이어의 정보를 어디선가 받아와야 된다.</h4>
</li>
</ul>
<hr>
<blockquote>
<h3 id="2-자신과-플레이어-오브젝트까지의-방향을-구한다">2. 자신과 플레이어 오브젝트까지의 방향을 구한다.</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/ca38fc6a-36dc-4ccc-be08-776b10345b63/image.png" alt=""></p>
</blockquote>
<ul>
<li><h4 id="플레이어-transform이-요구되고-normalized를-하여-단위-벡터를-구한다">플레이어 Transform이 요구되고, normalized를 하여 단위 벡터를 구한다.</h4>
</li>
</ul>
<hr>
<blockquote>
<h3 id="3-구한-방향을-향해서-플레이어를-쫓아간다">3. 구한 방향을 향해서 플레이어를 쫓아간다.</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/9212ff7e-aa9a-44f6-ad46-19584ff9e672/image.png" alt=""></p>
</blockquote>
<ul>
<li><h4 id="fixedupdate를-활용해서-전-과정에서-구한-단위-벡터값을-이용해-플레이어의-방향으로-이동한다">FixedUpdate를 활용해서 전 과정에서 구한 단위 벡터값을 이용해 플레이어의 방향으로 이동한다.</h4>
</li>
</ul>
<hr>
<hr>
<h2 id="플레이어의-위치를-항상-가진-상태의-추적">플레이어의 위치를 항상 가진 상태의 추적</h2>
<blockquote>
<p>플레이어의 Transform을 명확하게 아는 상태에서의 추적을 구현해보자
<img src="https://velog.velcdn.com/images/b_h_b/post/3c219b2f-4158-4994-be3c-819a016e3f60/image.png" alt=""></p>
</blockquote>
<ul>
<li>Player의 오브젝트를 싱글턴화할 수는 없으니 싱글턴된 게임 매니저를 이용해야한다. 
(스파르타 코딩클럽 스크립트를 참고해서 만들었습니다)</li>
</ul>
<hr>
<h3 id="1-플레이어-오브젝트-정보-받아오기">1. 플레이어 오브젝트 정보 받아오기</h3>
<pre><code class="language-c">public static GameManager Instance;
{
    public Transform Player { get; private set; }    // 플레이어의 위치 참조
    [SerializeField] private string playerTag = &quot;Player&quot;; // 플레이어 태그
&gt;
    private void Awake()
    {
        Instance = this;
        Player = GameObject.FindGameObjectWithTag(playerTag).transform; // 태그를 통해 플레이어 오브젝트를 찾는다.
    }
}
&gt;
// Enemy.cs
  private Transform Target { get; private set; } // 플레이어 트랜스폼 참조할 변수
&gt;
  private void Start()
  {
      Target = GameManager.Instance.Player; // 게임 매니저에 있는 플레이어 트랜스폼을 받아온다
  }</code></pre>
<ul>
<li>게임 매니저로부터 플레이어의 Transform을 받아온다</li>
</ul>
<hr>
<h3 id="2-플레이어-오브젝트-쪽-방향-구하기">2. 플레이어 오브젝트 쪽 방향 구하기</h3>
<pre><code class="language-c">private void FixedUpdate()
{
    direction = DirectionToTarget(); // FixedUpdate로 실시간으로 플레이어의 위치를 계산한다.
    ...
}
&gt;
private Vector2 DirectionToTarget()
{
    return (Target.position - transform.position).normalized; // 플레이어까지의 단위 벡터를 구한다.
}</code></pre>
<blockquote>
</blockquote>
<hr>
<h3 id="3-구한-방향-쪽으로-이동하기">3. 구한 방향 쪽으로 이동하기</h3>
<pre><code class="language-c">private void FixedUpdate()
{
    ...
    CallMoveEvent(direction); // CallMoveEvent 내에서 움직이는 메서드가 구현되어있습니다.
}</code></pre>
<ul>
<li>구한 단위 벡터를 이용해서 플레이어 쪽으로 향한다.</li>
</ul>
<hr>
<h3 id="결과물">결과물</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/adfa06fe-d1ab-469b-9d8e-73e02d693060/image.gif" alt=""></p>
<ul>
<li>플레이어가 어디에 있든 항상 따라온다. (뱀서류에 어울리는 추적 방식)</li>
<li>추가 구현 사항을 해주면 플레이어의 방향에 따라서 스프라이트를 뒤집거나 일정 거리 이상은 안 따라가게 하는 등의 기능을 넣을 수 있다.</li>
</ul>
<h2 id="collider를-이용한-플레이어-추적">Collider를 이용한 플레이어 추적</h2>
<blockquote>
<p>적 오브젝트가 고유의 Collider를 이용해 플레이어를 추격하는 로직을 구현해보자</p>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/f4222617-2d74-466b-a759-e1e406295239/image.png" alt=""></li>
<li>오브젝트 자체가 탐지 기능을 가지고 있기에 타 오브젝트의 의존도가 낮은 편이다.
<em>(사실 식별 말고는 2번째는 차이가 없으니 식별과 이동에만 집중해보자)</em></li>
</ul>
<hr>
<h3 id="플레이어-식별">플레이어 식별</h3>
<pre><code class="language-c">public class ColliderVision : TopDownController
{
    [SerializeField] private string targetTag = &quot;Player&quot;;    // 
    private GameObject FindTarget;    // 찾은 적 오브젝트 정보를 담는다.
&gt;
    [SerializeField] private Collider2D colliderVision; // 자식이 가지고 있는 Collider를 가져온다.
&gt;
      private void OnTriggerEnter2D(Collider2D collision) // 특정 오브젝트가 Collider에서 진입할 때 호출
    {
        if (collision.CompareTag(targetTag))    // 태그 확인
        {
            ... (추후에 작성) ...
            FindTarget = collision.gameObject;    
        }
    }
&gt;
    private void OnTriggerExit2D(Collider2D collision)    // 특정 오브젝트가 Collider에서 나갔을 때 호출
    {
        if (collision.CompareTag(targetTag))    // 태그 확인
        {
            ... (추후에 작성) ...
        }
  }</code></pre>
<ul>
<li><h4 id="collider-설정">Collider 설정</h4>
<img src="https://velog.velcdn.com/images/b_h_b/post/9f36d31e-3514-4dc2-98d7-8ce320e5c395/image.png" alt=""></li>
<li>Collider를 이와 같이 설정하였다.</li>
</ul>
<hr>
<h3 id="플레이어-추적">플레이어 추적</h3>
<pre><code class="language-c">private Vector2 targetDirection;    // 타겟 방향 (계산은 전 방법과 동일하니 생략)
&gt;
private bool isTargetFind = false;    // 타겟 식별 유무
private Vector2 stopMove;            // 정지를 위한 벡터
&gt;
[SerializeField] private Transform colliderTransform;    // 충돌체 Transform
&gt;
void Start()
{
    stopMove = Vector2.zero; // 0 벡터 할당
}
&gt;
private void FixedUpdate()    // 물리 처리 업데이트
{
    if (!isTargetFind)     // 타겟을 찾지 못했을 경우
    { 
        return; 
    }
        targetDirection = DirectionToTarget();    // 타겟 목표 방향 최신화
        CallMoveEvent(targetDirection);        // 이동 이벤트 호출
        ChangeRotation(targetDirection);    // 충돌체 방향 돌리기
}
&gt;
private void OnTriggerEnter2D(Collider2D collision)    // 충돌체 진입 시
{
    if (collision.CompareTag(targetTag))    // 태그 확인
    {
        isTargetFind = true;    // 타겟 식별 true
        FindTarget = collision.gameObject;    // 충돌체에 진입한 오브젝트를 할당한다
    }
}
&gt;
 private void OnTriggerExit2D(Collider2D collision)    // 충돌체 이탈 시
 {
     if (collision.CompareTag(targetTag))    // 태그 확인
     {
         isTargetFind = false;    // 타겟 식별 false
         targetDirection = Vector2.zero;    // 0벡터 할당
         CallMoveEvent(stopMove);    // 이동 이벤트 멈추기
     }
 }</code></pre>
<hr>
<h3 id="구현-결과">구현 결과</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/177f32f3-4671-4129-805e-d3b735d26c1a/image.gif" alt=""></li>
<li>콜라이더에 플레이어가 진입하게 되면 적이 추적을 하기 시작한다.</li>
<li>플레이어의 위치를 최대한 놓치지 않기 위해서 콜라이더도 플레이어의 방향에 따라 움직이게 만들었다.<h4 id="-추가-구현-탐색-기능-스프라이트-뒤집기">+ 추가 구현 (탐색 기능, 스프라이트 뒤집기)</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/6f6bb86b-d331-4807-baf1-0950559be94c/image.gif" alt=""></li>
<li>추가 구현을 통해 적 오브젝트에게 매우 간단한 탐색 기능을 만들어서 주변을 탐색하게 만들어봤다.</li>
</ul>
<hr>
<h2 id="상시-추적법과-collider-추적법의-장단점">상시 추적법과 Collider 추적법의 장단점</h2>
<blockquote>
<h3 id="상시-추적법">상시 추적법</h3>
</blockquote>
<ul>
<li><h4 id="장점">장점</h4>
<ul>
<li>매우 빠르고 간단하게 구현할 수 있다.</li>
<li>참조를 통해 플레이어의 정보를 가져오기 때문에 메모리가 덜 든다.</li>
</ul>
</li>
<li><h4 id="단점">단점</h4>
<ul>
<li>플레이어 정보를 주는 오브젝트에게 의존해야한다.</li>
<li>추적 방식의 확장성이 떨어진다.</li>
<li>플레이어 중간에 가려지는 오브젝트가 있을 경우의 식별 처리가 힘들다<blockquote>
<h3 id="collider-추적법">Collider 추적법</h3>
</blockquote>
</li>
</ul>
</li>
<li><h4 id="장점-1">장점</h4>
<ul>
<li>탐색과 추적 방식의 확장성이 높다.</li>
<li>타 오브젝트에게 의존하지 않아도 된다.</li>
</ul>
</li>
<li><h4 id="단점-1">단점</h4>
<ul>
<li>오브젝트가 많을 경우 그만큼 연산량이 많아진다.</li>
<li>플레이어 중간에 가려지는 오브젝트가 있을 경우의 식별 처리가 힘들다.</li>
</ul>
</li>
</ul>
<hr>
<hr>
<hr>
<h2 id="그-외에-알게된-점">그 외에 알게된 점</h2>
<blockquote>
<h3 id="fixedupdate와-이벤트">FixedUpdate와 이벤트</h3>
<p>FixedUpdate에서 이벤트에게 한번이라도 direction을 전달할 경우 그 이벤트는 direction 값이 변경되거나 다른 명령으로 인해 중지될 때까지 계속 활성화된다.</p>
</blockquote>
<h4 id="버그-리포트">버그 리포트)</h4>
<pre><code class="language-c">private void OnTriggerExit2D(Collider2D collision)
{
    if (collision.CompareTag(targetTag))
    {
        isTargetFind = false;
        targetDirection = Vector2.zero;
        // CallMoveEvent(stopMove); &lt;- 이를 제거할 경우
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/b7740043-9437-43c2-918e-ba7a1efd9238/image.gif" alt=""></p>
<ul>
<li>플레이어를 놓쳤음에도 플레이어를 식별했던 마지막 방향으로 떠나버린다.</li>
</ul>
<blockquote>
<h3 id="transformrotation와-transformeulerangles의-반환값">Transform.rotation와 Transform.eulerAngles의 반환값</h3>
</blockquote>
<ul>
<li>Transform.rotation는 쿼터니언으로 사실상 우리가 해석할 수 없는 값을 반환하고</li>
<li>Transform.eulerAngle는 0~360도 값을 반환해준다. 
(atant2 * Rad2Deg와 유니티에 보이는 rotation이랑 전혀 다른 값을 반환하니 주의하자)</li>
</ul>
<hr>
<hr>
<h2 id="사용한-에셋">사용한 에셋</h2>
<blockquote>
</blockquote>
<ul>
<li><a href="https://9e0.itch.io/cute-legends-cat-heroes">고양이 캐릭터 <strong>Cute Legends Cat Heroes</strong></a></li>
<li><a href="https://cupnooble.itch.io/sprout-lands-asset-pack">타일 <strong>Sprout Lands - Asset Pack</strong></a></li>
</ul>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>내가 만들고 싶은 게임을 구현하기 위해서 더 많은 기능 구현 방법에 대해서 알고 싶어졌다. 내가 아는 선 안에서는 최대한의 기능 구현이 가능한 상태이지만, 아직까지도 모르는 것이 많다고 느껴졌다. 좀 더 많은 학습을 통해서 게임 내의 모든 로직을 구현해 구동이 가능한 나만의 게임을 출시해보고 싶다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 5주차 2일차 TIL - 오브젝트 풀링]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-2%EC%9D%BC-TIL-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%ED%92%80%EB%A7%81</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-2%EC%9D%BC-TIL-%EC%98%A4%EB%B8%8C%EC%A0%9D%ED%8A%B8-%ED%92%80%EB%A7%81</guid>
            <pubDate>Tue, 14 May 2024 14:54:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>챌린지반 과제 진행</li>
<li>개인 프로젝트 진행 후 제출</li>
<li>오브젝트 풀링 파헤치기</li>
</ul>
<hr>
<p>오늘은 게임 개발에 널리 사용되는 테크닉 중 하나인 오브젝트 풀링에 대해서 알아보자</p>
<hr>
<h1 id="오브젝트-풀링">오브젝트 풀링</h1>
<h2 id="개념">개념</h2>
<blockquote>
<p>필요한 객체를 미리 생성해두고 사용이 필요할 때 pool에서 꺼내고, 사용이 끝나면 다시 pool에 반납하는 방식의 테크닉이다.</p>
</blockquote>
<h4 id="방법">방법</h4>
<ol>
<li>객체를 미리 생성해두는 방식</li>
<li>미리 안 만들어놓고 필요한 상황이 왔을 때 만드는 방식</li>
</ol>
<ul>
<li>_ 둘다 혼합해서 사용하는 경우도 있으니 요구 사항에 맞게 만들면 된다._</li>
</ul>
<hr>
<hr>
<h2 id="오브젝트-풀링을-사용하는-이유">오브젝트 풀링을 사용하는 이유</h2>
<blockquote>
<h3 id="1-메모리-할당과-가비지-컬렉팅으로-인한-프레임-드랍-최소화">1. 메모리 할당과 가비지 컬렉팅으로 인한 프레임 드랍 최소화</h3>
</blockquote>
<ul>
<li>프로그래밍에서 오브젝트를 생성하거나 파괴하는 작업은 꽤나 무거운 작업이다.</li>
<li>오브젝트 생성은 메모리를 새로 할당하고 리소스를 로드하는등의 초기화 과정이 필요하다</li>
<li>오브젝트 파괴는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있다.</li>
<li><blockquote>
<p>Destroy 함수로 게임 오브젝트를 파괴한다고 선언하면 곧바로 메모리에서 사라지는 것이아니라 가비지 콜렉터가 수거해서 파괴하기 전까지는 메모리에 남아있다. (쌓여있는 가비지가 많으면 많을수록 수거 파괴에 더 많은 시간이 걸린다.)</p>
</blockquote>
</li>
</ul>
<hr>
<h3 id="2-메모리-단편화">2. 메모리 단편화</h3>
<ul>
<li>메모리를 제거하는 과정에서 빈 공간이 생기는데, 만약에 새로 할당될 메모리 크기가 빈 메모리 공간보다 크다면 해당 메모리 공간을 사용하지 못하고, 들어갈 수 있는 메모리 공간이 있는지 다시 탐색한다. 설령 흩어져있는 빈 메모리 공간들의 크기가 해당 메모리보다 크더라도 각자의 빈 메모리 공간이 부족하다면 말이다. 이것을 <strong>외부 메모리 단편화</strong>라고 부른다. </li>
<li>외부 메모리 단편화의 문제점은 앞서 말했듯이 메모리 공간이 충분함에도 불구하고 메모리를 할당하지 못하는 현상이다. 유니티의 가비지 컬렉터는 이러한 메모리 외부 단편화를 해결해주지 못한다. (*****<a href="https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html">Compaction 기능을 지원하지 않는다!</a>)<h5 id="해당-문서-내용-중에서---the-garbage-collector-is-also-non-compacting-which-means-that-unity-doesnt-redistribute-any-objects-in-memory-to-close-the-gaps-between-objects">*해당 문서 내용 중에서 -&gt; &#39;The garbage collector is also non-compacting, which means that Unity doesn’t redistribute any objects in memory to close the gaps between objects&#39;</h5>
<h5 id="--유니티의-가비지-컬렉터는-비압축-방식으로-메모리-상의-객체들-사이의-공간을-메우기-위해-객체들을-재배치하지-않습니다">-&gt; &quot;유니티의 가비지 컬렉터는 비압축 방식으로 메모리 상의 객체들 사이의 공간을 메우기 위해 객체들을 재배치하지 않습니다.&quot;</h5>
<h4 id="외부-메모리-단편화-예시">외부 메모리 단편화 예시</h4>
</li>
<li>*[1] 메모리2를 가비지 콜렉터가 수거해 파괴했다.**
<img src="https://velog.velcdn.com/images/b_h_b/post/70022974-d20d-42e9-ac2a-94badc8bd6ed/image.png" alt=""><blockquote>
</blockquote>
</li>
</ul>
<hr>
<p><strong>[2] 메모리4를 할당하려하지만 해당 빈 메모리 공간의 크기가 부족해 할당을 할 수가 없다.</strong>
<img src="https://velog.velcdn.com/images/b_h_b/post/321e055e-91a6-4a94-86c3-62d1372ecefa/image.png" alt=""></p>
<blockquote>
</blockquote>
<hr>
<p>**[3] 메모리4는 자기가 들어갈 수 있는 공간을 찾아서 할당한다. **
<img src="https://velog.velcdn.com/images/b_h_b/post/8c9f27dd-be2d-4b8c-8d39-31402c8a6b4a/image.png" alt=""></p>
<blockquote>
</blockquote>
<hr>
<p><strong>[4] 이 과정이 지속되면 메모리 공간이 충분함에도 불구하고 할당하지 못하는 상황이 발생한다.</strong>
<img src="https://velog.velcdn.com/images/b_h_b/post/bf09a6f0-c181-4a80-ab1a-2d00f068d49a/image.png" alt=""></p>
<blockquote>
</blockquote>
<hr>
<p>그래서 위와 같은 이유로 인해 오브젝트 풀링을 사용해야하는 이유이다.</p>
<h2 id="object-pool의-2가지-구현-방법">Object Pool의 2가지 구현 방법</h2>
<h3 id="클래스로-생성하기">클래스로 생성하기</h3>
<blockquote>
</blockquote>
<h3 id="uml-간략화">UML 간략화</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/ecdc86a5-b6cd-436e-b9a8-3530de2196fe/image.png" alt=""></p>
<ul>
<li>해당 스크립트는 스파르타 코딩클럽에서 사용한 오브젝트 풀링 스크립트를 필수적인 부분만 남겨서 만든 스크립트입니다.</li>
</ul>
<hr>
<hr>
<h3 id="1-오브젝트-생성-및-보관">1. 오브젝트 생성 및 보관</h3>
<p>ObjectPool 클래스는 오브젝트를 초기 생성 후 타 클래스에게 오브젝트를 넘겨주는 역할을 해준다.
<img src="https://velog.velcdn.com/images/b_h_b/post/0039acac-4298-4c18-a58b-2017414a109b/image.png" alt=""></p>
<h4 id="코드-예시">코드 예시)</h4>
<pre><code class="language-c">public class ObjectPool : MonoBehaviour
{
    public GameObject prefab;     // 투사체 정보 보관 
    public int size;             // 초기 생성 갯수
&gt;
    public Queue&lt;GameObject&gt; objectPool;    // 오브젝트를 보관할 큐
&gt;
    void Awake()
    {
        objectPool = new Queue&lt;GameObject&gt;();        // 큐 초기화
        for (int i = 0; i &lt; size; i++)    // 정해진 갯수만큼 반복
        {
            GameObject obj = Instantiate(prefab);    // 오브젝트 생성
            obj.SetActive(false);                    // 비활성화
            objectPool.Enqueue(obj);                // 큐에 할당
        } // 해당 과정이 오브젝트를 생성하고 풀에 보관하는 과정이다.
    }</code></pre>
<h4 id="런타임">런타임</h4>
<ul>
<li>초기 갯수를 20으로 정해놓으면 프로젝트 실행 시 이와 같이 Prefab을 인스턴스화 해준다.
<img src="https://velog.velcdn.com/images/b_h_b/post/cb62e970-8f9b-419b-9983-ca1529afe39c/image.png" alt=""></li>
</ul>
<hr>
<hr>
<h3 id="2-오브젝트-전달">2. 오브젝트 전달</h3>
<p>오브젝트가 필요하게 되면 ObjectPool이 TopDownShooting에게 오브젝트를 전달해주는 과정이다.
<img src="https://velog.velcdn.com/images/b_h_b/post/b9fb2a7e-ee8d-4022-ba38-7942bd9e61b4/image.png" alt=""></p>
<h4 id="코드-예시-1">코드 예시)</h4>
<pre><code class="language-c">// ObjectPool.cs
   public GameObject SpawnFromPool()
   {
       GameObject obj = objectPool.Dequeue();    // 큐에서 나온 걸 obj에 할당
       objectPool.Enqueue(obj);                    // obj를 다시 큐에 할당
       obj.SetActive(true);        // obj 활성화
       return obj;                // obj를 반환
   }
&gt;   
// TopDownShooting.cs
    private TopDownController controller;    // 옵서버 관리 클래스
    private ObjectPool objectPool;            // 오브젝트 풀
    [SerializeField] private Transform projectileSpawnPosition; // 투사체 생성 위치
    private Vector2 aimDirection = Vector2.right;    // 마우스 위치 
&gt;
    void Awake()
    {
        controller = GetComponent&lt;TopDownController&gt;();
        objectPool = GetComponent&lt;ObjectPool&gt;();
    }
&gt;
  void Start()
  {
      controller.OnAttackEvent += OnShoot;    // 클릭할 시 호출되는 이벤트에 할당
      controller.OnLookEvent += OnAim;        // 마우스 위치를 전달해주는 이벤트에 할당
  }
  private void OnAim(Vector2 newAimDirection)
  { 
      aimDirection = newAimDirection.normalized;    // 받은 마우스 방향을 보관
  }
&gt;
  private void OnShoot()
  { 
      CreateProjectile();    // 투사체 생성
  }
&gt;
  private void CreateProjectile()    // 투사체를 가져오고 필요한 정보를 전달
  {
      GameObject obj = objectPool.SpawnFromPool();    // Pool로부터 obj 반환 받기
      ...
  }</code></pre>
<h4 id="런타임-1">런타임</h4>
<ul>
<li>마우스 클릭 이벤트로 인해 맨 위에 있는 오브젝트들부터 활성화되는 모습이다.
<img src="https://velog.velcdn.com/images/b_h_b/post/6ced3686-4e4a-474b-8cab-1f8c02215dfa/image.png" alt=""></li>
</ul>
<hr>
<hr>
<h3 id="3-오브젝트-사용-및-정보-전달">3. 오브젝트 사용 및 정보 전달</h3>
<p>전달받은 오브젝트에 필요한 정보를 전해준다.</p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/22d83ef2-2795-4404-8361-d3a52bace4ec/image.png" alt=""></li>
<li><code>TopDownShooting.cs</code></li>
</ul>
<hr>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/4dedd37f-efd1-4ef4-8692-0ccf0632df49/image.png" alt=""> </li>
<li><code>ProjectileController.cs</code></li>
</ul>
<hr>
<h4 id="코드-예시-2">코드 예시)</h4>
<pre><code class="language-c"> // TopDownShooting.cs 
     private void CreateProjectile()
    {
        ...
        obj.transform.position = projectileSpawnPosition.position;    // 생성 위치 전달
        ProjectileController attackController = obj.GetComponent&lt;ProjectileController&gt;();    // obj로부터 스크립트 가져오기
        attackController.InitializeAttack(aimDirection); // 가져온 스크립트의 메서드를 실행
    }
&gt;    
// ProjectileController.cs
    private Vector2 direction;    // 날라갈 방향
    private bool isReady;        // 생성 시간 동안 준비되지 않은 상태에서 움직이는 거 막는 용도
&gt;
    private Rigidbody2D rigidbody;    // 위치 이동에 필요한 컴포넌트
&gt;
    private void Start()
    {
        rigidbody = GetComponent&lt;Rigidbody2D&gt;();
    }
&gt;
    private void Update()
    {
        if (!isReady) { return; }
        ...
        rigidbody.velocity = direction * 5;    // 해당 방향으로 이동
    }
&gt;
    public void InitializeAttack(Vector2 direction)    // 호출된다면 초기 세팅 메서드
    {
        this.direction = direction;            // 전달받은 방향 할당
        transform.right = this.direction;    // 스프라이트의 렌더러 방향 정하는 용도
        ...
        isReady = true;    // 사용 준비 완료
    }</code></pre>
<h4 id="런타임-2">런타임</h4>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/0a54a723-ea95-4bf3-92ae-93a590a6ddc9/image.gif" alt=""></p>
<ul>
<li>전달받은 정보대로 투사체가 잘 날라가는 모습이다.<h3 id="4-사용이-끝나면-비활성화">4. 사용이 끝나면 비활성화</h3>
<img src="https://velog.velcdn.com/images/b_h_b/post/1b2cee65-7105-4dbf-827b-9298aea2a4f2/image.png" alt=""><h4 id="코드-예시-3">코드 예시)</h4>
<pre><code class="language-c">public class MyProjectileController : MonoBehaviour
{
  private float currentDuration; // 생성 경과 시간
   ...
&gt;   
  private void Update()
  {
      ...
      currentDuration += Time.deltaTime;    // 생성 경과 시간 업데이트
      if (currentDuration &gt; 2) { DestroyProjectile(); }    // 생성 경과 시간이 일정 이상 넘으면 파괴 메서드 호출
      ...
  }
&gt;    
  public void InitializeAttack(Vector2 direction)
  {
      currentDuration = 0;    // 생성 경과 시간 초기화
  }
&gt;
  private void DestroyProjectile() // 사용이 끝날 때 호출되는 메서드
  { gameObject.SetActive(false); }</code></pre>
<h4 id="런타임-3">런타임</h4>
<img src="https://velog.velcdn.com/images/b_h_b/post/2fe63b24-14c9-4020-8058-3574fead1431/image.gif" alt=""></li>
<li>생성 후 일정 이상의 시간이 넘어가면 사라지는 모습이다. </li>
</ul>
<hr>
<hr>
<h3 id="구현-후-프로젝트-실행의-모습">구현 후 프로젝트 실행의 모습</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/38cf9406-c9a6-4fa8-a7dd-cc8ffcc4b17b/image.gif" alt=""></li>
</ul>
<hr>
<hr>
<hr>
<h3 id="unityenginepool-사용하기">UnityEngine.Pool 사용하기</h3>
<blockquote>
<p><code>using UnityEngine.Pool</code>을 활용해서 오브젝트 풀링을 구현하는 방법이다.</p>
</blockquote>
<ul>
<li><strong>참고 자료</strong> -&gt; <a href="https://www.youtube.com/watch?v=JxP-kqstMAY"><strong>유니티 오브젝트 풀링 | 유니티 [UN-OP-1] 베르의 게임 개발 유튜브</strong></a></li>
</ul>
<blockquote>
<h3 id="uml-간략화-1">UML 간략화</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/9d8b0f8a-b12c-4fec-96b6-3ec587c172cd/image.png" alt=""></p>
</blockquote>
<ul>
<li>위 방식은 필요 시에 생성되고 재활용하는 방식을 채용했다.</li>
</ul>
<hr>
<hr>
<h3 id="namespace-unityenginepool">nameSpace UnityEngine.Pool</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/0025991a-4822-4070-818d-161bdf8eb150/image.png" alt=""></p>
<ul>
<li>해당 네임스페이스를 디컴파일해보면 위와 같은 형태로 되어있다. 
<span style="color:#E0E0E0">메서드 내부는 생략했다</span></li>
<li>각각의 필드를 해석해주자면</li>
<li><em>m_List*</em> - 오브젝트 풀</li>
<li><em>m_CreatFunc*</em> - 생성 델리게이트</li>
<li><em>m_ActionOnGet*</em> - 사용 델리게이트</li>
<li><em>m_ActionOnRelease*</em> - 해제 델리게이트</li>
<li><em>m_ActionOnDestroy*</em> - 파괴 델리게이트</li>
<li><em>m_MaxSize*</em> - 최대 보관 갯수</li>
<li><em>m_CollectionCheck*</em> - 컬렉션 체크 활성 또는 비활성화 (리스트로 돌아올 때 겹치는 오브젝트가 있는지 확인하는 용도이다.)</li>
<li><em>CountAll*</em> - 총 오브젝트 갯수</li>
<li><em>CountActive*</em> - 활성화 중인 오브젝트 갯수</li>
<li><em>CountInActive*</em> - 비활성화 중인 오브젝트 갯수</li>
<li><strong>++</strong>는 생성자에 해당하고 <strong>+</strong>는 메서드에 해당한다 </li>
</ul>
<hr>
<hr>
<h3 id="오브젝트-풀-생성하기">오브젝트 풀 생성하기</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/4faed65d-bfe2-4bdf-ac08-f4f4a7823280/image.png" alt=""></li>
<li>필수 요구 사항으로 생성 메서드만 줄 것 같아도 되지만, 그러면 말 그대로 생성만 되기 때문에 결국 원활한 사용을 위해서는 사용 메서드와 사용 해제 메서드까지는 있어야한다.<h4 id="코드-예시-4">코드 예시)</h4>
<pre><code class="language-c">public class Shooter : MonoBehaviour
{
  [SerializeField] private GameObject _BulletPrefab; // 투사체 Prefab
&gt;
  private IObjectPool&lt;Bullet&gt; _Pool;    // 오브젝트 풀
&gt;
  private void Awake()
  {
      _Pool = new ObjectPool&lt;Bullet&gt;(CreateBullet, OnGetBullet, OnReleaseBullet, OnDestroyBullet, maxSize:50); // 오브젝트 풀을 생성하게 담기
  }
&gt;
  private Bullet CreateBullet()    // 투사체 생성
  {
      Bullet bullet = Instantiate(_BulletPrefab).GetComponent&lt;Bullet&gt;(); // 투사체 생성
      bullet.SetManagedPool(_Pool);    // 투사체에 해제 및 파괴 메서드 사용 때문에 필요하다. 
      return bullet;
  } // 미리 생성되는 건 아니고 필요할 시 생성하게 된다. (ObjectPool의 Get() 발동 시)
&gt;
  private void OnGetBullet(Bullet bullet) // 투사체 사용 
  {
      bullet.gameObject.SetActive(true);
  }
&gt;
  private void OnReleaseBullet(Bullet bullet) // 투사체 해제
  {
      bullet.gameObject.SetActive(false);
  }
&gt;
  private void OnDestroyBullet(Bullet bullet) // 투사체 파괴 
  {
      Destroy(bullet.gameObject);
  }
}</code></pre>
</li>
<li>Shooter 클래스가 기능이 겹친 것처럼 보이지만 사실상 네임스페이스에 있는 ObjectPool 클래스를 가져와서 사용하는 것이라 메서드만 들고 있지 기능은 분리되어있다고 봐도 무방하다.</li>
</ul>
<hr>
<hr>
<h3 id="오브젝트-사용-및-정보-전달">오브젝트 사용 및 정보 전달</h3>
<h4 id="코드">코드)</h4>
<pre><code class="language-c">// Shooter.cs
    [SerializeField] private Transform _PlayerPosition;
&gt;
    private TopDownController controller;
&gt;    
    void Start()
    {
        controller = GetComponentInParent&lt;TopDownController&gt;();
        controller.OnAttackEvent += ShootBullet; // 클릭 이벤트 할당
    }
&gt;
    private void ShootBullet()
    {
        var direction = ((Vector2)transform.position - (Vector2)_PlayerPosition.position).normalized;
        var bullet = _Pool.Get(); // Pool에서 투사체를 가져온다. (없다면 생성해서 주고, 있다면 가진 오브젝트를 준다.)
        bullet.transform.position = transform.position; // 투사체 소환 지점 지정
        bullet.Shoot(direction); // 목표 방향을 전해준다.
    }
&gt;
// Bullet.cs
    private Vector2 _Direction; // 방향
&gt;
    [SerializeField]
    private float _Speed = 7f; // 속도
&gt;
    void Update()
    {
        transform.Translate(_Direction * Time.deltaTime * _Speed); // 지정된 방향으로 이동
    }
&gt;
    public void Shoot(Vector2 dir)
    {
        _Direction = dir; // 목표 방향을 할당 받는다.
         ...
    }</code></pre>
<hr>
<hr>
<h3 id="사용이-끝나면-비활성화">사용이 끝나면 비활성화</h3>
<pre><code class="language-c">public class Bullet : MonoBehaviour
{
    private IObjectPool&lt;Bullet&gt; _ManagedPool;
     ...
 &gt;   
    public void SetManagedPool(IObjectPool&lt;Bullet&gt; pool)
    {
        _ManagedPool = pool; // ObjectPool의 기능을 사용하기 위해 가져온다
    } 
&gt;
    public void Shoot(Vector2 dir)
    {
        ...
        Invoke(&quot;DestroyBullet&quot;, 2f); // 2초 후에 총알 파괴메서드 호출
    }
&gt;
    public void DestroyBullet()
    {
        _ManagedPool.Release(this);    // 현재 오브젝트에게 사용 해제 메서드를 호출한다.
    } // 지정한 Maxsize보다 많다면 해당 오브젝트는 파괴될 것이고, 적다면 리스트에 보관된다.
}</code></pre>
<hr>
<hr>
<h3 id="구현-후-프로젝트-실행의-모습-1">구현 후 프로젝트 실행의 모습</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/a9b83725-0805-4ccc-96f1-ff801adcedfc/image.gif" alt=""></li>
<li>필요한 만큼 생성하고, 사용되는 모습을 볼 수 있다.</li>
</ul>
<hr>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/d0e0b12d-f5b7-4b3b-b7bd-b1394559584e/image.gif" alt=""></li>
<li>maxSize보다 더 많은 오브젝트가 생성되었을 때 초과 생성된 오브젝트는 제거된다.</li>
</ul>
<h2 id="참고-자료">참고 자료</h2>
<blockquote>
<h4 id="에셋">에셋</h4>
</blockquote>
<ul>
<li><a href="https://9e0.itch.io/cute-legends-cat-heroes">고양이 캐릭터 <strong>Cute Legends Cat Heroes</strong></a></li>
<li><a href="https://cupnooble.itch.io/sprout-lands-asset-pack">타일 <strong>Sprout Lands - Asset Pack</strong></a><blockquote>
</blockquote>
<h4 id="object-pool">Object Pool</h4>
</li>
<li>*<em>베르의 게임 개발 유튜브 *</em></li>
<li><blockquote>
<p><a href="https://www.youtube.com/watch?v=JxP-kqstMAY">유니티 오브젝트 풀링 | 유니티 [UN-OP-1]</a></p>
</blockquote>
</li>
<li><blockquote>
<p><a href="https://www.youtube.com/watch?v=xiojw0lHzro">오브젝트 풀링 구현하기</a></p>
</blockquote>
</li>
<li><strong>글릭의 만들어가는 세상</strong></li>
<li><blockquote>
<p><a href="https://glikmakesworld.tistory.com/13">오브젝트 풀링 - 유니티로 공부하는 게임 제작기술(Object Pooling)</a></p>
</blockquote>
<h4 id="유니티-자료">유니티 자료</h4>
</li>
<li><a href="https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html"><strong>Incremental garbage collection</strong></a></li>
</ul>
<hr>
<hr>
<hr>
<h1 id="그-외에-알게된-점">그 외에 알게된 점</h1>
<blockquote>
<h3 id="버그-리포트">버그 리포트</h3>
</blockquote>
<ul>
<li>컴파일 오류가 생긴 상태에서 CreateAssetMenu를 사용하려 하면 적용이 안된다. (컴파일 오류를 해결하면 정상적으로 적용된다.)</li>
</ul>
<blockquote>
<h3 id="투사체에게-의도되지-않은-물리적인-충격을-입고-싶지-않다면">투사체에게 의도되지 않은 물리적인 충격을 입고 싶지 않다면</h3>
</blockquote>
<ul>
<li>투사체의 Collider에서 Layer Overrides에서 Force Send Layers를 Nothing으로 바꿔주면 된다.
<img src="https://velog.velcdn.com/images/b_h_b/post/9c8a2d97-8baa-4a38-a0f1-ed0f3292365c/image.png" alt=""></li>
</ul>
<hr>
<hr>
<hr>
<h1 id="작성하면서-느낀점">작성하면서 느낀점</h1>
<blockquote>
<p>코드를 작성하고 실행시키면서 알게된 사실은 코드를 읽는 능력이 매우 중요하다는 사실을 알게되었다. 특히 지금 단계와 같이 아직 모르는 코드들이 너무 많을 때에는 더 중요하게 작용하는 것 같다. 다양한 코드들을 해석해보는 시간과 모르는 코드들을 PsuedoCode화 시켜보는 시간을 많이 들여야될 것 같다.
 그리고 TIL을 좀 더 효율적으로 썼으면 한다. 아직까지 TIL 작성에 시간적 효율이 좋지 않은 것 같다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 5주차 1일차 TIL - RuleTile]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-RuleTile</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-5%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-RuleTile</guid>
            <pubDate>Mon, 13 May 2024 12:18:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>알고리즘 문제 풀기 (Leetcode 2373 문제)</li>
<li>챌린지반 배정 및 과제 진행</li>
<li>개인 프로젝트 진행하기</li>
</ul>
<hr>
<p>오늘은 미리 설정해두면 타일 작업을 빠르게 해줄 수 있는 RuleTile에 대해 알아보자</p>
<hr>
<h2 id="ruletile">RuleTile</h2>
<blockquote>
<h3 id="ruletile이란-유니티-주소">RuleTile이란? <a href="https://docs.unity3d.com/Packages/com.unity.2d.tilemap.extras@1.6/manual/RuleTile.html">유니티 주소</a></h3>
<p>타일에 규칙을 부여해 해당 타일이 배치된 3x3 범위의 주변 위치에 있는 타일의 존재 유무에 따라서 알맞은 타일을 정해진 규칙대로 설치하는 유니티의 타일 기능 중 하나이다.</p>
</blockquote>
<hr>
<p>타일 시트를 자르는 방법은 생략하겠다. <a href="https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL#%EC%8A%A4%ED%94%84%EB%9D%BC%EC%9D%B4%ED%8A%B8-%EC%8B%9C%ED%8A%B8%EB%A5%BC-%EA%B0%80%EC%9A%A9-%EA%B0%80%EB%8A%A5%ED%95%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80%EB%A1%9C-%EB%B0%94%EA%BE%B8%EB%8A%94-%EB%B0%A9%EB%B2%95">스프라이트 시트를 짜르는 걸 알고싶다면 이쪽으로.</a></p>
<hr>
<hr>
<h3 id="ruletile을-만들고-팔레트에-넣기">RuleTile을 만들고 팔레트에 넣기</h3>
<blockquote>
</blockquote>
<h3 id="룰-타일-만들기">룰 타일 만들기</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/e7a16875-bf07-452e-bee6-97411cde0dfc/image.png" alt=""></p>
<ul>
<li>적당한 에셋 공간을 찾아서 우클릭을 누른 후 Create/2D/Tiles/Rule Tile을 눌러서 생성한다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="룰타일-오브젝트-팔레트에-넣기">룰타일 오브젝트 팔레트에 넣기</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/5a7e6e69-c058-4ee0-a52e-7e548ea9efa2/image.png" alt=""></p>
<ul>
<li>룰타일 오브젝트를 집어서 팔레트 칸에 넣어준다.</li>
</ul>
<hr>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/69e08e47-f2da-4342-981a-08866cad2849/image.png" alt=""></li>
<li>생성된 RuleTile을 누르면 이와 같은 정보 창이 뜰텐데 하나하나에 대해서 설명해주겠다.</li>
</ul>
<hr>
<hr>
<h3 id="룰-타일-inspector-기본-정보">룰 타일 Inspector 기본 정보</h3>
<blockquote>
</blockquote>
<h4 id="기본값-스프라이트">기본값 (스프라이트)</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/a55e90a4-5301-4069-a3a3-2f66f078e284/image.png" alt=""></li>
<li>기본값 스프라이트(타일)로 만약에 아래쪽에 넣게될 규칙 타일 리스트 중에서 그 어떤 것도 해당되지 않으면 해당 스프라이트가 들어가게된다.<blockquote>
</blockquote>
<h4 id="기본값-gameobject-collider">기본값 (GameObject, Collider)</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/dbeb8079-5b16-43ab-930a-6f64692ea930/image.png" alt=""></li>
<li>만약에 기본값 스프라이트가 들어갔을 시 같이 들어가는 게임오브젝트와 충돌체이다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="-sprite와-grid의-차이">! Sprite와 Grid의 차이</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/8a90e812-758b-4a9d-9fd8-03f2e7f17f31/image.png" alt=""></li>
<li>왼쪽이 Sprite Collider이고, 오른쪽이 Grid Collider이다. Sprite는 Sprite의 모양에 따라서 충돌체를 결정하고 Grid는 타일 한 칸의 충돌체 모양을 가진다. </li>
</ul>
<hr>
<h4 id="number-of-tiling-rules">Number of Tiling Rules</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/91b7ba25-19bc-464c-93b2-a3574cda7692/image.png" alt=""></li>
<li>이번 TIL에서 가장 중요한 요소로 룰 타일의 리스트를 나열하는 것이다.</li>
<li><strong>+</strong>와 <strong>-</strong>를 통해서 선택한 목록을 추가 제거할 수 있고, </li>
<li>GameObject, Collider, Sprite는 규칙에 해당한다면 Sprite가 배치되고 가지게 되는 속성이다. (위 Default랑 하는 역할이 동일하다.)</li>
<li>우선 순위는 맨 위부터 시작하고 아래부터 깐다. 그러니까 동일한 조건의 타일이 있더라도 가장 위에 있는 타일부터 배치된다. (Default는 리스트에 아무런 조건도 부합되는 타일이 없다면 마지막으로 배치된다.)</li>
</ul>
<hr>
<hr>
<h3 id="룰타일-규칙-정하기">룰타일 규칙 정하기</h3>
<blockquote>
</blockquote>
<p>그렇다면 이제 타일 규칙 목록을 생성해서 규칙을 하나씩 넣어주자 </p>
<h4 id="타일-규칙-편집기">타일 규칙 편집기</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/6f767792-152d-4af3-a881-76e8182ca953/image.png" alt=""></li>
<li>타일 규칙 편집기로 기본적으로 위와 같이 되어있다.</li>
<li>아무 아이콘도 없다면 해당 칸이 깔려있던 말던 배치하라는 의미이다. 
(위 사진과 같은 상태가 Default와 동일한 상태이다.)</li>
<li>첫번째 클릭하면 초록색 화살표가 나오고 두번째 클릭하면 X자, 3번째 클릭은 다시 비어있는 상태가 된다.</li>
</ul>
<hr>
<h4 id="해당-칸에-타일이-존재해야한다">해당 칸에 타일이 존재해야한다</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/5911230f-f83d-4046-82d9-cfa3e146ce59/image.png" alt=""></li>
<li>해당 아이콘은 해당 칸에 타일이 존재해야한다는 조건을 넣어주는 아이콘이다.</li>
<li>해당하는 칸에 타일이 없다면 배치되지 않는다.</li>
<li>주로 다른 타일이랑 붙어지는 부분에다가 아이콘을 넣는 편이다.<h5 id="아이콘-배치-예시">아이콘 배치 예시)</h5>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/9e7faf0f-0817-4f86-b713-32eb37053c11/image.png" alt=""></li>
</ul>
<hr>
<h4 id="해당-칸이-비어야한다">해당 칸이 비어야한다</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/367eef00-1fbd-483e-aeb0-f039aaf50bbd/image.png" alt=""></li>
<li>해당 아이콘은 해당 칸에 아무 타일도 있어서는 안되는 조건을 넣어주는 아이콘이다.</li>
<li>해당하는 칸에 타일이 있다면 배치되지 않는다.</li>
<li>주로 다른 타일이랑 붙어지지 않는 부분에다가 해당 아이콘을 넣어주는 편이다.<h5 id="아이콘-배치-예시-1">아이콘 배치 예시)</h5>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/f692516c-4ae4-47fb-b23c-2d192f0e33d5/image.png" alt=""></li>
</ul>
<hr>
<hr>
<h3 id="규칙-확장-옵션">규칙 확장 옵션</h3>
<blockquote>
<p>중앙에 있는 아이콘이 달랐던 것을 볼 수 있는데 만약에 내가 타일 이미지가 별로 없거나 룰타일을 최소한으로 설정하고 싶다면 매우 유용한 설정이다.</p>
</blockquote>
<h3 id="rotate">Rotate</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/11dc5576-38ef-44be-92ea-7cb2790969e8/image.png" alt=""></li>
<li>타일 규칙을 90도, 180도, 270도로 돌렸을 때 부합하는 타일 배치가 있다면 배치해준다.</li>
<li>이게 놀라운 점은 그냥 배치하는 게 아니라 돌렸을 때 맞았던 방향대로 스프라이트도 돌아가서 맞춰준다. <h5 id="적용-예시">적용 예시)</h5>
<h4 id="적용-전">적용 전</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/41cb28ea-cafc-4070-bc0d-a04e107de70b/image.png" alt=""></li>
</ul>
<hr>
<h4 id="적용-후">적용 후</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/e2ac0ef3-8824-4912-99a5-1668105760f5/image.png" alt=""><h5 id="이게-있다면-타일-하나로-4개의-타일을-충당할-수-있는-효율이-생긴다">이게 있다면 타일 하나로 4개의 타일을 충당할 수 있는 효율이 생긴다.</h5>
</li>
</ul>
<hr>
<h3 id="mirrorx와-mirrory">MirrorX와 MirrorY</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/1ddaf46a-cdcb-462f-ad26-04e28ef97f09/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/17d9ae89-5efd-43b8-b418-8d1c28220c47/image.png" alt=""></li>
<li>상하(MirrorX) 또는 좌우(MirrorY)로 뒤집었을 경우에도 타일을 배치할 수 있도록 규칙을 확장해준다. </li>
</ul>
<hr>
<h3 id="mirrorxy">MirrorX+Y</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/6dec8c2c-81b0-48a3-8e96-1d8ea77762d7/image.png" alt=""></li>
<li>상하 좌우를 합친 버전이다.</li>
<li>Rotate와 같다고 생각하겠지만 엄연히 다르니, 타일맵 제작에 필요한 규칙을 사용해주면 된다.<h5 id="예시-rotate와-상하좌우의-차이">예시) Rotate와 상하좌우의 차이</h5>
<h3 id="rotate-1">Rotate</h3>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/be20583f-f39a-47a5-b611-85637e62ce4d/image.png" alt=""></li>
</ul>
<hr>
<h3 id="상하좌우">상하좌우</h3>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/a5261f35-6715-4341-926f-426829122f5c/image.png" alt=""></li>
</ul>
<hr>
<p>위 확장 옵션을 잘 이용하기만 한다면 적은 양의 타일만으로도 타일맵을 빠르게 표현할 수 있다.</p>
<h4 id="타일-3개만으로도-네모난-타일맵을-만들-수-있다">타일 3개만으로도 네모난 타일맵을 만들 수 있다.</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/ed37a254-1f8d-4af6-b03d-17316f152a63/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/3a1379ef-4d3d-4e94-bf07-bfa1e034f972/image.png" alt=""></li>
</ul>
<hr>
<hr>
<h3 id="output-설정">OutPut 설정</h3>
<blockquote>
<p>마지막으로 Output설정에 대해서 알아보자</p>
</blockquote>
<h3 id="single">Single</h3>
<ul>
<li>단순하게 sprite 칸에 있는 스프라이트대로 나온다.</li>
</ul>
<hr>
<hr>
<h3 id="random">Random</h3>
<ul>
<li>맵 타일 중간중간에 무작위로 다양한 타일 배치를 깔아준다.
<img src="https://velog.velcdn.com/images/b_h_b/post/8dbb05ce-3f72-4f0b-be83-4096921bbc38/image.png" alt=""></li>
</ul>
<hr>
<p><strong>Noise</strong> : 시드값을 담당하는 기능이다.</p>
<h5 id="noise-사용하는-모습">Noise 사용하는 모습)</h5>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/dfe7b7c6-234f-4b57-a9e2-fa64f139f5e7/image.gif" alt=""></li>
</ul>
<hr>
<p><strong>Shuffle</strong> : 타일의 배치를 돌리거나, 반대로 뒤집거나 하는 기능이다.</p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/6e4f8151-2541-4845-b6c1-0d7e7299292d/image.png" alt=""></li>
<li>무작위적으로 타일이 돌려져서 배치됨으로 이쁘게 안 나올 가능성이 크다.</li>
</ul>
<hr>
<p><strong>Size</strong> : 타일 리스트에 해당한다. 원하는만큼 추가시켜서 다양한 타일이 나오게 하자.</p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/02aacebc-d25f-4193-b588-ed5beff71311/image.png" alt=""></li>
<li>size 크기만큼 스프라이트를 넣어줄 수 있다.<h5 id="주의점">주의점!</h5>
</li>
<li>리스트에 빈 스프라이트를 방치한다면 구멍이 뚫린채로 나온다
<img src="https://velog.velcdn.com/images/b_h_b/post/95d4d774-111c-47b5-b1a1-586d8f4a4edf/image.png" alt=""></li>
</ul>
<hr>
<hr>
<h3 id="animation">Animation</h3>
<ul>
<li>타일의 애니메이션을 설정해준다.
<img src="https://velog.velcdn.com/images/b_h_b/post/fa82d51c-c8f8-4ca6-87c0-312f72e4d77c/image.png" alt=""></li>
<li>기존 애니메이션 타일과 동일한 기능을 가진다.</li>
</ul>
<hr>
<h4 id="단일-애니메이션-타일과-룰타일-애니메이션-타일">단일 애니메이션 타일과 룰타일 애니메이션 타일</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/369f18b9-1b14-4c82-8840-228bbc3403cb/image.gif" alt=""></li>
<li>윗쪽이 단일 애니메이션 타일, 아래쪽이 룰타일 애니메이션 타일이다.</li>
</ul>
<hr>
<hr>
<h3 id="참고-자료">참고 자료</h3>
<blockquote>
</blockquote>
<h4 id="ruletile-1">RuleTile</h4>
<ul>
<li><a href="https://wergia.tistory.com/200"><strong>Tilemap (2) - 룰 타일로 타일맵 자동 연결하기 - 베르의 프로그래밍 노트</strong></a><h4 id="에셋">에셋</h4>
</li>
<li><a href="https://cupnooble.itch.io/sprout-lands-asset-pack">타일 <strong>Sprout Lands - Asset Pack</strong></a></li>
</ul>
<hr>
<hr>
<hr>
<h2 id="개인-프로젝트를-진행하면서-알게된-점">개인 프로젝트를 진행하면서 알게된 점</h2>
<blockquote>
<h3 id="resourcesload-사용-시-주의점">Resources.Load() 사용 시 주의점</h3>
<p><strong>Resources.Load 사용 시 Resources 파일을 만들어둬야한다!</strong> (사용 시 Assets/Resources 이후의 파일과 폴더만 탐지한다)</p>
</blockquote>
<blockquote>
<h3 id="gameobjectname을-유의하자">GameObject.name을 유의하자!</h3>
<p>해당 프로퍼티는 <strong>오브젝트의 이름을 붙이는데 사용되는 프로퍼티</strong>이다. 혹여나 무언가에 이름을 붙일 일이 생긴다면 해당 프로퍼티명을 피하고 짓자</p>
</blockquote>
<blockquote>
<h3 id="ui에서-사용하는-recttransform과-월드-좌표에서-사용하는-transform의-차이">UI에서 사용하는 RectTransform과 월드 좌표에서 사용하는 Transform의 차이</h3>
<p> UI에서 <strong>위치로는 anchoredPosition</strong>, <strong>크기로는 sizeDelta</strong>를 사용한다</p>
</blockquote>
<blockquote>
<h3 id="씬을-비교하는-방법">씬을 비교하는 방법</h3>
<p><strong>SceneManager.GetActiveScene().name</strong> == &quot;<em>해당 씬 네임</em>&quot; 해당 코드를 사용하면 현재 활성화된 씬이 해당 씬 네임과 동일한 지 비교할 수 있다.</p>
</blockquote>
<blockquote>
<h3 id="getcomponentinchildren를-사용할-경우">GetComponentInChildren&lt;&gt;()를 사용할 경우</h3>
<p>사실 자기 자신의 컴포넌트부터 확인한 다음에 없을 경우에 자식의 맨 윗순번부터 컴포넌트를 확인하는 과정을 거친다.</p>
</blockquote>
<blockquote>
<h3 id="datetimenow">DateTime.Now</h3>
<p>현재 시각을 알아내는 메서드이다. ToString()을 할 때 다양한 옵션이 존재한다.</p>
</blockquote>
<hr>
<hr>
<hr>
<h2 id="오늘-느낀점">오늘 느낀점</h2>
<blockquote>
<p>오늘 챌린지 과제를 진행하면서 확실하게 정리해두지 않은 내용들은 어떻게 해결할 지 감이 안 잡힌다. 일을 처리하는 순서는 보이지만, 이 일들을 어떻게 처리할 지 모르니 게임에서 필수적인 요소들을 배워둬야할 필요성을 느꼈다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 4주차 4일차 TIL - 캐릭터 방향 전환]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-4%EC%9D%BC%EC%B0%A8-TIL</guid>
            <pubDate>Fri, 10 May 2024 14:14:10 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>알고리즘 문제 풀기 (LeetCode 786 문제)</li>
<li>스파르타 강의 진도 나가기 완료! </li>
<li>개인 프로젝트 진행하기</li>
</ul>
<hr>
<p>일단 모르는 내용은 살짝 미뤄둬고 당장 해야할 프로젝트를 진행하면서 캐릭터가 바라보는 방향을 다르게 해보는 코드를 짜보려한다.</p>
<hr>
<h2 id="캐릭터-방향-전환하기">캐릭터 방향 전환하기</h2>
<blockquote>
<h3 id="목표">목표</h3>
<p>이번에 이 친구가 마우스를 따라 상하좌우를 바라보게 하고, 움직이는 모습도 그에 맞춰서 코드를 짜볼 생각이다.</p>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/a395df4b-5e6d-4f1f-b1ff-ec5025c40697/image.png" alt=""></li>
</ul>
<hr>
<h3 id="해당-에셋을-다운-받을-수-있는-곳itchio"><a href="https://cupnooble.itch.io/sprout-lands-asset-pack">해당 에셋을 다운 받을 수 있는 곳(itch.io)</a></h3>
<hr>
<hr>
<h3 id="스프라이트-시트를-가용-가능한-이미지로-바꾸는-방법">스프라이트 시트를 가용 가능한 이미지로 바꾸는 방법</h3>
<blockquote>
</blockquote>
<h4 id="스프라이트-시트">스프라이트 시트</h4>
<ul>
<li>해당 에셋 캐릭터 이미지를 보면 이와 같이 되어있을 것이다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/5ee8af1d-48c6-4cb9-8da1-aa2d7a0f2e74/image.png" alt=""></li>
<li>사용하기 전에 전처리 과정을 해보도록 하자!</li>
</ul>
<hr>
<hr>
<h4 id="import-settings">Import Settings</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/fb349c8b-42ef-4f78-bb0c-ff714d4d6d29/image.png" alt=""></li>
<li>해당 스프라이트 시트를 가지고 와서 누르면 Inspector에서 이와 같은 설정이 나올텐데
여기서 Sprite Mode를 Multiple로 바꾸고, Filter Mode를 Point로 해주자
_ 필터 모드를 바꾸지 않으면 이미지가 흐릿하게 보인다._</li>
</ul>
<hr>
<hr>
<h4 id="sprite-editor">Sprite Editor</h4>
<ul>
<li>그 다음에 Sprite Editor로 들어가면 이와 같은 창이 나오게 될텐데
<img src="https://velog.velcdn.com/images/b_h_b/post/aa5efdbf-bd17-4565-a35b-9d24121bc3a4/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li>Slice에서 아래와 같은 설정을 만진 후 이미지들이 격자 무늬 안에 잘 들어가는지 확인하고 Slice를 해주자</li>
<li><em>Type*</em> : Grid By Cell Sice &amp;&amp; <strong>Pixel</strong> : Size 16, 16
<img src="https://velog.velcdn.com/images/b_h_b/post/753ef4ca-8bb6-4b63-9e06-50c14e6cf74d/image.png" alt="">
<img src="https://velog.velcdn.com/images/b_h_b/post/259f623f-9c29-4caa-9aaf-d1a6392926d1/image.png" alt=""></li>
<li>Slice를 완료하면 이와 같이나온다. </li>
</ul>
<hr>
<ul>
<li>자르기를 완료한 후에는 오른쪽 아래창에 작업하기 편한 이름을 붙여주고, Position값을 맞춰가면서 캐릭터의 스프라이트가 어긋나지 않도록 설정해주자.
<img src="https://velog.velcdn.com/images/b_h_b/post/4e204fee-09ca-44ee-b2fd-7c373865c438/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li>상단바 오른쪽 부분의 Apply를 눌러주면 작업한 것이 Asset에 반영된다!
<img src="https://velog.velcdn.com/images/b_h_b/post/35bfd08b-434c-442c-8f9e-ca6b598af71d/image.png" alt="">
<img src="https://velog.velcdn.com/images/b_h_b/post/7d8421c6-df5a-4dec-bd7e-8092efa7584c/image.png" alt=""></li>
<li>작업이 완료된 스프라이트 시트의 모습</li>
</ul>
<hr>
<h4 id="-주의할-점">! 주의할 점</h4>
<ul>
<li>Apply는 작업의 Save와 같은 역할을 해주니까. Apply를 누르는 것을 잊지 말자</li>
<li>반드시 이미지 사용 이전에 (렌더러에 붙이기, Animation Clip 활용) 이 작업을 완료하자</li>
</ul>
<hr>
<hr>
<h3 id="마우스의-방향-구하기">마우스의 방향 구하기</h3>
<blockquote>
</blockquote>
<ol>
<li><h4 id="벡터-계산하기">벡터 계산하기</h4>
<blockquote>
</blockquote>
마우스의 위치를 월드 좌표로 변환 후 캐릭터에서부터 마우스 좌표까지의 벡터를 구한다.</li>
</ol>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/b4d1f569-522a-47eb-9086-986426e77bc8/image.png" alt=""><h5 id="주요-코드">주요 코드</h5>
<pre><code class="language-c">Vector2 newAim = value.Get&lt;Vector2&gt;();
Vector2 worldPos = camera.ScreenToWorldPoint(newAim);
newAim = (worldPos - (Vector2)transform.position).normalized;</code></pre>
</li>
</ul>
<hr>
<h4 id="왜-벡터의-값을-구할-때-왜-순서가-중요한-건가요">왜 벡터의 값을 구할 때 왜 순서가 중요한 건가요?</h4>
<p>설명해주자면 좌표 간의 <strong>거리</strong>만 구한다면 <strong>&#39;스칼라&#39;</strong>라고 하고, 
그 <strong>스칼라에서 방향을 추가</strong>한다면  <strong>&#39;벡터&#39;</strong>라고 한다. 
(그 좌표 간의 거리를 크기라고 부릅니다)
벡터는 단순 거리 뿐만 아닌 <strong>&#39;방향&#39;</strong>이라는 요소도 존재하기 때문에 
<strong>A에서 B의 벡터와 B에서 A의 벡터는 전혀 다른 것</strong>이다.
이해가 안된다면 아래의 설명을 보며 들어가보자</p>
<h4 id="사진을-예시로-들어-계산">사진을 예시로 들어 계산</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/16979fa8-1528-449a-ae1e-85a7ef96a936/image.png" alt=""></li>
<li>캐릭터 좌표는 (0, 0) 마우스 좌표는 (2, 2)라고 치자. </li>
<li>사진처럼 캐릭터 좌표에서 마우스 좌표까지의 벡터가 존재하는데 사실 단순하게 생각만 해봐도 캐릭터 좌표에 얼마를 더하면 마우스 좌표가 되는지를 생각해보면 되는 것이다. 그래서 마우스 좌표에서 캐릭터 좌표를 뺀다면 &quot;(2, 2) - (0, 0) = (2, 2)&quot;가 나오고 이를 캐릭터 좌표에 대입해주면 마우스 좌표가 나오는 것이다. 그러니까 정확히 말해주자면 벡터는 좌표 간의 거리를 구한다기 보다는 해당 좌표에서 &#39;이러한 값&#39;을 더한다면 이 좌표는 이쪽으로 이동한다라는 것인데, 그 &#39;이러한 값&#39;이라는 게 바로 <strong>벡터</strong>이다. (위치값을 말하는 게 아니라 <strong>위치를 이동시키는 값</strong>이다.)</li>
<li>그래서 방향을 인지 못하고 잘못 계산해서 (0, 0) - (2, 2)로 계산해버린다면 그 벡터는 캐릭터 좌표에서 마우스 좌표의 벡터가 아니라 그 반대 방향의 벡터가 되어버린다. 캐릭터 좌표에서 -(2, 2)를 더한다면 아래처럼 되어버린다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/a8c2ba2c-4d5f-433d-8cba-41505c9b3839/image.png" alt=""><h4 id="-추가적으로-그러한-벡터를-normalized해버리면-단위벡터가-된다-크기가-1인-벡터를-단위-벡터라-부름">+ 추가적으로 그러한 벡터를 normalized해버리면 단위벡터가 된다. (크기가 1인 벡터를 단위 벡터라 부름)</h4>
</li>
<li>** 주로 벡터의 방향만을 구하고자 할 때 normalized를 해주는 편이다.**<h3 id="이해에-도움된-동영상"><a href="https://www.youtube.com/watch?v=g3n1VxiXgrE">이해에 도움된 동영상</a></h3>
</li>
</ul>
<hr>
<hr>
<blockquote>
</blockquote>
<ol start="2">
<li><h4 id="아크-탄젠트로-각degree-구하기">아크 탄젠트로 각(Degree) 구하기</h4>
<blockquote>
</blockquote>
아직 컴퓨터를 위한 각도를 구한 것은 아니니 아크탄젠트를 활용해 각도를 구해주자<h5 id="주요-코드-1">주요 코드</h5>
<pre><code class="language-c">float rotZ = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;</code></pre>
아크탄젠트는 탄젠트의 역함수로 높이(y)/밑변(x)을 통해서 얻은 탄젠트 값을 각도로 산출해내는 역삼각함수이다.
(직접 계산해볼 생각은 하지마라; 탄젠트 1값으로 산출되는 45도 말고는 다른 각도들을 구하는 건 수포자에겐 힘든 계산이다;)<blockquote>
</blockquote>
<ul>
<li>Mathf.Atan2는 라디안을 반환하므로 Mathf.Rad2Deg를 통해 각도로 환산해줘야한다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/9a2b1ac7-d345-45c8-8f24-36b964577582/image.png" alt=""></li>
</ul>
</li>
</ol>
<hr>
<hr>
<h4 id="3-마우스를-직접-움직여서-각도의-위치-구하기">3. 마우스를 직접 움직여서 각도의 위치 구하기</h4>
<p>그럼 이제 직접 마우스를 움직이면서 어디에 위치하냐에 따라서 각도가 반환되는지 알아보자!</p>
<h4 id="구분시키고-싶은-범위">구분시키고 싶은 범위</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/e4b9c40d-574e-41d9-8725-5dff7d24b51f/image.png" alt=""></li>
<li>위 사진과 같이 대각선으로 구분지어 4가지의 범위로 구분하여 지정하고자 한다.</li>
</ul>
<hr>
<h4 id="구하는-과정">구하는 과정</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/2644c655-10f7-4fe2-89a3-529ba3e25ccf/image.png" alt=""></li>
<li>테스트의 원활함을 위해 가로 세로 표시선 및 대각선으로 선을 긋고 UI Text에서 각의 값을 나오게 해보았다. 삼각형은 마우스 역할을 해주는 친구이다.</li>
</ul>
<hr>
<ul>
<li>우쪽</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/92bbd2f5-f688-4187-adf7-2202cf4314c4/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li>우측 상단</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/db4895be-d442-488d-8aa6-e52a430a23de/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li>좌측</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/34df579c-057a-448b-9170-50398e3b95d6/image.png" alt=""></li>
</ul>
<hr>
<ul>
<li>하단</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/81d5d977-6369-4f4c-8890-efbd6c9b5d36/image.png" alt=""></li>
<li>테스트 GIF</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/6c996582-30c2-4fec-a7be-eec1ac15e0c4/image.gif" alt=""></li>
</ul>
<hr>
<p>위 사진을 보면 알게되는 사실이 캐릭터가 좌표평면의 0, 0에 해당하고 마우스의 좌표까지의 벡터로 역삼각함수를 산출해내는 것을 알 수 있다.</p>
<blockquote>
</blockquote>
<hr>
<hr>
<h4 id="위-결과로-알아내게-된-마우스의-위치에-따라서-나오는-각도값">위 결과로 알아내게 된 마우스의 위치에 따라서 나오는 각도값</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/5dc7a356-cf04-40c4-87dd-95b2dfaa1bec/image.png" alt=""></li>
</ul>
<hr>
<h4 id="-이제-위-값들을-이용해-조건들을-세우고-애니메이션을-전환시켜보자">! 이제 위 값들을 이용해 조건들을 세우고 애니메이션을 전환시켜보자!</h4>
<hr>
<hr>
<h3 id="조건에-따라서-애니메이션-전환해보기">조건에 따라서 애니메이션 전환해보기</h3>
<blockquote>
<p>조건문을 세우고 애니메이터를 만져보자</p>
</blockquote>
<h4 id="1-애니메이터-조건-설정">1. 애니메이터 조건 설정</h4>
<p> 매개변수를 int로 지정해준 다음, 아래와 같이 애니메이션 흐름을 짜고 조건을 자기의 번호에 해당하면 변환하게 해주자.</p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/3bbf48c4-367a-40c1-88b9-416b0641d436/image.png" alt=""> </li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/501b1751-5bb0-4c27-b479-9558e48f170c/image.png" alt=""></li>
<li>direction : 0 = 뒤쪽 ||| 1 = 앞쪽 ||| 2 = 왼쪽 ||| 3 = 오른쪽이다.</li>
</ul>
<hr>
<hr>
<h4 id="2-조준-스크립트에-애니메이션-조건문-만들기">2. 조준 스크립트에 애니메이션 조건문 만들기</h4>
<pre><code class="language-c">if (rotZ &lt; 135 &amp;&amp; rotZ &gt; 45)
{
    animator.SetInteger(&quot;direction&quot;, 0);
}
else if (rotZ &lt; -45 &amp;&amp; rotZ &gt; -135)
{
    animator.SetInteger(&quot;direction&quot;, 1);
}
else if (Mathf.Abs(rotZ) &gt; 135)
{
    animator.SetInteger(&quot;direction&quot;, 2);
}
else if (rotZ &lt; 45 &amp;&amp; rotZ &gt; -45)
{
    animator.SetInteger(&quot;direction&quot;, 3);
}</code></pre>
<hr>
<hr>
<h4 id="결과">결과</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/ccbe8eef-74ff-43a4-8af6-f1cea1ba85e5/image.gif" alt=""></li>
<li>의도된 대로 잘 돌아간다.</li>
</ul>
<hr>
<hr>
<h3 id="뛰는-모션도-넣어보자">뛰는 모션도 넣어보자</h3>
<blockquote>
<p>방향에 따라서 뛰는 애니메이션도 달리해보자</p>
</blockquote>
<h4 id="1-애니메이터-조건-설정-1">1. 애니메이터 조건 설정</h4>
<ul>
<li>달리는 애니메이션은 보는 방향 뿐만 아니라 추가적인 조건이 존재함으로 그 점을 잘 신경써줘야한다.
<img src="https://velog.velcdn.com/images/b_h_b/post/3fe34ffe-a639-4ead-a476-a69b5d4ae98a/image.png" alt=""></li>
<li>각각의 바라보는 방향에다가 일치하는 달리기들을 붙여준다.
<img src="https://velog.velcdn.com/images/b_h_b/post/5141ef2c-9a4a-4f68-ab74-be4a1f6ba9b0/image.png" alt=""></li>
<li>달릴 때는 달리는 애니메이션을 설정해주고
<img src="https://velog.velcdn.com/images/b_h_b/post/cad8983a-362a-4665-8c94-26e9b0ea3e13/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/37117add-8397-429e-94ed-c669cb960336/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/c078fc19-2936-4d6f-9550-8cb5b892e663/image.png" alt=""></li>
<li>달리기가 풀릴 때는 두 개의 Transitions를 붙여서 각각 조건을 바라보는 방향이 달라지거나, 움직이지 않을 때로 설정한다.</li>
</ul>
<hr>
<h4 id="-transition를-하나로-해서-조건을-2개-붙이면-어떻게-되나요">! Transition를 하나로 해서 조건을 2개 붙이면 어떻게 되나요?</h4>
<ul>
<li>Transitions 하나에 조건을 2개 붙이게 된다면 그것은 if 조건문에서 &amp;&amp;에 해당하는 조건이다. 만약에 조건을 합쳐서 붙인다면 바라보는 방향대로 움직이고 있다가 멈추면 계속해서 달리기 애니메이션이 실행되게 되는 것이다. 그래서 Transition을 2개 나눠서 각각의 조건을 붙이면 if 조건문의 ||에 해당하게 되므로 달리기를 멈추거나 방향을 바꾸면 달리기 애니메이션을 탈출하게 되는 것이다.</li>
</ul>
<hr>
<h4 id="transitions-하나에-조건-2개를-붙인다면">Transitions 하나에 조건 2개를 붙인다면;</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/b9b898d5-7f38-4387-8df1-9123af7393f0/image.gif" alt=""></li>
<li>움직임을 멈췄을 때 달리기 애니메이션을 끝내지 않고, 방향까지 바꿔야 비로소 애니메이션을 멈춘다;</li>
</ul>
<hr>
<hr>
<h4 id="코드-짜기">코드 짜기</h4>
<ul>
<li>새 스크립트를 짠 후에 InputSystem을 활용하여 Move 버튼을 누르고 있을 때 참을 할당하게 설정해주었다!<pre><code class="language-c">using UnityEngine.InputSystem;
&gt;
public class PlayerAnimation : MonoBehaviour
{
  private Animator animator;
&gt;
  void Awake()
  {
      animator = GetComponent&lt;Animator&gt;();
  }
&gt;
  private void OnMoveAnimation(InputValue value)    // 매우 중요한 부분!
  {
      animator.SetBool(&quot;Move&quot;, value.isPressed);
  }
}</code></pre>
<blockquote>
</blockquote>
<h4 id="결과-1">결과</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/7c2eb6d6-ec53-4305-b999-480b4205d98c/image.gif" alt=""></li>
<li>의도된 대로 잘 움직여진다.</li>
<li>굳이 부자연스러운 점 한가지가 있다면 마우스를 빙빙 돌릴 때 따로 처리를 해줘야할 애니메이션이 있다면 더 부드러워질 것이다.</li>
</ul>
<hr>
<hr>
<hr>
<h2 id="그-외에-알게된-점">그 외에 알게된 점</h2>
<blockquote>
<h3 id="vector2magnitude"><a href="https://docs.unity3d.com/ScriptReference/Vector2-magnitude.html">Vector2.magnitude</a></h3>
<p>벡터의 크기를 반환해준다. (x^2+y^2) 제곱근으로 계산해준다.
제곱근의 과정없이 더 빠르게 계산해주는 친구로는 <a href="https://docs.unity3d.com/ScriptReference/Vector2-sqrMagnitude.html"><strong>Vector2.sqrMagnitude</strong></a>가 있다. 
이 sqrMagnitude 같은 경우에는 두 벡터의 크기를 비교하는 경우에는 이 sqrMagnitude를 사용하는 것이 좋다.</p>
</blockquote>
<hr>
<hr>
<hr>
<h2 id="작성하면서-느낀점">작성하면서 느낀점</h2>
<blockquote>
<p>한번 보고 따라쳐본 코드를 내가 이 코드를 알았다. 내가 이 기능을 넣을 수 있다는 능력을 얻었다라고는 보기 힘든 것 같다. 분명 내가 본 내용임에도 불구하고 이거 어떻게 해야되지하면서 다시 강의 자료를 찾아보며 코드를 짜면서 이해과정을 거치게 되었다. 아무래도 개인 프로젝트를 진행하면서 내가 강의에서 봤던 코드들을 다시 한번 이해해보면서 내 것으로 만드는 과정을 잘 거쳐줘야 비로소 강의 내용들이 내 능력이 될 수 있음을 깨닫게 되었다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 4주차 3일차 TIL - ScriptableObject]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-ScriptableObject</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-3%EC%9D%BC%EC%B0%A8-TIL-ScriptableObject</guid>
            <pubDate>Thu, 09 May 2024 18:45:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>알고리즘 문제 풀기 (LeetCode 3075 문제)</li>
<li>스파르타 강의 진도 나가기 (~1-13)</li>
<li>ScriptAbleObject 파헤치기</li>
</ul>
<hr>
<p>오늘은 강의 진도를 나아가면서 여러가지를 알게 되었는데 그 중에 Unity의 주요 기능 중 하나인 ScriptableObject에 대해서 자세히 알아보려고 한다.</p>
<hr>
<h2 id="scriptableobject">ScriptableObject</h2>
<h3 id="간단-설명">간단 설명</h3>
<blockquote>
<p>클래스 인스턴스와는 별도로 대량의 데이터를 저장하는 데 사용할 수 있는 데이터 컨테이너입니다. 에디터 세션 동안 데이터를 저장 및 보관하고, 데이터를 프로젝트의 에셋으로 저장하여 런타임 시 사용합니다.</p>
</blockquote>
<hr>
<h3 id="간략한-사용-과정">간략한 사용 과정</h3>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/88fe692a-1339-4cce-8f1d-cc239a420b16/image.png" alt=""></p>
<blockquote>
<h4 id="1-scriptableobject를-상속받는-스크립트-작성">1. ScriptableObject를 상속받는 스크립트 작성</h4>
<h4 id="2-작성-후-추가된-createassetmenu에서-커스텀-에셋-생성">2. 작성 후 추가된 CreateAssetMenu에서 커스텀 에셋 생성</h4>
<h4 id="3-생성된-scriptableobject의-field-데이터들을-조정">3. 생성된 ScriptableObject의 field 데이터들을 조정</h4>
<h4 id="4-scriptableobject의-데이터-사용">4. ScriptableObject의 데이터 사용</h4>
</blockquote>
<hr>
<hr>
<h3 id="직접-만들어보자-사용한-코드---unity-메뉴얼">직접 만들어보자 (사용한 코드 - <a href="https://docs.unity3d.com/kr/2022.3/Manual/class-ScriptableObject.html">Unity 메뉴얼</a>)</h3>
<blockquote>
<h3 id="1-scriptableobject를-상속받는-스크립트-작성-1">1. ScriptableObject를 상속받는 스크립트 작성</h3>
</blockquote>
<ul>
<li><h4 id="개체-생성에-관한-정보를-담는-so-스크립트">개체 생성에 관한 정보를 담는 SO 스크립트</h4>
<pre><code class="language-c">using UnityEngine;
&gt;
[CreateAssetMenu(fileName = &quot;Data&quot;, menuName = &quot;ScriptableObjects/SpawnManagerScriptableObject&quot;, order = 1)]
public class SpawnManagerScriptableObject : ScriptableObject
{
  // 생성된 애들 이름
  public string prefabName;
&gt;
  // 얼마의 프리팹을 만들까? 
  public int numberOfPrefabsToCreate;
  // 생성 지점 리스트
  public Vector3[] spawnPoints;
}</code></pre>
그럼 위에 있는 코드들을 하나씩 하나씩 설명해보겠다.<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h3 id="createassetmenu"><a href="https://docs.unity3d.com/kr/2022.3/ScriptReference/CreateAssetMenuAttribute.html">CreateAssetMenu</a></h3>
<h4 id="설명">설명</h4>
<p>ScripableObject를 상속 받은 스크립트를 Assets/Create 메뉴에 표시하여 Asset에 쉽게 생성하고 저장하기 위해 사용되는 속성(Attribute)이다.</p>
<blockquote>
</blockquote>
<hr>
<ul>
<li><strong>fileName</strong><ul>
<li>생성될 시 기본적으로 부여되는 파일 이름</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/f1647fad-abcc-43dd-a63b-74c256ba4059/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><strong>menuName</strong><ul>
<li>Create 하위 메뉴바에서 보여지게 될 이름을 정한다 &#39;/&#39;처리를 할 때마다 새로운 하위 메뉴로 이어간다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/31e8ec2f-e66e-4bc5-957b-9e389cda96d4/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><strong>order</strong><ul>
<li>Create 메뉴바에서 보여지게될 우선순위를 정해주는 변수이다. </li>
<li>order = 1<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/2e101893-2fba-450f-b24c-3db176de8bc7/image.png" alt=""></li>
<li>맨 상단에 위치</li>
</ul>
</li>
<li>order = int.MaxValue<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/6f6c8bad-3fe5-4f86-a726-3a410136e6cf/image.png" alt=""></li>
<li>맨 하단에 위치</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<hr>
<blockquote>
</blockquote>
<h3 id="scriptableobject-1">ScriptableObject</h3>
<h4 id="설명-1">설명</h4>
<p>Unity의 주요 기능 중 하나로 게임에서 주로 재사용 가능한 데이터 또는 설정을 저장하는데 사용되는 클래스입니다. 
기본 유니티 프로젝트에서 파생되나, MonoBehaviour과 달리 게임 오브젝트에 연결할 수 없으므로 CreateAssetMenu와 함께 사용하여 프로젝트 에셋으로 저장한 후에 활용해야합니다.
Unity 에디터와 통합되어 인스펙터 창에서 직접 수정하고 관리할 수 있습니다.</p>
<blockquote>
</blockquote>
<ul>
<li>사실 CreateAssetMenu를 제외한 딱히 사용되는 코드는 없고, 이걸 상속을 받게된다면 Create 메뉴바로 해당 오브젝트를 빠르게 생성하고 해당 스크립트 내 작성한 클래스의 필드들을 마음껏 커스터마이징할 수 있는 기능을 얻게 된다.<blockquote>
</blockquote>
<h4 id="-주의할-점">! 주의할 점</h4>
</li>
<li>필드들의 접근 제한자는 최소한 public 범위 정도는 되어야한다. 
(protected 이하로 내려가면 다른 오브젝트에서 해당 필드의 사용이 힘들다.)</li>
</ul>
<hr>
<blockquote>
</blockquote>
<h4 id="-headcategory">+ [Head(&quot;Category&quot;)]</h4>
<ul>
<li>인스펙터 창에서 데이터를 관리할 때 가독성을 높여주는 역할을 해준다. <ul>
<li>사용하는 방법<pre><code class="language-c">[Header(&quot;Attack Info&quot;)]
public float size;
public float delay;
public float power;
public float speed;
public LayerMask target;
&gt;
[Header(&quot;Knock Back Info&quot;)]
public bool isOnKnockback;
public float knockbackPower;
public float knockbackTime;</code></pre>
</li>
</ul>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/0b244f53-cead-4ac3-b511-58ab143c5084/image.png" alt=""></li>
<li>사진처럼 데이터들을 목록화하여 보기 좋게 관리해줄 수 있다.</li>
</ul>
<hr>
<hr>
<blockquote>
<h3 id="2-작성-후-추가된-createassetmenu에서-커스텀-에셋-생성-1">2. 작성 후 추가된 CreateAssetMenu에서 커스텀 에셋 생성</h3>
<p><strong>1. 파일을 이와 같이 만들어 정리를 쉽게 한다</strong></p>
</blockquote>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/25ca98b1-3636-44fe-a323-8840b3bc7fc7/image.png" alt=""></li>
<li>Scripts는 ScriptableObject 스크립트들이 모이는 곳, 뒤에 SO 붙은 게 ScriptableObject 에셋을 생성하는 곳이다.</li>
</ul>
<hr>
<p><strong>2. Scriptable Object 생성</strong></p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/82bc1ef3-1f6b-4f08-a37a-229f69b9b2bc/image.png" alt=""></li>
<li>우클릭 -&gt; Create -&gt; menuName 지정해준대로 찾아가주면 이와 같이 생성된다.</li>
</ul>
<hr>
<p><strong>3. Scriptable Object 이름 지정</strong></p>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/c8748582-4f41-42f3-a5d0-143b20ea8395/image.png" alt=""></li>
<li>F2를 눌러 이름을 지어주자</li>
</ul>
<hr>
<p><strong>사실 이 항목은 크게 중요한 거 없고 정리를 잘해주자</strong></p>
<hr>
<hr>
<blockquote>
<h3 id="3-생성된-scriptableobject의-field-데이터들을-조정-1">3. 생성된 ScriptableObject의 field 데이터들을 조정</h3>
</blockquote>
<ul>
<li>생성한 오브젝트의 Inspector 보면 밑의 사진처럼 나와있을 것이다.</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/2fdd3f9b-9f0b-4998-a783-fb98fd611db4/image.png" alt=""></li>
<li>원하는대로 필드를 마음껏 조종해보자<blockquote>
</blockquote>
</li>
</ul>
<hr>
<h4 id="-rangeminvalue-maxvalue">+ <a href="https://docs.unity3d.com/ScriptReference/RangeAttribute.html">Range(minValue, maxValue)</a></h4>
<ul>
<li><strong>값의 범위를 지정해 제한 시키는 속성이다.</strong><pre><code class="language-c">[Range(1, 10)] public int numberOfPrefabsToCreate;</code></pre>
위와 같이 접근 제한자보다 앞세워서 사용하는 속성(Attribute)인데 이처럼 코드를 짜주면,<blockquote>
</blockquote>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/85e75d2a-68fe-4b94-a453-fca2c748d472/image.png" alt=""></li>
<li>Inpsector에서 위와 같이 슬라이더가 생기는데</li>
</ul>
<hr>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/282501ac-4b59-44bd-8549-ee8feea5c1cf/image.png" alt=""></li>
<li>최소값과 최대값의 범위만큼 움직일 수 있다. </li>
</ul>
<hr>
<hr>
<blockquote>
<h3 id="4-scriptableobject의-데이터-사용-1">4. ScriptableObject의 데이터 사용</h3>
</blockquote>
<ul>
<li>SO를 참조하는 스크립트<pre><code class="language-c">public class Spawner : MonoBehaviour
{
  // 생성에 사용될 게임 오브젝트
  public GameObject entityToSpawn;
&gt;
  // 정의되었던 SO를 담아줄 변수이다.
  public SpawnManagerScriptableObject spawnManagerValues; // 생성 매니저 SO
&gt;
  // 개체를 생성할 때마다 이름에 같이 붙여줄 숫자로, 개체가 생성될 때마다 증가한다.
  int instanceNumber = 1;
&gt;
  void Start()
  {
      SpawnEntities();    // 스폰 메서드
  }
&gt;
  void SpawnEntities()
  {
      int currentSpawnPointIndex = 0; // 소환 작업에 쓰일 인덱스
&gt;
      for (int i = 0; i &lt; spawnManagerValues.numberOfPrefabsToCreate; i++) // SO에 있는 Prefabs 소환 횟수
      {
          // SO에서 정해진 스폰 지점에서 프리팹의 인스턴스 생성 
          GameObject currentEntity = Instantiate(entityToSpawn, spawnManagerValues.spawnPoints[currentSpawnPointIndex], Quaternion.identity);
&gt;
          // SO에 정의된 문자열에 고유한 숫자를 덧붙여줘서 생성된 개체의 이름을 지어준다.  
          currentEntity.name = spawnManagerValues.prefabName + instanceNumber;
&gt;
          // 다음 소환 지점의 인덱스로 이동한다.. 인덱스의 범위를 넘어간다면, 시작점으로 다시 되돌아간다.
          currentSpawnPointIndex = (currentSpawnPointIndex + 1) % spawnManagerValues.spawnPoints.Length;
&gt;
          instanceNumber++;
      }
  }
}</code></pre>
</li>
<li>SO를 담아줄 변수와 그 SO를 사용하는 메서드를 구현하고자 하는 기능대로 짜주면 된다.</li>
<li><em>! 스크립트 파일의 이름이 클래스와 동일해야한다.(Unity의 규칙)*</em></li>
</ul>
<hr>
<ul>
<li><h4 id="inspector-세팅">Inspector 세팅</h4>
</li>
<li>SpawnManagerSO<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/84b3a115-9676-43a0-87fd-cb2b0d942ea0/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<ul>
<li>Spawner Object<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/f9ebbb36-d260-446f-8966-4575052fb377/image.png" alt=""></li>
</ul>
</li>
</ul>
<hr>
<ul>
<li><h4 id="실행-결과">실행 결과</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/aecc4f6f-7128-4dbc-a20b-5ae6a8151221/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/ba5418df-8f8c-42b2-99b1-28b2ca558300/image.png" alt=""></li>
<li>위와 같이 지정한 스폰 횟수와 스폰 지점대로 생성이 잘 된다.</li>
</ul>
</li>
</ul>
<hr>
<hr>
<h3 id="장단점">장단점</h3>
<blockquote>
<h3 id="장점">장점</h3>
</blockquote>
<h4 id="1-클래스-필드의-다양한-값들을-쉽고-빠르게-만들-수-있다">1. 클래스 필드의 다양한 값들을 쉽고 빠르게 만들 수 있다.</h4>
<ul>
<li>Create 메뉴바에서 SO를 만든 후 Inspector에서 원하는 대로 건들여주면 된다.</li>
</ul>
<hr>
<h4 id="2-기존에-프리팹을-생성할-때마다-해당-데이터의-자체-사본이-생성되는데-이러한-방법을-사용하여-중복-데이터를-저장하는-대신-scriptableobject를-이용하여-데이터를-저장한-후-모든-프리팹의-참조를-통해-동일한-정보를-전달할-수-있다">2. 기존에 프리팹을 생성할 때마다 해당 데이터의 자체 사본이 생성되는데, 이러한 방법을 사용하여 중복 데이터를 저장하는 대신 ScriptableObject를 이용하여 데이터를 저장한 후 모든 프리팹의 참조를 통해 동일한 정보를 전달할 수 있다.</h4>
<ul>
<li>기존 방식<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/a889fb5a-d5a1-446e-a50a-d1b45684e735/image.png" alt=""></li>
<li>같은 값을 계속해서 만들고 있다.</li>
</ul>
</li>
<li>SO를 이용한 방식<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/747d9662-e760-468f-870f-7225badfbd0e/image.png" alt=""></li>
<li>SO를 참조하여 값을 가져오고 있다. (불필요한 중복 데이터로 인한 데이터 낭비 감소)</li>
<li>그래서 많은 양의 동일한 정보를 사용하는 오브젝트를 생성할 때 유리하다.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="3-생성된-스크립터블-오브젝트는-asset파일로-저장되기-때문에-다른-unity-프로젝트로-복사할-수-있다">3. 생성된 스크립터블 오브젝트는 asset파일로 저장되기 때문에 다른 Unity 프로젝트로 복사할 수 있다.</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/fe2b8cd2-8a49-456e-b835-66243932a7df/image.png" alt=""></li>
<li>1주차 팀프로젝트 과제로 옮겨보면 잘 옮겨진다.<ul>
<li>주의사항</li>
</ul>
</li>
<li>SO의 본체인 스크립트를 빼먹으면 안된다.</li>
<li>동일한 파일 경로를 유지해줘야한다. (Unity가 에셋의 <a href="https://velog.io/@bami/%ED%8C%8C%EC%9D%BC-%EA%B2%BD%EB%A1%9C-%EC%A0%88%EB%8C%80-%EA%B2%BD%EB%A1%9C%EC%99%80-%EC%83%81%EB%8C%80-%EA%B2%BD%EB%A1%9C">상대 경로</a>를 사용한다.)</li>
<li>버전 간 호환성 및 의존되는 스크립트나 플러그인을 잘 봐줘야한다.</li>
<li>파일을 이동할 때는 Unity의 Export Package 기능을 사용하면 다른 프로젝트로 쉽게 이동할 수 있다.</li>
</ul>
<hr>
<blockquote>
<h3 id="단점">단점</h3>
</blockquote>
<h4 id="1-런타임-중에서는-scriptableobject의-데이터를-수정할-수-없다-그래서-런타임-중에-매번-변동되어야하는-데이터는-사용을-지양하는-것이-좋다">1. 런타임 중에서는 ScriptableObject의 데이터를 수정할 수 없다. 그래서 런타임 중에 매번 변동되어야하는 데이터는 사용을 지양하는 것이 좋다.</h4>
<h3 id="주로-만들-수-있는-것">주로 만들 수 있는 것</h3>
<blockquote>
<ul>
<li><h4 id="변수-설계-데이터-컨테이너-열거형">변수 설계 (데이터 컨테이너, 열거형)</h4>
</li>
</ul>
</blockquote>
<ul>
<li><h4 id="이벤트-설계-델리게이트-및-옵서버-패턴">이벤트 설계 (델리게이트 및 옵서버 패턴)</h4>
</li>
<li><h4 id="런타임-세트">런타임 세트</h4>
<ul>
<li><a href="https://unity.com/kr/how-to/architect-game-code-scriptable-objects">참고 자료 1</a> </li>
<li><a href="https://blog.unity.com/kr/engine-platform/6-ways-scriptableobjects-can-benefit-your-team-and-your-code">참고 자료 2</a></li>
</ul>
</li>
</ul>
<h3 id="지금-내가-생각하는-사용-시점">지금 내가 생각하는 사용 시점</h3>
<blockquote>
</blockquote>
<h4 id="1-게임에서-재사용이-잦은-스텟이-있을-때-hp-attack-speed와-같은-데이터들">1. 게임에서 재사용이 잦은 스텟이 있을 때 (Hp, Attack, Speed와 같은 데이터들)</h4>
<h4 id="2-아이템-사전이나-몬스터-사전과-같이-오브젝트끼리-같은-변수를-사용하지만-서로-간의-값을-달리하려할-때-아이템-enum-몬스터-enum-등">2. 아이템 사전이나 몬스터 사전과 같이 오브젝트끼리 같은 변수를 사용하지만 서로 간의 값을 달리하려할 때 (아이템 enum, 몬스터 enum 등)</h4>
<h4 id="3-오브젝트의-생성을-담당하는-오브젝트가-존재할-때-생성-지점-생성-횟수-생성-조건-등등">3. 오브젝트의 생성을 담당하는 오브젝트가 존재할 때 (생성 지점, 생성 횟수, 생성 조건 등등)</h4>
<h3 id="참고자료">참고자료</h3>
<blockquote>
<ul>
<li><a href="https://docs.unity3d.com/kr/2022.3/Manual/class-ScriptableObject.html">Unity 문서</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://www.youtube.com/watch?v=7Qt4QNhM4nY">스크립터블 오브젝트 | 유니티 - 베르의 게임 개발 유튜브</a></li>
<li><a href="https://unity.com/kr/how-to/architect-game-code-scriptable-objects">스크립터블 오브젝트로 게임을 설계하는 세 가지 방법</a> </li>
<li><a href="https://blog.unity.com/kr/engine-platform/6-ways-scriptableobjects-can-benefit-your-team-and-your-code">협업과 코딩 측면에서 유용한 스크립터블 오브젝트 활용법 6가지</a></li>
</ul>
<hr>
<hr>
<hr>
<h2 id="추가로-알게된-점들">추가로 알게된 점들</h2>
<blockquote>
<h3 id="--attribute속성">- Attribute(속성)</h3>
<p>이 녀석들은 []&quot;대괄호&quot;를 주로 사용하고 접근 제한자보다 앞서 사용하는 특징을 가졌다.</p>
</blockquote>
<ul>
<li><a href="https://docs.unity3d.com/ScriptReference/HeaderAttribute.html">[Header()]</a></li>
<li><a href="https://docs.unity3d.com/ScriptReference/Serializable.html">[Serializable]</a>, <a href="https://docs.unity3d.com/ScriptReference/SerializeField.html">[SerializeField]</a></li>
<li><a href="https://docs.unity3d.com/kr/2022.3/ScriptReference/CreateAssetMenuAttribute.html">[CreateAssetMenu(fileName = string, menuName = string/string  , order = int32)]</a></li>
<li><a href="https://docs.unity3d.com/ScriptReference/RangeAttribute.html">[Range(,)]</a></li>
</ul>
<hr>
<hr>
<hr>
<h2 id="작성하면서-느낀점">작성하면서 느낀점</h2>
<blockquote>
<p>위 내용을 작성하면서 알게된 게, 내가 처음에 정확한 자료가 별로 없는 줄 알고 Unity 매뉴얼과 봤던 유튜브 영상만으로 내용을 작성하다가 뭔가 부족하다 싶어서 더 조사해본 결과 스크립터블 오브젝트를 전문적으로 다룬 자료를 찾아내게 되었다. 아무래도 TIL을 작성하기 전에 자료 조사 측면에서 좀 더 신경을 쓰고 조사를 해야겠다는 생각이 들었다.
그리고 추후에 시간이 된다면 이벤트 설계와 런타임 세트를 활용한 스크립터블 오브젝트도 한번 활용해볼 생각이다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 4주차 2일차 TIL - 옵서버 패턴]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-TIL-%EC%98%B5%EC%84%9C%EB%B2%84-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-2%EC%9D%BC%EC%B0%A8-TIL-%EC%98%B5%EC%84%9C%EB%B2%84-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Wed, 08 May 2024 14:18:38 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>팀 노션 페이지 짜기</li>
<li>스파르타 코딩클럽 진도 나가기 (~1-6)</li>
<li>옵서버 패턴에 대해 분석하기 </li>
</ul>
<hr>
<p>오늘은 강의 내용에서 새로 알게된 디자인 패턴 옵저버 패턴에 대해서 알아보려고 합니다. 자료를 찾아보면서 여러가지 좋은 설명들을 보고 그걸 토대로 제가 해석한 결과를 적어볼려고 합니다.</p>
<hr>
<h2 id="옵서버-패턴">옵서버 패턴</h2>
<h3 id="설명">설명</h3>
<blockquote>
</blockquote>
<p>알림 메서드(event를 활용한)가 있는 특정 대상에게 옵저버를 붙여서 대상자의 정보나 상태가 변했을 때 대상자는 그 정보를 자신에게 연결된 모든 옵저버에게 신호를 보내고 그 신호를 받은 옵저버들은 전달 받은 정보를 토대로 자신의 기능을 작동시킨다.</p>
<hr>
<h3 id="개념적인-구조">개념적인 구조</h3>
<blockquote>
</blockquote>
<h4 id="옵서버-패턴의-인터페이스">옵서버 패턴의 인터페이스</h4>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/c9f33adf-63a3-4bcb-9325-44b3296722da/image.png" alt=""></p>
<pre><code class="language-c">// 옵서버의 인터페이스
 public interface IObserver
 {
     // 대상자로부터 업데이트를 받습니다.
     void Update(ISubject subject);
 }
 &gt;
 // 발신자, 대상자의 인터페이스
   public interface ISubject
  {
        // 인터페이스에 필드를 선언할 수는 없지만 이 인터페이스를 상속 받은 
      // 발신자 클래스들은 List&lt;IObserver&gt;의 필드를 가진다.
  &gt;
      // 대상자에게 옵저버를 붙입니다.
      void Attach(IObserver observer);
      // 대상자로부터 옵저버를 분리합니다.
      void Detach(IObserver observer);
      // 모든 옵저버에게 이벤트에 대해 알립니다.
      void Notify();
  }</code></pre>
<hr>
<h4 id="옵서버-추가">옵서버 추가</h4>
<blockquote>
</blockquote>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/924dd022-d83f-4349-9fe7-fabe33f1c6b3/image.png" alt=""></p>
<pre><code class="language-c">// 발신자(subject) class 
// 옵저버 관리 메서드
public void Attach(IObserver observer)
{
    Console.WriteLine(&quot;주체: 옵저버를 연결합니다.&quot;);
    this._observers.Add(observer);
}
&gt;
public void Detach(IObserver observer)
{
    this._observers.Remove(observer);
    Console.WriteLine(&quot;주제: 옵저버를 분리합니다.&quot;);
}
&gt;
 var subject = new Subject(); // 발신자(subject) 인스턴스화
 &gt;
 // 옵서버 인스턴스화 및 발신자 구독
 var observerA = new ConcreteObserverA();
 subject.Attach(observerA);
&gt; 
 var observerB = new ConcreteObserverB();
 subject.Attach(observerB);</code></pre>
<blockquote>
</blockquote>
<hr>
<h4 id="옵서버-리스트-내에-있는-모든-옵서버에게-알림">옵서버 리스트 내에 있는 모든 옵서버에게 알림</h4>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/994bb9fc-f7bc-4b21-a375-a002c6f6dccd/image.png" alt=""></p>
<pre><code class="language-c">// 발신자 (Subject)
// 특정 정보 또는 상태
public int State { get; set; } = -0;
&gt;
// 구독자들에게 업데이트를 날립니다.
public void Notify()
{
    Console.WriteLine(&quot;발신자: 옵저버들에게 알립니다...&quot;);
&gt;
    foreach (var observer in _observers)
    {
        observer.Update(this);
    }
}
&gt;
// 보통, 구독 로직은 오직 대상자가 할 수 있는 일의 일부분입니다.
// 대상자는 일반적으로 중요한 작업수행 로직을 가지고 있으며 
// 중요한 작업 수행이 벌어졌을 때 알림 메서드를 작동한다. (또는 그 이후에)
public void SomeBusinessLogic()
{
    Console.WriteLine(&quot;\n발신자: 중요한 일을 하고 있습니다.&quot;);
    this.State = new Random().Next(0, 10);
&gt;
    Thread.Sleep(15);
&gt;
    Console.WriteLine(&quot;발신자: 저의 정보가 이와 같이 변했습니다: &quot; + this.State);
    this.Notify(); // 알림 메서드 발동
}
&gt;
  // 구체적인 옵저버들은 자기가 구독한 대상자로부터 발신된 업데이트에 반응합니다.
  class ConcreteObserverA : IObserver
  {
      public void Update(ISubject subject)
      {
          if ((subject as Subject).State &lt; 3)  // 값에 따라서 출력하는 조건을 붙여놓았다
          {
              Console.WriteLine(&quot;구체적인관찰자A: 이벤트에 반응했습니다.&quot;);
          }
      }
  }
&gt;
  class ConcreteObserverB : IObserver
  {
      public void Update(ISubject subject)
      {
          if ((subject as Subject).State == 0 || (subject as Subject).State &gt;= 2)
          {
              Console.WriteLine(&quot;구체적인관찰자B: 이벤트에 반응했습니다.&quot;);
          }
      }
  }
&gt;
// Program.Main
   subject.SomeBusinessLogic(); // 알림 메서드가 들어간 메서드 발동
   subject.SomeBusinessLogic();</code></pre>
<h4 id="출력-결과">출력 결과</h4>
<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/0e417dd5-d4cf-4f67-8fc4-9e65028c7560/image.png" alt=""></li>
<li>출력을 보면 옵서버가 정보를 무작정 받아들이는 게 아니라 자신의 조건문에 따라서 출력을 결정한다. (물론 들어오는 신호를 받고 싶지 않다면 Detach 메서드를 이용해주면 된다.)<blockquote>
</blockquote>
<h4 id="옵서버-분리">옵서버 분리</h4>
<pre><code class="language-c">// Program.main
subject.Detach(observerB); // 옵저버를 제거하는 메서드
&gt;
subject.SomeBusinessLogic();</code></pre>
<h4 id="출력-결과-1">출력 결과</h4>
</li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/dbb96b73-11f7-44a0-b25d-ad46e00b37ce/image.png" alt=""></li>
<li>0 또는 2 이상의 정보를 받으면 작동하는 B옵저버의 Console.WriteLine()이 호출되지 않았다.</li>
</ul>
<hr>
<h3 id="장단점">장단점</h3>
<blockquote>
<h3 id="장점">장점</h3>
</blockquote>
<ul>
<li>비동기적으로 상태 변경을 감지할 수 있다. (주기적인 조회를 통한 데이터 낭비 최소화)</li>
<li>런타임 시점에서 원하는 기능을 자유롭게 연결 해제할 수 있다.</li>
<li>발신자의 코드를 변경하지 않고도 새 클래스들을 옵저버 리스트를 통해서 기능을 추가할 수 있다. (개방/폐쇄의 원칙)</li>
</ul>
<blockquote>
</blockquote>
<h3 id="단점">단점</h3>
<ul>
<li>연결된 옵서버들의 알림이 무작위로 받는다. (추가되는 방식으로 옵저버를 연결하면 정확히는 들어온 순서대로 호출되긴 한다.) [하지만 어렵다고 한다..]<ul>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/3a34723b-5a4b-4471-b855-cd8093bf453c/image.png" alt=""></li>
<li><img src="https://velog.velcdn.com/images/b_h_b/post/79693b90-6128-4459-8695-069be430f614/image.png" alt=""></li>
</ul>
</li>
<li>다수의 옵저버 객체를 등록 이후 해지하지 않는다면 불필요한 메모리 누수가 발생할 수도 있다.</li>
</ul>
<hr>
<h3 id="주-사용-용도">주 사용 용도</h3>
<blockquote>
</blockquote>
<ul>
<li>대상 객체의 상태를 감지할 필요가 있을 경우 (체력이나 몬스터 처치 횟수 등등)</li>
<li>하나의 정보를 여럿이서 받아야하는 상황일 때 (조작키로 받은 데이터를 -&gt; 플레이어의 이동, 플레이어의 애니메이션, 몬스터의 플레이어 추적에 사용해야할 때 등등)</li>
<li>런타임 중에 유동적으로 기능을 연결 분리해야할 때 (퀘스트, 특정 아이템의 기능)</li>
</ul>
<blockquote>
<h3 id="참고-자료">참고 자료</h3>
</blockquote>
<ul>
<li><a href="https://refactoring.guru/ko/design-patterns/observer/csharp/example#example-0">사용한 코드 및 참고한 개념</a> (리팩토링 구루)</li>
<li><a href="https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%98%B5%EC%A0%80%EB%B2%84Observer-%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">옵저버 패턴 - 완벽 마스터하기</a> (인파 티스토리)</li>
</ul>
<hr>
<h3 id="강의에서-본-코드-해석하기-6강-내용">강의에서 본 코드 해석하기 (6강 내용)</h3>
<p><img src="https://velog.velcdn.com/images/b_h_b/post/92a60054-8952-4d75-8fda-95917f8c5fb8/image.png" alt=""></p>
<ul>
<li>한 번에 코드를 다 치면 내용이 너무 많으니 UML과 같이 간략히 정리해보았다.</li>
<li>보라색은 입력 처리를 받는 특수 에셋, 노란색은 클래스 스크립트이다. 
(특수 에셋에게 OnMove, OnLook, OnFire의 이벤트 함수가 있다)</li>
<li>아직은 TopDownController가 하나의 클래스에만 정보를 전달하고 있지만, 다른 기능의 클래스가 플레이어의 입력 처리 정보가 필요할 경우에 TopDownController의 수정 없이 추가적으로 등록하여 정보를 받아올 수 있다. (확장성이 매우 좋다)</li>
</ul>
<hr>
<hr>
<hr>
<h2 id="작성하면서-느낀점">작성하면서 느낀점</h2>
<blockquote>
<p>유니티 수업을 들으면서 기초를 잘 다져놓지 않으면 이해 못했을 내용들이 수두룩하다고 생각한다. 그래서 TIL을 작성하고 난다면 주기적으로도 부족한 부분들을 확인하고 꾸준히 채워나아가야만 앞으로의 개발 실력을 늘려나갈 수 있는 길이라 생각한다. </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[내일배움캠프 4주차 1일차 TIL - 복습하기]]></title>
            <link>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-%EB%B3%B5%EC%8A%B5%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@b_h_b/%EB%82%B4%EC%9D%BC%EB%B0%B0%EC%9B%80%EC%BA%A0%ED%94%84-4%EC%A3%BC%EC%B0%A8-1%EC%9D%BC%EC%B0%A8-TIL-%EB%B3%B5%EC%8A%B5%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 07 May 2024 14:38:49 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h3 id="오늘-한-일">오늘 한 일</h3>
</blockquote>
<ul>
<li>발표 준비 및 프로젝트 발표 </li>
<li>게임 개발 특강 듣기</li>
</ul>
<hr>
<p>오늘은 발표로 인해 배운 게 없지만, 들어가기에 앞서 내가 배웠던 자료형들의 명령들을 정리해볼 생각이다.</p>
<h3 id="참고한-사이트"><a href="https://learn.microsoft.com/ko-kr/dotnet/csharp/">참고한 사이트</a></h3>
<hr>
<h2 id="리스트">리스트</h2>
<blockquote>
<h4 id="특징">특징</h4>
<p>동적으로 변할 수 있는 배열 (C#에서는 자세히 파고 들면 배열의 파생이다;)</p>
</blockquote>
<blockquote>
</blockquote>
<h3 id="생성자">생성자</h3>
<pre><code class="language-c">List&lt;T&gt;()        //    기본 생성자
List&lt;T&gt;(Int32)    //    초기 용량 설정 생성자</code></pre>
<blockquote>
</blockquote>
<h3 id="속성">속성</h3>
<pre><code class="language-c">Capacity        // 크기를 조정하지 않고 내부 데이터 구조가 보유할 수 있는 전체 요소 수를 가져오거나 설정
Count            // 요소 수 (배열의 길이와 비슷하다)
Item[Int32]     // 인덱싱</code></pre>
<blockquote>
</blockquote>
<h3 id="메서드">메서드</h3>
<blockquote>
</blockquote>
<h4 id="검사">검사</h4>
<pre><code class="language-c">GetType()
리스트의 타입 가져오기</code></pre>
<h4 id="추가-제거">추가, 제거</h4>
<pre><code class="language-c">Add(T)
리스트 끝 부분에 개체 추가
&gt;
Insert(Int32, T)
지정된 인덱스에 개체 추가
&gt;
Clear()
리스트 비우기
&gt;
Remove(T)
맨 처음 발견되는 특정 개체 제거
&gt;
RemoveAt(Int32)
지정된 인덱스에 있는 요소 제거
&gt;
RemoveRange(Int32, Int32)
지정된 범위의 요소 제거</code></pre>
<h4 id="탐색">탐색</h4>
<pre><code class="language-c">Contains(T)    
해당 리스트에 있는지 여부를 확인
&gt;
Equals(Object)
지정된 개체가 현재 개체와 같은지 확인</code></pre>
<h4 id="정렬">정렬</h4>
<pre><code class="language-c">Reverse()
리스트 전체의 순서를 반대로 바꾼다.
&gt;
Sort()
기본 비교자를 사용하여 전체 리스트의 요소를 정렬</code></pre>
<h4 id="복사-반환">복사, 반환</h4>
<pre><code class="language-c">CopyTo(Int32, T[], Int32, Int32)
배열의 지정된 인덱스에서 시작하여, 리스트에 있는 일련의 요소를 호환되는 1차원 배열에 복사
&gt;
CopyTo(T[])
배열의 처음부터 시작하여 전테 리스트를 호환되는 1차원 배열에 복사
&gt;
CopyTo(T[], Int32)
배열의 지정된 인덱스에서 시작하여 전체 리스트를 호환되는 1차원 배열에 복사
&gt;
Slice(Int32, Int32)
해당 범위에 있는 일련의 요소에 대한 단순 복사본 만들기
&gt;
ToArray()
새 배열에 복사
&gt;
ToString()
문자열로 반환</code></pre>
<h4 id="작업-수행">작업 수행</h4>
<pre><code class="language-c">ForEach(Action&lt;T&gt;)
리스트 내에 있는 모든 Action을 호출</code></pre>
<h2 id="딕셔너리">딕셔너리</h2>
<blockquote>
</blockquote>
<h4 id="특징-1">특징</h4>
<p>고유한 키와 값의 컬렉션을 나타낸다.</p>
<blockquote>
</blockquote>
<h3 id="생성자-1">생성자</h3>
<pre><code class="language-c">Dictionary&lt;TKey,TValue&gt;()    // 기본 생성자</code></pre>
<blockquote>
</blockquote>
<h3 id="속성-1">속성</h3>
<pre><code class="language-c">Count        // 딕셔너리에 포함된 키/값의 수
&gt;
Item[TKey]    // 지정된 키에 연결된 값을 가져오거나 설정
&gt;
Keys        // 딕셔너리의 키를 포함하는 컬렉션을 가져온다
&gt;
Values        // 딕셔너리의 값을 포함하는 컬렉션을 가져온다

&gt;
### 메서드
&gt;
#### 검사
```c
ContainsKey(TKey)    
지정한 키값이 포함되어있는지 확인
&gt;
ContainsValue(TValue)    
지정한 값이 있는지 확인
&gt;
Equals(Object)    
지정된 개체가 현재 개체와 같은지 확인
&gt;
GetType()    
현재 인스턴스의 타입을 가져온다.</code></pre>
<blockquote>
</blockquote>
<h4 id="추가-제거-1">추가, 제거</h4>
<pre><code class="language-c">Add(TKey, TValue)    
지정한 키와 값을 사전에 추가
&gt;
Clear()    
딕셔너리 비우기
&gt;
Remove(TKey)    
딕셔너리에서 지정한 키가 있는 값을 제거
&gt;
Remove(TKey, TValue)    
딕셔너리에에서 지정된 키를 갖는 값을 제거 후 value 매개 변수에 요소를 복사
&gt;
TryAdd(TKey, TValue)    
지정된 키와 값을 사전에 할당 시도 //TryParse와 비슷한 역할</code></pre>
<blockquote>
</blockquote>
<h4 id="복사-반환-1">복사, 반환</h4>
<pre><code class="language-c">ToString()    
현재 개체를 나타내는 문자열을 반환
&gt;
TryGetValue(TKey, TValue)    
지정한 키와 연결된 값을 가져오려고 시도</code></pre>
<h2 id="stack">Stack</h2>
<blockquote>
<p>특징 : LIFO 후입선출 (나중에 들어간 거 먼저 빠진다)</p>
</blockquote>
<blockquote>
</blockquote>
<h3 id="생성자-2">생성자</h3>
<pre><code class="language-c">Stack&lt;T&gt;()        // 기본 생성자</code></pre>
<blockquote>
</blockquote>
<h3 id="속성-2">속성</h3>
<pre><code class="language-c">Count            // 스택에 있는 요소의 갯수</code></pre>
<blockquote>
</blockquote>
<h3 id="메서드-1">메서드</h3>
<h4 id="검사-1">검사</h4>
<pre><code class="language-c">Contains(T)    
스택에 요소가 있는지 여부를 확인
&gt;
Equals(Object)    
지정된 개체가 현재 개체와 같은지 확인
&gt;
GetType()    
현재 인스턴스의 타입을 가져온다.</code></pre>
<blockquote>
</blockquote>
<h4 id="추가-제거-2">추가, 제거</h4>
<pre><code class="language-c">Clear()    
스택 비우기
&gt;
Pop()    
스택의 맨 위에서 개체를 제거하고 반환합니다.
&gt;
Push(T)    
개체를 스택의 맨 위에 삽입합니다.
&gt;
TryPop(T)    
스택의 맨 위에 개체가 있는지 여부를 나타내는 값을 반환하고, 개체가 있는 경우 result 매개 변수에 복사 후에 제거</code></pre>
<h4 id="복사-반환-2">복사, 반환</h4>
<pre><code class="language-c">CopyTo(T[], Int32)    
지정된 배열 인덱스에서 시작하는 기존 1차원 배열에 스택을 복사
&gt;
MemberwiseClone()    
현재 객체의 단순 복사본을 만든다
&gt;
Peek()    
스택의 맨 위에서 개체를 제거하지 않고 반환합니다.
&gt;
TryPeek(T)    
스택의 맨 위에 개체가 있는지 여부를 나타내는 값을 반환하고, 개체가 있는 경우 이를 result 매개 변수에 복사
&gt;
ToArray()    
스택을 새 배열에 복사합니다.
&gt;
ToString()    
현재 개체를 나타내는 문자열을 반환합니다.</code></pre>
<h2 id="queue">Queue</h2>
<blockquote>
</blockquote>
<h4 id="특징-2">특징</h4>
<p>FIFO 선입선출 (먼저 들어간 게 먼저 빠진다)</p>
<blockquote>
</blockquote>
<h3 id="생성자-3">생성자</h3>
<pre><code class="language-c">Queue&lt;T&gt;()        // 기본 생성자</code></pre>
<blockquote>
</blockquote>
<h3 id="속성-3">속성</h3>
<pre><code class="language-c">Count            // 큐에 포함된 요소 수</code></pre>
<blockquote>
</blockquote>
<h3 id="메서드-2">메서드</h3>
<blockquote>
</blockquote>
<h4 id="검사-2">검사</h4>
<pre><code class="language-c">Contains(T)    
큐에 해당 요소가 있는지 판별
&gt;
EnsureCapacity(Int32)    
이 큐의 용량이 지정된 용량 이상인지 확인하고, 현재 용량이 보다 지정돤 용량보다 작으면 지정된 용량 이상으로 증가
&gt;
Equals(Object)    
지정된 개체가 현재 개체와 같은지 확인
&gt;
GetType()    
현재 인스턴스의 타입을 가져온다
&gt;</code></pre>
<h4 id="추가-제거-3">추가, 제거</h4>
<pre><code class="language-c">Clear()    
큐 비우기
&gt;
Dequeue()    
큐의 시작 부분에서 개체를 제거하고 반환
&gt;
Enqueue(T)    
개체를 큐의 끝 부분에 추가
&gt;
TryDequeue(T)    
큐의 시작 부분에서 개체를 제거하고, 이를 result 매개 변수에 복사</code></pre>
<blockquote>
</blockquote>
<h4 id="복사-반환-3">복사, 반환</h4>
<pre><code class="language-c">CopyTo(T[], Int32)    
큐 요소를 지정한 배열 인덱스에서 시작하여 기존의 1차원 배열에 복사
&gt;
MemberwiseClone()    
현재 객체의 단순 복사본을 만든다
&gt;
Peek()    
큐의 시작 부분에서 개체를 제거하지 않고 반환
&gt;
TryPeek(T)    
큐의 시작 부분에 개체가 있는지 여부를 나타내는 값을 반환하고, 개체가 있는 경우 이를 result 매개 변수에 복사
&gt;
ToArray()    
큐 요소를 새 배열에 복사
&gt;
ToString()    
현재 개체를 나타내는 문자열을 반환</code></pre>
<h2 id="hashset">HashSet</h2>
<blockquote>
</blockquote>
<h4 id="특징-3">특징</h4>
<p>컬렉션 내에 중복된 값이 없다.</p>
<blockquote>
</blockquote>
<h3 id="생성자-4">생성자</h3>
<pre><code class="language-c">HashSet&lt;T&gt;()    //기본 생성자</code></pre>
<blockquote>
</blockquote>
<h3 id="속성-4">속성</h3>
<pre><code class="language-c">Count            // 집합에 포함된 요소 수</code></pre>
<blockquote>
</blockquote>
<h3 id="메서드-3">메서드</h3>
<h4 id="검사-3">검사</h4>
<pre><code class="language-c">Contains(T)    
해쉬셋에 지정된 요소가 포함되어 있는지 확인
&gt;
EnsureCapacity(Int32)    
이 해시 집합이 백업 스토리지의 추가 확장 없이 지정된 수의 요소를 보유할 수 있는지 확인
&gt;
Equals(Object)    
지정된 개체가 현재 개체와 같은지 확인
&gt;
GetType()    
현재 인스턴스의 타입을 가져온다</code></pre>
<blockquote>
</blockquote>
<h4 id="추가-제거-4">추가, 제거</h4>
<pre><code class="language-c">Add(T)    
지정된 요소를 집합에 추가
&gt;
Clear()    
해쉬셋 비우기
&gt;
Remove(T)    
해쉬셋에서 지정된 요소를 제거
&gt;
TrimExcess()    
해쉬셋의 용량을 실제로 포함된 요소 수로 설정하고 구현별로 다른 근방 값으로 올린다</code></pre>
<h4 id="복사-반환-4">복사, 반환</h4>
<pre><code class="language-c">CopyTo(T[])    
해쉬셋의 요소를 배열에 복사
&gt;
CopyTo(T[], Int32)    
지정된 배열 인덱스에서 시작하여 해쉬셋의 요소를 배열에 복사
&gt;
CopyTo(T[], Int32, Int32)    
지정된 배열 인덱스에서 시작하여 해쉬셋에서 지정된 수의 요소를 배열에 복사
&gt;
MemberwiseClone()    
현재 Object의 단순 복사본을 만든다
&gt;
ToString()    
현재 개체를 나타내는 문자열을 반환
&gt;
TryGetValue(T, T)    
집합에서 지정된 값을 검색하여 동일한 값을 찾은 경우 해당 값을 반환</code></pre>
<hr>
<hr>
<hr>
<h2 id="작성하면서-느낀점">작성하면서 느낀점</h2>
<blockquote>
<p>이후에도 부족한 부분이 무엇인지 확인하고 시간 계획을 정해서 배운 것들을 정리하고 채워나아가야할 것 같다.
앞으로 이 사전은 사용해본 것이나 필요하다고 생각되는 게 생길 때마다 주기적으로도 계속 업데이트 할 예정이다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>