是该文章记录框架使用的一些api,目前取自TE框架Demo分支,介绍的总结。

一、开始热更逻辑(GameApp)

    /// <summary>
    /// 开始游戏业务层逻辑。
    /// <remarks>显示UI、加载场景等。</remarks>
    /// </summary>
    private void StartGameLogic()
    {
        StartBattleRoom().Forget();
    }

    private async UniTaskVoid StartBattleRoom()
    {
        //加载场景
        await GameModule.Scene.LoadScene("scene_battle").ToUniTask();
        //战斗系统单例
        BattleSystem.Instance.LoadRoom().Forget();
    }

二、单例

1、行为单例(BehaviourSingleton)

using Cinemachine;
using Cysharp.Threading.Tasks;
using GameLogic.GameLogic;
using TEngine;
using UnityEngine;
using UnityEngine.SceneManagement;
using YooAsset;
using AudioType = TEngine.AudioType;

namespace GameLogic
{
    /// <summary>
    /// 战斗房间
    /// </summary>
    [Update]
    public class BattleSystem : BehaviourSingleton<BattleSystem>
    {
        private enum ESteps
        {
            None,
            Ready,
            Spawn,
            WaitSpawn,
            WaitWave,
            GameOver,
        }

        private GameObject _roomRoot;

        private ESteps _steps = ESteps.None;
        protected bool m_IsPaused;
        /// <summary>
        /// 加载房间
        /// </summary>
        public async UniTaskVoid LoadRoom()
        {
            //_startWaitTimer = 1f;

            //await UniTask.Yield();
            await GameModule.Scene.LoadScene("Demo",LoadSceneMode.Single,false,100,
                OnLoadSceneSuccess,true,OnLoadSceneProgress);
            // 创建房间根对象
            //GameModule.UI.ShowUI<UILoading>();
      

            // 加载背景音乐
            //GameModule.Audio.Play(AudioType.Music, "music_background", true);

            // 创建玩家实体对象
            //GameModule.Entity.ShowEntity<EntityPlayer>("Player");
            
           

            // 显示战斗界面
            //GameModule.UI.ShowUIAsync<UIBattleWindow>();
            //GameModule.Entity.ShowEntity<EntityPlayer>();
        
            
            // 监听游戏事件
            //GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.PlayerDead, OnPlayerDead);
            //GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.EnemyDead, OnEnemyDead);
            //GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.AsteroidExplosion, OnAsteroidExplosion);
            //GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.PlayerFireBullet, OnPlayerFireBullet);
            //GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.EnemyFireBullet, OnEnemyFireBullet);

            _steps = ESteps.Ready;
        }

        public override void Update()
        {
            if (Input.GetKeyDown(KeyCode.Escape))
            {
                if (GameModule.UI.HasWindow<UI_Menu>())
                {
                    GameModule.UI.CloseUI<UI_Menu>();
                }
                else
                {
                    GameModule.UI.ShowUI<UI_Menu>();
                }
            }
            UpdateRoom();
        }

        private void OnLoadSceneProgress(float progressValue)
        {
  
        }

        private void OnLoadSceneSuccess(SceneHandle obj)
        {
            _roomRoot = new GameObject("BattleRoom");
            var SpawnPoint=GameObject.Find("World/Dyp SpawnPoint");
            var handle = PoolManager.Instance.GetGameObject("Player", parent: _roomRoot.transform);
            handle.transform.position = SpawnPoint.transform.position;
            var entity = handle.GetComponent<EntityPlayer>();
            //
            var vcam_obj=GameObject.Find("Cameras/Main Vcam");
            vcam_obj.TryGetComponent<CinemachineVirtualCamera>(out var vcam);
            vcam.Follow = handle.transform;
        }
        /// <summary>
        /// 更新房间
        /// </summary>
        public void UpdateRoom()
        {
            if (_steps == ESteps.None || _steps == ESteps.GameOver)
                return;

            // if (_steps == ESteps.Ready)
            // {
            //     _startWaitTimer -= Time.deltaTime;
            //     if (_startWaitTimer <= 0)
            //     {
            //         _steps = ESteps.Spawn;
            //     }
            // }
            //
            // if (_steps == ESteps.Spawn)
            // {
            //     var enemyLocation = _entityLocations[Random.Range(0, 4)];
            //     Vector3 spawnPosition = new Vector3(Random.Range(-_spawnValues.x, _spawnValues.x), _spawnValues.y, _spawnValues.z);
            //     Quaternion spawnRotation = Quaternion.identity;
            //
            //     if (enemyLocation == "enemy_ship")
            //     {
            //         // 生成敌人实体
            //         var gameObject = PoolManager.Instance.GetGameObject(enemyLocation,parent: _roomRoot.transform);
            //         gameObject.transform.position = spawnPosition;
            //         gameObject.transform.rotation = spawnRotation;
            //         var entity = gameObject.GetComponent<EntityEnemy>();
            //         entity.InitEntity();
            //     }
            //     else
            //     {
            //         // 生成小行星实体
            //         var gameObject = PoolManager.Instance.GetGameObject(enemyLocation,parent: _roomRoot.transform);
            //         gameObject.transform.position = spawnPosition;
            //         gameObject.transform.rotation = spawnRotation;
            //         var entity = gameObject.GetComponent<EntityAsteroid>();
            //         entity.InitEntity();
            //     }
            //
            //     _waveSpawnCount++;
            //     if (_waveSpawnCount >= EnemyCount)
            //     {
            //         _steps = ESteps.WaitWave;
            //     }
            //     else
            //     {
            //         _steps = ESteps.WaitSpawn;
            //     }
            // }
            //
            // if (_steps == ESteps.WaitSpawn)
            // {
            //     _spawnWaitTimer -= Time.deltaTime;
            //     if (_spawnWaitTimer <= 0)
            //     {
            //         _spawnWaitTimer = 0.75f;
            //         _steps = ESteps.Spawn;
            //     }
            // }
            //
            // if (_steps == ESteps.WaitWave)
            // {
            //     _waveWaitTimer -= Time.deltaTime;
            //     if (_waveWaitTimer <= 0)
            //     {
            //         _waveWaitTimer = 4f;
            //         _waveSpawnCount = 0;
            //         _steps = ESteps.Spawn;
            //     }
            // }
        }

