Patterns
Monobehavior
- to perceive it is Component
Singleton
- Uniqueness guaranteed
Managers
- Managers.cs
public class Managers : MonoBehaviour
{
    static Managers s_instance;
    static Managers Instance{ get { Init(); return s_instance; } }
    InputManager _input = new InputManager();
    ResourceManager _resource = new ResourceManager();
    public static InputManager Input { get { return Instance._input; } }
    public static ResourceManager Resource { get { return Instance._resource; } }
    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>();
        }
    }
}- if there is not ManagersObject, make new one
- this new ManagersObject stay during play
-  it links other Manager objects with each Managers 
- InputManager.cs
public class InputManager
{
    public Action KeyAction = null;
    
    public void OnUpdate()
    {
        if (Input.anyKey == false)
            return;
        if (KeyAction != null)
            KeyAction.Invoke();
    }
}-  If there is key input during play, receive key input 
-  ResourceManager.cs 
public class ResourceManager
{
    public T Load<T>(string path) where T: Object
    {
        return Resources.Load<T>(path);
    }
    public GameObject Instantiate(string path, Transform parent = null)
    {
        GameObject prefab = Load<GameObject>($"Prefabs/{path}");
        if (prefab == null)
        {
            Debug.Log($"Failed to load prefab: {path}");
            return null;
        }
        return Object.Instantiate(prefab, parent);
    }
    public void Destroy(GameObject go)
    {
        if (go == null)
            return;
        Object.Destroy(go);
    }
}- 
Instantiatecan make GameObject
- 
Destroycan remove GameObject
- 
Loadcan load GameObject
- to use Resources.Load, there should be Resources folder
Controller
- PlayerController.cs
public class PlayerController : MonoBehaviour
{
    [SerializeField]
    float _speed = 10.0f;
    void Start()
    {
        Managers.Input.KeyAction -= OnKeyboard;
        Managers.Input.KeyAction += OnKeyboard;
    }
    void Update()
    {
    }
    void OnKeyboard()
    {
        if (Input.GetKey(KeyCode.W))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.forward), 0.2f);
            transform.position += Vector3.forward * Time.deltaTime * _speed;
        }
        if (Input.GetKey(KeyCode.S))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.back), 0.2f);
            transform.position += Vector3.back * Time.deltaTime * _speed;
        }
        if (Input.GetKey(KeyCode.A))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.left), 0.2f);
            transform.position += Vector3.left * Time.deltaTime * _speed;
        }
        if (Input.GetKey(KeyCode.D))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), 0.2f);
            transform.position += Vector3.right * Time.deltaTime * _speed;
        }
    }
}- 
Quaternion.Slerpcan change direction
Collision
Gravity
- Player
 
 - Add Rigidbodyand checkUse Gravity
- Add Capsule Collider
-  Is Kinematiccan ignore all physical laws in Unity
- Plane
 
 - Add Mesh Collider
Collider and Trigger
- Collider
 
 - There is Rigidbodyon me (Is Kinematic : Off)
- There is Collideron me (Is Trigger : Off)
- There is Collideron other (Is Trigger : Off)
private void OnCollisionEnter(Collision collision)
{
    Debug.Log($"Collision @ {collision.gameObject.name} !");
}- Trigger
 
 - There are Collideron me and other
- There is Rigidbodyon one
- There is Is Triggeron one
private void OnTriggerEnter(Collider other)
{
    Debug.Log($"Trigger @ {other.gameObject.name}!");
}Raycasting
- shoot a raser and detect object
void Update()
{
        
    Vector3 look = transform.TransformDirection(Vector3.forward);
    Debug.DrawRay(transform.position + Vector3.up, look * 10, Color.red);
    RaycastHit[] hits;
    hits = Physics.RaycastAll(transform.position + Vector3.up, look, 10);
    foreach(RaycastHit hit in hits)
        Debug.Log($"Raycast {hit.collider.gameObject.name}!");
}- 
TransformDirectioncan change player’s direction to world direction
- 
RaycastHitcan save all objects that is hitted by player’s raser
- 
Physics.RaycastAllcan shoot a raser and detect objects
- if you want to detect only one object, use Physics.Raycast
Projection
-  Local ↔ World ↔ Viewport ↔ Screen 
-  Screen - Pixel standard
 
void Update()
{
    Debug.Log(Input.mousePosition);    
}- Viewport - Pixel ratio standard
 
void Update()
{
    Debug.Log(Camera.main.ScreenToViewportPoint(Input.mousePosition));
}- Camera - You can change view range in Clipping Planes
- 
Nearcan change where the camera range start
- 
Farcan change where the camera range end
 
