[ ์น์ 7. UI ]
๏ผ UI ๊ธฐ์ด
- UI ์ถ๊ฐ์ ์๋์ผ๋ก Canvas๊ฐ ์์ฑ๋๋๋ฐ ์ด๋ ๋ํ์ง ์ญํ ์ ํ๋ค.
- UI๋ Rect Transform Component๋ฅผ ํตํด ์ขํ๋ฅผ ์ค์ ํ๋ค.
- UI๋ ์๊ทผ๋ฒ์ ์ ์ฉ๋ฐ์ง ์๋๋ค.
- Shift๋ฅผ ๋๋ฅธ์ฑ๋ก UI ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ ๊ฒฝ์ฐ ๋น์จ์ ์ ์งํ๋ฉฐ ํฌ๊ธฐ๋ฅผ ์กฐ์ ํ ์ ์๋ค.
- Anchor๋ UI์์ ์๋นํ ์ค์ํ ์ญํ ์ ํ๋ค.
๏ผ Rect Transform
- ๋๋ฐ์ด์ค์ ์ข ๋ฅ์ ๋ฐ๋ผ Screen์ ํฌ๊ธฐ๋ ์ ๊ฐ๊ฐ์ด๋ค. ์ด๋ Anchor ๊ธฐ๋ฅ์ด ์ค์ํ๊ฒ ์์ฉํ๋ค.
- Anchor๋ Rect Transform์ Component๋ก ๊ฐ์ง ๋ถ๋ชจ๋ฅผ ๊ฐ์ ธ์ผ ํ์ฑํ๊ฐ ๋๋ค.
- ๋ถ๋ชจ์ Anchor ์ฌ์ด์ ๊ฑฐ๋ฆฌ๋ ๋น์จ๋ก, Anchor์ ๋ณธ์ธ ์ฌ์ด์ ๊ฑฐ๋ฆฌ๋ ๊ณ ์ ๊ฑฐ๋ฆฌ๋ก ์ฐ์ฐ์ ํ๋ค.
- 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(); } }โ
- ํ์ ์ฐฝ ๋ค 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๋ฌธ์ ๋ฐฐ์ด์ ์ํํ๋ฉด์ ๊ฐ๊ฐ์ ๋ฐ์ดํฐ ์์๋ค์ ์์๋๋ก ์ ๊ทผํ๋ฉฐ ๋ฐฐ์ด์ ๋์ ๋๋ฌํ ๊ฒฝ์ฐ ์๋์ผ๋ก ๋ฐ๋ณต์ด
์ข ๋ฃ๋๋ค.
- 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 : ๋ช ์ด ํ ์คํ)
- 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 ] ๋ฅผ ํตํด ๋ง๋ค ์ ์๊ณ ์๋์ ๊ฐ์ด ๋ง๋ ๋ค.
- 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๋ฅผ ์์๋ฐ์ ์ ์๋ค.