EveryDay.DevUp

[Unity] 프레임 워크 개발 - UI Manager( v.0.1.1 ) 본문

FrameWork

[Unity] 프레임 워크 개발 - UI Manager( v.0.1.1 )

EveryDay.DevUp 2020. 7. 8. 00:01

▣ 기본 설정

https://everyday-devup.tistory.com/44

 

[Unity] 프레임 워크 가이드 - 패키지 다운 및 기본 씬 설정 ( v.0.1.1 )

※ Unity 2019.3.10f1, Android Build 환경 ● Framework Package를 다음의 링크에서 다운로드 https://everyday-devup.tistory.com/40 [Unity] 프레임 워크 기능 및 사용 가이드 프레임워크의 기능 업데이트 및..

everyday-devup.tistory.com

▣ 기반 지식

https://everyday-devup.tistory.com/54

 

[Unity] 게임 UI를 관리하기 위한 Stack 시스템

게임 UI ( User Interface ) : 게임과 유저 사이의 의사소통을 위해 제공하는 물리적 가상적 매개체로 가시적으로 보이는 화면 ( 버튼, 스크롤 등)과 터치, 드래그와 같은 조작 방법을 이야기 한다. ▶ �

everyday-devup.tistory.com

UIManager는 Stack으로 UI를 관리하는 기능을 하며, UI을 추가/삭제/업데이트의 기능을 담당한다. 모든 UI는 UIBase 클래스를 상속받아서 구현하여 UIBase로 관리된다.

using UnityEngine;
using UnityEngine.AddressableAssets;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.EventSystems;

namespace EverydayDevup.Framework
{
	/// <summary>
	/// UI를 관리하는 클래스
	/// </summary>
	public class UIManager : MonoBehaviour, IManager
	{
		/// <summary>
		/// 게임에서 사용하는 모든 UI가 정의된 ScriptableObject
		/// </summary>
		public UIAddressable uIAddressable;
		/// <summary>
		/// UI의 최상위 Canvas로, 해당 Canvas하위에 UI가 생성되고 삭제됨
		/// </summary>
		public Canvas rootCanvas;

		/// <summary>
		/// 현재 생성되고 있는 UI 정보 
		/// </summary>
		UIAddressableData currentData = null;
		/// <summary>
		/// UI를 순서에 따라 순차적으로 생성하기 위해 Queue로 관리 
		/// </summary>
		Queue<int> requestUIQueue = new Queue<int>();
		/// <summary>
		/// 현재 화면에 보여지는 UI를 Stack으로 관리 
		/// </summary>
		Stack<UIBase> uiBases = new Stack<UIBase>();

		/// <summary>
		/// 화면에 새로운 UI를 추가하기 위해 호출되는 함수
		/// </summary>
		/// <param name="type">추가할 UI의 type</param>
		public void Push(UI_TYPE type)
		{
			requestUIQueue.Enqueue( (int)type );
		}

		/// <summary>
		/// 매 프레임마다 현재 UI를 업데이트하며, 추가할 ui가 있는 경우 ResourceManager에서 읽어옴 
		/// </summary>
		public void ManagerUpdate()
		{
			UIBase uiBase;
			UIBase[] uiBaseArray = uiBases.ToArray();
			for( int i = 0, icount = uiBaseArray.Length; i < icount; ++i )
			{
				uiBase = uiBaseArray[i];
				if( uiBase != null )
				{
					uiBase.UIUpdate();
				}
			}

			/// 추가할 UI가 있는 경우
			if( requestUIQueue != null && requestUIQueue.Count > 0 )
			{
				///현재 로드 중인 UI가 없는 경우
				if( currentData == null )
				{
					UI_TYPE requestUI = (UI_TYPE)requestUIQueue.Dequeue();
					OnLoad( requestUI );
				}
			}
		}