- You can change view range in 
Camera Raycasting
- camera can shoot raser and detect object
void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 100.0f))
            Debug.Log($"Raycast Camera @ {hit.collider.gameObject.name}");
    }
}LayerMask
- you can set layers(also you can se Tags)
- from layers, you can define whether to detect object(s) or not
void Update()
{
    if (Input.GetMouseButtonDown(0))
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
        LayerMask mask = LayerMask.GetMask("Monster") | LayerMask.GetMask("Wall");
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 100.0f, mask))
            Debug.Log($"Raycast Camera @ {hit.collider.gameObject.tag}");
    }
}Camera
Follow Player
- if camera is in player’s hierarchy, camera will follow palyer
-  but the rotation is also changed, this is really messy for user 
- Scripts\Utils\Define.cs
public class Define
{
    public enum CameraMode
    {
        QuaterView,
    }
}- CameraController.cs
public class CameraController : MonoBehaviour
{
    [SerializeField]
    Define.CameraMode _mode = Define.CameraMode.QuaterView;
    [SerializeField]
    Vector3 _delta = new Vector3(0.0f, 6.0f, -5.0f);
    [SerializeField]
    GameObject _player = null;
    void LateUpdate()
    {
        if (_mode == Define.CameraMode.QuaterView)
        {
            transform.position = _player.transform.position + _delta;
            transform.LookAt(_player.transform);
        }
    }
    public void SetQuaterView(Vector3 delta)
    {
        _mode = Define.CameraMode.QuaterView;
        _delta = delta;
    }
}- KeyInput and Camera transform is crushed by their update time. LateUpdatecan delay Camera transform updating time. So Camera transform is excuted after player’s move
Click Moving
-  by clicking map, player can move to destination. 
-  Scripts\Utils\Define.cs 
public class Define
{
    public enum MouseEvent
    {
        Press,
        Click,
    }
    public enum CameraMode
    {
        QuaterView,
    }   
}- InputManager.cs
public class InputManager
{
    public Action KeyAction = null;
    public Action<Define.MouseEvent> MouseAction = null;
    bool _pressed = false;
    
    public void OnUpdate()
    {
        if (Input.anyKey && KeyAction != null)
            KeyAction.Invoke();
        if(MouseAction != null)
        {
            if (Input.GetMouseButton(0))
            {
                MouseAction.Invoke(Define.MouseEvent.Press);
                _pressed = true;
            }
            else
            {
                if (_pressed)
                    MouseAction.Invoke(Define.MouseEvent.Click);
                _pressed = false;
            }
        }
    }
}-  Left Mouse Click is 0 and Right Mouse Click is 1 
-  PlayerController.cs 
public class PlayerController : MonoBehaviour
{
    ...
    bool _moveToDest = false;
    Vector3 _desPos;
    void Start()
    {
        ...
        Managers.Input.MouseAction += OnMouseClicked;
        Managers.Input.MouseAction -= OnMouseClicked;
    }
    void Update()
    {
        if (_moveToDest)
        {
            Vector3 dir = _desPos - transform.position;
            if(dir.magnitude < 0.0001f)
            {
                _moveToDest = false;
            }
            else
            {
                float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
                transform.position += dir.normalized * moveDist;
                transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
                transform.LookAt(_desPos);
            }
        }
    }
    void OnKeyboard()
    {
        ...
        _moveToDest = false;
    }
    void OnMouseClicked(Define.MouseEvent evt)
    {
        if (evt != Define.MouseEvent.Click)
            return;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
        RaycastHit hit;
        if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
        {
            _desPos = hit.point;
            _moveToDest = true;
        }
    }
}- 
magnitudereturns length of vector
- 
hit.pointreturns impact point in world space where the ray hit the collider
Camera Position
-  if player is hidden by wall, camera position will be change 
-  CameraController.cs 
void LateUpdate()
{
    if (_mode == Define.CameraMode.QuaterView)
    {
        RaycastHit hit;
        if(Physics.Raycast(_player.transform.position, _delta, out hit, _delta.magnitude, LayerMask.GetMask("Wall")))
        {
            float dist = (hit.point - _player.transform.position).magnitude * 0.8f;
            transform.position = _player.transform.position + _delta.normalized * dist;
        }
        else
        {
            transform.position = _player.transform.position + _delta;
            transform.LookAt(_player.transform);
        }
    }
}Animation
WAIT & RUN
- Animation Controller - 
Animation Controllercan make animation
- customize Animation
- Add controller in Animator
 
- 
 
  
  
 - PlayerController.cs