        #region 接收事件

        private void OnPlayerDead(Vector3 position, Quaternion rotation)
        {
            // 创建爆炸效果
            // var gameObject = PoolManager.Instance.GetGameObject("explosion_player",parent: _roomRoot.transform);
            // gameObject.transform.position = position;
            // gameObject.transform.rotation = rotation;
            // var entity = gameObject.GetComponent<EntityEffect>();
            // entity.InitEntity();
            // _steps = ESteps.GameOver;
            // GameEvent.Send(ActorEventDefine.GameOver);
        }

        private void OnEnemyDead(Vector3 position, Quaternion rotation)
        {
            // 创建爆炸效果
            // var gameObject = PoolManager.Instance.GetGameObject("explosion_enemy",parent: _roomRoot.transform);
            // gameObject.transform.position = position;
            // gameObject.transform.rotation = rotation;
            // var entity = gameObject.GetComponent<EntityEffect>();
            // entity.InitEntity();
            //
            // _totalScore += EnemyScore;
            // GameEvent.Send(ActorEventDefine.ScoreChange, _totalScore);
        }

        private void OnAsteroidExplosion(Vector3 position, Quaternion rotation)
        {
            // 创建爆炸效果
            // var gameObject = PoolManager.Instance.GetGameObject("explosion_asteroid",parent: _roomRoot.transform);
            // gameObject.transform.position = position;
            // gameObject.transform.rotation = rotation;
            // var entity = gameObject.GetComponent<EntityEffect>();
            // entity.InitEntity();
            //
            // _totalScore += AsteroidScore;
            // GameEvent.Send(ActorEventDefine.ScoreChange, _totalScore);
        }

        private void OnPlayerFireBullet(Vector3 position, Quaternion rotation)
        {
            // 创建子弹实体
            // var gameObject = PoolManager.Instance.GetGameObject("player_bullet",parent: _roomRoot.transform);
            // gameObject.transform.position = position;
            // gameObject.transform.rotation = rotation;
            // var entity = gameObject.GetComponent<EntityBullet>();
            // entity.InitEntity();
        }

        private void OnEnemyFireBullet(Vector3 position, Quaternion rotation)
        {
            // 创建子弹实体
            // var gameObject = PoolManager.Instance.GetGameObject("enemy_bullet",parent: _roomRoot.transform);
            // gameObject.transform.position = position;
            // gameObject.transform.rotation = rotation;
            // var entity = gameObject.GetComponent<EntityBullet>();
            // entity.InitEntity();
        }

        #endregion

        #region UI事件
        
        /// <summary>
        /// Pause game暂停游戏
        /// </summary>
        public void Pause()
        {
            Time.timeScale = 0;
            GameModule.UI.ShowUIAsync<UI_Menu>();
            m_IsPaused = true;
        }
        
        /// <summary>
        /// Resume game.重新开始游戏
        /// </summary>
        public void Resume()
        {
            Time.timeScale = 1;
            GameModule.UI.HideUI<UI_Menu>();
            m_IsPaused = false;
        }
        
        /// <summary>
        /// Quit game.退出游戏
        /// </summary>
        public void Quit()
        {
            Application.Quit();
        }
        

        #endregion
    }
}

三、UI

1、打开/关闭UI

        // 显示界面
        GameModule.UI.ShowUIAsync<UIBattleWindow>();
		// 关闭界面		
		GameModule.UI.CloseUI<UIBattleWindow>();

2、UI脚本

using UnityEngine.UI;
using TEngine;
using TMPro;

namespace GameLogic
{
    [Window(UILayer.UI)]
    class UITset : UIWindow
    {
        public static readonly int UITestEventID = 8;
        #region 脚本工具生成的代码
        private Text m_textTitle;
        private TMP_Text m_tmp_textTitle;
        private Button m_btnTestButton;
        protected override void ScriptGenerator()
        {
            m_textTitle = FindChildComponent<Text>("m_textTitle");
            m_tmp_textTitle = FindChildComponent<TMP_Text>("m_tmp_textTitle");
            m_btnTestButton = FindChildComponent<Button>("m_btnTestButton");
            m_btnTestButton.onClick.AddListener(OnClickTestButtonBtn);

        }
        #endregion

        #region 事件
        protected override void RegisterEvent() {
            AddUIEvent(UITestEventID, Tlske);
        }

        private void OnClickTestButtonBtn()
        {
            GameEvent.Send(UITestEventID);
        }

        private void Tlske()
        {
            Log.Debug("UI测试");
        }
        #endregion

    }
}

