Unity客户端框架,包含资源管理、音频管理、场景管理、UI管理、Excel工具、打包工具、热更新、网络等。
Window -> Package Manager -> + -> Add package from git URL...
输入https://gitee.com/yajink/FusionFrameworks.git?path=Assets/[模块包名]#v[MAJOR.MINOR.PATCH]
, 点击Add
[模块包名]
- com.fusion.utilities 工具模块(*必选)
- com.fusion.async 异步处理模块(*必选)
- com.fusion.frameworks 主框架(*必选)
- com.fusion.protobuf Protobuf模块(可选)
- com.fusion.net 网络模块(可选)
- com.fusion.hotfix 热更模块(可选)由于热更模块中已经对网络模块做了配置,因此需要先导入网络模块。
https://gitee.com/yajink/fusion-test
https://gitee.com/yajink/cyber-zero
所有需要打包为AssetBundle(以下简称AB)的资源都需要存放在 Assets/GameAssets/[xxx] 文件夹下,可以根据自己的习惯为不同的资源划分多个子文件夹,但根目录必须创建一个 BuildSetting 配置文件,并且GameAssets下不能放资源。
BuildSetting 可以通过 Assets/Create/FusionConfig/Build Setting 来创建
*注意:相同目录下资源不能重名,后缀名不同也不行。例如:a.jpg 和 a.mat 不能存放在相同文件夹中。
参数 | 说明 |
---|---|
Build Mode |
打包模式 1. Debug 调试版本 2. Release 正式版本 |
Compress Type |
压缩方式 1. LZMA 2. Uncompress 不压缩 3. LZ4 |
Build Target Type |
平台 1. Use Current Target 使用当前平台 2. Android 3. Standalone Windows 4. Standalone Windows 64 * 暂时不支持其他平台 |
Init Scene |
初始场景 初始场景文件会由Unity打入单独的AssetBundle中,因此不需要放在GameAssets下 |
Version |
版本号 在打完AssetBundle后,会与老版本的资源进行对比,若Large Middle Small相同,并且Resource大于老版本,就会生成一个对应老版本的Patch文件用于热更新 |
AB名称会在打AssetBundle时自动设置。
为某个资源设置AB名称时,会在当前文件夹中查找 BuildProperty 配置文件,若找不到,则递归寻找父文件夹。
若到GameAssets文件夹还未找到,则使用默认 BuildProperty 配置。
BuildProperty 可以通过 Assets/Create/FusionConfig/Build Property 来创建
参数 | 说明 |
---|---|
Type |
AB命名方式 1. None 名称为null 2. File 用文件名命名 3. Folder 用所在文件夹名称命名 |
Compress Type |
压缩方式 * 若压缩方式与Build Setting配置不同,则该资源不会被记录在依赖资源文件中,只能作为主要资源加载 1. LZMA 2. Uncompress 不压缩 3. LZ4 4. Use Build Setting 使用Build Setting中的压缩方式 |
若需要将Sprite打入图集,可以通过 AtlasProperty 配置文件,该配置文件的查找方式与 BuildProperty 相同。
只会将符合配置条件的Sprite打入图集,其余资源还是按照 BuildProperty 来设置。
AtlasProperty 可以通过 Assets/Create/FusionConfig/Atlas Property 来创建
参数 | 说明 |
---|---|
Pack Unit |
图集粒度 0 文件夹中所有符合条件的Sprite打入一张图集 -1 不打图集 [n] 符合条件的n张Sprite打入一张图集 |
Ignore Size |
过滤Sprite分辨率 X Sprite的宽 Y Sprite的高 大于这个分辨率的Sprite不会被打入图集 |
将资源放入指定文件夹并且完成上述配置后,就可以通过 Build/BuildAssets 菜单选项进行打包。AssetBundle会被入 StreamingAssets/ManagedAssets 文件夹中。
可以在 项目根目录/FusionTemp 文件夹下找到当前版本的资源、老版本资源MD5以及热更Patchs等。
操作 | 说明 |
---|---|
Build/BuildAssets | 打AssetsBundle |
Build/BuildPlayer | 直接打可执行文件 |
Build/BuildDynamic | 打出热更文件 |
Build/Build | 打AssetsBundle,并且打出可执行文件 |
AssetsManager/ClearAssetBundleName | 清除AssetBundle名称 |
AssetsManager/CopyAssetsToStreamingAssets | 将FusionTemp下的资源拷贝到StreamingAssets下 |
AssetsManager/SwichEditorAssetLoadType | 切换Editor下资源加载方式 AssetBundle AssetDatabase |
AssetsManager/Build | 打AssetsBundle,但不会拷贝到StreamingAssets下 |
可以通过 AssetsManager 或 AssetsUtility 来对资源进行加载和销毁。
资源应的AssetBundle拥有两个引用值:非持久引用、持久引用。
当调用资源加载方法时,其对应的AssetBundle的引用值(persistentAsset=false为非持久引用,true为持久引用)会加1。
当调用资源销毁方法时,其对应的AssetBundle的引用值(persistentAsset=false为非持久引用,true为持久引用)会减1。
当AssetBundle 非持久引用 和 持久引用 和(后续使用引用值表示它们的和)为0时,AssetBundle会被销毁。
切换场景时,非持久引用 会被清零。
默认加载资源引用是 非持久引用。
Sprite sprite = AssetsManager.Instance.Load<Sprite>("Sprites/hearts");
persistentAsset 传入 true,可将资源标记为持久资源。
AssetsManager.Instance.LoadAsync("Sprites/hearts", delegate (Sprite sprite)
{
//在回调中对加载的资源进行处理
});
persistentAsset 传入 true,可将资源标记为持久资源。
AssetsUtility.Release("Sprites/hearts");
引用值为0时,为防止AssetBundle销毁后又立刻被加载,会有一个5秒的延迟。 persistentAsset 传入 true,可销毁持久资源。
AssetsUtility.ReleaseImmediate("Sprites/hearts");
引用值为0时,AssetBundle立刻被销毁。 persistentAsset 传入 true,可销毁持久资源。
GameObject go = AssetsUtility.CreateGameObject("Prefabs/Cube");
AssetsUtility.CreateGameObjectAsync("Prefabs/Cube", finishCallback: delegate (GameObject go)
{
//在回调中可以处理加载的对象
});
AssetsUtility.Release(go);
引用值为0时,为防止AssetBundle销毁后又立刻被加载,会有一个5秒的延迟。
AssetsUtility.ReleaseImmediate(go);
引用值为0时,AssetBundle立刻被销毁。
AssetsUtility.SetSpriteOfImage(gameObject, "Sprites/hearts");
为Image设置图片。
当Image对象或父对象被销毁时,资源对应的AssetBundle的非持久引用会减去1。
AssetsUtility.SetAudioSourceClip(audioSource, "Audios/1");
为AudioSource设置图片。
当AudioSource对象或父对象被销毁时,资源对应的AssetBundle的非持久引用会减去1。
音频播放需要 AudioPlayer 对象
AudioPlayer audioPlayer = new AudioPlayer();
构造方法中可传入gameObject,这样可以让AudioPlayer变为3D播放。
构造方法中可传入persistent,标记该音频播放器是持久的。持久音频切换场景不会被销毁,只有在切换音频时,会将上一个音频销毁。
audioPlayer.FadeInSetter(1.0f);
audioPlayer.FadeOutSetter(1.0f);
duration 淡入淡出的时间
audioPlayer.LoopSetter(true);
audioPlayer.VolumeSetter(0.5f);
参数为[0.0, 1.0] 区间的浮点数
audioPlayer.Play("Audios/1");
audioPlayer.Stop();
audioPlayer.Pause();
audioPlayer.Release();
为音频播放器设置标签,可以批量修改某个标签下的所有音频播放器的参数。
audioPlayer.TagSetter("BGM");
AudioManager.Instance.SetTagVolume("BGM", 0.5f);
ScenesManager.Instance.Load("Scenes/First");
LoadSceneMode.Single 加载单个场景 LoadSceneMode.Additive 额外场景 clearFlag 是否清理资源
ScenesManager.Instance.LoadAsync("Scenes/First");
LoadSceneMode.Single 加载单个场景 LoadSceneMode.Additive 额外场景
startCallback 回调方法参数为 AsyncOperation
finishCallback 加载结束回调
clearFlag 是否清理资源
异步加载任务是通过LoadAsyncTask类来加载场景,这种加载方式可以加载一个Single场景和多个Addtion场景并且自动合并Single与Addition的加载进度。除此之外,还提供了场景数据的保存于加载功能。
ScenesManager.LoadAsyncTask loadAsyncTask = new ScenesManager.LoadAsyncTask("Scenes/First");
loadAsyncTask.AddAdditive("Scenes/Second");
loadAsyncTask.updateCallback = delegate (float progress)
{
//在此处理进度变更逻辑
};
loadAsyncTask.finishCallback = delegate ()
{
//在此处理加载完成逻辑
};
loadAsyncTask.AddAdditive("Scenes/Second");
loadAsyncTask.AddSceneDataHandler(new SceneUIHandler());
SceneUIHandler 可以保存旧场景的UI栈。
当从场景A跳转到场景B时,SceneUIHandler会保存场景A的UI栈。下一次跳转回场景A时,会自动加载SceneUIHandler中的UI栈。自定义SceneHandler
- 新建一个类,实现SceneDataHandler接口。
- 在接口Save中写保存数据的逻辑。
- 在Load中写加载数据的逻辑。
ScenesManager.LoadAsyncOperation loadAsyncOperation = loadAsyncTask.Schedule();
loadAsyncOperation 中也可以获取到进度Progress,以及是否完成IsDone。
使用UI管理功能之前,需要将Prefab Packages/com.fusion.frameworks/Runtime/Prefabs/UI/UI 拖入初始场景中。
可以修改UI Prefab的属性,但不能更改子节点的层级关系。
UI支持热更,详情在热更部分
一个UI Prefab需要对应一个UI脚本。
UI Prefab所在的相对路径就是创建UI页面的路径。
UI脚本的命名空间+类名,必须与UI Prefab路径对应。
以 Prefabs/UI/Page1 为例:
using Fusion.Frameworks.UI;
namespace Prefabs.UI
{
public class Page1 : UIObject
{
public Page1()
{
}
public Page1(UIData data) : base(data)
{
}
public override void Init()
{
base.Init();
}
public override void Update()
{
base.Update();
}
public override void OnFinished()
{
base.OnFinished();
}
public override void OnDestroy()
{
base.OnDestroy();
}
}
}
必须有一个默认构造函数,反射需要调用。
生命周期函数
Init 仅在UI创建的时候执行一次。
Update 创建UI时,会执行一次Update。当UI对象的updateLastUI属性为true时,Finish会自动调用上一个页面的Update方法进行页面更新。 OnFinished 当调用UI的Finish方法时,会触发此回调。
OnDestroy 当页面被销毁时,会触发此回调。
UIManager.Instance.Launch("Prefabs/UI/Page1");
UIManager.Instance.LaunchAsync("Prefabs/UI/Page1");
向UI页面传递数据,需要一个数据类。
这个类继承自UIData
public class PageData : UIData
{
// 可以添加自定义的变量,例如下面的text变量。
public string text;
}
PageData pageData = new PageData();
pageData.text = "Passed Value";
UIManager.Instance.LaunchAsync("Prefabs/UI/Page1", pageData);
可以在UI脚本的有参构造方法中初始化数据
PageData pageData;
public Page1(UIData data) : base(data)
{
pageData = (PageData)data;
}
在 Init 方法中获取控件。
TextMeshProUGUI text;
Image image;
Button jumpToBtn;
Button closeBtn;
public override void Init()
{
base.Init();
text = Utility.Find<TextMeshProUGUI>(gameObject, "Text");
image = Utility.Find<Image>(gameObject, "Image");
jumpToBtn = Utility.Find<Button>(gameObject, "Jump");
closeBtn = Utility.Find<Button>(gameObject, "Close");
UIUtility.RegisterButtonAction(closeBtn, Finish);
UIUtility.RegisterButtonAction(jumpToBtn, JumpTo);
}
在 Update 方法中为控件赋值
public override void Update()
{
base.Update();
UIUtility.SetText(text, pageData.text);
UIUtility.SetImage(image, "Sprites/hearts");
}
SetImage以及UIUtility中的其他方法中加载的AssetBundle会进入资源管理模块,当页面被关闭时,资源会自动进行检测并释放。
UI的类型分为Window和Pop。
必须在UI脚本的构造方法中修改。
默认类型是Window。
public Page2(UIData data) : base(data)
{
type = UIType.Pop;
}
Window 展示Window UI时,会把上一个Window类型的UI及其上层Pop全部隐藏。
Pop 展示Pop UI时,不会隐藏任何UI。
运行模式分为Standard和SingleTop。
需要通过UIData来传输。
默认值是Standard。
UIData data = new UIData();
data.LaunchMode = UILaunchMode.SingleTop;
Standard 打开UI页面时,无论是否已经存在相同的页面,总是打开一个新的。
SingleTop 打开UI页面时,如果已经存在,则展示已存在的界面,并关闭此页面上方所有页面(除了lifeType = UILifeType.Persistent的页面)。
lifeType 需要在UI脚本的构造方法中设置。
Excel文件需要放在 项目根目录/Excels 下。
支持热更,详情可以参考热更文档。
Excel文件名:作为数据类的类名
第1行数据:类型
第2行数据:名称
第3 + n行: 数据
*第一列的数据类型必须是int,名称必须是id,数据不能重复。
配置范例:
int | int | string | double | int | int | int | int | string |
---|---|---|---|---|---|---|---|---|
id | intValue | stringValue | doubleValue | Array[ | ] | classValue{ | } | |
1 | 1 | S1 | 1 | 0 | 1 | 2 | 1 | CS1 |
2 | 4 | S2 | 2.1 | 1 | 2 | 3 | 2 | CS2 |
Excel数据配置完成后,可以点击 Excels/Build 来生成数据类和数据Json。
数据类生成目录:Assets/Scripts/DataStorages
数据Json生成目录:Assets/GameAssets/DataStorages
其他Build选项:
Excels/BuildClass 只生成数据类
Excels/BuildJson 只生成数据Json
以Excel文件Example为例。
Example example = ExampleStorage.Get(1);
int intValue = example.intValue;
string stringValue = example.stringValue;
double doubleValue = example.doubleValue;
List<int> list = example.array;
(int id, string name) classValue = example.classValue;
ExampleStorage.Clear();
在不需要这些数据时,可以调用Clear清除。
热更新使用ILRuntime实现。
使用热更新功能之前,需要在 GameAssets 文件夹下创建DLLSetting配置文件。
需要在PlayerSetting中启用Allow 'unsafe' Code。
BuildSetting 可以通过 Assets/Create/FusionConfig/DLL Setting 来创建
参数 | 说明 |
---|---|
Scripts For Pack |
脚本文件夹 构建DLL时,会将配置的文件夹中所有CSharp文件打入一个dll 只能是文件夹 可以拖入多个文件夹 |
Additional References |
依赖DLL 依赖DLL的相对路径 例如:Library/ScriptAssemblies/Assembly-CSharp-firstpass.dll |
Custom Adapters |
自定义类适配器 跨域继承时,需要配置被继承的父类。 格式:[ClassFullName],[AssemblyName] 例如:UnityEngine.MonoBehaviour,UnityEngine |
Custom Method Delegates |
自定义委托适配器 运行时若ILRuntime抛出缺少某种类型的委托适配器时,可在此配置。 按照参数顺序添加数据 类需要包名+类名,例如:UnityEngine.GameObject |
Custom Function Delegates |
自定义方法适配器 运行时若ILRuntime抛出缺少某种类型的方法适配器时,可在此配置。 按照参数顺序添加数据,返回值放在最后一个 类需要包名+类名,例如:UnityEngine.GameObject |
Custom Delegate Convertors |
自定义委托转换器 如果你需要将一个不是Action或者Func类型的委托实例传到ILRuntime外部使用的话,除了委托适配器,还需要额外写一个转换器,将Action和Func转换成你真正需要的那个委托类型。 Delegate Type 委托类型:Acion或Func Delegate Full Name 被转换的委托, 例如:System.Comparison<Fusion.Hotfix.Adapters.DLLMonoBaseAdapter.Adapter> Data Action或Func的参数,与自定义委托适配器或方法适配器配置方式相同 |
Allow Unsafe Code | 允许不安全的代码 |
UI热更新只需要将UI脚本所在的文件夹拖入 Scripts For Pack 配置即可。
由于DOTween代码在Plugins中,热更新脚本中如果使用DOTween,就需要在 Additional References 中配置 Library/ScriptAssemblies/Assembly-CSharp-firstpass.dll。
Plugins 中的代码会被打入Assembly-CSharp-firstpass.dll中。
using UnityEngine;
using Fusion.Hotfix.Mono;
namespace Hotfix.Mono
{
public class HotfixMono : DLLMonoBase
{
public HotfixMono(DLLMonoBehaviour mono, string monoData) : base(mono, monoData)
{
}
public override void Awake()
{
base.Awake();
Debug.Log("Awake");
}
public override void Start()
{
base.Start();
Debug.Log("Start");
}
public void Update()
{
Debug.Log("Update");
}
}
}
Class Full Name: 热更脚本的全名,例如:Hotfix.Mono.HotfixMono
Mono Data Asset: MonoBehaviour的序列化参数路径,可在序列化配置中查看使用方式。
除了Update,也可以添加MonoBehaviour的其他生命周期函数。
using Fusion.Hotfix.Mono.Editor;
using UnityEngine;
[CreateAssetMenu(menuName = "DLLMonoData/HotfixMonoData", fileName = "HotfixMonoData")]
public class HotfixMonoData_MonoDataEditor : ScriptableMonoData
{
public string test = "HotfixMonoData_String";
}
menuName 创建序列化对象菜单位置。 fileName 创建序列化对象的名字,后续配置会用到。
using UnityEngine;
using Fusion.Hotfix.Mono;
using LitJson.DynamicDLL;
namespace Hotfix.Mono
{
public class HotfixMono : DLLMonoBase
{
HotfixMonoData hotfixMonoData;
public HotfixMono(DLLMonoBehaviour mono, string monoData) : base(mono, monoData)
{
hotfixMonoData = JsonMapper.ToObject<HotfixMonoData>(monoData);
}
public override void Awake()
{
base.Awake();
Debug.Log(hotfixMonoData.test);
Debug.Log("Awake");
}
public override void Start()
{
base.Start();
Debug.Log("Start");
}
public void Update()
{
Debug.Log("Update");
}
}
}
新建一个 HotfixMono2 热更脚本
using UnityEngine;
using Fusion.Hotfix.Mono;
namespace Hotfix.Mono
{
public class HotfixMono2 : DLLMonoBase
{
public HotfixMono2(DLLMonoBehaviour mono, string monoData) : base(mono, monoData)
{
}
public override void OnDisable()
{
base.OnDisable();
Debug.Log("OnDisable2");
}
public override void OnEnable()
{
base.OnEnable();
Debug.Log("OnEnable2");
}
public override void Awake()
{
base.Awake();
Debug.Log("Awake2");
}
public override void Start()
{
base.Start();
Debug.Log("Start2");
}
public void Update()
{
Debug.Log("Update2");
}
}
}
在HotfixMono的Start方法中通过gameObject.GetMono方法获取HotfixMono2脚本,并把HotfixMono2的enabled值设置为false。
*只能在Start或之后的方法中获取热更脚本。
using UnityEngine;
using Fusion.Hotfix.Mono;
using LitJson.DynamicDLL;
using Fusion.Hotfix;
namespace Hotfix.Mono
{
public class HotfixMono : DLLMonoBase
{
HotfixMonoData hotfixMonoData;
public HotfixMono(DLLMonoBehaviour mono, string monoData) : base(mono, monoData)
{
hotfixMonoData = JsonMapper.ToObject<HotfixMonoData>(monoData);
}
HotfixMono2 hotfixMono2;
public override void Awake()
{
base.Awake();
Debug.Log(hotfixMonoData.test);
Debug.Log("Awake");
}
public override void Start()
{
base.Start();
Debug.Log("Start");
hotfixMono2 = (HotfixMono2)gameObject.GetMono("Hotfix.Mono.HotfixMono2");
hotfixMono2.enabled = false;
}
public void Update()
{
Debug.Log("Update");
}
}
}
*MonoBehaviour热更新如果包含频繁执行的方法时,例如Update,会比较耗时,每一帧都需要跨域执行生命周期方法。
UI
UI热更新只需要将UI脚本所在的文件夹拖入 Scripts For Pack 配置即可。
Excel
Excel数据热更新只需要将 Scripts/DataStorages 文件夹拖入 Scripts For Pack 配置即可。
using UnityEngine;
using Fusion.Net.TCP;
using Fusion.Net;
using System.Text;
public class TCPServerTest : MonoBehaviour
{
TCPServer server;
// Start is called before the first frame update
void Start()
{
server = new TCPServer(20000);
server.OnOpen += OnOpen;
server.OnClosed += OnClose;
server.OnConnected += OnConnect;
server.OnError += OnError;
server.OnMessage += OnMessage;
}
//回调
public void OnOpen(SocketBase server)
{
Debug.Log($"服务器{server.IP}已启动");
}
public void OnClose(SocketBase server, int code)
{
Debug.Log($"套接字{server.IP}已关闭,Cose:{code}");
}
public void OnConnect(SocketBase client)
{
Debug.Log($"客户端{client.IP}已连接");
}
public void OnError(SocketBase server, ExceptionData e)
{
Debug.LogError($"Error: {e.ErrorCode}");
}
public void OnMessage(SocketBase client, byte[] data)
{
Debug.Log($"消息[IP:{client.IP}]: {Encoding.UTF8.GetString(data)}");
}
// 测试方法
public void Open()
{
server.Open();
}
public void Send()
{
server.SendBroadcast(Encoding.UTF8.GetBytes($"我是服务器:{NetUtility.GetIP()}"));
}
public void Close()
{
server.Close();
}
}
using UnityEngine;
using Fusion.Net.TCP;
using Fusion.Net;
using System.Text;
public class TCPClientTest : MonoBehaviour
{
TCPClient client;
// Start is called before the first frame update
void Start()
{
client = new TCPClient("127.0.0.1", 20000);
client.OnOpen += OnOpen;
client.OnClosed += OnClose;
client.OnError += OnError;
client.OnMessage += OnMessage;
}
//回调
public void OnOpen(SocketBase client)
{
}
public void OnClose(SocketBase client, int code)
{
Debug.Log($"套接字{client.IP}已关闭,Cose:{code}");
}
public void OnError(SocketBase client, ExceptionData e)
{
Debug.LogError($"Error: {e.ErrorCode}");
}
public void OnMessage(SocketBase server, byte[] data)
{
Debug.Log($"消息[IP:{server.IP}]: {Encoding.UTF8.GetString(data)}");
}
// 测试方法
public void Open()
{
client.Open();
}
public void Send()
{
client.Send(Encoding.UTF8.GetBytes($"我是客户端:{NetUtility.GetIP()}"));
}
public void Close()
{
client.Close();
}
}
using UnityEngine;
using Fusion.Net.UDP;
using Fusion.Net;
using System.Text;
public class UDPClientTest : MonoBehaviour
{
public int port;
public int targetPort;
UDPClient client;
// Start is called before the first frame update
void Start()
{
client = new UDPClient(port);
client.OnOpen += OnOpen;
client.OnClosed += OnClose;
client.OnError += OnError;
client.OnMessage += OnMessage;
}
//回调
public void OnOpen(SocketBase client)
{
}
public void OnClose(SocketBase client, int code)
{
Debug.Log($"套接字{client.IP}已关闭,Cose:{code}");
}
public void OnError(SocketBase client, ExceptionData e)
{
Debug.LogError($"Error: {e.ErrorCode}");
}
public void OnMessage(SocketBase target, byte[] data)
{
Debug.Log($"消息[IP:{target.IP}]: {Encoding.UTF8.GetString(data)}");
}
// 测试方法
public void Open()
{
client.Open();
}
public void Send()
{
client.Send("127.0.0.1", targetPort, Encoding.UTF8.GetBytes($"我是User:{NetUtility.GetIP()}:{port}"));
}
public void Close()
{
client.Close();
}
}
构造方法传入UDPType.ReliableOrder即可。
client = new UDPClient(port, UDPType.ReliableOrder);
使用Protobuf时,需要在项目根目录下创建 Protobuf 文件夹,所有的Protobuf文件都放在这里。
以 Lobby.proto 为例。
syntax = "proto3";
package Protos.Lobby;
message RoomProfileMessage {
string name = 1;
int32 currentNum = 2;
int32 maxNum = 3;
string ownerName = 4;
string ownerAddress = 5;
}
proto文件创建完成后,点击 Protobuf/Generate/CSharp 生成C#代码。
生成的代码存放在 Sctipts/Protobuf 文件夹。
通过 [ProtoClassName].Pool.Get()
方法可以获取到Proto对象。
RoomProfileMessage roomProfileMessage = RoomProfileMessage.Pool.Get();
设置完对应字段之后便可以调用 roomProfileMessage.ToByteArray()
得到byte[]数据。
也可以调用 [ProtoClassName].Parser.ParseFrom()
把byte[]转换成对应的Proto对象。
使用Proxy可以更方便的管理数据、同步数据。
使用Proxy之前,需要在项目根目录下创建 ProxyXML 文件夹。
所有的XML配置必须放在这里。
<?xml version="1.0" encoding="UTF-8" ?>
<package>
<class name = "TestClass1" tag = "root">
<field name = "typeBool" type = "bool"/>
<field name = "typeInt" type = "int"/>
<field name = "typeLong" type = "long"/>
<field name = "typeFloat" type = "float"/>
<field name = "typeDouble" type = "double"/>
<field name = "typeString" type = "string"/>
<field name = "typeList" type = "List[int]"/>
<!-- Map的key只能是基本类型 -->
<field name = "typeMap" type = "Map[int,string]"/>
<field name = "typeObject" type = "SimpleVector3"/>
<field name = "typeListObject" type = "List[SimpleVector3]"/>
<field name = "typeMapObject" type = "Map[int,SimpleVector3]"/>
</class>
<class name = "SimpleVector3">
<field name = "x" type = "float"/>
<field name = "y" type = "float"/>
<field name = "z" type = "float"/>
</class>
<class name = "TestClass2" tag = "root">
<field name = "typeInt" type = "int"/>
</class>
</package>
package 根标签
class 类标签 参数:name 类名 tag 标记是否是根对象
field 属性标签 参数:name 属性名 type 属性类型
配置完成后,点击 Proxy/Generate/CSharp 生成C#代码。
生成的代码在 Scripts/Proxy 文件夹中。
ProxyMessage 是内置的Protobuf的一个类。
通过它可以初始化数据、更新数据。
TestClass1 originData = new TestClass1();
// 从对象池中获取ProxyMessage
ProxyMessage proxyMessage = ProxyMessage.Pool.Get();
// 第一次需要先初始化数据
ProxyManager.FillObject(ref proxyMessage, ProxyIndex.TestClass1, originData);
// 设置基本类型
ProxyManager.FillFieldBool(ref proxyMessage, ProxyIndex.TestClass1_TypeBool, true);
ProxyManager.FillFieldDouble(ref proxyMessage, ProxyIndex.TestClass1_TypeDouble, 5.0);
ProxyManager.FillFieldFloat(ref proxyMessage, ProxyIndex.TestClass1_TypeFloat, 4.0f);
ProxyManager.FillFieldInt(ref proxyMessage, ProxyIndex.TestClass1_TypeInt, 3);
ProxyManager.FillFieldLong(ref proxyMessage, ProxyIndex.TestClass1_TypeLong, 6);
ProxyManager.FillFieldString(ref proxyMessage, ProxyIndex.TestClass1_TypeString, "ProxyString");
// 设置对象
SimpleVector3 simpleVector3 = SimpleVector3.Pool.Get();
simpleVector3.X = 1; simpleVector3.Y = 2; simpleVector3.Z = 3;
ProxyManager.FillFieldObject(ref proxyMessage, ProxyIndex.TestClass1_TypeObject, simpleVector3);
// 向list中添加数据
ProxyManager.FillGeneric(ref proxyMessage, ProxyIndex.TestClass1_TypeList, GenericAction.Add, null, ProxyManager.GetIntValue(10));
ProxyManager.FillGeneric(ref proxyMessage, ProxyIndex.TestClass1_TypeList, GenericAction.Add, null, ProxyManager.GetIntValue(7));
ValueMessage[] keys = new ValueMessage[]
{
ProxyManager.GetIntValue(5)
};
ValueMessage[] keys2 = new ValueMessage[]
{
ProxyManager.GetIntValue(7)
};
// 向Map中添加数据
ProxyManager.FillGeneric(ref proxyMessage, ProxyIndex.TestClass1_TypeMap, GenericAction.Add, keys, ProxyManager.GetStringValue("TestMap1"));
ProxyManager.FillGeneric(ref proxyMessage, ProxyIndex.TestClass1_TypeMap, GenericAction.Add, keys2, ProxyManager.GetStringValue("TestMap2"));
// 转换成byte数组 可以进行网络传输
byte[] data = proxyMessage.ToByteArray();
通过ProxyManager把接收到的byte数组转换成对应的数据。
// 假设从网络中拿到数据 data
ProxyMessage recvedMessage = ProxyMessage.Parser.ParseFrom(data);
//注册数据修改回调
ProxyManager.Instance.RegisterCallback(ProxyIndex.TestClass1_TypeList, this, delegate (ProxyCallbackData proxyCallbackData)
{
Debug.LogWarning($"TypeList被修改: Value: {proxyCallbackData.Data.IntValue}");
});
// 将Message传入ProxyManager,自动设置接收到的数据
ProxyManager.Instance.Update(proxyMessage);
//打印数据
TestClass1 testClass1 = ProxyManager.Instance.Get<TestClass1>(ProxyIndex.TestClass1);
Debug.LogError("Base");
Debug.Log(testClass1.TypeBool);
Debug.Log(testClass1.TypeDouble);
Debug.Log(testClass1.TypeFloat);
Debug.Log(testClass1.TypeInt);
Debug.Log(testClass1.TypeLong);
Debug.Log(testClass1.TypeString);
Debug.LogError("SimpleVector");
Debug.Log(testClass1.TypeObject.X);
Debug.Log(testClass1.TypeObject.Y);
Debug.Log(testClass1.TypeObject.Z);
Debug.LogError("List");
for (int i = 0; i < testClass1.TypeList.Count; i++)
{
Debug.Log(testClass1.TypeList[i]);
}
Debug.LogError("Map");
for (int i = 0; i < testClass1.TypeMap.Count; i++)
{
Debug.Log($"Key: {testClass1.TypeMap.GetKey(i)} Value: {testClass1.TypeMap.Get(i)}");
}
数据修改回调
//注册数据修改回调
ProxyManager.Instance.RegisterCallback(ProxyIndex.TestClass1_TypeList, this, delegate (ProxyCallbackData proxyCallbackData)
{
//处理数据
});
//删除数据修改回调
ProxyManager.Instance.RemoveCallback(ProxyIndex.TestClass1_TypeList, this);
可以通过 Timer 类使用计时器,使用前需要引用命名空间。
using Fusion.Async.Timers;
Timer timer = new Timer();
timer.TypeSetter(Timer.InvokeType.Time);
默认值 InvokeType.Time
Timer.InvokeType.Frame 按照帧数执行
Timer.InvokeType.Time 按照时间来执行
timer.FrequencySetter(1.0f);
默认值 1
Type = Frame frequency是帧数
Type = Time frequency是秒数
timer.CountSetter(1);
默认值 1
timer.FirstImmediatelySetter(false);
默认值 false
timer.RoundSetter(2);
默认值 0 无限执行
timer.PersistentSetter(false);
默认值 false 设置为true,场景切换不会销毁这个计时器。
timer.Invoke(delegate ()
{
// 自定义方法
});
timer.Pause();
timer.Stop();
using Fusion.Frameworks.Notification;
public static class NotificationKey
{
public readonly static int Test_1 = 1;
}
public struct NotificationTestData : INotificationData
{
public int value;
}
NotificationManager.Register(this, NotificationKey.Test_1, Func);
void Func(INotificationData notificationData)
{
NotificationTestData notificationTestData = (NotificationTestData)notificationData;
Debug.Log($"Notification: {notificationTestData.value}");
}
object o 消息绑定对象
int key 消息类型
Action 消息
NotificationTestData notificationTestData = new NotificationTestData { value = 4 };
NotificationManager.Send(NotificationKey.Test_1, notificationTestData);
NotificationManager.Remove(this);
移除绑定在object上的所有消息
NotificationManager.Remove(this, NotificationKey.Test_1);
移除绑定在object上的某一个类型的消息
GameObject box = Utility.Find(gameObject, "Cube");
BoxCollider boxCollider = Utility.Find<BoxCollider>(gameObject, "Cube");
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。