有時會想, 我學這些幹甚麼?!
在香港大概沒有公司需要我的材能. 反正沒有生產甚麼大製作的遊戲公司.
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