3、UI事件

using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
using TEngine;

namespace GameLogic
{
    [Window(UILayer.UI)]
    class UIBattleWindow : UIWindow
    {
        #region 脚本工具生成的代码

        private Text m_textScore;
        private GameObject m_goOverView;
        private Button m_btnRestart;
        private Button m_btnHome;

        protected override void ScriptGenerator()
        {
            m_textScore = FindChildComponent<Text>("ScoreView/m_textScore");
            m_goOverView = FindChild("m_goOverView").gameObject;
            m_btnRestart = FindChildComponent<Button>("m_goOverView/m_btnRestart");
            m_btnHome = FindChildComponent<Button>("m_goOverView/m_btnHome");
            m_btnRestart.onClick.AddListener(UniTask.UnityAction(OnClickRestartBtn));
            m_btnHome.onClick.AddListener(UniTask.UnityAction(OnClickHomeBtn));
        }

        #endregion

        protected override void RegisterEvent()
        {
            AddUIEvent<int>(ActorEventDefine.ScoreChange, OnScoreChange);
            AddUIEvent(ActorEventDefine.GameOver, OnGameOver);
        }

        protected override void OnRefresh()
        {
            m_textScore.text = "Score : 0";
            m_goOverView.SetActive(false);
        }

        #region 事件

        private async UniTaskVoid OnClickRestartBtn()
        {
            await UniTask.Yield();
            await GameModule.Scene.LoadScene("scene_battle").ToUniTask();
    
            BattleSystem.Instance.DestroyRoom();
            BattleSystem.Instance.LoadRoom().Forget();
        }

        private async UniTaskVoid OnClickHomeBtn()
        {
            await UniTask.Yield();
            // yield return YooAssets.LoadSceneAsync("scene_home");	
            // yield return UniWindow.OpenWindowAsync<UIHomeWindow>("UIHome");
            //
            // // 释放资源
            // var package = YooAssets.GetPackage("DefaultPackage");
            // package.UnloadUnusedAssets();
        }

        #endregion

        private void OnScoreChange(int currentScores)
        {
            m_textScore.text = $"Score : {currentScores}";
        }

        private void OnGameOver()
        {
            m_goOverView.SetActive(true);
        }
    }
}

四、资源

1、YooAssets加载释放资源

    //点击回主页按钮 释放资源
    private async UniTaskVoid OnClickHomeBtn()
    {
        await UniTask.Yield();
        //等待主场景
        // yield return YooAssets.LoadSceneAsync("scene_home");	
        //等待主页面
        // yield return UniWindow.OpenWindowAsync<UIHomeWindow>("UIHome");
        //
        // 释放资源
        // var package = YooAssets.GetPackage("DefaultPackage");
        // package.UnloadUnusedAssets();
    }
    //关闭程序自动调用

    private void OnApplicationQuit()
	{
		YooAssets.Destroy();
	}

2、框架调用加载资源等

资源模块默认使用Addressable可寻址定位。(!注意需要打包的资源不可以重名)

资源模块运行模式有EditorSimulateMode、OfflinePlayMode以及HostPlayMode 编辑器模式下以顶部导航栏的选项卡为优先选项,打包后以Scene场景中ResourceModule脚本的Enum选项卡为优先选项(打包后不会走EditorSimulateMode)

Scene窗口Resource对象可以设置一些资源模块的常用设置,比如打包后的资源模式、资源校验等级以及自动卸载资源间隔等。


//同步加载。
GameModule.Resource.LoadAsset<SkillDisplayData>(location);

//异步加载。
GameModule.Resource.LoadAssetAsync<SkillDisplayData>(location, OnLoadSuccess);
private void OnLoadSuccess(AssetOperationHandle assetOperationHandle){}

//使用UniTask异步加载。
await GameModule.Resource.LoadAssetAsync<SkillDisplayData>(location,CancellationToken.None);

3、常用接口 

/// <summary>
/// 获取当前资源包版本。
/// </summary>
/// <returns>资源包版本。</returns>
public string GetPackageVersion();

/// <summary>
/// 异步更新最新包的版本。
/// </summary>
/// <param name="appendTimeTicks">请求URL是否需要带时间戳。</param>
/// <param name="timeout">超时时间。</param>
/// <returns>请求远端包裹的最新版本操作句柄。</returns>
public UpdatePackageVersionOperation UpdatePackageVersionAsync(bool appendTimeTicks = false, int timeout = 60);

/// <summary>
/// 向网络端请求并更新清单
/// </summary>
/// <param name="packageVersion">更新的包裹版本</param>
/// <param name="autoSaveVersion">更新成功后自动保存版本号,作为下次初始化的版本。</param>
/// <param name="timeout">超时时间(默认值:60秒)</param>
public UpdatePackageManifestOperation UpdatePackageManifestAsync(string packageVersion, bool autoSaveVersion = true, int timeout = 60);

/// <summary>
/// 创建资源下载器,用于下载当前资源版本所有的资源包文件。
/// </summary>
public ResourceDownloaderOperation CreateResourceDownloader();


/// <summary>
/// 清理包裹未使用的缓存文件。
/// </summary>
public ClearUnusedCacheFilesOperation ClearUnusedCacheFilesAsync();


/// <summary>
/// 清理沙盒路径。
/// </summary>
public void ClearSandbox();

