一个Unity游戏框架

一个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 等系统

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值