		/// <summary>
		/// 매 초마다 현재 UI를 업데이트
		/// </summary>
		public void ManagerLoop()
		{
			UIBase uiBase;
			UIBase[] uiBaseArray = uiBases.ToArray();
			for( int i = 0, icount = uiBaseArray.Length; i < icount; ++i )
			{
				uiBase = uiBaseArray[i];
				if( uiBase != null )
				{
					uiBase.UILoop();
				}
			}
		}

		/// <summary>
		/// UI를 불러오는 함수 
		/// </summary>
		/// <param name="type"></param>
		void OnLoad(UI_TYPE type)
		{
			/// 정의된 UI 데이터로 부터 추가할 UI 데이터를 얻은 후 리소스 매니저를 통해 로드 
			currentData = uIAddressable.GetUIAddressableDataByUIType( type );
			Game.Instance.resourcesManager.Load( currentData.reference, rootCanvas.transform, OnComplete, OnFail );
		}

		/// <summary>
		/// UI가 정상적으로 로드 되었을 때 불리는 함수 
		/// </summary>
		/// <param name="go"></param>
		void OnComplete(GameObject go)
		{
			/// UI의 기본 좌표 및 앵커 값을 수정
			RectTransform rectTransform = go.GetComponent<RectTransform>();
			rectTransform.SetParent( rootCanvas.transform );
			rectTransform.offsetMax = Vector2.zero;
			rectTransform.offsetMin = Vector2.zero;
			rectTransform.localPosition = Vector3.zero;

			/// 추가된 UI가 전체화면인 경우 기존 UI에서 전체화면인 UI를 비활성화
			UIBase addUI = go.GetComponent<UIBase>();
			/// 추가된 UI 설정
			addUI.UIInit( currentData, uiBases.Count );
			addUI.UIActive();
			addUI.UIUpdate();

			if( addUI.IsFullScreen )
			{
				UIBase[] uiBaseArray = uiBases.ToArray();
				UIBase uiBase;
				for( int i = uiBaseArray.Length - 1; i >= 0; --i )
				{
					uiBase = uiBaseArray[i];
					if( uiBase.IsFullScreen )
					{
						uiBase.UIInActive();
						break;
					}
				}
			}

			/// 추가된 UI를 Stack에 추가
			uiBases.Push( addUI );
			currentData = null;
		}

		/// <summary>
		/// UI 로드에 실패할 경우 호출되는 함수 
		/// </summary>
		void OnFail()
		{
			currentData = null;
		}

		/// <summary>
		/// UI를 삭제할 때 호출되는 함수 
		/// </summary>
		public void Pop(UI_TYPE type)
		{
			if( uiBases.Count <= 0 )
			{
				return;
			}

			/// Pop할 UI_TYPE을 찾기 위해 임시 Stack을 설정 
			Stack<UIBase> tempStack = new Stack<UIBase>();
			UIBase uiBase;
			while( uiBases.Count > 0 )
			{
				uiBase = uiBases.Pop();
				if( uiBase.type == type )
				{
					/// pop하는 UI가 전체 화면이였던 경우, 남은 UI중 전체화면인 가장 상단의 UI를 활성화 
					if( uiBase.IsFullScreen )
					{
						UIBase otherUIBase;
						UIBase[] uiBaseArray = uiBases.ToArray();
						for( int i = uiBaseArray.Length - 1; i >= 0; --i )
						{
							otherUIBase = uiBaseArray[i];
							if( otherUIBase.IsFullScreen )
							{
								otherUIBase.UIActive();
								break;
							}
						}
					}

					Game.Instance.resourcesManager.UnLoad( uiBase.AssetReference, uiBase.gameObject, uiBase.IsCaching );
					uiBase.UIClear();
				}
				else
				{
					tempStack.Push( uiBase );
				}
			}

			// 기존 UI의 순서를 맞추기 위해 tempStack을 다시 옮김 
			while( tempStack.Count > 0 )
			{
				uiBase = tempStack.Pop();
				uiBases.Push( uiBase );
			}
		}