/// <summary>
/// 强制执行释放未被使用的资源。
/// </summary>
/// <param name="performGCCollect">是否使用垃圾回收。</param>
public void ForceUnloadUnusedAssets(bool performGCCollect);

/// <summary>
/// 检查资源是否存在。
/// </summary>
/// <param name="assetName">要检查资源的名称。</param>
/// <returns>检查资源是否存在的结果。</returns>
public HasAssetResult HasAsset(string assetName);

/// 同步加载资源。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>资源实例。</returns>
T LoadAsset<T>(string assetName) where T : Object;

/// <summary>
/// 同步加载资源。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <param name="parent">父节点位置。</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>资源实例。</returns>
T LoadAsset<T>(string assetName, Transform parent) where T :Object;

/// <summary>
/// 同步加载资源。
/// </summary>
/// <param name="handle">资源操作句柄。</param>
/// <param name="assetName">要加载资源的名称。</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>资源实例。</returns>
T LoadAsset<T>(string assetName,out AssetOperationHandle handle) where T : Object;

/// <summary>
/// 同步加载资源。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <param name="handle">资源操作句柄。</param>
/// <param name="parent">父节点位置。</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>资源实例。</returns>
T LoadAsset<T>(string assetName, Transform parent,out AssetOperationHandle handle) where T :Object;

/// <summary>
/// 异步加载资源。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <param name="cancellationToken">取消操作Token。</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>异步资源实例。</returns>
UniTask<T> LoadAssetAsync<T>(string assetName,CancellationToken cancellationToken) where T : Object;

/// <summary>
/// 异步加载游戏物体。
/// </summary>
/// <param name="assetName">要加载的游戏物体名称。</param>
/// <param name="cancellationToken">取消操作Token。</param>
/// <returns>异步游戏物体实例。</returns>
UniTask<UnityEngine.GameObject> LoadGameObjectAsync(string assetName,CancellationToken cancellationToken);

/// <summary>
/// 同步加载资源并获取句柄。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>同步加载资源句柄。</returns>
AssetOperationHandle LoadAssetGetOperation<T>(string assetName) where T : Object;

/// <summary>
/// 异步加载资源并获取句柄。
/// </summary>
/// <param name="assetName">要加载资源的名称。</param>
/// <typeparam name="T">要加载资源的类型。</typeparam>
/// <returns>异步加载资源句柄。</returns>
AssetOperationHandle LoadAssetAsyncHandle<T>(string assetName) where T : Object;

/// <summary>
/// 同步加载子资源对象
/// </summary>
/// <typeparam name="TObject">资源类型</typeparam>
/// <param name="location">资源的定位地址</param>
public SubAssetsOperationHandle LoadSubAssetsSync<TObject>(string location) where TObject : UnityEngine.Object;

/// <summary>
/// 异步加载子资源对象
/// </summary>
/// <typeparam name="TObject">资源类型</typeparam>
/// <param name="location">资源的定位地址</param>
public SubAssetsOperationHandle LoadSubAssetsAsync<TObject>(string location) where TObject : UnityEngine.Object;

/// <summary>
/// 同步加载子资源对象
/// </summary>
/// <param name="assetInfo">资源信息。</param>
public SubAssetsOperationHandle LoadSubAssetsSync(AssetInfo assetInfo);

/// <summary>
/// 异步加载场景。
/// </summary>
/// <param name="location">场景的定位地址</param>
/// <param name="sceneMode">场景加载模式</param>
/// <param name="activateOnLoad">加载完毕时是否主动激活</param>
/// <param name="priority">优先级</param>
/// <returns>异步加载场景句柄。</returns>
SceneOperationHandle LoadSceneAsync(string location, LoadSceneMode sceneMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100);

/// <summary>
/// 异步加载场景
/// </summary>
/// <param name="assetInfo">场景的资源信息</param>
/// <param name="sceneMode">场景加载模式</param>
/// <param name="activateOnLoad">加载完毕时是否主动激活</param>
/// <param name="priority">优先级</param>
/// <returns>异步加载场景句柄。</returns>
SceneOperationHandle LoadSceneAsync(AssetInfo assetInfo, LoadSceneMode sceneMode = LoadSceneMode.Single, bool activateOnLoad = true, int priority = 100);

五、对象池

1、对象池管理

using System;
using System.Collections.Generic;
using TEngine;
using UnityEngine;

namespace GameLogic
{
    public class PoolManager : MonoBehaviour
    {
        private static PoolManager _instance;

        public static PoolManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = FindObjectOfType<PoolManager>();
                }

                if (_instance == null)
                {
                    GameObject gameObject = new GameObject();
                    gameObject.name = nameof(PoolManager);
                    _instance = gameObject.AddComponent<PoolManager>();
                    _instance.poolRootObj = gameObject;
                    DontDestroyOnLoad(_instance);
                }

