有時會想, 我學這些幹甚麼?!
在香港大概沒有公司需要我的材能. 反正沒有生產甚麼大製作的遊戲公司.
Technical Artist 的名頭好像只能自己 FF 一下. 反正入職後都一概叫 Programmer 不是嗎?
高級清潔工跟清潔工基本沒分別的.
本次很簡單, 用一堆小弟 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 寫在裡面.
[2] 自家制醜醜的 loading screen. 共用的 prefab.
Menu 整個獨佔一個 scene.
運作中境況. loading