		/// <summary>
		/// 현재 ui를 모두 지움
		/// </summary>
		public void ManagerClear()
		{
			UIBase baseUI;
			while( uiBases.Count > 0 )
			{
				baseUI = uiBases.Pop();
				if( baseUI != null )
				{
					baseUI.UIClear();
				}
			}
		}
	}
}
using UnityEngine;
using UnityEngine.AddressableAssets;

namespace EverydayDevup.Framework
{
	/// <summary>
	/// UI가 기본적으로 상속받는 클래스
	/// </summary>
	[RequireComponent( typeof( Canvas ))]
	public class UIBase : MonoBehaviour, IBase
	{
		/// <summary>
		/// UI 데이터를 가지고 있는 ScriptableObject
		/// </summary>
		UIAddressableData uiAddressableData;
		/// <summary>
		/// UI의 최상위에 있는 Canvas
		/// </summary>
		Canvas canvas;
		
		/// <summary>
		/// 해당 UI가 전체 화면인지 체크
		/// </summary>
		public bool IsFullScreen
		{
			get
			{
				return uiAddressableData.isFullScreen;
			}
		}

		/// <summary>
		/// 해당 UI가 캐싱되는지 체크
		/// </summary>
		public bool IsCaching
		{
			get
			{
				return uiAddressableData.isCaching;
			}
		}

		/// <summary>
		/// 해당 UI의 Addressable 주소 
		/// </summary>
		public AssetReference AssetReference
		{
			get
			{
				return uiAddressableData.reference;
			}
		}

		/// <summary>
		/// 해당 UI의 UI_TYPE
		/// </summary>
		public UI_TYPE type
		{
			get
			{
				return uiAddressableData.type;
			}
		}

		/// <summary>
		/// UI가 로드 되었을 때 불리는 초기화 함수
		/// </summary>
		/// <param name="uiAddressableData"> UI 정보</param>
		/// <param name="stackCount"> UI의 스택 위치 </param>
		public void UIInit(UIAddressableData uiAddressableData, int stackCount )
		{
			this.uiAddressableData = uiAddressableData;
			canvas = GetComponent<Canvas>();
			canvas.overrideSorting = true;
			canvas.sortingOrder = stackCount;

			OnInit();
		}

		/// <summary>
		/// UI가 활성화 되면 불리는 함수 
		/// </summary>
		public void UIActive()
		{
			canvas.enabled = true;
			OnActive();
		}

		/// <summary>
		/// UI가 비활성화 되면 불리는 함수
		/// </summary>
		public void UIInActive()
		{
			canvas.enabled = false;
			OnInActive();
		}

		/// <summary>
		/// UI가 업데이트 될때마다 호출되는 함수
		/// </summary>
		public void UIUpdate()
		{
			if( canvas.enabled == false )
			{
				return;
			}

			OnUpdate();
		}

		/// <summary>
		/// 매 초마다 호출되는 함수
		/// </summary>
		public void UILoop()
		{
			if( canvas.enabled == false )
			{
				return;
			}

			OnLoop();
		}

		/// <summary>
		/// UI가 사라질 때 호출 되는 함수 
		/// </summary>
		public void UIClear()
		{
			OnClear();
			uiAddressableData = null;
			GameObject.Destroy( gameObject, 0f );
		}

		public virtual void OnInit() {}

		public virtual void OnActive() {}

		public virtual void OnInActive() {}

		public virtual void OnUpdate() {}

		public virtual void OnLoop() {}

		public virtual void OnClear() {}
	}
}

▣ 사용 가이드

: UILoading을 구현하여, Game.cs에서 Start에서 로고 UI를 출력할 수 있음

using UnityEngine;
using EverydayDevup.Framework;

public class UILoading : UIBase
{
	public UIReference refData;

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

		TMPro.TMP_Text text = refData.GetData<TMPro.TMP_Text>( "company_name" );
		text.SetText( "EveryDay.DevUp" );
	}
}
public class Game : Singleton<Game>
{
	protected virtual void Start()
	{
		uiManager.Push( UI_TYPE.LOADING );
	}
}