                return _instance;
            }
        }

        [SerializeField] private GameObject poolRootObj;
        public Dictionary<string, GameObjectPoolData> gameObjectPoolDic = new Dictionary<string, GameObjectPoolData>();
        public Dictionary<string, ObjectPoolData> objectPoolDic = new Dictionary<string, ObjectPoolData>();

        public GameObject GetGameObject(string assetName, Transform parent = null)
        {
            GameObject obj = null;
            if (gameObjectPoolDic.TryGetValue(assetName, out var gameObjectPoolData) && gameObjectPoolData.poolQueue.Count > 0)
            {
                obj = gameObjectPoolData.GetObj(parent);
            }

            if (obj == null)
            {
                obj = GameModule.Resource.LoadGameObject(assetName, parent: parent);
                obj.name = assetName;
            }
            return obj;
        }

        public void PushGameObject(GameObject obj)
        {
            string objName = obj.name;
            if (gameObjectPoolDic.TryGetValue(objName, out var gameObjectPoolData))
            {
                gameObjectPoolData.PushObj(obj);
            }
            else
            {
                gameObjectPoolDic.Add(objName, new GameObjectPoolData(obj, poolRootObj));
            }
        }

        public T GetObject<T>() where T : class, new()
        {
            return CheckObjectCache<T>() ? (T)objectPoolDic[typeof(T).FullName].GetObj() : new T();
        }

        public void PushObject(object obj)
        {
            string fullName = obj.GetType().FullName;
            if (objectPoolDic.ContainsKey(fullName))
            {
                objectPoolDic[fullName].PushObj(obj);
            }
            else
            {
                objectPoolDic.Add(fullName, new ObjectPoolData(obj));
            }
        }

        private bool CheckObjectCache<T>()
        {
            string fullName = typeof(T).FullName;
            return fullName != null && objectPoolDic.ContainsKey(fullName) && objectPoolDic[fullName].poolQueue.Count > 0;
        }

        public void Clear(bool clearGameObject = true, bool clearCObject = true)
        {
            if (clearGameObject)
            {
                for (int index = 0; index < poolRootObj.transform.childCount; ++index)
                {
                    Destroy(poolRootObj.transform.GetChild(index).gameObject);
                }
                gameObjectPoolDic.Clear();
            }

            if (!clearCObject)
            {
                return;
            }
            objectPoolDic.Clear();
        }

        public void ClearAllGameObject() => Clear(clearCObject: false);

        public void ClearGameObject(string prefabName)
        {
            GameObject obj = poolRootObj.transform.Find(prefabName).gameObject;
            if (obj == null)
            {
                return;
            }

            Destroy(obj);
            gameObjectPoolDic.Remove(prefabName);
        }

        public void ClearGameObject(GameObject prefab) => ClearGameObject(prefab.name);

        public void ClearAllObject() => Clear(false);

        public void ClearObject<T>() => objectPoolDic.Remove(typeof(T).FullName);

        public void ClearObject(Type type) => objectPoolDic.Remove(type.FullName);
    }

    public class ObjectPoolData
    {
        public readonly Queue<object> poolQueue = new Queue<object>();

        public ObjectPoolData(object obj) => PushObj(obj);

        public void PushObj(object obj) => poolQueue.Enqueue(obj);

        public object GetObj() => poolQueue.Dequeue();
    }

    public class GameObjectPoolData
    {
        public readonly GameObject fatherObj;
        public readonly Queue<GameObject> poolQueue;

        public GameObjectPoolData(GameObject obj, GameObject poolRootObj)
        {
            fatherObj = new GameObject(obj.name);
            fatherObj.transform.SetParent(poolRootObj.transform);
            poolQueue = new Queue<GameObject>();
            PushObj(obj);
        }

        public GameObjectPoolData(GameObject fatherObj)
        {
            this.fatherObj = fatherObj;
        }

        public void PushObj(GameObject obj)
        {
            poolQueue.Enqueue(obj);
            obj.transform.SetParent(fatherObj.transform);
            obj.SetActive(false);
        }

        public GameObject GetObj(Transform parent = null)
        {
            GameObject go = poolQueue.Dequeue();
            go.SetActive(true);
            go.transform.SetParent(parent);
            if (parent == null)
            {
                UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(go, UnityEngine.SceneManagement.SceneManager.GetActiveScene());
            }

            return go;
        }
    }
}
// 创建玩家实体对象
        var handle = PoolManager.Instance.GetGameObject("player_ship", parent: _roomRoot.transform);
        var entity = handle.GetComponent<Entity_Player>();
	PoolManager.Instance.PushGameObject(this.gameObject);

2、对象池模块

对象池较中量级,在客户端开发中是一个经常使用的技术,技术点我相信大家都懂,这里不过多讨论。

使用案例

/// <summary>
/// Actor对象。
/// </summary>
public class Actor : ObjectBase
{
    /// <summary>
    /// 释放对象。
    /// </summary>
    /// <param name="isShutdown">是否是关闭对象池时触发。</param>
    protected override void Release(bool isShutdown){}

    /// <summary>
    /// 创建Actor对象。
    /// </summary>
    /// <param name="actorName">对象名称。</param>
    /// <param name="target">对象持有实例。</param>
    /// <returns></returns>
    public static Actor Create(string name, object target)
    {
        var actor = MemoryPool.Acquire<Actor>();
        actor.Initialize(name, target);
        return actor;
    }
}

/// <summary>
/// Actor对象池。
/// </summary>
private IObjectPool<Actor> _actorPool;
    
void Start()
{
    //创建允许单次获取的对象池。
    _actorPool = GameModule.ObjectPool.CreateSingleSpawnObjectPool<Actor>(Utility.Text.Format("Actor Instance Pool ({0})", name));
}

/// <summary>
/// 创建Actor对象。
/// </summary>
/// <param name="actorName">对象名称。</param>
/// <param name="target">对象持有实例。</param>
/// <returns>Actor对象实例</returns>
public Actor CreateActor(string actorName, GameObject target)
{
    Actor ret = null;
    if (_actorPool.CanSpawn())
    {
        ret = _actorPool.Spawn();
    }
    else
    {
        ret = Actor.Create(actorName, target);
        _actorPool.Register(ret,true);
    }

    return ret;
}

