Unity TEngine5开源框架 学习记录 四、模块介绍
是该文章记录框架使用的一些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对象可以设置内存池检查,防止回收问题与内存泄漏问题。
/// <summary>
/// 资源组数据。
/// <remarks>DisposeGroup。</remarks>
/// </summary>
public class AssetGroup : IMemory
{
public void Clear(){}
}
//内存池中获取/生成内存对象。
AssetGroup assetGroup = MemoryPool.Acquire<AssetGroup>();
//释放内存对象还给内存池。
MemoryPool.Release(assetGroup);
十、配置表模块(ConfigSystem)
接入最佳游戏配置解决方案 - 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)]
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐

所有评论(0)