void Update()
{
    ...
    if (_moveToDest)
    {
        Animator anim = GetComponent<Animator>();
        anim.Play("RUN");
    }
    else
    {
        Animator anim = GetComponent<Animator>();
        anim.Play("WAIT");
    }
}
void OnMouseClicked(Define.MouseEvent evt)
{
    //if (evt != Define.MouseEvent.Click)
    //    return;
    ...
}Animation Blending
- player can move sequencialy, not suddenly
 
  
 - Blend Tree - blend sevral animations
- 
Thresholdsets blend values
- when the value comes closer to threshold value, the animation will be excuted
 
- PlayerController.cs
public class PlayerController : MonoBehaviour
{
  float wait_run_ratio;
  ...
  void Update()
  {
    ...
    if (_moveToDest)
    {
      wait_run_ratio = Mathf.Lerp(wait_run_ratio, 1, 10.0f * Time.deltaTime);
      Animator anim = GetComponent<Animator>();
      anim.SetFloat("wait_run_ratio", wait_run_ratio);
      anim.Play("WAIT_RUN");
    }
    else
    {
      wait_run_ratio = Mathf.Lerp(wait_run_ratio, 0, 10.0f * Time.deltaTime);
      Animator anim = GetComponent<Animator>();
      anim.SetFloat("wait_run_ratio", wait_run_ratio);
      anim.Play("WAIT_RUN");
    }   
  }
}State Pattern
- manage Player state
-  from this section, we don’t use keyboard KeyInput 
- PlayerController.cs
public class PlayerController : MonoBehaviour
{
  ...
  PlayerState _state = PlayerState.Idle;
  void Start()
  {
    //Managers.Input.KeyAction -= OnKeyboard;
    //Managers.Input.KeyAction += OnKeyboard;
    ...
  }
  public enum PlayerState
  {
      Die,
      Moving,
      Idle,
  }
    
  void UpdateDie()
  {
  }
  void UpdateMoving()
  {
    Vector3 dir = _desPos - transform.position;
    if (dir.magnitude < 0.0001f)
    {
      _state = PlayerState.Idle;
    }
    else
    {
      float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
      transform.position += dir.normalized * moveDist;
      transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
      wait_run_ratio = Mathf.Lerp(wait_run_ratio, 1, 10.0f * Time.deltaTime);
      Animator anim = GetComponent<Animator>();
      anim.SetFloat("wait_run_ratio", wait_run_ratio);
      anim.Play("WAIT_RUN");
    }
  }
  void UpdateIdle()
  {
    wait_run_ratio = Mathf.Lerp(wait_run_ratio, 0, 10.0f * Time.deltaTime);
    Animator anim = GetComponent<Animator>();
    anim.SetFloat("wait_run_ratio", wait_run_ratio);
    anim.Play("WAIT_RUN");
  }
  void Update()
  {
    switch (_state)
    {
      case PlayerState.Die:
        UpdateDie();
        break;
      case PlayerState.Moving:
        UpdateMoving();
        break;
      case PlayerState.Idle:
        UpdateIdle();
        break;
    }
  }
  void OnMouseClicked(Define.MouseEvent evt)
  {
    if (_state == PlayerState.Die)
      return;
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
    {
      _desPos = hit.point;
      _state = PlayerState.Moving;
    }
  }
}State Machine
- you can set your animation in GUI
 
 - PlayerController.cs
public class PlayerController : MonoBehaviour
{
  ...
  float wait_run_ratio;
  ...
  void UpdateMoving()
  {
    ...
    else
    {
      float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
      transform.position += dir.normalized * moveDist;
      transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
      //wait_run_ratio = Mathf.Lerp(wait_run_ratio, 1, 10.0f * Time.deltaTime);
      Animator anim = GetComponent<Animator>();
      //anim.SetFloat("wait_run_ratio", wait_run_ratio);
      //anim.Play("WAIT_RUN");
    }
  }
  void UpdateIdle()
  {
    //wait_run_ratio = Mathf.Lerp(wait_run_ratio, 0, 10.0f * Time.deltaTime);
    Animator anim = GetComponent<Animator>();
    //anim.SetFloat("wait_run_ratio", wait_run_ratio);
    //anim.Play("WAIT_RUN");
  }
  ...
}- Ordring animations - you can order animations in Transitionssection
 
- you can order animations in 
 
 - Conditioning - you can make condition in animations
 
 
  
  
 KeyFrame Animation
-  you can make KeyFrame Animation 
-  Animation Tool 
 
 - Click object
 
  
 - Add all Transform Properties
 
  
 - change position at 60 sec and 120 sec
 
  
  
 - keyframe recording mode
 
 Animation Event