3、延迟销毁(如特效)

using GameLogic;
using TEngine;
using UnityEngine;

public class EntityEffect : MonoBehaviour
{
	public float DelayDestroyTime = 1f;

	public void InitEntity()
	{
		Invoke(nameof(DelayDestroy), DelayDestroyTime);
	}
	private void DelayDestroy()
	{
		PoolManager.Instance.PushGameObject(this.gameObject);
	}
}

六、事件

1、Unitask插件(高性能,零GC的async/await异步方案)

    /// <summary>
    /// 开始游戏业务层逻辑。
    /// <remarks>显示UI、加载场景等。</remarks>
    /// </summary>
    private void StartGameLogic()
    {
        StartBattleRoom().Forget();
    }

    private async UniTaskVoid StartBattleRoom()
    {
        BattleSystem.Instance.LoadRoom().Forget();
        await GameModule.Scene.LoadScene("scene_battle").ToUniTask();
    }

2、定义事件(GameEvent)

高效且无GC的事件系统GameEvent,可以指定事件ID/事件String监听和分发事件。通过事件来驱动模块,如战斗的角色身上的事件流、UI和网络以及Model的数据流、开发中的绝大部分情况都可以通过事件来进行驱动。(配合UI模块或者拓展的战斗模块实现MVE[Model - View - Event]事件驱动架构)

事件模块支持string和int作为事件Id,但推荐是使用int因为可以避免事件字典的哈希碰撞。这里实现了StringId.StringToHash的方法来定义事件ID达到事件系统的最佳性能。

注:UI模块的事件和UI生命周期存在绑定,销毁UI时可以自动移除UI所监听的事件,开发过程中只需要关心添加事件,避免了关闭UI但没有移除事件监听的问题,角色模块也可以参考实现。(AddUIEvent)

using TEngine;

namespace GameLogic
{
    public static class ActorEventDefine
    {
        public static readonly int ScoreChange = RuntimeId.ToRuntimeId("ActorEventDefine.ScoreChange");
        public static readonly int GameOver = RuntimeId.ToRuntimeId("ActorEventDefine.GameOver");
        public static readonly int EnemyDead = RuntimeId.ToRuntimeId("ActorEventDefine.EnemyDead");
        public static readonly int PlayerDead = RuntimeId.ToRuntimeId("ActorEventDefine.PlayerDead");
        public static readonly int AsteroidExplosion = RuntimeId.ToRuntimeId("ActorEventDefine.AsteroidExplosion");
        public static readonly int EnemyFireBullet = RuntimeId.ToRuntimeId("ActorEventDefine.EnemyFireBullet");
        public static readonly int PlayerFireBullet = RuntimeId.ToRuntimeId("ActorEventDefine.PlayerFireBullet");
    }
}

3、添加和移除事件

      // 监听游戏事件
      GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.PlayerDead, OnPlayerDead);
      GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.EnemyDead, OnEnemyDead);
      GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.AsteroidExplosion, OnAsteroidExplosion);
      GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.PlayerFireBullet, OnPlayerFireBullet);
      GameEvent.AddEventListener<Vector3, Quaternion>(ActorEventDefine.EnemyFireBullet, OnEnemyFireBullet);

	// 移除游戏事件
    GameEvent.RemoveEventListener<Vector3, Quaternion>(ActorEventDefine.PlayerDead, OnPlayerDead);
    GameEvent.RemoveEventListener<Vector3, Quaternion>(ActorEventDefine.EnemyDead, OnEnemyDead);
    GameEvent.RemoveEventListener<Vector3, Quaternion>(ActorEventDefine.AsteroidExplosion, OnAsteroidExplosion);
    GameEvent.RemoveEventListener<Vector3, Quaternion>(ActorEventDefine.PlayerFireBullet, OnPlayerFireBullet);
    GameEvent.RemoveEventListener<Vector3, Quaternion>(ActorEventDefine.EnemyFireBullet, OnEnemyFireBullet);

//***************************************************************************
    //ui事件
	AddUIEvent<int>(ActorEventDefine.ScoreChange, OnScoreChange);
    AddUIEvent(ActorEventDefine.GameOver, OnGameOver);

4、激活事件

(1)游戏结束

    GameEvent.Send(ActorEventDefine.GameOver);

 (2)有间隔的开火

同时传入位置和方向

	private float _nextFireTime = 0f;
void Update()
{
	if (Input.GetButton("Fire1") && Time.time > _nextFireTime)
	{
		_nextFireTime = Time.time + FireRate;
		_audioSource.Play();
		GameEvent.Send(ActorEventDefine.PlayerFireBullet,_shotSpawn.position, _shotSpawn.rotation);
	}
}

5、事件源生成器

Unity TEngine开源框架 学习记录 五、事件源生成器-CSDN博客

 示例1-注册监听/激活

按1注册监听角色登录事件,按2登录,输出。

示例2 

public static readonly int Hellp = StringId.StringToHash("Hellp.Hellp");

class A
{
   public A()
   {
     //添加事件监听string
     GameEvent.AddEventListener("TEngine很好用",TodoSomeThings);
     //添加事件监听int 事件ID
     GameEvent.AddEventListener(Hellp,TodoSomeThings2);
   }
}

