[ Splash Screen 설정 방법 ]

[ File ] - [ Build Settings ] - [ Player Settings... ] 에서 [ Player ] - [ Splash Image ] 에서 변경 가능

 

[ 마우스 Cursor 설정 방법 ]

원하는 마우스 Image의 Inspector 창에서 Texture Type을 Cursor로 설정, Advaneced - Read/Write를 체크

[ File ] - [ Build Settings ] - [ Player Settings... ] 에서 [ Player ] - [ Default Cursor ] 에서 변경 가능

(상황에 따른 Cursor 변경 없이 1개의 Cursor만 사용할 경우)

 

 

 

 

 

[ 용어 정리 ]

Repository : 프로젝트를 저장하기 위한 저장소 

Local Repository : 내 PC에서 관리하는 Git 저장소

Remote Repository : 서버나 네트워크에 있는 저장소 (ex : GitHub)

Init : 소스코드 파일을 Git으로 관리하기위해 Git 저장소를 초기화

        ( Init 전까지는 일반 폴더에 해당 )

Clone : Remote Repository를 Local Repository로 복사하여 내 PC에 새로운 저장소 생성

            ( GitHub 상 오픈소스 혹은 이미 만들어진 프로젝트 개발에 참여시 )

Branch : Git Repository 생성시 기본적으로 main branch가 생성되며,

              Branch를 통해 여러 작업을 각자 독립적인 저장소에서 진행이 가능하다.

Commit : 프로젝트에서 수정한 사항 중 의미있는 변경 작업들을 Git Repository에 기록하는 동작

               ( Local Repository에는 반영 O, Remote Repository에는 아직 반영 X )

Push : Commit한 파일을 Remote Repository에 반영

Merge : 2개 이상의 Branch를 하나로 합치는 작업

Fetch : Local Repository에는 없지만 Remote Repository에는 반영된 데이터를 모두 가져옴

            ( Merge 되지 않으며, 변경 내용들을 확인만 하려고 할때 )

Pull : Local Repository에는 없지만 Remote Repository에는 반영된 데이터를 모두 가져와,

         자동으로 현재 작업중인 Local Branch와 Merge

         ( 즉, Fetch + Merge )

 

[ Branch란? ]

Git Repository 생성시 기본적으로 main branch가 생성되며 main branch는 언제든지 배포/출시가 가능한 코드들만 올라와 있어야 한다. 따라서 main branch를 대상으로 하는 Commit, Push, Pull은 Repository 생성 후 프로젝트 초기 세팅을 제외한 경우에는 발생하지 않도록 주의해야한다.

 

간단한 개인 프로젝트는 main branch 위에서만 작업해도 충분하다. 하지만 협업 개발 프로젝트에서 모든 개발이 main branch 위에서 진행될 경우 같은 파일을 동시 수정, 다수의 사용자가 commit 시 문제가 발생할 수 있다. 이를 해결하기 위한 것이 Branch이다.

 

main branch를 나둔채로 최신 버전의 main branch에서 새로운 branch를 따 해당 branch에서 작업한 뒤 모든 검증이 완료된 후 main branch로 합치는 방식으로 작업을 진행한다.

 

branch를 나누는 기준은 작업자, 기능 추가, 버그 수정 등 다양하다.

 

작업한 branch를 main branch와 합치는 것을 merge라고 하며, merge 작업을 요청하는 것을 pull request 라고 한다.

 

[ 프로그램 정리 ]

GitHub Desktop : GitHub의 Repository를 Desktop에 다운받아 관리하는 것을 도와주는 프로그램

 

[ GitHub Desktop 사용 방법 ]

# Push 방법 - 처음 프로젝트 생성시

1. GitHub Desktop에서 [ Current repository ] - [ Add ] - [ Clone Repository ] 클릭 후 Repository 선택

2. GitHub Desktop에서 Show in Explorer 클릭

3. Unity에서 새로운 프로젝트 생성

4. Unity에서 Assets 폴더 [ 우클릭 ] - [ Show in Explorer ] 클릭

5. 2번을 통한 Local Repository 창에 4번을 통한 프로젝트의 내용물들을 복붙

6. GitHub Desktop에서 변경사항들에 대한 Summary, Description 작성 후 Commit to master 클릭

7. Commit to master 완료 후 Push origin 클릭

 

# Push 방법 - 새로운 branch 생성시

1. GitHub Desktop에서 [ Current repository ] 에서 Repository 선택

2. GitHub Desktop에서 [ Current branch ] 에서 Filter 입력창에 새로운 Branch 이름 작성 후 Create new branch 클릭

   ( GitHub Webpage와 다른 개발자에게도 새로 만든 Branch를 보여주기 위해서는 Publish branch 클릭 )

3. GitHub Desktop에서 Show in Explorer 클릭

4. Unity에서 Assets 폴더 [ 우클릭 ] - [ Show in Explorer ] 클릭

5. 3번을 통한 Local Repository 창에 4번을 통한 프로젝트의 내용물들을 복붙

6. GitHub Desktop에서 변경사항들에 대한 Summary, Description 작성 후 Commit to master 클릭

7. Commit to master 완료 후 Push origin 클릭

8. 해당 branch에서 작업한 내용을 main branch와 합치기 위해 Create Pull Request 클릭

 

# Pull 방법

1. GitHub Desktop에서 Fetch origin 클릭

( Pull로 가져올 변경사항의 유무를 알 수 있다. )

2. Pull origin 클릭

( 저장소에서 변경사항을 다운 )

 

[ GitHub Desktop 참고 사항 ]

1. 파란색 줄 표시 선택 및 해제를 통해 Commit 여부를 선택할 수 있다.

2. changed files에서 [ 오른쪽 마우스 ] - [ Discard all changes ] 를 통해 자신의 변경사항을 지우고 파일을 마지막으로 Commit한 상태로 되돌릴 수 있다.

( 2번은 파일단위로 Discard하는 것이고 1번처럼 줄단위로도 Discard가 가능)

3. 충돌 발생시 Merge 도구를 통해 수정한다.

 

[ Unity 프로젝트를 GitHub로 관리시 주의사항 ]

1. Unity 버전을 통일한다.

( meta 파일 내용이 버전에 따라 다르기 때문 )

2. Asset 직렬화 방식을 Binary가 아닌 Text로 통일한다.

( [ Edit ] - [ Project Settings ] - [ Editor ] - [ Asset Serialization ] 의 Mode를 Force Text로 변경 )

3. meta 파일의 생성/파괴 패턴 주의 

( Unity는 meta 파일에 기록된 guid로 Scene에 포함된 Prefab, Prefab에 포함된 Model, Model이 사용하고 있는 Material, Material에 들어간 Texture등을 찾아내 서로 연결시켜준다. 따라서 meta 파일이 재생성되어 guid 값이 바뀌면 참조에 문제가 발생 )

( Editor가 아닌 탐색기에서 파일을 이동시키거나 이름을 변경한 경우, .meta 파일을 commit 하지 않고 push할 경우,

.meta 파일의 원본이 없는 경우에 meta 파일이 재생성 )

( 따라서 GitHub를 사용하지 않고 다른 개발자에게 파일을 보낼 경우 .meta 파일이 포함된 Unity Package로 파일을 전달 )

4. meta 파일의 갱신 주의

( 파일 Parameter가 변경된 경우 Save Project/Scene, Unity Editor 종료 시점 기준으로 meta 파일이 갱신 )

5. Git 서버에서 변경사항을 받아올때마다 meta 파일 갱신 주의

( [ Edit ] - [ Project Settings ] - [ Editor ] - [ Asset Serialization ] 에서 [ Version Control ] 과 [ Asset Serialization ] 을 통일하고 Unity Editor의 버전을 통일 )

( [ Version Control ] 은 Visible Meta Files를, [ Asset Serialization ] 은 Force Text를 권장 )

 

 

 

 

 

Title Scene

 

Game Scene

 

게임 동작 화면

 

< 개발 후 느낀 점 >
 
해당 게임을 통해
정말 많은 것을 배웠다
 
코루틴도 처음 사용해보고
스플래쉬 화면과 커서도
처음 변경해보고
프리팹 동적 생성과 파괴
리스트와 딕셔너리의 사용 등..
 
개발해보고 싶은 게임은 너무 많은데
에셋 제작이 너무 귀찮아서
대충 기능만 구현해봤다.
 
덱 랜덤 상점,
덱 구매시 처음 가진 덱일 경우
인벤토리 Slot 추가,
덱 구매시 가지고 있던 덱일 경우
인벤토리 Slot 업데이트,
인벤토리 덱 랜덤 배치,
Player 덱과 음식, 물이
인접할 경우 배고픔, 갈증 감소,
덱 조합이 가능한 덱들이 인접시
조합 후 조합에 사용된 덱의 개수 감소 등...
 
처음에는 막막했는데
생각했던 기능들이 잘 돌아가서
만족스럽다
 
Slot의 개수, 덱의 개수 등
데이터에 예민하다보니
증가, 감소를 깜빡하고 빼먹으면
계속 게임이 터졌는데
그럴땐 반드시 유니티 재실행 전에
프로젝트 경로의
[ Temp ] - [ __Backupscenes ] 에 있는
0.backup 파일의 확장자를 0.Unity로 변경해주면 된다.
 
아 그리고 ㅋㅋ 잘 구현했으면서
Random.Range(시작, 끝) 명령어에서
시작과 끝의 Type이 int일때
끝 부분이 범위에 포함 안 된다는걸
몰라서 몇시간을 고생했다 ㅋㅎ
 

 

 

 

 

 