- Animation Event - Add Animation Event
 
 
 - Scripts\CubeEventTest.cs
public class CubeEventTest : MonoBehaviour
{
    void TestEventCallback()
    {
        Debug.Log("Evnet Received!");
    }
}- in added animation event, change function
 
 - change unitychan_RUN00_F- Add Event
 
 
  
 - PlayerController.cs
void OnRunEvent()
{
    Debug.Log("walk walk");
}send reference value
- change unitychan_RUN00_F- change string
 
 
 - PlayerController.cs
void OnRunEvent(string a)
{
    Debug.Log($"walk{a} walk{a}");
}UI
Pivot
- it is central point of UI object
- object rotate round pivot when rotation is changed
Anchor
- set ratio and size of object when screen size is changed
UI Anomatification
Object Detection
- Detect Objects - change object name
 
 
 - Utils\Util.cs
public class Util
{
    public static T FindChild<T>(GameObject go, string name = null, bool recursive = false) where 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;
    }
}- Scripts\UI\UI_Button.cs
public class UI_Button : MonoBehaviour
{
    // Text _text;
    Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>();
    enum Buttons
    {
        PointButton,
    }
    enum Texts
    {
        PointText,
        ScoreText,
    }
    private void Start()
    {
        Bind<Button>(typeof(Buttons));
        Bind<Text>(typeof(Texts));
    }
    void Bind<T>(Type type) where T: UnityEngine.Object
    {
        string [] names = Enum.GetNames(type);
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects);
        for(int i=0; i<names.Length; i++)
        {
            objects[i] = Util.FindChild<T>(gameObject, names[i], true);
        }
    }
    int _score = 0;
    public void OnButtonClicked()
    {
        _score++;
        //_text.text = $"Score : {_score}";
    }
} 
 Base UI
- Create Base UI - use prefab
 
- Delete in Hierarchy - first, delete UI_Buttonin prefeb
- drag UI_Buttonin Hierarchy to prefab
- delete UI_Buttonin Hierarchy
- this step is to change UI_Buttonprefeb
 
- first, delete 
 
 - UI_Button.cs
public class UI_Button : UI_Base
{
  ...
    enum GameObjects
    {
        TestObject,
    }
    private void Start()
    {
        ...
        Bind<GameObject>(typeof(GameObjects));
        GetText((int)Texts.ScoreText).text = "Bind Test";
    }
    ...
}- Script\UI\UI_Base.cs
public class UI_Base : MonoBehaviour
{
    Dictionary<Type, UnityEngine.Object[]> _objects = new Dictionary<Type, UnityEngine.Object[]>();
    protected void Bind<T>(Type type) where T : UnityEngine.Object
    {
        string[] names = Enum.GetNames(type);
        UnityEngine.Object[] objects = new UnityEngine.Object[names.Length];
        _objects.Add(typeof(T), objects);
        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);
            if (objects[i] == null)
                Debug.Log($"Faild to bind{name[i]}");
        }
    }
    protected T Get<T>(int idx) where T : UnityEngine.Object
    {
        UnityEngine.Object[] objects = null;
        if (_objects.TryGetValue(typeof(T), out objects) == false)
            return null;
        return objects[idx] as T;
    }
    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); }
}- PlayerController.cs
void Start()
{
    ...
    Managers.Resource.Instantiate("UI/UI_Button");
}Event Handler
- Drag Image - add UI_Buttonin Hierarchy
- add ItemIconImage inUI_Button
 
- add 
 
 - PlayerController.cs
void Start()
{
    ...
    //Managers.Resource.Instantiate("UI/UI_Button");
}- Script\UI\UI_EventHandler.cs - add this component in ItemIconImage
 
- add this component in 
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);
    }
}- UI_Button.cs
enum Images
{
    ItemIcon,
}
private void Start()
{
    ...
    Bind<Image>(typeof(Images));
    ...
    GameObject go = GetImage((int)Images.ItemIcon).gameObject;
    UI_EventHandler evt = go.GetComponent<UI_EventHandler>();
    evt.OnDragHandler += ((PointerEventData data) => { evt.gameObject.transform.position = data.position; });
}UI Event
- Add Script Component - by code, add script component in each game object atomatically
 
- UI_Base.cs
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; });
    }- Define.cs
public class Define
{
    public enum UIEvent
    {
        Click,
        Drag,
    }
    ...
}- Util.cs
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;
    }
    ...
}- UI_EventHandler.cs
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);
    }
}- UI_Button.cs
private void Start()
{
    ...
    GetButton((int)Buttons.PointButton).gameObject.AddUIEvent(OnButtonClicked);
    GameObject go = GetImage((int)Images.ItemIcon).gameObject;
    AddUIEvent(go, (PointerEventData data) => { go.transform.position = data.position; }, Define.UIEvent.Drag);
    }
    ...