class B
{
  private void SaySomeThings()
  {
      //发送事件流
      GameEvent.Send("TEngine很好用");
      GameEvent.Send(Hellp);
  }
}

【举个例子:游戏中血量扣除的时候,服务器发来了一个减少Hp的消息包,
我们可以在收到这个消息包的时候发送一个事件流,在玩家头顶的HP进度
条组件/左上角Hp的UI血条组件添加一个监听事件,各个模块负责自己监听后的逻辑】
Server -> SendMessage(ReduceHP)

class ClientHandle
{
  private void HandleMessage(MainPack mainpack)
  {
    ...
    HpPack hpPack = mainpack.hpPack;
    int playerId = mainpack.playerId;
    var player = PlayerMgr.Instance.GetPlayer(playerId);
    if(player != null){
      player.Event.Send("Hpchange",hpPack);       //局部的事件管理器
      GameEvent.Send("Hpchange",hpPack);  //全局事件中心
    }
  }
}

class PlayerHp
{
  public ECSEventCmpt Event { get; set; }
  PlayerHp(){
    Event.AddEventListener<HpPack>(Hellp,HandleUpChange);
  }
}

[Window(UILayer.UI)]
class BattleMainUI: UIWindow
{
    public override void RegisterEvent()
    {
        AddUIEvent<HpPack>(Hellp,HandleUpChange);
    }

    public void HandleUpChange(HpPack pack){}
}

七、场景

1、加载场景

        await GameModule.Scene.LoadScene("scene_battle").ToUniTask();

2、场景挂起

private async UniTaskVoid StartBattleRoom()
   {
       await GameModule.Scene.LoadScene("Game", UnityEngine.SceneManagement.LoadSceneMode.Single, true).ToUniTask();
   }

private void OnClickBtn()
 {
		Log.Debug("点击");
    //取消挂起
		GameModule.Scene.UnSuspend("Game");
 }

八、音乐

1、加载播放/暂停音乐

    // 加载背景音乐
    GameModule.Audio.Play(AudioType.Music, "music_background", true);
    //停止音乐
    GameModule.Audio.Stop(AudioType.Music, true);

九、内存池模块

内存池更为轻量化,相对于对象池更适合一些更抽象碎片化的内存对象。

Scene窗口MemoryPool对象可以设置内存池检查,防止回收问题与内存泄漏问题。 

image

/// <summary>
/// 资源组数据。
/// <remarks>DisposeGroup。</remarks>
/// </summary>
public class AssetGroup : IMemory
{
    public void Clear(){}
}

//内存池中获取/生成内存对象。
AssetGroup assetGroup = MemoryPool.Acquire<AssetGroup>();

//释放内存对象还给内存池。
MemoryPool.Release(assetGroup);

十、配置表模块(ConfigSystem)

接入最佳游戏配置解决方案 - Luban

Luban文档

在TEngine中Luban配置表目录位于以下目录

安装luban配置表

1.在TEngine根目录同级克隆下最新的luban-next仓库。 

 2.Tools目录执行build-luban完成 

3.转表则去luban配置目录执行对应bat

TEngine内置默认使用懒加载配置,也支持基于UniTask的异步加载,同步加载,包括服务器的Task异步加载,使用对应转表的bat即可。

介绍

luban是你的最佳游戏配置解决方案。

luban高效地处理游戏开发中常见的excel、json、xml之类的数据,检查数据错误,生成c#等各种语言的代码,导出成bytes或json等多种格式。

luban统一了游戏配置开发工作流,极大提升了策划和程序的工作效率。

核心特性 强大的数据解析和转换能力 {excel(csv,xls,xlsx)、json、bson、xml、yaml、lua、unity ScriptableObject} => {binary、json、bson、xml、lua、yaml、erlang、 custom format} 增强的excel格式,可以简洁地配置出像简单列表、子结构、结构列表,以及任意复杂的深层次的嵌套结构。 完备的类型系统,支持OOP类型继承,搭配excel、json、lua、xml等格式数据灵活优雅表达行为树、技能、剧情、副本之类复杂GamePlay数据 支持生成c#、java、go、c++、lua、python、javascript、typescript、erlang、rust、gdscript 代码 支持生成 protobuf(schema + binary + json)、flatbuffers(schema + json)、msgpack(binary) 强大的数据校验能力。ref引用检查、path资源路径、range范围检查等等 完善的本地化支持。静态文本值本地化、动态文本值本地化、时间本地化、main-patch多地区版本 强大灵活的自定义能力,支持自定义代码模板和数据模板 通用型生成和缓存工具。也可以用于生成协议、数据库之类的代码,甚至可以用作对象缓存服务。


使用案例

/// <summary>
/// 道具配置表管理器。
/// </summary>
public class ItemConfigMgr: Singleton<ItemConfigMgr>
{
    /// <summary>
    /// 道具Table。
    /// </summary>
    private TbItem TbItem => ConfigLoader.Instance.Tables.TbItem;

    /// <summary>
    /// 获取道具配置表。
    /// </summary>
    /// <param name="itemId">道具Id。</param>
    /// <returns>道具配置表。</returns>
    public ItemConfig GetItemConfig(int itemId)
    {
        TbItem.DataMap.TryGetValue(itemId, out var config);
        return config;
    }
}

十一、商业化流程模块(ProcedureModule)

ProcedureLaunch - 流程启动

ProcedureSplash - 流程闪屏