[ 섹션 7. UI ]

 UI 기초

  - UI 추가시 자동으로 Canvas가 생성되는데 이는 도화지 역할을 한다.

  - UI는 Rect Transform Component를 통해 좌표를 설정한다.

  - UI는 원근법을 적용받지 않는다.

  - Shift를 누른채로 UI 크기를 조절할 경우 비율을 유지하며 크기를 조절할 수 있다.

좌 : Pivot / 우: Anchor

  -   Anchor는 UI에서 상당히 중요한 역할을 한다.

 

 Rect Transform

  - 디바이스의 종류에 따라 Screen의 크기는 제각각이다. 이때 Anchor 기능이 중요하게 작용한다.

  - Anchor는 Rect Transform을 Component로 가진 부모를 가져야 활성화가 된다.

Anchor의 개념

  -   부모와 Anchor 사이의 거리는 비율로, Anchor와 본인 사이의 거리는 고정 거리로 연산을 한다.

Anchor Presets

  -   Rect Transform의 Anchor Presets을 통해 쉽고 간단하게 Anchor 설정을 할 수 있다.

  -   Anchor Presets에서 Shift를 누른 상태로 Anchor 설정시 Pivot도 함께 이동한다.

  -   Anchor Presets에서 Alt를 누른 상태로 Anchor 설정시 본인의 위치도 함께 이동한다.

  -   Anchor Presets에서 Shift와 Alt를 누른 상태로 Anchor 설정시 Pivot과 본인의 위치도 함께 이동한다.

 

 Button Event

  - Canvas에 UI를 모두 추가한 뒤 최종적으로 해당 Canvas를 Prefab 형태로 저장하곤 한다.

  - 해당 Canvas내의 UI에 관한 Event는 Event 관리를 하는 담당하는 Script를 생성한 뒤 해당 Script를 Canvas의

    Component로 추가하고 UI의 On Click()의 객체로 Canvas를 추가, 실행하고자 하는 Event 함수를 선택한다.

    (Event 함수를 Public으로 선언해야 함수 선택 가능)

  - Canvas를 Prefab 형태로 저장할 경우 코드를 통해 필요할때마다 사용할 수 있다.

  - 여러개의 Canvas로 인해 겹치는 현상이 발생할 경우 이는 Canvas Component의 Sort Order을 통해 해결 가능하다.

     (팝업창 구현시 유용)

Button Event를 위한 UI_Button Script 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Button : MonoBehaviour
{
    [SerializeField]
    Text _text;

    int _score = 0;

    public void OnButtonClicked()
    {
        _score++;
        _text.text = $"점수 : {_score}";
    }
}​

 

UI Click시 캐릭터 이동은 제한되도록 InputManager 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class InputManager
{
    public Action KeyAction = null;
    public Action<Define.MouseEvent> MouseAction = null;

    bool _pressed = false;

    // 체크하는 부분이 유일해짐
    public void OnUpdate()
    {
        // using UnityEngine.EventSystems; 추가시 사용 가능
        if (EventSystem.current.IsPointerOverGameObject()) // UI가 Click된 상황일 경우 
            return;

        if (Input.anyKey && KeyAction != null)
            KeyAction.Invoke();

        if (MouseAction != null)
        {
            if (Input.GetMouseButton(0))
            {
                MouseAction.Invoke(Define.MouseEvent.Press); // Press Event를 발생시켜 알려준다.
                _pressed = true;
            }
            else
            {
                if (_pressed)
                    MouseAction.Invoke(Define.MouseEvent.Click); // Click Event를 발생시켜 알려준다.
                _pressed = false;
            }
        }
    }
}​

 

Prefab으로 저장된 UI를 코드를 통해 불러오기 위한 것
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    void Start()
    {
        Managers.Resource.Instantiate("UI_Button"); // 다음과 같이 UI Prefab을 불러올 수 있다.
    }
}​

 

UI 자동화 #1

  - 게임 규모가 커질수록 UI의 Event 연결을 Tool을 이용하여 On Click()을 통해 연결하는 것이 힘들어진다.

     (뿐만 아닌 [SerializeField]에 선언한 UI를 Tool을 이용하여 연결하는 것도 힘들어진다.)

UI 자동화를 위한 UI_Button 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Button : MonoBehaviour
{
    Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>(); // Dictionary 사용

    enum Buttons
    {
        PointButton
    }

    enum Texts
    {
        PointText,
        ScoreText
    }

    private void Start()
    {
        Bind<Button>(typeof(Buttons)); // 넘기고자 하는 enum은 Buttons이며, Button이라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<Text>(typeof(Texts)); // 넘기고자 하는 enum은 Texts이며, Text라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
    }

    // C#의 Reflection 기능을 통해 enum을 해당 함수의 인자로 넘겨줄 수 있다.
    // enum 값을 인자로 넘겨주면 enum 안의 이름에 해당하는 객체를 찾아 자동으로 연결해주는 역할을 하는 함수이다.
    void Bind<T>(Type type) where T : UnityEngine.Object // using System;을 추가해야 Type 사용이 가능하다.
    {
        // 해당 enum에 존재하는 모든 이름들을 names에 담는 것
        string[] names = Enum.GetNames(type); // String 배열을 반환 (C#에서 제공하는 기능)

        // Unity 모든 객체의 최상위 부모가 UnityEngine.Object
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects); // Dictionary에 추가

        for (int i = 0; i < names.Length; i++)
        {
            objects[i] = Util.FindChild<T>(gameObject, names[i], true); // enum에 존재하는 이름에 해당하는 객체를 찾아 objects에 저장
        }
    }

    int _score = 0;

    public void OnButtonClicked()
    {
        _score++;
    }
}​

 

Util 성격을 가진 함수들을 관리하는 Util Script 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Util
{
    // 최상위 부모를 받고, 이름을 입력하지 않을 경우 타입만 일치해도 리턴, 재귀적으로 찾을거냐? (즉, 자식의 자식도)
    public static T FindChild<T>(GameObject go, string name = null, bool recursive = false) where T : UnityEngine.Object // T가 UnityEngine.Object 인것만 찾을 것이다.
    {
        if (go == null)
            return null;

        if (recursive == false)
        {
            for(int i = 0; i < go.transform.childCount; i++)
            {
                Transform transform = go.transform.GetChild(i);
                if (string.IsNullOrEmpty(name) || transform.name == name)
                {
                    T component = transform.GetComponent<T>();
                    if (component != null)
                        return component;
                }
            }
        }
        else
        {
            foreach (T component in go.GetComponentsInChildren<T>())
            {
                if (string.IsNullOrEmpty(name) || component.name == name)
                    return component;
            }
        }

        return null;
    }
}​

 