public void OnButtonClicked(PointerEventData data)
{
    _score++;
    GetText((int)Texts.ScoreText).text = $"Score: {_score}";
}- Extension.cs - extense function
 
public static class Extension
{
    public static void AddUIEvent(this GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {
        UI_Base.AddUIEvent(go, action, type);
    }
}UI Manager
Create Popup
- Popup UI - create Popup in code
 
- organize folder - Create Popupfolder in Prefabs\UI and Scripts\UI
- Dreate Scenefolder in Prefabs\UI and Scripts\UI
- Move UI_Buttonto Prefabs\UI\Popup and Scripts\UI\Popup
 
- Create 
- Scripts\UI\Popup\UI_Popup.cs
public class UI_Popup : UI_Base{ }- Managers\UIManagers.cs - use stack to manage popup
 
public class UIManager
{
    int _order = 0;
    Stack<UI_Popup> _popupStack = new Stack<UI_Popup>();
    public T ShowPopupUI<T>(string name = null) where T : UI_Popup
    {
        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();
    }
}- UI_Button.cs
public class UI_Button : UI_Popup
{
  ...
}- Managers.cs
public class Managers : MonoBehaviour
{
  ...
  UIManager _ui = new UIManager();
  ...
  public static UIManager UI { get { return Instance._ui; } }
}- PlayerController.cs
void Start()
{
    ...
    UI_Button ui = Managers.UI.ShowPopupUI<UI_Button>();
    Managers.UI.ClosePopupUI(ui);
}Popup ordering
- Popup Order - Popup Ordering is essential when showing and closing popup
- Popup Blocker is essential if there is several Popups behind
 
- UIManager.cs
public class UIManager
{
    int _order = 10;
    ...
    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)
    {
        Canvas canvas = Util.GetOrAddComponent<Canvas>(go);
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        canvas.overrideSorting = true;
        if (sort)
        {
            canvas.sortingOrder = _order;
            _order++;
        }
  }
    public T ShowSceneUI<T>(string name = null) where T : UI_Scene
    {
        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
    {
        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;
    }
}- UI_Popup.cs
public class UI_Popup : UI_Base
{
    public virtual void init()
    {
        Managers.UI.SetCanvas(gameObject, true);
    }
    public virtual void ClosePopupUI()
    {
        Managers.UI.ClosePopupUI(this);
    }
}- Scripts\UI\UI_Scene.cs
public class UI_Scene : UI_Base
{
    public virtual void init()
    {
        Managers.UI.SetCanvas(gameObject, false);
    }
}- PlayerController.cs
void Start()
{
    ...
    //Managers.UI.ClosePopupUI(ui);
}- UI_Button.cs
void Start()
{
    init();
}
    
public override void init()
{
    base.init();
    Bind<Button>(typeof(Buttons));
    Bind<Text>(typeof(Texts));
    Bind<GameObject>(typeof(GameObjects));
    Bind<Image>(typeof(Images));
    GetButton((int)Buttons.PointButton).gameObject.AddUIEvent(OnButtonClicked);
    GameObject go = GetImage((int)Images.ItemIcon).gameObject;
    AddUIEvent(go, (PointerEventData data) => { go.transform.position = data.position; }, Define.UIEvent.Drag);
}- Blocker - Blocker blocks behind popup moving
- Create Image and set size to cover whole UI_Button
- set alpha value of color to 0
- set Hierarchy like below
 
 
 Inventory
Panel
- Before starting, you should delete UI_Buttonin Hierarchy
-  and change PlayerController.cs 
- PlayerController.cs
void Start()
{
    ...
    //UI_Button ui = Managers.UI.ShowPopupUI<UI_Button>();
}- Asset Store - search Unity Samples : UI
- import only Textures and Sprites
 
- search 
 
  
  
 - and down load 2D Spritein [window]-[Package Manager]
 
 Binding
- Inventory Panel - Create Pannel and Change Canvas name to UI_Inven
- and use UIPanelin [Textures and Sprite]-[Rounded UI] to Panel
 
- Create Pannel and Change Canvas name to 
 
 - create Panel in Panel and change name to UI_Inven_Item
- create Image in UI_Inven_Item
- use icon1in [Textures and Sprite]-[Decoration] to Image
 
 - Add Grid Layout Groupin Panel
 
 - prefab UI_Inven_Itemand Duplicate this
- prefab UI_Inven
 
  
 - Change Prefab - in UI_Invenprefab, change panel name toGridPanel
 
- in 
 
