Technical Artist : test case

有時會想, 我學這些幹甚麼?!
在香港大概沒有公司需要我的材能. 反正沒有生產甚麼大製作的遊戲公司.
Technical Artist 的名頭好像只能自己 FF 一下. 反正入職後都一概叫 Programmer 不是嗎?
高級清潔工跟清潔工基本沒分別的.

tech_artist

本次很簡單, 用一堆小弟 Photoshop 自製的破圖把界面堆到盡可能的 Sci-fi.
由於 Photoshop 的功力不深的關係本次只用 Pixel Art (檔案少)
也順道測試一下開發中的 Panel Manager的使用者流程.
Multi scene loader 的操作也感覺不錯.

 

SceneLoader.cs

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

using Kit.UI;

namespace Kit.SceneControl
{
	public enum eLoadState
	{
		Idle = 0,
		Loading = 1, // start loading scene
		SceneLoaded = 2, // every scene loaded
		PostLoading = 3, // period will wait for program request.
		Loaded = 100 // back to Idle
	}

	/// <summary>main progress can use Coroutine Start() to listen the eloadState, in order to align Start moment of scene.</summary>
	public class SceneLoader : Singleton<SceneLoader>
	{
		[SerializeField] Camera m_LoadingCamera;
		[SerializeField] Panel m_LoadingUi;
		[SerializeField] float m_MinPostLoadingSecond = 3f, m_MaxPostLoadingSecond = 7f;

		List<AsyncSceneObj> m_Loaders = new List<AsyncSceneObj>();
		IEnumerator m_Coroutine;
		UnityEngine.SceneManagement.Scene m_NextActiveScene;
		eLoadState m_State = eLoadState.Idle;
		bool m_Dirty = false;
		HashSet<int> m_PostLock = new HashSet<int>();
		public eLoadState State { get { return m_State; } private set { m_State = value; m_Dirty = true; } }
		public event System.Action OnStateChange, OnLoadFinished;

		struct AsyncSceneObj
		{
			public AsyncOperation operation;
			public string sceneName;
		}

		protected override void Awake()
		{
			base.Awake();
			DontDestroyOnLoad(this);
			m_LoadingUi.OpenStart += OnPanelOpenStart;
			m_LoadingUi.CloseEnd += OnPanelCloseEnd;
			State = eLoadState.Idle;
		}

		protected void Update()
		{
			if(m_Dirty)
			{
				m_Dirty = false;
				if (OnStateChange != null)
					OnStateChange();
			}
		}

		protected override void OnDestroy()
		{
			base.OnDestroy();
			m_LoadingUi.OpenStart -= OnPanelOpenStart;
			m_LoadingUi.CloseEnd -= OnPanelCloseEnd;
		}
		
		public void LoadScene(string[] sceneNames)
		{
			if (State > eLoadState.Idle && State < eLoadState.Loaded)
				throw new System.Exception("loader in use, LoadScene request Failed");
			if (sceneNames.Length == 0)
				return;

			// force reset lock
			m_PostLock.Clear();
			m_Loaders.Clear();
			State = eLoadState.Loading;

			m_LoadingUi.Open();
			m_LoadingCamera.enabled = true;
			foreach (string sceneName in sceneNames)
			{
				if (!string.IsNullOrEmpty(sceneName))
				{
					m_Loaders.Add(
						new AsyncSceneObj()
						{
							operation = SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive),
							sceneName = sceneName
						});
					m_Loaders[m_Loaders.Count - 1].operation.allowSceneActivation = true;
				}
			}
			m_NextActiveScene = SceneManager.GetSceneByName(sceneNames[0]);

			if (m_Coroutine != null)
			{
				StopCoroutine(m_Coroutine);
				m_Coroutine = null;
			}
			m_Coroutine = Loading();
			StartCoroutine(m_Coroutine);
		}

		IEnumerator Loading()
		{
			while (m_Loaders.Count > 0)
			{
				if (m_Loaders[0].operation.isDone)
					m_Loaders.RemoveAt(0);
				yield return null;
			}

			State = eLoadState.SceneLoaded;
			SceneManager.SetActiveScene(m_NextActiveScene);
			yield return new WaitForEndOfFrame();
			
			// min time to wait for register m_PostLock
			yield return new WaitForSeconds(m_MinPostLoadingSecond);

			float countdown = m_MaxPostLoadingSecond;
			while (m_PostLock.Count > 0 && countdown > 0)
			{
				yield return null;
				countdown -= Time.deltaTime;
			}
			if(m_PostLock.Count > 0)
				Debug.LogWarning("SceneLoader Instance locked timeout : Total = " + m_PostLock.Count + "\n" + string.Join(", ", m_PostLock.Select(o => o.ToString()).ToArray()));
			
			m_LoadingUi.Close();
			State = eLoadState.Loaded;
			if (OnLoadFinished != null)
				OnLoadFinished();
		}

		private void OnPanelOpenStart(IPanel obj) { m_LoadingCamera.enabled = true; }
		private void OnPanelCloseEnd(IPanel obj) { m_LoadingCamera.enabled = false; }

		public void Lock(Object obj) { m_PostLock.Add(obj.GetInstanceID()); }
		public void Unlock(Object obj) { m_PostLock.Remove(obj.GetInstanceID()); }
	}
}

SceneInitializer.cs

using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections;

namespace Kit.SceneControl
{
	public class SceneInitializer : MonoBehaviour
	{
		[SerializeField] string[] m_AutoLoadScene;
		[SerializeField] string m_LoaderScene = "Loading";
		[SerializeField] GameObject m_LoaderScreen;
		private string m_ActiveScene;
		static bool IsInited = false;

		void Awake()
		{
			if (IsInited)
			{
				// we only active once in entire game
				DestroyImmediate(gameObject);
			}
			else if (SceneLoader.Instance == null)
			{
				Instantiate(m_LoaderScreen, Vector3.up * 10f, Quaternion.LookRotation(Vector3.down, Vector3.forward));
			}
			IsInited = true;
		}

		IEnumerator Start()
		{
			while (SceneLoader.Instance == null)
				yield return null;
			yield return 1;
			if (m_AutoLoadScene.Length > 0)
				SceneLoader.Instance.LoadScene(m_AutoLoadScene);

			if (!string.IsNullOrEmpty(m_LoaderScene))
			{
				while (SceneManager.GetActiveScene().name == m_LoaderScene)
					yield return null;
				yield return 1;
				SceneManager.UnloadScene(m_LoaderScene);
			}
		}
	}
}

設定概念很簡單, 把場景都獨立設定然後動態載入到 active scene.
正常流程是由開始畫面 -> 載入 Menu UI, Menu Background -> start game -> destroy Background, destroy menu -> In Game menu & SceneA,

開發流程則可能由 SceneA 開始 -> load in game menu -> end game -> load Menu UI, Menu Background

目的就是方便開發流程,使遊戲可以在任何 scene 作為 start point.
[1] 各場景的設定, 把缺失的 Scene Name 寫在裡面.
Init

[2] 自家制醜醜的 loading screen. 共用的 prefab.
loading_prefab

Menu 整個獨佔一個 scene.
menu

運作中境況. loading
real_loading

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

*

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料