+ 추가 검색 (https://engineer-mole.tistory.com/174)
 - Dictionary는 Index 대신 중복 불가능한 Key를 사용하고 Value와 함께 다룬다. 이처럼 Key와 Value 세트로 다루는 배열을

   "연관 배열"이라고 한다.

 - C#에서 연관 배열을 다루기 위한 Class가 바로 Dictionary Class이다. Dictionary Class는 Key를 통해 Value의 값을 얻을

   수 있다.

 - 요소를 추가하기 위해서는 Add Method를 사용한다.

 

+ 추가 검색 (https://velog.io/@yongseok1000/%EC%9C%A0%EB%8B%88%ED%8B%B0-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98)
 - C#은 Reflection 기능을 지원한다. 이는 객체(Instance)를 토대로 데이터타입의 메타적인 정보를 가져오는 기법이다.

 - Reflection은 조사, Instance 생성, 기존 개체에서 형식을 가져와 호출, 접근 기능을 제공한다.

 

+ 추가 검색 (https://www.csharpstudy.com/CSharp/CSharp-generics.aspx)
 - C#은 Generics 기능을 지원한다. 이는 데이터의 Type을 확정하지 않고 데이터 Type 자체를 Parameter로 받아들인다.

 - 이는 여러 데이터 형식에 동일한 Logic을 적용해야 할 때 유용하다.

 - Generic Type 선언시 where T : ~ 를 통해 Type Parameter에 제약 조건을 설정할 수 있다. 

 

UI 자동화 #2

  - Bind 함수는 enum에 존재하는 이름에 해당하는 객체를 찾아 저장한다. 이때 저장된 객체들 중 index를 인자로 받아

    해당하는 객체를 반환하는 Get 함수를 추가할 수 있다.

  - Bind 함수처럼 Component를 찾는 것이 아닌 Object 자체를 Mapping 하는 경우가 있을 수 있는데, 이에 GameObject를

    위한 FindChild 함수를 추가할 수 있다.

Get 함수를 추가한 후 UI_Base Script를 생성하여 UI_Button과 분리(UI_Button 이 UI_Base를 상속받도록)
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Base : MonoBehaviour
{
    protected Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>(); // Dictionary 사용

    // C#의 Reflection 기능을 통해 enum을 해당 함수의 인자로 넘겨줄 수 있다.
    // enum 값을 인자로 넘겨주면 enum 안의 이름에 해당하는 객체를 찾아 자동으로 연결해주는 역할을 하는 함수이다.
    protected void Bind<T>(Type type) where T : UnityEngine.Object // using System;을 추가해야 Type 사용이 가능하다.
    {
        // 해당 enum에 존재하는 모든 이름들을 names에 담는 것
        string[] names = Enum.GetNames(type); // String 배열을 반환 (C#에서 제공하는 기능)

        // Unity 모든 객체의 최상위 부모가 UnityEngine.Object
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects); // Dictionary에 추가

        for (int i = 0; i < names.Length; i++)
        {
            if (typeof(T) == typeof(GameObject))
                objects[i] = Util.FindChild(gameObject, names[i], true);
            else
                objects[i] = Util.FindChild<T>(gameObject, names[i], true); // enum에 존재하는 이름에 해당하는 객체를 찾아 objects에 저장
        }
    }

    // index를 인자로 받아 해당하는 객체를 반환
    protected T Get<T>(int idx) where T : UnityEngine.Object
    {
        // Key 값을 이용하여 추출
        UnityEngine.Object[] objects = null;
        if (_objects.TryGetValue(typeof(T), out objects) == false) // 만약 추출에 실패한 경우
            return null;

        return objects[idx] as T; // 추출에 성공한 경우 T로 Casting하여 반환 (objects는 UnityEngine.Object 이므로)
    }

    // 자주 사용하는 것들은 굳이 Get을 사용하지 않도록
    protected Text GetText(int idx) { return Get<Text>(idx); }
    protected Button GetButton(int idx) { return Get<Button>(idx); }
    protected Image GetImage(int idx) { return Get<Image>(idx); }
}​

 

UI_Base를 상속 받는 UI_Button 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Button : UI_Base
{
    enum Buttons
    {
        PointButton
    }

    enum Texts
    {
        PointText,
        ScoreText
    }

    enum GameObjects
    {
        TestObject,
    }

    private void Start()
    {
        Bind<Button>(typeof(Buttons)); // 넘기고자 하는 enum은 Buttons이며, Button이라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<Text>(typeof(Texts)); // 넘기고자 하는 enum은 Texts이며, Text라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<GameObject>(typeof(GameObjects)); // Component를 찾는 것이 아닌 Object 자체를 Mapping하는 경우

        GetText((int)Texts.ScoreText).text = "Bind Test"; // 다음과 같이 사용
    }

    int _score = 0;

    public void OnButtonClicked()
    {
        _score++;
    }
}

 

GameObject를 위한 FindChild 함수를 추가한 Util 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Util
{
    // GameObject를 위한 FindChild 생성
    public static GameObject FindChild(GameObject go, string name = null, bool recursive = false)
    {
        Transform transform = FindChild<Transform>(go, name, recursive); // 모든 GameObject는 Transform Component를 가지므로
        if (transform == null)
            return null;
        return transform.gameObject;
    }

    // 최상위 부모를 받고, 이름을 입력하지 않을 경우 타입만 일치해도 리턴, 재귀적으로 찾을거냐? (즉, 자식의 자식도)
    public static T FindChild<T>(GameObject go, string name = null, bool recursive = false) where T : UnityEngine.Object // T가 UnityEngine.Object 인것만 찾을 것이다.
    {
        if (go == null)
            return null;

        if (recursive == false)
        {
            for(int i = 0; i < go.transform.childCount; i++)
            {
                Transform transform = go.transform.GetChild(i);
                if (string.IsNullOrEmpty(name) || transform.name == name)
                {
                    T component = transform.GetComponent<T>();
                    if (component != null)
                        return component;
                }
            }
        }
        else
        {
            foreach (T component in go.GetComponentsInChildren<T>())
            {
                if (string.IsNullOrEmpty(name) || component.name == name)
                    return component;
            }
        }

        return null;
    }
}​

 

UI 자동화 #3

  - UI Event 연동을 위해서는 Event System을 이용하는 것이 중요하다.

UI Event를 관리하는 UI_EventHandler Script 생성
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class UI_EventHandler : MonoBehaviour, IBeginDragHandler, IDragHandler
{
    public Action<PointerEventData> OnBeginDragHandler = null;
    public Action<PointerEventData> OnDragHandler = null;

    public void OnBeginDrag(PointerEventData eventData)
    {
        if (OnBeginDragHandler != null)
            OnBeginDragHandler.Invoke(eventData);
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (OnDragHandler != null)
            OnDragHandler.Invoke(eventData);
    }
}​

 

Event 추가를 위한 UI_Button 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class UI_Button : UI_Base
{
    enum Buttons
    {
        PointButton
    }

    enum Texts
    {
        PointText,
        ScoreText
    }

    enum GameObjects
    {
        TestObject,
    }

    enum Images
    {
        ItemIcon,
    }

    private void Start()
    {
        Bind<Button>(typeof(Buttons)); // 넘기고자 하는 enum은 Buttons이며, Button이라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<Text>(typeof(Texts)); // 넘기고자 하는 enum은 Texts이며, Text라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<GameObject>(typeof(GameObjects)); // Component를 찾는 것이 아닌 Object 자체를 Mapping하는 경우
        Bind<Image>(typeof(Images));

        GetText((int)Texts.ScoreText).text = "Bind Test"; // 다음과 같이 사용

        // 이벤트 추가
        GameObject go = GetImage((int)Images.ItemIcon).gameObject; // ItemIcon을 찾아 Bind한 것의 Image Component를 뽑아온 뒤, 해당 객체 자체를 가져온 것 (GameObject)
        UI_EventHandler evt = go.GetComponent<UI_EventHandler>(); // 해당 객체의 UI_EventHandler Component를 뽑아온 것
        evt.OnDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });
    }

    int _score = 0;

    public void OnButtonClicked()
    {
        _score++;
    }
}

 

+ 추가 검색 

 - Delegate, 무명 Method, 람다식의 개념을 아래 유튜브 영상을 통해 쉽게 이해할 수 있었다.

( https://www.youtube.com/watch?v=6FomZi4QiRY&ab_channel=%EC%BC%80%EC%9D%B4%EB%94%94  )

 - Delegate는 Method Parameter와 Return Type에 대한 정의 후, 동일한 Parameter와 Return Type을 가진 Method를 서로

   호환해서 불러 사용할 수 있도록 만들어준다.

 - 무명 Method는 어떤 Method가 일회용으로 단순한 문장들로 구성된 경우 별도의 Method로 정의하지 않고 사용할 수 있

   도록 만들어준다. 이는 delegate 키워드와 함께 선언하며 이름은 지정하지 않는다. (Delegate를 통해서만 호출 가능)
 - 람다식은 무명 함수를 만들기 위해 사용하며 간결하고 직관적인 형태로 함수를 정의하는 방식이다. 이는 C# 컴파일러가

   형식 유추 기능을 제공하므로 Parameter의 Type을 굳이 지정하지 않아도 된다.

   (즉, 람다식을 통해 무명 Method보다 더 짧은 코드로 무명 Method를 만들 수 있다.)

Delegate, 무명 Method, 람다식 예제
int a = 5;
int b = 5;

int sum;

void Add() {
	sum = A + b;
}

void Back() {
	sum = 0;
}

delegate void MyDelegate();
MyDelegate() myDelegate;

void Start() {
    myDelegate = Add;
    myDelegate += delegate() { print(sum); }; // 무명 메소드
    myDelegate += () => print(sum); // 람다식
    myDelegate += Back;
    
    myDelegate();
}

 

UI 자동화 #4

  - UI Event 추가 코드를 정리

OnBeginDrag 삭제 후 OnPointerClick을 추가한 UI_EventHandler 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class UI_EventHandler : MonoBehaviour, IPointerClickHandler, IDragHandler
{
    public Action<PointerEventData> OnClickHandler = null;
    public Action<PointerEventData> OnDragHandler = null;

    public void OnPointerClick(PointerEventData eventData)
    {
        if (OnClickHandler != null)
            OnClickHandler.Invoke(eventData);
    }

    public void OnDrag(PointerEventData eventData)
    {
        if (OnDragHandler != null)
            OnDragHandler.Invoke(eventData);
    }
}​

 

UIEvent enum을 추가한 Define 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Define
{
    public enum UIEvent
    {
        Click,
        Drag,
    }

    public enum MouseEvent
    {
        Press,
        Click,
    }

    public enum CameraMode
    {
        QuaterView, 
    }
}​

 

GetOrAddComponent 함수를 추가한 Util 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Util
{
    public static T GetOrAddComponent<T>(GameObject go) where T : UnityEngine.Component
    {
        T component = go.GetComponent<T>();
        if (component == null)
            component = go.AddComponent<T>();
        return component;
    }

    // GameObject를 위한 FindChild 생성
    public static GameObject FindChild(GameObject go, string name = null, bool recursive = false)
    {
        Transform transform = FindChild<Transform>(go, name, recursive); // 모든 GameObject는 Transform Component를 가지므로
        if (transform == null)
            return null;
        return transform.gameObject;
    }

    // 최상위 부모를 받고, 이름을 입력하지 않을 경우 타입만 일치해도 리턴, 재귀적으로 찾을거냐? (즉, 자식의 자식도)
    public static T FindChild<T>(GameObject go, string name = null, bool recursive = false) where T : UnityEngine.Object // T가 UnityEngine.Object 인것만 찾을 것이다.
    {
        if (go == null)
            return null;

        if (recursive == false)
        {
            for(int i = 0; i < go.transform.childCount; i++)
            {
                Transform transform = go.transform.GetChild(i);
                if (string.IsNullOrEmpty(name) || transform.name == name)
                {
                    T component = transform.GetComponent<T>();
                    if (component != null)
                        return component;
                }
            }
        }
        else
        {
            foreach (T component in go.GetComponentsInChildren<T>())
            {
                if (string.IsNullOrEmpty(name) || component.name == name)
                    return component;
            }
        }

        return null;
    }
}​

 

AddUIEvent 함수를 추가한 UI_Base 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class UI_Base : MonoBehaviour
{
    protected Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>(); // Dictionary 사용

    // C#의 Reflection 기능을 통해 enum을 해당 함수의 인자로 넘겨줄 수 있다.
    // enum 값을 인자로 넘겨주면 enum 안의 이름에 해당하는 객체를 찾아 자동으로 연결해주는 역할을 하는 함수이다.
    protected void Bind<T>(Type type) where T : UnityEngine.Object // using System;을 추가해야 Type 사용이 가능하다.
    {
        // 해당 enum에 존재하는 모든 이름들을 names에 담는 것
        string[] names = Enum.GetNames(type); // String 배열을 반환 (C#에서 제공하는 기능)

        // Unity 모든 객체의 최상위 부모가 UnityEngine.Object
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects); // Dictionary에 추가

        for (int i = 0; i < names.Length; i++)
        {
            if (typeof(T) == typeof(GameObject))
                objects[i] = Util.FindChild(gameObject, names[i], true);
            else
                objects[i] = Util.FindChild<T>(gameObject, names[i], true); // enum에 존재하는 이름에 해당하는 객체를 찾아 objects에 저장
        }
    }

    // index를 인자로 받아 해당하는 객체를 반환
    protected T Get<T>(int idx) where T : UnityEngine.Object
    {
        // Key 값을 이용하여 추출
        UnityEngine.Object[] objects = null;
        if (_objects.TryGetValue(typeof(T), out objects) == false) // 만약 추출에 실패한 경우
            return null;

        return objects[idx] as T; // 추출에 성공한 경우 T로 Casting하여 반환 (objects는 UnityEngine.Object 이므로)
    }

    // 자주 사용하는 것들은 굳이 Get을 사용하지 않도록
    protected Text GetText(int idx) { return Get<Text>(idx); }
    protected Button GetButton(int idx) { return Get<Button>(idx); }
    protected Image GetImage(int idx) { return Get<Image>(idx); }

    public static void AddUIEvent(GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {
        UI_EventHandler evt = Util.GetOrAddComponent<UI_EventHandler>(go);

        switch (type)
        {
            case Define.UIEvent.Click:
                evt.OnClickHandler -= action;
                evt.OnClickHandler += action;
                break;
            case Define.UIEvent.Drag:
                evt.OnDragHandler -= action;
                evt.OnDragHandler += action;
                break;
        }

        evt.OnDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });
    }
}​

 