 - in UI_Inven_Itemprefab, change image name toItemIcon
- create Text and change name to ItemNameText
 
  
 - Scripts\UI\Scene\UI_Inven.cs
public class UI_Inven : UI_Scene
{
    enum GameObjects
    {
        GridPanel,
    }
    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($"Sward {i}");
        }
    }
}- PlayerController.cs
void Start()
{
    ...
    Managers.UI.ShowSceneUI<UI_Inven>();
}- UI_Base.cs - Making Init in here is better
 
public abstract class UI_Base : MonoBehaviour
{
    ...
    public abstract void Init();
    ...
}- UI_Popup.cs
public class UI_Popup : UI_Base
{
    public override void Init()
    {
        Managers.UI.SetCanvas(gameObject, true);
    }
}- UI_Scene.cs
public class UI_Scene : UI_Base
{
    public override void Init()
    {
        Managers.UI.SetCanvas(gameObject, false);
    }
}- Scripts\UI\Scene\UI_Inven_Item.cs
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} Item clicked!"); } );
    }
    public void SetInfo(string name)
    {
        _name = name;
    }
}- Add Component - in UI_Invenprefab, add ComponenetUI_Inven.cs
- in UI_Inven_Itemprefab, add ComponentUI_Inven_Item.cs
 
- in 
 
  
 Fix Codes
- folder organization - move UI_Inven_Itemprefab and script
 
- move 
 
  
 - ResourceManager.cs - it removes “(Clone)” string when creating new object by prefab
 
public GameObject Instantiate(string path, Transform parent = null)
{
    ...
    GameObject go = Object.Instantiate(prefab, parent);
    int index = go.name.IndexOf("(Clone)");
    if (index > 0)
        go.name = go.name.Substring(0, index);
    return go;
}- UIManager.cs
public T MakeSubItem<T>(Transform parent = null, string name = null) where T : UI_Base
{
    if (string.IsNullOrEmpty(name))
        name = typeof(T).Name;
    GameObject go = Managers.Resource.Instantiate($"UI/SubItem/{name}");
    if (parent != null)
        go.transform.SetParent(parent);
        
    return Util.GetOrAddComponent<T>(go);
}- UI_Base.cs
public abstract class UI_Base : MonoBehaviour
{
    ...
    protected GameObject GetObject(int idx) { return Get<GameObject>(idx); }
    public static void BindEvent(GameObject go, Action<PointerEventData> action, Define.UIEvent type = Define.UIEvent.Click)
    {
      ...
    }
}-  and change all AddUIEventtoBindEventin the project
-  Extension.cs 
public static class Extension
{
    public static T GetOrAddComponent<T>(this GameObject go) where T : UnityEngine.Component
    {
        return Util.GetOrAddComponent<T>(go);
    }
    ...
}Scene
organize Scenes
- PlayerController.cs
void Start()
{
    ...
    //Managers.UI.ShowSceneUI<UI_Inven>();
}- Scripts\Scenes\BaseScene.cs
public abstract class BaseScene : MonoBehaviour
{
    public Define.Scene SceneType { get; protected set; } = Define.Scene.Unknown;
    void Awake()
    {
        Init();
    }
    protected virtual void Init()
    {
        Object obj = GameObject.FindObjectOfType(typeof(EventSystem));
        if (obj == null)
            Managers.Resource.Instantiate("UI/EventSystem").name = "@EventSystem";
    }
    public abstract void Clear();
}- Scripts\Scenes\GameScene.cs
public class GameScene : BaseScene
{
    protected override void Init()
    {
        base.Init();
        SceneType = Define.Scene.Game;
        Managers.UI.ShowSceneUI<UI_Inven>();
    }
    public override void Clear() { }
}- Scripts\Scenes\LoginScene.cs
public class LoginScene : BaseScene
{
    protected override void Init()
    {
        base.Init();
        SceneType = Define.Scene.Login;
    }
    public override void Clear() { }
}- Define.cs
public class Define
{
    public enum Scene
    {
        Unknown,
        Login,
        Lobby,
        Game,
    }
    ...
}- Game - save SampleScenetoGame
- Create Empty GameObject and change name to @Scene
- Add GameScene.csComponenet to@Scene
 
- save 
 
 - prefab EventSystem in Prefabs\UI
- Remove EventSystem object in Hierarchy
 
 - Login - Create New Scene and save name to Login
- Create Empty GameObject and change name to @Scene
- Add LoginScene.csComponenet to@Scene
 
- Create New Scene and save name to 
 
 Changing Scenes
- Build Settings - [Files]-[Build Settings]
- You can add Scene by Drag in Asset
 
 
 -  it is for just setting, dont’ click build button, just exit this window 
