1 Star 0 Fork 0

YaJinK / FusionFrameworks

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README

FusionFrameworks

Unity客户端框架,包含资源管理、音频管理、场景管理、UI管理、Excel工具、打包工具、热更新、网络等。

使用插件

  1. Protobuf v23.3
  2. ILRuntime 2.1.0
  3. EPPlus

安装

  1. Window -> Package Manager -> + -> Add package from git URL...

  2. 输入https://gitee.com/yajink/FusionFrameworks.git?path=Assets/[模块包名]#v[MAJOR.MINOR.PATCH], 点击Add

    [模块包名]

    1. com.fusion.utilities 工具模块(*必选)
    2. com.fusion.async 异步处理模块(*必选)
    3. 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 不能存放在相同文件夹中。

  • BuildSetting参数说明
参数 说明
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文件用于热更新

资源AssetBundle名称规则

AB名称会在打AssetBundle时自动设置。
为某个资源设置AB名称时,会在当前文件夹中查找 BuildProperty 配置文件,若找不到,则递归寻找父文件夹。
若到GameAssets文件夹还未找到,则使用默认 BuildProperty 配置。

BuildProperty 可以通过 Assets/Create/FusionConfig/Build Property 来创建

  • BuildProperty参数说明
参数 说明
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 来创建

  • AtlasProperty参数说明
参数 说明
Pack Unit 图集粒度
0 文件夹中所有符合条件的Sprite打入一张图集
-1 不打图集
[n] 符合条件的n张Sprite打入一张图集
Ignore Size 过滤Sprite分辨率
X Sprite的宽
Y Sprite的高
大于这个分辨率的Sprite不会被打入图集

AssetBundle打包

将资源放入指定文件夹并且完成上述配置后,就可以通过 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下

资源加载与销毁

可以通过 AssetsManagerAssetsUtility 来对资源进行加载和销毁。

资源应的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的加载进度。除此之外,还提供了场景数据的保存于加载功能。

  • 创建LoadAsyncTask
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管理

使用UI管理功能之前,需要将Prefab Packages/com.fusion.frameworks/Runtime/Prefabs/UI/UI 拖入初始场景中。
可以修改UI Prefab的属性,但不能更改子节点的层级关系。
UI支持热更,详情在热更部分

创建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 当页面被销毁时,会触发此回调。

  • 同步创建UI
UIManager.Instance.Launch("Prefabs/UI/Page1");
  • 异步创建UI
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类型

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

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 来创建

  • DLLSetting参数说明
参数 说明
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热更新只需要将UI脚本所在的文件夹拖入 Scripts For Pack 配置即可。

Plugins代码引用(DOTween为例)

由于DOTween代码在Plugins中,热更新脚本中如果使用DOTween,就需要在 Additional References 中配置 Library/ScriptAssemblies/Assembly-CSharp-firstpass.dll

Plugins 中的代码会被打入Assembly-CSharp-firstpass.dll中。

MonoBehaviour热更新

基础配置
  1. 为GameObject添加DLL Mono Behaviour组件。
  2. 在Hotfix(也可以是其他专门放热更脚本的文件夹)中创建热更类,继承DLLMonoBase。
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");
        }
    }
}
  1. 配置DLL Mono Behaviour的属性

Class Full Name: 热更脚本的全名,例如:Hotfix.Mono.HotfixMono
Mono Data Asset: MonoBehaviour的序列化参数路径,可在序列化配置中查看使用方式。

  1. 将热更脚本文件夹配置在DLLSetting中。
  2. 点击DLLManager/BuildToAssetsBundle
  3. 运行之后,Awake Start Update方法会在热更域中执行。

除了Update,也可以添加MonoBehaviour的其他生命周期函数。

序列化参数
  1. 在Assets文件夹下创建 MonoData/Editor/ScriptsMonoData/Editor/MonoAssets 两个文件夹。
  2. MonoData/Editor/Scripts 创建序列化脚本类。
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 创建序列化对象的名字,后续配置会用到。

  1. MonoData/Editor/MonoAssets 文件夹下点击 DLLMonoData/HotfixMonoData,创建出序列化对象并配置属性。
  2. 将 DLL Mono Behaviour 的 Mono Data Asset 属性配置为 HotfixMonoData。
  3. HotfixMono的构造方法中初始化序列化对象。
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");
        }
    }
}
  1. 点击 DLLManager/GenerateMonoData, 生成文件夹 Scripts/MonoData, 并把这个文件夹配置在DLLSetting中。
  2. 点击 DLLManager/Build, 构建新的热更DLL。
  3. 点击 Build/BuildAssets, 打新的Assetbundle。
  4. 运行,查看日志,Awake前输出了序列化对象中配置的字符串。
DLLMonoBase通信

新建一个 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 配置即可。

七、网络

TCP

服务器
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();
    }
}

UDP

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();
    }
}
可靠有序UDP

构造方法传入UDPType.ReliableOrder即可。

client = new UDPClient(port, UDPType.ReliableOrder);

Protobuf

使用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与C#代码生成

使用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

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

通过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 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上的某一个类型的消息

Utility

GameObject box = Utility.Find(gameObject, "Cube");
BoxCollider boxCollider = Utility.Find<BoxCollider>(gameObject, "Cube");

空文件

简介

Unity Frameworks 展开 收起
取消

发行版 (3)

全部

贡献者

全部

近期动态

加载更多
不能加载更多了
C#
1
https://gitee.com/yajink/FusionFrameworks.git
git@gitee.com:yajink/FusionFrameworks.git
yajink
FusionFrameworks
FusionFrameworks
master

搜索帮助