一個Unity遊戲架構
主要把各種需求包裝成一個一個系統和一種種服務,再用一個腳本(ServicesLoader)去控制這些系統和服務的導入,再用一個loader 場景去顯示導入的程序,類似于遊戲中的導入畫面。
主要腳本包括
AsynCloader
public abstract class AsynCloader : MonoBehaviour
{
private class RoutineInfo
{
public RoutineInfo(IEnumerator routine, int weight, Func<float> progress)
{
this.routine = routine;
this.weight = weight;
this.progress = progress;
}
public readonly IEnumerator routine;
public readonly int weight;
public readonly Func<float> progress;
}
private Queue<RoutineInfo> _pending = new Queue<RoutineInfo>();
private bool _completedWithoutError = true;
private static event Action _loadingcomplete;
protected virtual void ProgressUpdated(float percentComplete) { }
public static bool Complete { get; private set; } = false;
public static float Progress { get; private set; } = 0.0f;
protected void Enqueue(IEnumerator routine, int weight, Func<float> progress = null)
{
_pending.Enqueue(new RoutineInfo(routine, weight, progress));
}
protected abstract void Awake();
private IEnumerator Start()
{
if (Complete)
{
Progress = 1.0f;
_pending.Clear();
yield break;
}
float percentCompleteByFullSetions = 0.0f;
int outof = 0;
var running = new Queue<RoutineInfo>(_pending);
_pending.Clear();
foreach (var routineInfo in running)
{
outof += routineInfo.weight;
}
while (running.Count != 0)
{
var routineInfo = running.Dequeue();
var routine = routineInfo.routine;
while (routine.MoveNext())
{
if (routineInfo.progress != null)
{
var routinePercent = routineInfo.progress() * (float)routineInfo.weight / (float)outof;
Progress = percentCompleteByFullSetions + routinePercent;
ProgressUpdated(Progress);
}
yield return routine.Current;
}
percentCompleteByFullSetions += (float)routineInfo.weight / (float)outof;
Progress = percentCompleteByFullSetions;
ProgressUpdated(Progress);
}
if (!_completedWithoutError)
{
Debug.Log("A fatal error occcured while initialization. Please check your logs and fix the error.");
}
Complete = true;
_loadingcomplete?.Invoke();
}
public static void CallOnComplete(Action callback)
{
if (Complete)
{
callback();
}
else
{
_loadingcomplete += callback;
}
}
}
GameLoader
public class GameLoader : AsynCloader
{
public int sceneIndexToLoad = 1;
public List<Component> GameModules;
public GameObject UIManagerPrefab = null;
public LoadingScreen loadingScreen = null;
private static int _sceneIndex = 1;
private static GameLoader _instance;
protected override void Awake()
{
Debug.Log("GameLoader Starting");
if (_instance != null && _instance != this)
{
Debug.Log("A Duplicated instance of the GameLoader was found, and will be ignored. Only one instance is permitted.");
Destroy(gameObject);
return;
}
_instance = this;
DontDestroyOnLoad(gameObject);
if (sceneIndexToLoad < 0 || sceneIndexToLoad >= SceneManager.sceneCountInBuildSettings)
{
Debug.Log($"Invalid Scene Index {sceneIndexToLoad} ... using default value of {_sceneIndex}");
}
else
{
_sceneIndex = sceneIndexToLoad;
}
GameObject systemGO = new GameObject("[Services]");
systemGO.tag = "Services";
Transform systemParent = systemGO.transform;
DontDestroyOnLoad(systemGO);
loadingScreen.UpdateLoadingStep("Loading Game Systems");
Enqueue(InitializeCoreSystems(systemParent), 50, UpdateCoreSystemsProgress);
Enqueue(InitializeModularSystems(systemParent), 50, UpdateModularSystemsProgress);
CallOnComplete(OnComplete);
}
private float _coreLoadTotalSteps = 20.0f;
private float _coreLoadCurrentStep = 0.0f;
private float UpdateCoreSystemsProgress()
{
return _coreLoadCurrentStep / _coreLoadTotalSteps;
}
private float _modularLoadTotalSteps = 300.0f;
private float _modularLoadCurrentStep = 0.0f;
private float UpdateModularSystemsProgress()
{
return _modularLoadCurrentStep / _modularLoadTotalSteps;
}
protected override void ProgressUpdated(float percentComplete)
{
base.ProgressUpdated(percentComplete);
loadingScreen.UpdateLoadingBar(percentComplete);
loadingScreen.UpdateLoadingStep("Progress: " + Mathf.Ceil(percentComplete * 100.0f) + "%");
}
private IEnumerator InitializeCoreSystems(Transform systemsParent)
{
Debug.Log("Loading core system");
_coreLoadCurrentStep = 1.0f;
GameObject uiGO = GameObject.Instantiate(UIManagerPrefab);
uiGO.transform.SetParent(systemsParent);
var uiMngr = uiGO.GetComponent<UIManager>();
ServiceLocator.Register<UIManager>(uiMngr);
yield return null;
_coreLoadCurrentStep += 1.0f;
GameObject gmGO = new GameObject("GameManager");
gmGO.transform.SetParent(systemsParent);
var gm = gmGO.AddComponent<GameManager>();
ServiceLocator.Register<GameManager>(gm.Initialize(sceneIndexToLoad));
yield return null;
for (int i = 0; i < _coreLoadTotalSteps - 2.0f; ++i)
{
_coreLoadCurrentStep += 1.0f;
yield return null;
}
}
private IEnumerator InitializeModularSystems(Transform systemsParent)
{
Debug.Log("Loading modular systems...");
// yield return null;
foreach (Component c in GameModules)
{
if (c is IGameModule)
{
IGameModule module = (IGameModule)c;
yield return module.LoadModule();
}
}
yield return null;
}
private void OnComplete()
{
Debug.Log("GameLoader Completed");
StartCoroutine(LoadInitialScene(_sceneIndex));
}
private IEnumerator LoadInitialScene(int index)
{
Debug.Log("GameLoader Starting Scene load");
var loadOp = SceneManager.LoadSceneAsync(index);
loadingScreen.UpdateLoadingStep("Loading Scene: " + index.ToString());
while (!loadOp.isDone)
{
loadingScreen.UpdateLoadingBar(loadOp.progress);
yield return loadOp;
}
}
}
ServiceLocator
public class ServiceLocator : MonoBehaviour
{
static private readonly Dictionary<System.Type, object> m_systems = new Dictionary<System.Type, object>();
static public T Register<T>(object target)
{
if (Contains<T>())
{
Debug.Log("There is already a type of: " + typeof(T) + " that exists.");
}
else
{
Debug.Log("Registering " + typeof(T));
m_systems.Add(typeof(T), target);
}
return (T)target;
}
static public T Get<T>()
{
object ret = null;
m_systems.TryGetValue(typeof(T), out ret);
if (ret == null)
{
Debug.Log("Could not find [" + (typeof(T)) + "] as a registered system.");
}
return (T)ret;
}
static public bool Contains<T>()
{
return m_systems.ContainsKey(typeof(T));
}
}
運用方法的話,分别以GameManager系統和objectpoolManager服務為例
GameManager
public class GameManager : MonoBehaviour
{
private static readonly Dictionary<int, int> _killEnemiesByLevel = new Dictionary<int, int>()
{
{1,4},
{2,3}
};
private int _topScore = 0;
private int _numEnemyKilled = 0;
private int _currentScore = 0;
private int _currentCoin = 0;
public int CurrentScore { get { return _currentScore; } }
private int _currentLevevl = 0;
public int CurrentLevel { get { return _currentLevevl; } }
public int CurrentCoin { get { return _currentCoin; } }
private UIManager _uiManager = null;
public GameManager Initialize(int startLevel)
{
//_topScore= ServiceLocator.Get<SaveSystem>().LoadBinary<int>("topScore");
GameLoader.CallOnComplete(OnGameLoaderComplete);
SetLevel(startLevel);
return this;
}
private void OnGameLoaderComplete()
{
_uiManager = ServiceLocator.Get<UIManager>();
}
private void SetLevel(int level)
{
_currentLevevl = level;
}
private void LoadNextLevel()
{
int nextLevel = ++_currentLevevl;
if (nextLevel > _killEnemiesByLevel.Count)
{
_uiManager.DisplayMessageText("YOU WIN");
Time.timeScale = 0;
return;
}
SceneManager.LoadScene(nextLevel);
SetLevel(nextLevel);
_numEnemyKilled = 0;
_uiManager.DisplayMessageText("");
}
public void UpdateScore(int score)
{
_currentScore += score;
_numEnemyKilled++;
_uiManager.UpdateScoreDisplay(_currentScore);
if (_currentScore >= _topScore)
{
//ServiceLocator.Get<SaveSystem>().SaveBinary<int>(_currentScore, "topScore");
}
CheckWinCondition();
}
public void GainCoin()
{
_currentCoin++;
}
public void UpdateHealthBar(float health)
{
_uiManager.UpdateHealthBar(health);
}
public void SetHealthBar(float maxhealth)
{
_uiManager.SetHealthBar(maxhealth);
}
public void UpdateTime()
{
_uiManager.UPdateTimeDisplay();
}
public void UpdateMessageText(string text)
{
_uiManager.DisplayMessageText(text);
}
private void CheckWinCondition()
{
int numberRequiredToWin = _killEnemiesByLevel[_currentLevevl];
if (_numEnemyKilled >= numberRequiredToWin)
{
_uiManager.DisplayMessageText("Level Completed");
LoadNextLevel();
}
}
public void Pause()
{
_uiManager.Pause();
}
}
ObjectPoolManager
public class ObjectPoolManager : MonoBehaviour, IGameModule
{
[Serializable]
public class PooledObject
{
public string name;
public GameObject prefab;
public int poolSize;
}
public List<PooledObject> objectsToPool = new List<PooledObject>();
public bool IsInitialized { get { return _isInitialized; } }
private bool _isInitialized = false;
private readonly Dictionary<string, List<GameObject>> _objectPoolByName = new Dictionary<string, List<GameObject>>();
#region IGameModule Implementation
IEnumerator IGameModule.LoadModule()
{
Debug.Log("Loading Object Pool");
InitializePool();
yield return new WaitUntil(() => { return IsInitialized; });
ServiceLocator.Register<ObjectPoolManager>(this);
yield return null;
}
#endregion
public GameObject GetObjectFromPool(string poolName)
{
GameObject ret = null;
if (_objectPoolByName.ContainsKey(poolName))
{
ret = GetNextObject(poolName);
}
else
{
Debug.Log("No pool exists with name: " + poolName);
}
return ret;
}
public List<GameObject> GetAllObjectsFromPool(string poolName)
{
if (_objectPoolByName.ContainsKey(poolName))
{
return _objectPoolByName[poolName];
}
Debug.Log("No Pool exists with name: " + poolName);
return new List<GameObject>();
}
public void RecycleObject(GameObject go)
{
go.SetActive(false);
}
private GameObject GetNextObject(string poolName)
{
List<GameObject> pooledObjects = _objectPoolByName[poolName];
foreach (GameObject go in pooledObjects)
{
if (go == null)
{
Debug.Log("Pooled Object is Null.");
continue;
}
if (go.activeInHierarchy)
{
continue;
}
else
{
return go;
}
}
Debug.Log("Object Pool Deleted: No Unused Objects To Return.");
return null;
}
private void InitializePool()
{
GameObject PoolManagerGO = new GameObject("Object Pool");
PoolManagerGO.transform.SetParent(GameObject.FindWithTag("Services").transform);
foreach (PooledObject poolObj in objectsToPool)
{
if (!_objectPoolByName.ContainsKey(poolObj.name))
{
Debug.Log(string.Format(" Creating Pool: {0} Size: {1}", poolObj.name, poolObj.poolSize));
GameObject poolGO = new GameObject(poolObj.name);
poolGO.transform.SetParent(PoolManagerGO.transform);
_objectPoolByName.Add(poolObj.name, new List<GameObject>());
for (int i = 0; i < poolObj.poolSize; ++i)
{
GameObject go = Instantiate(poolObj.prefab);
go.name = string.Format("{0}_{1:000}", poolObj.name, _objectPoolByName[poolObj.name].Count);
go.transform.SetParent(poolGO.transform);
go.SetActive(false);
_objectPoolByName[poolObj.name].Add(go);
}
}
else
{
Debug.Log("Warning: Attempting to create multiple pools with the same name: " + poolObj.name);
continue;
}
}
_isInitialized = true;
}
}
為了記錄哪些功能屬于服務,運用了interface IGameModule
IGameModule
public interface IGameModule
{
IEnumerator LoadModule();
}
另外還可以根據需求添加UIManager, AudioManager, SaveSystem 等系統