위의 수정을 통해 간단하게 이벤트를 추가할 수 있도록 UI_Button Event 호출 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class UI_Button : UI_Base
{
    enum Buttons
    {
        PointButton
    }

    enum Texts
    {
        PointText,
        ScoreText
    }

    enum GameObjects
    {
        TestObject,
    }

    enum Images
    {
        ItemIcon,
    }

    private void Start()
    {
        Bind<Button>(typeof(Buttons)); // 넘기고자 하는 enum은 Buttons이며, Button이라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<Text>(typeof(Texts)); // 넘기고자 하는 enum은 Texts이며, Text라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<GameObject>(typeof(GameObjects)); // Component를 찾는 것이 아닌 Object 자체를 Mapping하는 경우
        Bind<Image>(typeof(Images));

        GetText((int)Texts.ScoreText).text = "Bind Test"; // 다음과 같이 사용

        // 이벤트 추가
        GameObject go = GetImage((int)Images.ItemIcon).gameObject; // ItemIcon을 찾아 Bind한 것의 Image Component를 뽑아온 뒤, 해당 객체 자체를 가져온 것 (GameObject)
        AddUIEvent(go, (PointerEventData data) => {go.transform.position = data.position; }, Define.UIEvent.Drag);
    }

    int _score = 0;

    public void OnButtonClicked()
    {
        _score++;
    }
}​

 