ProcedureInitPackage - 流程初始化Package

ProcedureInitResources - 流程初始化Resources

ProcedureUpdateVersion - 流程更新版本Version

ProcedureUpdateManifest - 流程更新Mainfest清单

ProcedureCreateDownloader - 流程创建下载器

ProcedureDownloadFile - 流程下载文件

ProcedureDownloadOver - 流程下载文件结束

ProcedureClearCache - 流程清理缓存

ProcedureLoadAssembly - 流程加载进入热更新程序集

十二、商业化UI模块​​​​​​(UIModule)

一个游戏70%都是UI,剩下30%才是GamePlay,所以有一套简洁强大的商业化UI模块以及UI开发工作流将是项目的一大利器,能够提高至少一倍的开发效率。(配合事件模块实现MVE[Model - View - Event]事件驱动架构)

UI脚本为纯C#实现,脱离Mono的生命周期,由UIModule的帧更新驱动并管理UI的生命周期。

IUIBehaviour为UI通用行为接口、UIBase为UI基类、UIWindow为UI窗口基类,UIWidget为UI组件基类。

前期配置:

注意!!!!m_item节点为特殊节点表示是UI下的UIWidget组件,不会继续往下遍历生成UI代码。若需要这个UIWidget组件m_item的代码则在m_item右键生成这个组件的UI脚本。

Scene窗口下右键ScriptGenerator菜单下About目录有默认UI命名前缀规范。 

有自定义需求可以在TEngineSetting下进行自定义。 

添加TMP

找到FrameworkGlobalSettings脚本, 复制命令宏ENABLE_TEXTMESHPRO

 

开发工作流

1.遵守前期默认配置或者自定义配置进行UI编排 

2.在UI的根节点右键ScriptGenerator生成UI代码到剪贴板上!!!(注-使用-UniTask的生成代码可以做异步事件流驱动的UI。) 

3.自行创建UI脚本到需要的目录下并复制UI脚本。

举例示范

// 同步打开面板
GameModule.UI.ShowUI<GameMainUI>([nullable]userData);

// 异步打开面板
GameModule.UI.ShowUIAsync<GameMainUI>([nullable]userData);

namespace GameLogic
{
    /// <summary>
    /// BattleMainUI面板
    /// <remarks>UIWindow需要以下特性,UILayer可以自行定义,fullScreen表示为全屏面板会停止和隐藏这个面板堆栈后面的面板。</remarks>
    /// </summary>
    [Window(UILayer.Bottom,fullScreen:true)]
    class BattleMainUI : UIWindow
    {
        private TouchMove m_touchView;
        
        #region 脚本工具生成的代码
        private RectTransform m_rectContainer;
        private GameObject m_itemTouch;
        private Button m_btnLeaveBattle;
        private GameObject m_goTopInfo;
        private Button m_btnPause;
        public override void ScriptGenerator()
        {
            m_rectContainer = FindChildComponent<RectTransform>("m_rectContainer");
            m_itemTouch = FindChild("m_rectContainer/m_itemTouch").gameObject;
            m_btnLeaveBattle = FindChildComponent<Button>("m_btnLeaveBattle");
            m_goTopInfo = FindChild("m_goTopInfo").gameObject;
            m_btnPause = FindChildComponent<Button>("m_goTopInfo/m_btnPause");
            m_btnLeaveBattle.onClick.AddListener(OnClickLeaveBattleBtn);
            m_btnPause.onClick.AddListener(OnClickPauseBtn);
        }
        #endregion

        #region 事件

        private void OnClickPauseBtn()
        {
            BattleSys.Instance.Pause = !BattleSys.Instance.Pause;
        }

        private void OnClickLeaveBattleBtn()
        {
            BattleSys.Instance.StopBattle(isBattleEnd:false,isWin:false);
        }
        #endregion

        //注册事件举例
        public override void RegisterEvent()
        {
            //通过AddUIEvent这样注册事件会把事件的生命周期绑定给面板,面板销毁的时候自动移除监听。
            AddUIEvent(ActorLogicEventDefined.OnMainPlayerBagDataChange, RefreshUI);
        }

        public override void BindMemberProperty()
        {
            //特殊的m_item节点的域不属于父级UIWindows,所以如注意所说需要同样创建这个UIWidget的脚本并生成代码过去。 可以如下创建或者走type、path创建。
            m_touchView = CreateWidget<TouchMove>(m_itemTouch);
        }

        ......
    }
}

namespace GameLogic
{
    /// <summary>
    /// 移动操作UIWidget。
    /// </summary>
    class TouchMove : UIWidget, IUICtrlMove
    {
        public override void BindMemberProperty()
        {
        }

        //注意Update只有在重写了此方法才会驱动这个Widget或者面板的Update。
        public override void OnUpdate()
        {
            TProfiler.BeginSample("CheckMoveTouchFinger");
            CheckMoveTouchFinger();
            TProfiler.EndSample();

            TProfiler.BeginSample("UpdateTouchMovePos");
            UpdateTouchMovePos();
            TProfiler.EndSample();

            TProfiler.BeginSample("UpdateKeyMove");
            UpdateKeyMove();
            TProfiler.EndSample();
        }
        ......
    }
}

UI进阶

UI面板需要标记UIWindowAttribute,以标识层级(可以自行定义)和是否全屏。全屏面板则会把下层面板的Visible设置为false。 

  [Window(UILayer.Bottom,fullScreen:true)]

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