-  Scripts\Managers\SceneManagerEx.cs 
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)
    {
        string name = System.Enum.GetName(typeof(Define.Scene), type);
        return name;
    }
}- Managers.cs
public class Managers : MonoBehaviour
{
  ...
  SceneManagerEx _scene = new SceneManagerEx();
  ...
  public static SceneManagerEx Scene { get { return Instance._scene; } }
}- LoginScene.cs - scene will change when you press Q
 
private void Update()
{
    if(Input.GetKeyDown(KeyCode.Q))
    {
        Managers.Scene.LoadScene(Define.Scene.Game);
    }
}
public override void Clear()
{
    Debug.Log("LoginScene Clear!");
}Sound
Sound ingredients
- Audio Source - Player
 
- Audio Clip - Sound file
 
- Audio Listener - audience
 
Sound Manager
- move sound clips - Create [Sounds] folder
- if player meet box, then sounds will play
 
 
 - Add Sound Source Component - Delete Cube Two
- Add Sound Source Component on Cube One
- check Is Triggeron Cube One
- and Audio Listner is already in Main Camera, so you don’t have to add Audio Listner
- if you want, you can add Aduio Listner on player
 
 
 - Scripts\Managers\SoundManager.cs
public class SoundManager
{
    AudioSource[] _audioSources = new AudioSource[(int)Define.Sound.MaxCount];
    Dictionary<string, AudioClip> _audioClips = new Dictionary<string, 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;
            }
            _audioSources[(int)Define.Sound.Bgm].loop = true;
        }
    }
    public void Clear()
    {
        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)
    {
        AudioClip audioClip = GetOrAddAudioClip(path, type);
        Play(audioClip, type, pitch);
    }
    public void Play(AudioClip audioClip, Define.Sound type = Define.Sound.Effect, float pitch = 1.0f)
    {
        if (audioClip == null)
            return;
        if (type == Define.Sound.Bgm)
        {
            AudioSource audioSource = _audioSources[(int)Define.Sound.Bgm];
            if (audioSource.isPlaying)
                audioSource.Stop();
            audioSource.pitch = pitch;
            audioSource.clip = audioClip;
            audioSource.Play();
        }
        else
        {
            AudioSource audioSource = _audioSources[(int)Define.Sound.Effect];
            audioSource.pitch = pitch;
            audioSource.PlayOneShot(audioClip);
        }
    }
    AudioClip GetOrAddAudioClip(string path, Define.Sound type = Define.Sound.Effect)
    {
        if (path.Contains("Sounds/") == false)
            path = $"Sounds/{path}";
        AudioClip audioClip = null;
        if (type == Define.Sound.Bgm)
        {
            audioClip = Managers.Resource.Load<AudioClip>(path);
        }
        else
        {
            if (_audioClips.TryGetValue(path, out audioClip) == false)
            {
                audioClip = Managers.Resource.Load<AudioClip>(path);
                _audioClips.Add(path, audioClip);
            }
        }
        if (audioClip == null)
        {
            Debug.Log($"AudioClip Missing! {path}");
        }
        return audioClip;
    }
}- Managers.cs
public class Managers : MonoBehaviour
{
    ...
    SoundManager _sound = new SoundManager();
    ...
    public static SoundManager Sound { get { return Instance._sound; } }
    ...
}- Define.cs
public class Define
{
    ...
    public enum Sound
    {
        Bgm,
        Effect,
        MaxCount,
    }
    ...
}- TestSound.cs
public class TestSound : MonoBehaviour
{
    public AudioClip audioClip;
    public AudioClip audioClip2;
    int i = 0;
    private void OnTriggerEnter(Collider other)
    {
        i++;
        if(i%2==0)
            Managers.Sound.Play(audioClip, Define.Sound.Bgm);
        else
            Managers.Sound.Play(audioClip2, Define.Sound.Bgm);
    }
}Memory Optimization
- Managers.cs
public class Managers : MonoBehaviour
{
    ...
    public static void Clear()
    {
        Input.Clear();
        Sound.Clear();
        Scene.Clear();
        UI.Clear();
    }
}- InputManager.cs
public void Clear()
{
    KeyAction = null;
    MouseAction = null;
}- UIManager.cs
public void Clear()
{
    CloseAllPopupUI();
    _sceneUI = null;
}- SceneManagerEX.cs
public void LoadScene(Define.Scene type)
{
    Managers.Clear();
    //CurrentScene.Clear();
    ...
}
...
public void Clear()
{
    CurrentScene.Clear();
}- LoginScene.cs
public override void Clear()
{
    Debug.Log("LoginScene Clear!");
}3D Sound
- make 3D Sound on Cube One - change Spatial Blend to 1. This mean this Sound Source is 3D Sound.
- change min and max distance
 
 
 - for Next chapter, you need to remove TestSounc.cs and Sound Source Component on Cube One