+ 추가 검색 (https://developer-talk.tistory.com/477)

 - 확장 Method는 Class 또는 Interface를 상속하거나 재구성하지 않고 Class에 Method를 추가할 수 있는 기능이다.

 - 확장 Method는 기존 Class에 존재하지 않으며 Static Class에서 정의해야 한다. 또한 확장 Method의 첫번째 Parameter

   를 Binding Parameter라고 하며, 해당 Parameter의 Type은 Binding 되어야 하는 Class이다. 이때 Class 이름 앞에 this

   Keyword가 존재해야한다.

 

UI Manager #1

  - UI는 PopUp UI와 Scene UI로 나눌 수 있다.

  - UI_Manager를 통해 PopUp UI Canvas Component의 Sort Order를 관리하고, 이를 Stack을 통해 관리한다.

UI 관리를 위한 UIManager 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager
{
    int _order = 0; // 최근에 사용한 order를 저장하기 위한 변수

    // Stack을 통해 Popup을 관리 (가장 마지막에 띄운 PopUp이 가장 먼저 삭제되어야 하므로)
    Stack<UI_Popup> _popupStack = new Stack<UI_Popup>(); // 사실상 GameObject는 빈 깡통과 같다. 해당 GameObject의 Component에 많은 정보들이 담겨있는것 (때문에 UI_PopUp Component 정보를 담는 것)

    public T ShowPopupUI<T>(string name = null) where T : UI_Popup // name은 Prefab의 이름과, T는 Script와 관련이 있다. (name은 필수가 아닌 옵션으로)
    {
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;

        GameObject go = Managers.Resource.Instantiate($"UI/Popup/{name}");
        T popup = Util.GetOrAddComponent<T>(go);
        _popupStack.Push(popup);

        return popup;
    }

    public void ClosePopupUI(UI_Popup popup)
    {
        if (_popupStack.Count == 0)
            return;

        if(_popupStack.Peek() != popup)
        {
            Debug.Log("Close Popup Failed!");
            return;
        }

        ClosePopupUI();
    }

    public void ClosePopupUI()
    {
        if (_popupStack.Count == 0)
            return;

        UI_Popup popup = _popupStack.Pop();
        Managers.Resource.Destroy(popup.gameObject);
        popup = null;

        _order--;
    }

    public void CloseAllPopupUI()
    {
        while (_popupStack.Count > 0)
            ClosePopupUI();
    }
}​

 

Managers에 ResourceManager 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Managers : MonoBehaviour
{
    static Managers s_instance; // 유일성 보장
    static Managers Instance { get { Init(); return s_instance; } } // 유일한 매니저를 가져온다

    InputManager _input = new InputManager();
    ResourceManager _resource = new ResourceManager();
    UIManager _ui = new UIManager();

    public static InputManager Input { get { return Instance._input; } }
    public static ResourceManager Resource { get { return Instance._resource; } }
    public static UIManager UI {  get { return Instance._ui; } }

    void Start()
    {
        Init();
    }

    void Update()
    {
        _input.OnUpdate();
    }

    static void Init()
    {
        // 초기화
        if (s_instance == null) {
            GameObject go = GameObject.Find("Managers");
            if (go == null) {
                go = new GameObject { name = "Managers" };
                go.AddComponent<Managers>();
            }
            DontDestroyOnLoad(go);
            s_instance = go.GetComponent<Managers>();
        }
    }
}​

 

+ 추가 검색 (https://jeong-f.tistory.com/3)

 - Stack에서의 Peek() 함수는 빼내고자 하는 데이터를 확인하기 위한 함수이다.

 

UI Manager #2

  - Unity의 Hierarchy에는 폴더가 없기 때문에 Create Empty를 통한 GameObject를 폴더로 사용한다.

Virtual Method인 Init() 함수를 추가한 UI_Popup 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Popup : UI_Base
{
    public virtual void Init()
    {
        Managers.UI.SetCanvas(gameObject, true);
    }

    public virtual void ClosePopupUI()
    {
        Managers.UI.ClosePopupUI(this);
    }
}

 

Virtual Method인 Init() 함수를 추가한 UI_Scene 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Scene : UI_Base
{
    public virtual void Init()
    {
        Managers.UI.SetCanvas(gameObject, false);
    }
}​

 

UI_Popup의 Init() 함수를 Override한 UI_Button 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class UI_Button : UI_Popup
{
    enum Buttons
    {
        PointButton
    }

    enum Texts
    {
        PointText,
        ScoreText
    }

    enum GameObjects
    {
        TestObject,
    }

    enum Images
    {
        ItemIcon,
    }

    private void Start()
    {
        Init();
    }

    public override void Init()
    {
        base.Init();

        Bind<Button>(typeof(Buttons)); // 넘기고자 하는 enum은 Buttons이며, Button이라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<Text>(typeof(Texts)); // 넘기고자 하는 enum은 Texts이며, Text라는 Component를 가진 객체를 찾아 Mapping 해달라는 것
        Bind<GameObject>(typeof(GameObjects)); // Component를 찾는 것이 아닌 Object 자체를 Mapping하는 경우
        Bind<Image>(typeof(Images));

        GetButton((int)Buttons.PointButton).gameObject.AddUIEvent(OnButtonClicked);

        // 이벤트 추가
        GameObject go = GetImage((int)Images.ItemIcon).gameObject; // ItemIcon을 찾아 Bind한 것의 Image Component를 뽑아온 뒤, 해당 객체 자체를 가져온 것 (GameObject)
        AddUIEvent(go, (PointerEventData data) => { go.transform.position = data.position; }, Define.UIEvent.Drag);
    }

    int _score = 0;

    public void OnButtonClicked(PointerEventData data)
    {
        _score++;


        GetText((int)Texts.ScoreText).text = $"점수 : { _score}";
    }
}​

 

Sort Order 관리를 위한 UIManager 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager
{
    int _order = 10; // 최근에 사용한 order를 저장하기 위한 변수

    // Stack을 통해 Popup을 관리 (가장 마지막에 띄운 PopUp이 가장 먼저 삭제되어야 하므로)
    Stack<UI_Popup> _popupStack = new Stack<UI_Popup>(); // 사실상 GameObject는 빈 깡통과 같다. 해당 GameObject의 Component에 많은 정보들이 담겨있는것 (때문에 UI_PopUp Component 정보를 담는 것)
    UI_Scene _sceneUI = null;

    public GameObject Root
    {
        get
        {
            GameObject root = GameObject.Find("@UI_Root");
            if (root == null)
                root = new GameObject { name = "@UI_Root" };
            return root;
        }
    }

    public void SetCanvas(GameObject go, bool sort = true) // 외부에서 Popup UI 생성시 자신의 Canvas에 존재하는 UI의 우선순위를 결정
    {
        Canvas canvas = Util.GetOrAddComponent<Canvas>(go);
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        canvas.overrideSorting = true; // Canvas가 중첩된 경우 부모와 독립하여 자신만의 sortingOrder를 갖는다는 것

        if (sort)
        {
            canvas.sortingOrder = _order;
            _order++;
        }
        else // sort 요청을 안 한 경우는 Popup UI와 관련이 없는 일반 UI라는 것
        {
            canvas.sortingOrder = 0;
        }    
    }

    public T ShowSceneUI<T>(string name = null) where T : UI_Scene // name은 Prefab의 이름과, T는 Script와 관련이 있다. (name은 필수가 아닌 옵션으로)
    {
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;

        GameObject go = Managers.Resource.Instantiate($"UI/Scene/{name}");
        T sceneUI = Util.GetOrAddComponent<T>(go);
        _sceneUI = sceneUI;

        go.transform.SetParent(Root.transform);

        return sceneUI;
    }

    public T ShowPopupUI<T>(string name = null) where T : UI_Popup // name은 Prefab의 이름과, T는 Script와 관련이 있다. (name은 필수가 아닌 옵션으로)
    {
        if (string.IsNullOrEmpty(name))
            name = typeof(T).Name;

        GameObject go = Managers.Resource.Instantiate($"UI/Popup/{name}");
        T popup = Util.GetOrAddComponent<T>(go);
        _popupStack.Push(popup);

        go.transform.SetParent(Root.transform);

        return popup;
    }

    public void ClosePopupUI(UI_Popup popup)
    {
        if (_popupStack.Count == 0)
            return;

        if(_popupStack.Peek() != popup)
        {
            Debug.Log("Close Popup Failed!");
            return;
        }

        ClosePopupUI();
    }

    public void ClosePopupUI()
    {
        if (_popupStack.Count == 0)
            return;

        UI_Popup popup = _popupStack.Pop();
        Managers.Resource.Destroy(popup.gameObject);
        popup = null;

        _order--;
    }

    public void CloseAllPopupUI()
    {
        while (_popupStack.Count > 0)
            ClosePopupUI();
    }
}​

 

Blocker 생성

  - 팝업창 뒤 UI들의 Event 발생 방지를 위해서는 일종의 Blocker를 생성해야 한다.

  - Blocker는 [ Hierarchy ] - [ UI ] - [ Image 또는 Panel ] 을 생성하고 Alpha 값을 0으로 설정한 뒤 크기를 크게 늘려준다. 

    이때 Blocker의 Raycast Target은 반드시 설정되어 있어야 한다.

   > Hierarchy 창에서 Blocker 아래에 위치한 UI들은 Blocker가 대신 Raycast를 받기 때문에 Event 발생이 방지된다.

 

+ 추가 검색 (https://developer-talk.tistory.com/469)

 - Virtual Method란 자식 클래스에서 부모 Method의 Parameter 및 Return Type 재정의를 허용하는 것이다.

 - C#에서 Method 재정의를 허용하기 위해 부모 클래스의 Method를 Virtual 키워드로 선언한다. 이때 Virtual 키워드로 선언

   된 Method를 Virtual Method라고 한다.

 - 자식 클래스는 Override 키워드를 통해 Property 또는 Method를 재정의한다.

 

인벤토리 실습 #1

  -  Layout Group Component를 통해 Inventory Item을 배치를 변경할 수 있다.

     (Grid Layout Group, Horizontal Layout Group, Vertical Layout Group)

 

 인벤토리 실습 #2

abstract Method인 Init() 함수를 추가한 UI_Base 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public abstract class UI_Base : MonoBehaviour
{
    protected Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>(); // Dictionary 사용

    public abstract void Init();

    // C#의 Reflection 기능을 통해 enum을 해당 함수의 인자로 넘겨줄 수 있다.
    // enum 값을 인자로 넘겨주면 enum 안의 이름에 해당하는 객체를 찾아 자동으로 연결해주는 역할을 하는 함수이다.
    protected void Bind<T>(Type type) where T : UnityEngine.Object // using System;을 추가해야 Type 사용이 가능하다.
    {
        // 해당 enum에 존재하는 모든 이름들을 names에 담는 것
        string[] names = Enum.GetNames(type); // String 배열을 반환 (C#에서 제공하는 기능)

        // Unity 모든 객체의 최상위 부모가 UnityEngine.Object
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects); // Dictionary에 추가

        for (int i = 0; i < names.Length; i++)
        {
            if (typeof(T) == typeof(GameObject))
                objects[i] = Util.FindChild(gameObject, names[i], true);
            else
                objects[i] = Util.FindChild<T>(gameObject, names[i], true); // enum에 존재하는 이름에 해당하는 객체를 찾아 objects에 저장
        }
    }

    // index를 인자로 받아 해당하는 객체를 반환
    protected T Get<T>(int idx) where T : UnityEngine.Object
    {
        // Key 값을 이용하여 추출
        UnityEngine.Object[] objects = null;
        if (_objects.TryGetValue(typeof(T), out objects) == false) // 만약 추출에 실패한 경우
            return null;

        return objects[idx] as T; // 추출에 성공한 경우 T로 Casting하여 반환 (objects는 UnityEngine.Object 이므로)
    }

    // 자주 사용하는 것들은 굳이 Get을 사용하지 않도록
    protected Text GetText(int idx) { return Get<Text>(idx); }
    protected Button GetButton(int idx) { return Get<Button>(idx); }
    protected Image GetImage(int idx) { return Get<Image>(idx); }

    public static void AddUIEvent(GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {
        UI_EventHandler evt = Util.GetOrAddComponent<UI_EventHandler>(go);

        switch (type)
        {
            case Define.UIEvent.Click:
                evt.OnClickHandler -= action;
                evt.OnClickHandler += action;
                break;
            case Define.UIEvent.Drag:
                evt.OnDragHandler -= action;
                evt.OnDragHandler += action;
                break;
        }

        evt.OnDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });
    }
}​

 

UI_Base를 상속받는 UI_Popup 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Popup : UI_Base
{
    public override void Init()
    {
        Managers.UI.SetCanvas(gameObject, true);
    }

    public virtual void ClosePopupUI()
    {
        Managers.UI.ClosePopupUI(this);
    }
}​

 

UI_Base를 상속받는 UI_Scene 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Scene : UI_Base
{
    public override void Init()
    {
        Managers.UI.SetCanvas(gameObject, false);
    }
}​

 

UI_Scene을 상속받는 UI_Inven 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UI_Inven : UI_Scene
{
    enum GameObjects
    {
        GridPanel
    }

    // Start is called before the first frame update
    void Start()
    {
        Init();
    }

    public override void Init()
    {
        base.Init();

        Bind<GameObject>(typeof(GameObjects));

        GameObject gridPanel = Get<GameObject>((int)GameObjects.GridPanel);
        foreach (Transform child in gridPanel.transform)
            Managers.Resource.Destroy(child.gameObject);

        // 실제 인벤토리 정보를 참고해서
        for (int i = 0; i < 8; i++)
        {
            GameObject item = Managers.Resource.Instantiate("UI/Scene/UI_Inven_Item");
            item.transform.SetParent(gridPanel.transform);

            UI_Inven_Item invenItem = Util.GetOrAddComponent<UI_Inven_Item>(item);
            invenItem.SetInfo($"집행검{i}번");
        }
    }
}​

 

UI_Base를 상속받는 UI_Inven_Item 생성
 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class UI_Inven_Item : UI_Base
{
    enum GameObjects
    {
        ItemIcon,
        ItemNameText,
    }

    string _name;

    void Start()
    {
        Init();
    }

    public override void Init()
    {
        Bind<GameObject>(typeof(GameObjects));
        Get<GameObject>((int)GameObjects.ItemNameText).GetComponent<Text>().text = _name;
        Get<GameObject>((int)GameObjects.ItemIcon).AddUIEvent((PointerEventData) => { Debug.Log($"아이템 클릭! {_name}"); });
    }

    public void SetInfo(string name)
    {
        _name = name;
    }
}​

 

+ 추가 검색 (https://eboong.tistory.com/63)

 - foreach문은 배열을 순회하면서 각각의 데이터 요소들에 순서대로 접근하며 배열의 끝에 도달할 경우 자동으로 반복이

   종료된다.

 

+ 추가 검색 (https://jshzizon.tistory.com/entry/C-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5-%EC%B6%94%EC%83%81-%ED%95%A8%EC%88%98-%EC%B6%94%EC%83%81-%ED%81%B4%EB%9E%98%EC%8A%A4-Abstract-Mehtod-Abstract-Class)

 - Abstract Method는 몸체가 없는 함수를 의미한다. Abstract Method 사용시 몸체 앞에 반드시 abstract 키워드를 붙여야 

  하며, 이러한 Abstract Method를 1개라도 포함하고 있는 Class 또한 반드시 abstract 키워드를 붙여 Abstract Class임을

  명시해야 한다.

 - Abstract으로 선언된 Abstract Method는 상속 받은 하위 Class에서 반드시 Override 키워드를 통해 오버라이딩 해준다. 



[ 섹션 8. Scene ]

 Scene Manager #1

Scene과 관련된 enum을 추가한 Define 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Define
{
    public enum Scene{
        Unknown,
        Login,
        Lobby,
        Game,
    }

    public enum UIEvent
    {
        Click,
        Drag,
    }

    public enum MouseEvent
    {
        Press,
        Click,
    }

    public enum CameraMode
    {
        QuaterView, 
    }
}​

 

BaseScene 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public abstract class BaseScene : MonoBehaviour
{
    public Define.Scene SceneType {get; protected set;} = Define.Scene.Unknown;

    void Awake() {
        Init();
    }

    protected virtual void Init() {
        // Event System Component를 들고 있는 GameObject를 찾는 것
        Object obj = GameObject.FindObjectOfType(typeof(EventSystem));
        if (obj == null)
            Managers.Resource.Instantiate("UI/EventSystem").name = "@EventSystem";
    }

    public abstract void Clear();
}​

 

BaseScene을 상속받는 GameScene 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameScene : BaseScene
{
    protected override void Init() {
        base.Init();

        SceneType = Define.Scene.Game;

        Managers.UI.ShowSceneUI<UI_Inven>();
    }

    public override void Clear() {

    }
}​

 

BaseScene을 상속받는 LoginScene 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LoginScene : BaseScene
{
    protected override void Init() {
        base.Init();

        SceneType = Define.Scene.Login;
    }
    
    public override void Clear() {

    }
}​

 

 Scene Manager #2

SceneManagerEx 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneManagerEx
{
    public BaseScene CurrentScene { get { return GameObject.FindObjectOfType<BaseScene>(); } }

    public void LoadScene(Define.Scene type) {
        CurrentScene.Clear();
        SceneManager.LoadScene(GetSceneName(type));
    }

    string GetSceneName(Define.Scene type) {
        // C#의 Reflection 기능을 사용
        string name = System.Enum.GetName(typeof(Define.Scene), type);
        return name;
    }
}​

 

Managers에 SceneManagerEx 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Managers : MonoBehaviour
{
    static Managers s_instance; // 유일성 보장
    static Managers Instance { get { Init(); return s_instance; } } // 유일한 매니저를 가져온다

    InputManager _input = new InputManager();
    ResourceManager _resource = new ResourceManager();
    SceneManagerEx _scene = new SceneManagerEx();
    UIManager _ui = new UIManager();

    public static InputManager Input { get { return Instance._input; } }
    public static ResourceManager Resource { get { return Instance._resource; } }
    public static SceneManagerEx Scene {get { return Instance._scene; }}
    public static UIManager UI {  get { return Instance._ui; } }

    void Start()
    {
        Init();
    }

    void Update()
    {
        _input.OnUpdate();
    }

    static void Init()
    {
        // 초기화
        if (s_instance == null) {
            GameObject go = GameObject.Find("Managers");
            if (go == null) {
                go = new GameObject { name = "Managers" };
                go.AddComponent<Managers>();
            }
            DontDestroyOnLoad(go);
            s_instance = go.GetComponent<Managers>();
        }
    }
}​

 

LoginScene 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement; 

public class LoginScene : BaseScene
{
    protected override void Init() {
        base.Init();

        SceneType = Define.Scene.Login;
    }

    private void Update() {
        if (Input.GetKeyDown(KeyCode.Q)){
            Managers.Scene.LoadScene(Define.Scene.Game);
        }
    }
    
    public override void Clear() {
        Debug.Log("LoginScene Clear!");
    }
}​

 

+ 추가 검색 (https://rucira-tte.tistory.com/115)

 - Find("~~")는 Object의 이름을 통해 찾는 함수, FindObjectOfType< ~~>()는 Script 이름을 통해 찾는 함수, 

   FindGameObjectWithTag("~~")는 Tag를 통해 찾는 함수이다.


 
[ 섹션 9. Sound ]

 Sound Manager #1

  - Sound를 위해서는 Sound를 재생하기 위한 Player, Sound를 위한 음원, 이를 듣는 관객 총 3가지가 필요하다.

  - 차례대로 AudioSource, AudioClip, AudioListener가 위를 담당한다.

 

 Sound Manager #2

Sound와 관련된 enum을 추가한 Define 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Define
{
    public enum Scene{
        Unknown,
        Login,
        Lobby,
        Game,
    }
    
    public enum Sound
    {
        Bgm,
        Effect,
        MaxCount,
    }

    public enum UIEvent
    {
        Click,
        Drag,
    }

    public enum MouseEvent
    {
        Press,
        Click,
    }

    public enum CameraMode
    {
        QuaterView, 
    }
}​

 

SoundManager 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SoundManager
{
    AudioSource[] _audioSources = new AudioSource[(int)Define.Sound.MaxCount];

    public void Init()
    {
        GameObject root = GameObject.Find("@Sound");
        if (root == null)
        {
            root = new GameObject { name = "@Sound" };
            Object.DontDestroyOnLoad(root);

            string[] soundNames = System.Enum.GetNames(typeof(Define.Sound));
            for (int i = 0; i < soundNames.Length - 1; i++)
            {
                GameObject go = new GameObject { name = soundNames[i] };
                _audioSources[i] = go.AddComponent<AudioSource>();
                go.transform.parent = root.transform; // UI는 Rect Transform이므로 SetParent를 사용, 일반적인 경우에는 parent를 사용
            }

            _audioSources[(int)Define.Sound.Bgm].loop = true;
        }
    }

    public void Play(string path, Define.Sound type = Define.Sound.Effect, float pitch = 1.0f)
    {
        if (path.Contains("Sounds/") == false)
            path = $"Sounds/{path}";

        if (type == Define.Sound.Bgm)
        {
            AudioClip audioClip = Managers.Resource.Load<AudioClip>(path);
            if (audioClip == null)
            {
                Debug.Log($"AudioClip Missing! {path}");
                return;
            }

            AudioSource audioSource = _audioSources[(int)Define.Sound.Bgm];
            if (audioSource.isPlaying)
                audioSource.Stop();

            audioSource.pitch = pitch;
            audioSource.clip = audioClip;
            audioSource.Play();
        }
        else
        {
            AudioClip audioClip = Managers.Resource.Load<AudioClip>(path);
            if (audioClip == null)
            {
                Debug.Log($"AudioClip Missing! {path}");
                return;
            }

            AudioSource audioSource = _audioSources[(int)Define.Sound.Effect];
            audioSource.pitch = pitch;
            audioSource.PlayOneShot(audioClip);
        }
    }
}​

 

Manager에 SoundManager 추가 및 SoundManager의 Init 함수 실행
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Managers : MonoBehaviour
{
    static Managers s_instance; // 유일성 보장
    static Managers Instance { get { Init(); return s_instance; } } // 유일한 매니저를 가져온다

    InputManager _input = new InputManager();
    ResourceManager _resource = new ResourceManager();
    SceneManagerEx _scene = new SceneManagerEx();
    SoundManager _sound = new SoundManager();
    UIManager _ui = new UIManager();

    public static InputManager Input { get { return Instance._input; } }
    public static ResourceManager Resource { get { return Instance._resource; } }
    public static SceneManagerEx Scene {get { return Instance._scene; }}
    public static SoundManager Sound { get { return Instance._sound; }}
    public static UIManager UI {  get { return Instance._ui; } }

    void Start()
    {
        Init();
    }

    void Update()
    {
        _input.OnUpdate();
    }

    static void Init()
    {
        // 초기화
        if (s_instance == null) {
            GameObject go = GameObject.Find("Managers");
            if (go == null) {
                go = new GameObject { name = "Managers" };
                go.AddComponent<Managers>();
            }
            DontDestroyOnLoad(go);
            s_instance = go.GetComponent<Managers>();

            s_instance._sound.Init();
        }
    }
}​

 

 Sound Manager #3

  - BGM 변경은 자주 발생하지 않아 자주 실행되지 않으나, Effect는 자주 실행된다. 이를 경로를 통해 불러오는 것은

    부하를 유발할 수 있으므로 Cashing을 통해 해결하고자 한다.

Cashing을 위한 SoundManager 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SoundManager
{
    AudioSource[] _audioSources = new AudioSource[(int)Define.Sound.MaxCount];
    Dictionary<string, AudioClip> _audioClips = new Dictionary<string, AudioClip>(); // Cashing을 위한 Dictionary (경로와 AudioClip을 가짐)

    public void Init()
    {
        GameObject root = GameObject.Find("@Sound");
        if (root == null)
        {
            root = new GameObject { name = "@Sound" };
            Object.DontDestroyOnLoad(root);

            string[] soundNames = System.Enum.GetNames(typeof(Define.Sound));
            for (int i = 0; i < soundNames.Length - 1; i++)
            {
                GameObject go = new GameObject { name = soundNames[i] };
                _audioSources[i] = go.AddComponent<AudioSource>();
                go.transform.parent = root.transform; // UI는 Rect Transform이므로 SetParent를 사용, 일반적인 경우에는 parent를 사용
            }

            _audioSources[(int)Define.Sound.Bgm].loop = true;
        }
    }

    public void Clear() // 메모리 낭비 방지를 위해 Scene 이동시 초기화하기 위한 함수
    {
        foreach (AudioSource audioSource in _audioSources)
        {
            audioSource.clip = null;
            audioSource.Stop();
        }
        _audioClips.Clear();
    }

    public void Play(string path, Define.Sound type = Define.Sound.Effect, float pitch = 1.0f)
    {
        if (path.Contains("Sounds/") == false)
            path = $"Sounds/{path}";

        if (type == Define.Sound.Bgm)
        {
            AudioClip audioClip = Managers.Resource.Load<AudioClip>(path);
            if (audioClip == null)
            {
                Debug.Log($"AudioClip Missing! {path}");
                return;
            }

            AudioSource audioSource = _audioSources[(int)Define.Sound.Bgm];
            if (audioSource.isPlaying)
                audioSource.Stop();

            audioSource.pitch = pitch;
            audioSource.clip = audioClip;
            audioSource.Play();        
        }
        else
        {
            AudioClip audioClip = GetOrAddAudioClip(path);
            if (audioClip == null)
            {
                Debug.Log($"AudioClip Missing! {path}");
                return;
            }

            AudioSource audioSource = _audioSources[(int)Define.Sound.Effect];
            audioSource.pitch = pitch;
            audioSource.PlayOneShot(audioClip);
        }
    }

    AudioClip GetOrAddAudioClip(string path) // Cashing을 위한 함수
    {
        AudioClip audioClip = null;
        if (_audioClips.TryGetValue(path, out audioClip) == false)
        {
            audioClip = Managers.Resource.Load<AudioClip>(path);
            _audioClips.Add(path, audioClip);
        }
        return audioClip;
    }
}​

 

+ 추가 검색

 - Dictionary는 특정 Key를 통해 데이터를 삭제하는 Remove() Method와 모든 데이터를 삭제하는 Clear() Method를 

   제공한다.

 - Dictionary는 특정 Key가 포함되어 있는지의 여부를 확인하는 ContainsKey() Method와 TryGetValue() Method를

   제공한다. 이때 out Keyword를 통해 별도의 변수를 선언하지 않아도 반환값을 사용할 수 있다.

   (둘 다 반환값은 Bool Type이며, TryGetValue() Method가 더 효율적) 

 

 Sound Manager #4

  - Audio Source Component의 Spatial Blend를 2D에서 3D로 변경할 경우 3D Sound를 지원한다.

    (해당 Object 자체가 소리의 진원지가 되는 것)

  - PlayClipAtPoint(AudioClip, Vector3) Method를 통해 특정 좌표에서 Sound를 재생시킬 수 있다.


 
[ 섹션 10. Object Pooling ]

Pool Manager #1

  - Object를 생성하거나 파괴하는 작업은 꽤나 무거운 작업으로 분류된다. Object 생성은 Memory를 새로 할당하고 

    Resource를 Load하는 등의 초기화하는 과정으로, Object 파괴는 파괴 이후에 발생하는 Garbage Collecting으로 인한

    Frame Drop이 발생할 수 있다.

  - 이러한 문제를 해결하기 위해 사용되는 기법이 Object Pooling이다.

  - Pooling할 Object를 담은 Object Pool을 구성한 뒤 외부에서 해당 Object가 필요하면 Object Pool에서 꺼내 사용한다.

  - Object Pool에서 꺼낸 Object의 사용이 끝나면 Object를 Pool에 돌려준다.

  - 만약 Object Pool에서 Object를 가져오려고 할 때, 모든 Object가 이미 사용중이라면 새로운 Object를 생성한다.

 

+ 추가 검색 (https://wergia.tistory.com/203)

 

 Pool Manager #2

  - Poolable Script를 Component로 들고 있다면 Memory Pooling 대상이 된다.

 Poolable 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Poolable : MonoBehaviour
{
    // Poolable Script를 Component로 들고 있다면 Memory Pooling 대상이 된다.
    public bool IsUsing; // 현재 Pooling이 된 상태인지
}​

 

PoolManager 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PoolManager
{
    #region Pool
    class Pool{
        public GameObject Original {get; private set;}
        public Transform Root {get; set;}

        Stack<Poolable> _poolStack = new Stack<Poolable>();

        public void Init(GameObject original, int count = 5){
            Original = original
            Root = new GameObject().transform;
            Root.name = $"{original.name}_Root";

            for (int i = 0; i < count; i++)
                Push(Create());
        }

        Poolable Create() {
            GameObject go = object.Instantiate<GameObject>(Original);
            go.name = Original.name;
            return go.GetOrAddComponent<Pollable>();
        }

        public void Push(Poolable poolable) {
            if (poolable == null)
                return;
            
            poolable.transform.parent = Root;
            poolable.gamoObject.SetActive(false);
            poolable.IsUsing = false;

            _poolStack.Push(poolable);
        }

        public Poolable Pop(Transform parent) {
            Poolable poolable;

            if (_poolStack.Count > 0)
                poolable = _poolStack.Pop();
            else
                poolable = Create();

            poolable.gameObject.SetActive(true);
            poolable.transform.parent = parent;
            poolable.IsUsing = true;

            return poolable;
        }
    }
    #endregion

    // PoolManager는 여러개의 Pool을 가지며 각각의 Pool들은 이름을 통해 관리를 할 것이다.
    Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();

    Transform _root;
    // Pool들의 Root를 만드는 것 (대기실 역할)
    public void Init() {
        if (_root == null) {
            _root = new GameObject { name = "@Pool_Root" }.transform;
            object.DontDestroyOnLoad(_root);
        }
    }

    public void CreatePool(GameObject original, int count = 5) {
        Pool pool = new Pool();
        pool.Init(original, count);
        pool.Root.parent = _root;

        _pool.Add(original.name, pool);
    }

    // Pool에 Object를 집어넣는 것
    public void Push(Poolable poolable) {
        string name = poolable.gameObject.name;
        if(_pool.ContainsKey(name) == false) {
            GameObject.Destroy(poolable.gameObject);
            return;
        }

        _pool[name].Push(poolable);
    }

    public Poolable Pop(GameObject original, Transform parent = null) {
        if (_pool.ContainsKey(original.name) == false)
            CreatePool(original);
        return _pool[original.name].Pop(parent);
    }

    public GameObject GetOriginal(string name) {
        if (_pool.ContainsKey(name) == false)
            return null;
        return _pool[name].Original;
    }

    public void Clear() {
        foreach (Transform child in _root)
            GameObject.Destroy(child.gameObject);
        
        _pool.Clear();
    }
}​

 

Manager에 PoolManager 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Managers : MonoBehaviour
{
    static Managers s_instance; // 유일성 보장
    static Managers Instance { get { Init(); return s_instance; } } // 유일한 매니저를 가져온다

    InputManager _input = new InputManager();
    PoolManager _pool = new PoolManager();
    ResourceManager _resource = new ResourceManager();
    SceneManagerEx _scene = new SceneManagerEx();
    SoundManager _sound = new SoundManager();
    UIManager _ui = new UIManager();

    public static InputManager Input { get { return Instance._input; } }
    public static PoolManager Pool { get { return Instance._pool; } }
    public static ResourceManager Resource { get { return Instance._resource; } }
    public static SceneManagerEx Scene {get { return Instance._scene; }}
    public static SoundManager Sound { get { return Instance._sound; }}
    public static UIManager UI {  get { return Instance._ui; } }

    void Start()
    {
        Init();
    }

    void Update()
    {
        _input.OnUpdate();
    }

    static void Init()
    {
        // 초기화
        if (s_instance == null) {
            GameObject go = GameObject.Find("Managers");
            if (go == null) {
                go = new GameObject { name = "Managers" };
                go.AddComponent<Managers>();
            }
            DontDestroyOnLoad(go);
            s_instance = go.GetComponent<Managers>();

            s_instance._sound.Init();
        }
    }

    public static void Clear()
    {
        Input.Clear();
        Sound.Clear();
        Scene.Clear();
        UI.Clear();
    }
}​

 

+ 추가 검색  (https://crazykim2.tistory.com/540)

 - C#은 #region과 #endregion을 통해 코드를 정리할 수 있다.

 - #region과 #endregion 옆에 Comment를 붙여 부가 설명을 추가할 수 있다.

 

+ 추가 정리

 Pool Manager #3

PoolManager를 통한 ResourceManager 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ResourceManager
{
    public T Load<T>(string path) where T : Object
    {
        if(typeof(T) == typeof(GameObject)) { // 만약 같다면 Prefab일 확률이 높다.
            string name = path;
            int index = name.LastIndexOf('/');
            if (index >= 0)
                name = name.Substring(index + 1);

            GameObject go = Managers.Pool.GetOriginal(name);
            if (go != null)
                return go as T;
        }

        return Resources.Load<T>(path);
    }

    public GameObject Instantiate(string path, Transform parent = null)
    {
        GameObject original = Load<GameObject>($"Prefabs/{path}");
        if (original == null)
        {
            Debug.Log($"Failed to load prefab : {path}");
            return null;
        }

        if(original.GetComponent<Poolable>() != null) // 만약 해당 GameObject가 Pooling 대상이라면 Pool에서 꺼내온다.   
            return Managers.Pool.Pop(original, parent).gameObject;    

        GameObject go = Object.Instantiate(original, parent);
        go.name = original.name;
        return go;
    }

    public void Destroy(GameObject go)
    {
        if (go == null)
            return;

        Poolable poolable = go.GetComponent<Poolable>();
        if (poolable != null) { // 만약 해당 GameObject가 Pooling 대상이라면 다시 Pool에 집어넣는다.
            Managers.Pool.Push(poolable);
            return;
        }

        Object.Destroy(go);
    }
}

 

DontDestroyOnLoad 해제를 위한 코드를 추가한 PoolManager 수정
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PoolManager
{
    #region Pool
    class Pool{
        public GameObject Original {get; private set;} // Pool 안에 생성할 GameObject
        public Transform Root {get; set;} // Pool의 Root

        Stack<Poolable> _poolStack = new Stack<Poolable>();

        public void Init(GameObject original, int count = 5){
            Original = original;
            Root = new GameObject().transform;
            Root.name = $"{original.name}_Root";

            for (int i = 0; i < count; i++)
                Push(Create());
        }

        Poolable Create() {
            GameObject go = Object.Instantiate<GameObject>(Original); // GameObject를 동적으로 생성
            go.name = Original.name;
            return go.GetOrAddComponent<Poolable>();
        }

        public void Push(Poolable poolable) {
            if (poolable == null)
                return;
            
            poolable.transform.parent = Root;
            poolable.gameObject.SetActive(false);
            poolable.IsUsing = false;

            _poolStack.Push(poolable);
        }

        public Poolable Pop(Transform parent) {
            Poolable poolable;

            if (_poolStack.Count > 0)
                poolable = _poolStack.Pop();
            else
                poolable = Create();

            poolable.gameObject.SetActive(true);

            // DontDestroyOnLoad 해제 용도
            if (parent == null)
                poolable.transform.parent = Managers.Scene.CurrentScene.transform;

            poolable.transform.parent = parent;
            poolable.IsUsing = true;

            return poolable;
        }
    }
    #endregion

    // PoolManager는 여러개의 Pool을 가지며 각각의 Pool들은 이름을 통해 관리를 할 것이다.
    Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();

    Transform _root;
    // Pool들의 Root를 만드는 것 (대기실 역할)
    public void Init() {
        if (_root == null) {
            _root = new GameObject { name = "@Pool_Root" }.transform;
            Object.DontDestroyOnLoad(_root);
        }
    }

    public void CreatePool(GameObject original, int count = 5) {
        Pool pool = new Pool();
        pool.Init(original, count);
        pool.Root.parent = _root;

        _pool.Add(original.name, pool);
    }

    // Pool에 Object를 집어넣는 것
    public void Push(Poolable poolable) {
        string name = poolable.gameObject.name;
        if(_pool.ContainsKey(name) == false) {
            GameObject.Destroy(poolable.gameObject);
            return;
        }

        _pool[name].Push(poolable);
    }

    public Poolable Pop(GameObject original, Transform parent = null) {
        if (_pool.ContainsKey(original.name) == false)
            CreatePool(original);
        return _pool[original.name].Pop(parent);
    }

    public GameObject GetOriginal(string name) {
        if (_pool.ContainsKey(name) == false)
            return null;
        return _pool[name].Original;
    }

    public void Clear() {
        foreach (Transform child in _root)
            GameObject.Destroy(child.gameObject);
        
        _pool.Clear();
    }
}​

 


 
[ 섹션 11. Coroutine ]

Coroutine #1

  - Coroutine을 통해 함수의 상태를 저장/복원할 수 있다. 따라서 원하는 타이밍에 함수를 잠시 중단/복원이 가능하다.

  - Coroutine을 통해 시간 관리가 가능하다. (Ex : 몇 초 후 실행)

 

+ 추가 검색  (https://codeposting.tistory.com/entry/Unity-%EC%9C%A0%EB%8B%88%ED%8B%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EC%82%AC%EC%9A%A9%EB%B2%95-%EC%A0%95%EC%A7%80-Coroutine-%EC%9D%B4%EC%9C%A0-%EC%B5%9C%EC%A0%81%ED%99%94)

 - Coroutine이란 코드 내에서 구문 실행 도중에 처리를 대기시키거나 순차 처리에 함수를 병렬로 동시에 처리할 수 있도록

   만들어 준다. (Thread와는 다른 개념)

 - Coroutine은 IEnumerator 형태의 데이터를 반환하는 함수이며, yield Keyword를 통해 return을 필수적으로 해야한다.

 

 Coroutine #2

Coroutine 예제
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CoroutineEx : MonoBehaviour
{
    Coroutine co;
    
    void Awake()
    {
    	co = StartCoroutine("ExplodeAfterSeconds", 4.0f);
        StartCoroutine("StopExplode", 2.0f);
    }
    
    IEnumerator ExplodeAfterSeconds(float seconds)
    {
    	Debug.Log("Explode Enter");
        yield return new WaitForSeconds(seconds);
        Debug.Log("Explode Execute!");
    }
    
    IEnumerator StopExplode(float seconds)
    {
    	Debug.Log("Stop Enter");
        yield return new WaitForSeconds(seconds);
        Debug.Log("Stop Execute!");
        if (co != null)
        {
            Stopcoroutine(co);
            co = null;
        }
    }
}

 
[ 섹션 12. Data ]

Data Manager #1

  - 보통 Assets/Resources/Data 경로에 Data 파일들을 저장한다.

  - json 파일은 Unity에서 [ 오른쪽 마우스 ] - [ Create ] 를 통해 만들 수 없고 아래와 같이 만든다.

Resources 폴더 선택 후 [ 오른쪽 마우스 ] - [ Show In Explorer ] -> Resources/Data 경로에 [ 오른쪽 마우스 ] - [ 새로 만들기 ] - [ 텍스트 문서 ]  -> 확장자를 txt에서 json으로 수정

 

  - json에서 []는 리스트를, {}는 구조체를 나타낸다.

  - json 파일의 데이터를 저장할 Class는 반드시 [Serializable]을 붙여줘야 하며, 또한 해당 Class의 변수 역시 public 또는

    [SerializeField]를 붙여줘야 한다. 이때 Class의 변수명이 json 파일의 구조체 안 변수명과 같아야 한다. 

Stat과 관련된 데이터를 저장하는 StatData를 json 파일로 생성
{
    "stats": [
        {
            "level": "1",
            "hp": "100",
            "attack": "10"
        },
        {
            "level": "2",
            "hp": "150",
            "attack": "15"
        },
        {
            "level": "3",
            "hp": "200",
            "attack": "20"
        }
    ]
}​

 

DataManager 생성
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[Serializable]
public class Stat {
    public int level;
    public int hp;
    public int attack;
}

[Serializable]
public class StatData {
    public List<Stat> stats = new List<Stat>();
}

public class DataManager
{
    public void Init() {
        // StatData.json 파일을 불러오기 위한 것
        TextAsset textAsset = Managers.Resource.Load<TextAsset>($"Data/StatData");

        // Unity에서 제공하는 json Parsing
        StatData data = JsonUtility.FromJson<StatData>(textAsset.text);
    }
}​

 

Manager에 DataManager 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Managers : MonoBehaviour
{
    static Managers s_instance; // 유일성 보장
    static Managers Instance { get { Init(); return s_instance; } } // 유일한 매니저를 가져온다

    DataManager _data = new DataManager();
    InputManager _input = new InputManager();
    PoolManager _pool = new PoolManager();
    ResourceManager _resource = new ResourceManager();
    SceneManagerEx _scene = new SceneManagerEx();
    SoundManager _sound = new SoundManager();
    UIManager _ui = new UIManager();

    public static DataManager Data { get { return Instance._data; }}
    public static InputManager Input { get { return Instance._input; } }
    public static PoolManager Pool { get { return Instance._pool; } }
    public static ResourceManager Resource { get { return Instance._resource; } }
    public static SceneManagerEx Scene {get { return Instance._scene; }}
    public static SoundManager Sound { get { return Instance._sound; }}
    public static UIManager UI {  get { return Instance._ui; } }

    void Start()
    {
        Init();
    }

    void Update()
    {
        _input.OnUpdate();
    }

    static void Init()
    {
        // 초기화
        if (s_instance == null) {
            GameObject go = GameObject.Find("Managers");
            if (go == null) {
                go = new GameObject { name = "Managers" };
                go.AddComponent<Managers>();
            }
            DontDestroyOnLoad(go);
            s_instance = go.GetComponent<Managers>();

            s_instance._data.Init();
            s_instance._pool.Init();
            s_instance._sound.Init();
        }
    }

    public static void Clear()
    {
        Input.Clear();
        Sound.Clear();
        Scene.Clear();
        UI.Clear();
        Pool.Clear();
    }
}​

 

 Data Manager #2

DataManager 수정
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ILoader<Key, Value> 
{
    Dictionary<Key, Value> MakeDict();
}

public class DataManager
{
    public Dictionary<int, Stat> StatDict { get; private set; } = new Dictionary<int, Stat>();

    public void Init() 
    {
        StatDict = LoadJson<StatData, int, Stat>("StatData").MakeDict();
    }

    Loader LoadJson<Loader, Key, Value>(string path) where Loader : ILoader<Key, Value>
    {
        // json 파일을 불러오기 위한 것
        TextAsset textAsset = Managers.Resource.Load<TextAsset>($"Data/{path}");

        // Unity에서 제공하는 json Parsing
        return JsonUtility.FromJson<Loader>(textAsset.text);
    }
}​

 

Data.Contents 생성
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#region Stat

[Serializable]
public class Stat
{
    public int level;
    public int hp;
    public int attack;
}

[Serializable]
public class StatData : ILoader<int, Stat>
{
    public List<Stat> stats = new List<Stat>();

    public Dictionary<int, Stat> MakeDict()
    {
        Dictionary<int, Stat> dict = new Dictionary<int, Stat>();
        foreach(Stat stat in stats)
            dict.Add(stat.level, stat);
        return dict;
    }
}

#endregion​

 

+ 추가 검색  (https://seroi-programming.tistory.com/entry/C-%EC%96%B8%EC%96%B4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95)

 - Interface는 객체 지향 프로그래밍의 핵심 개념 중 하나이다.

 - Interface는 Class와 비슷하지만, Class와 달리 구현되지 않은 Method와 Property를 가질 수 있다.

 - Interface는 접근 제한 한정자를 사용할 수 없고, 모든 것이 public으로 선언된다.

 - Interface는 Class가 따라야하는 규약을 정의하는데 사용되며, Class는 Interface에 정의된 모든 멤버를 구현해야한다.

 - Interface를 구현하는 Class는 Class 이름 뒤에 콜론(:)을 붙이고 구현하고자 하는 Interface의 이름을 지정한다.

 - Interface는 Instance를 만들 수 없지만, Interface를 상속받는 Class의 Instance를 만드는 것은 가능하다.

 - Class는 여러개의 Interface를 상속받을 수 있다.

 

+ Recent posts