Pool
- Pooling - If there is so much object in game, it will make some trouble when loading.
- to decrese this overloading, pool manager will work
- pool manager loads object before they are activate setting non-activate
- when the are needed in game, pool manager will switch their status to activate from non-activate
 
Pool Manager
- Manager.cs
public class Managers : MonoBehaviour
{
    ...
    PoolManager _pool = new PoolManager();
    ...
    public static PoolManager Pool { get { return Instance._pool; } }
    ...
    static void Init()
    {
        if (s_instance == null)
        {
            ...
            s_instance._pool.Init();
            ...
        }
    }
    public static void Clear()
    {
        ...
        Pool.Clear();
    }
}- Scripts\Managers\Poolable.cs
public class Poolable : MonoBehaviour
{
    public bool IsUsing;
}- Scripts\Managers\PoolManager.cs
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<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);
            if (parent == null)
                poolable.transform.parent = Managers.Scene.CurrentScene.transform;
            poolable.transform.parent = parent;
            poolable.IsUsing = true;
            return poolable;
        }
    }
    #endregion
    Dictionary<string, Pool> _pool = new Dictionary<string, Pool>();
    Transform _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);
    }
    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();
    }
}- ResourceManager.cs
public T Load<T>(string path) where T: Object
{
    if (typeof(T) == typeof(GameObject))
    {
        ...
        GameObject go = Managers.Pool.GetOriginal(name);
        if (go != null)
            return go as T;
    }
    ...
}
    
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)
        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)
    {
        Managers.Pool.Push(poolable);
        return;
    }
    Object.Destroy(go);
}Test
- GameScene.cs
protected override void Init()
{
    ...
    for (int i = 0; i < 5; i++)
        Managers.Resource.Instantiate("UnityChan");
}- LoginScene.cs
protected override void Init()
{
    ...
    List<GameObject> list = new List<GameObject>();
    for (int i = 0; i < 5; i++)
        list.Add(Managers.Resource.Instantiate("UnityChan"));
    foreach(GameObject obj in list)
    {
        Managers.Resource.Destroy(obj);
    }
}- Test - create new prefab for player
- remove player in hierarchy
- add player in CameraController component
 
 
  
 Coroutine
- can Save/Restore status of function - stop a work spending so much time
- stop/restore function when you want
 
return
- yield retun object - return type what you want(even class)
 
- yield return null - pass this tic
 
- yield return break; - stop this coroutine
 
Test
- GameScene.cs
  
public class GameScene : BaseScene
{
    Coroutine co;
    protected override void Init()
    {
        ...
        co = StartCoroutine("ExplodeAfterSeconds", 4.0f);
        StartCoroutine("CoStopExplode", 6.0f);
    }
    IEnumerator CoStopExplode(float seconds)
    {
        Debug.Log("Stop Enter");
        yield return new WaitForSeconds(seconds);
        Debug.Log("Stop Execute!");
        if (co != null)
        {
            StopCoroutine(co);
            co = null;
        }
    }
    IEnumerator ExplodeAfterSeconds(float seconds)
    {
        Debug.Log("Explode Enter");
        yield return new WaitForSeconds(seconds);
        Debug.Log("Explode Execute!");
        co = null;
    }
}Data
- JSON - Normally, data is managed by json files
 
- Assets\Resources\Data\StatData.json
{
  "stats": [
    {
      "level": "1",
      "hp": "100",
      "attack": "10"
    },
    {
      "level": "2",
      "hp": "150",
      "attack": "15"
    },
    {
      "level": "3",
      "hp": "200",
      "attack": "20"
    }
  ]
}Data Manager
- Data Manager - control the json files of data
 
- Managers.cs
public class Managers : MonoBehaviour
{
    ...
    DataManager _data = new DataManager();
    ...
    public static DataManager Data { get { return Instance._data; } }
    ...
    static void Init()
    {
        if (s_instance == null)
        {
            ...
            s_instance._data.Init();
            ...
        }
    }
}- Scripts\Managers\DataManager.cs
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>
    {
        TextAsset textAsset = Managers.Resource.Load<TextAsset>($"Data/{path}");
        return JsonUtility.FromJson<Loader>(textAsset.text);
    }
}Test
- GameScene.cs
protected override void Init()
{
    ...
    Dictionary<int,Stat> dict = Managers.Data.StatDict;
}