Unity常见框架探索-ET框架探索

news2024/12/23 10:09:22

简介

ET框架是类ECS的一个Unity前后端框架

论坛地址为:https://et-framework.cn

Git地址为:https://github.com/egametang/ET

预备知识

Unity程序集的使用

接入流程

本文将会以7.2版本进行分析。所以直接clone github上的仓库,将工程导入到本地,之后将分支切换到最新的release分支,"release7.2"

菜单栏相关

ENABLE_CODE选项

ET->ChangeDefine->ADD_ENABLE_CODE/REMOVE_ENABLE_CODE

一般在开发阶段使用Editor时需要启用ENABLE_CODE选项。该选项启用时,修改脚本之后,会直接重新编译所有的代码,Editor在运行时会直接使用最新的程序集。如果ENABLE_CODE选项是关闭的,框架启动后会加载之前生成的程序集文件(这个文件需要在ET->BuildTool界面生成),导致每次需要应用修改,都要重新生成程序集文件。

框架解析

框架入口解析

启动流程如下

  • 入口文件为Init,之后调用CodeLoader对代码进行加载
  • 如果是EnableCodes模式则直接加载程序集。否则通过AB加载文件,之后调用LoadHotfix函数
  • LoadHotfix会加载程序集,并且调用EventSystem,根据特性注册对应事件的监听。
  • 之后调用ET.Entry的Start方法。
  • ET.Entry.Start 进行初始化之后,推送对应的EntryEvent事件
  • 推送EntryEvent3,EntryEvent3_InitClient接收后推送AppStartInitFinish
  • AppStartInitFinish_CreateLoginUI接收该事件后,创建UI场景

UI系统

UI界面的生成流程

ET是通过异步方式创建UI,如下方例子,调用UIHelper.Create方法,指定创建UI的场景,UI类型和对应的层级

C#
        protected override async ETTask Run(Scene scene, EventType.AppStartInitFinish args)
        {
            await UIHelper.Create(scene, UIType.UILogin, UILayer.Mid);
        }

调用scene挂载的UIComponent组件,处理Create事件

C#
        public static async ETTask<UI> Create(Scene scene, string uiType, UILayer uiLayer)
        {
            return await scene.GetComponent<UIComponent>().Create(uiType, uiLayer);
        }

之后会标记有对应UIEvent特性的类,处理该事件,开始加载资源并生成对应的GameObject

C#
    [UIEvent(UIType.UILogin)]
    public class UILoginEvent: AUIEvent
    {
        public override async ETTask<UI> OnCreate(UIComponent uiComponent, UILayer uiLayer)
        {
            await uiComponent.DomainScene().GetComponent<ResourcesLoaderComponent>().LoadAsync(UIType.UILogin.StringToAB());
            GameObject bundleGameObject = (GameObject) ResourcesComponent.Instance.GetAsset(UIType.UILogin.StringToAB(), UIType.UILogin);
            GameObject gameObject = UnityEngine.Object.Instantiate(bundleGameObject, UIEventComponent.Instance.GetLayer((int)uiLayer));
            UI ui = uiComponent.AddChild<UI, string, GameObject>(UIType.UILogin, gameObject);
            ui.AddComponent<UILoginComponent>();
            return ui;
        }

        public override void OnRemove(UIComponent uiComponent)
        {
            ResourcesComponent.Instance.UnloadBundle(UIType.UILogin.StringToAB());
        }
    }

UI组件解析

以UILogin为例子,对应的Prefab实际上只挂载了ReferenceCollector,ReferenceCollector负责将结点进行绑定

生成该GameObject之后,调用AddComponent

C#
GameObject gameObject = UnityEngine.Object.Instantiate(bundleGameObject, UIEventComponent.Instance.GetLayer((int)uiLayer));
UI ui = uiComponent.AddChild<UI, string, GameObject>(UIType.UILogin, gameObject);
ui.AddComponent<UILoginComponent>();

其中UILoginComponent负责显示对应成员

C#
[ComponentOf(typeof(UI))]
public class UILoginComponent: Entity, IAwake
{
    public GameObject account;
    public GameObject password;
    public GameObject loginBtn;
}

AddComponent之后,会调用对应的System,这里UILoginComponentSystem就是对应的System,在Awake阶段通过ReferenceCollector对UILoginComponent进行了绑定,以及实现了对应的UI逻辑

C#
[ObjectSystem]
public class UILoginComponentAwakeSystem : AwakeSystem<UILoginComponent>
{
    protected override void Awake(UILoginComponent self)
    {
        ReferenceCollector rc = self.GetParent<UI>().GameObject.GetComponent<ReferenceCollector>();
        self.loginBtn = rc.Get<GameObject>("LoginBtn");
        self.loginBtn.GetComponent<Button>().onClick.AddListener(()=> { self.OnLogin(); });
        self.account = rc.Get<GameObject>("Account");
        self.password = rc.Get<GameObject>("Password");
     }
}

场景切换

关于ET的场景切换相关逻辑可以查看

UILobbyComponentSystem处理进入Map的操作,先是调用EnterMap异步函数,等待EnterMapHelper异步返回后删除界面

C#
        //UILobbyComponentSystem
        public static async ETTask EnterMap(this UILobbyComponent self)
        {
            await EnterMapHelper.EnterMapAsync(self.ClientScene());
            await UIHelper.Remove(self.ClientScene(), UIType.UILobby);
        }

之后EnterMapHelper会向服务器发起进入Map的请求

C#
        //EnterMapHelper
        public static async ETTask EnterMapAsync(Scene clientScene)
        {
            try
            {
                G2C_EnterMap g2CEnterMap = await clientScene.GetComponent<SessionComponent>().Session.Call(new C2G_EnterMap()) as G2C_EnterMap;
                clientScene.GetComponent<PlayerComponent>().MyId = g2CEnterMap.MyId;
               
                // 等待场景切换完成
                await clientScene.GetComponent<ObjectWait>().Wait<Wait_SceneChangeFinish>();
               
                EventSystem.Instance.Publish(clientScene, new EventType.EnterMapFinish());
            }
            catch (Exception e)
            {
                Log.Error(e);
            }       
        }

网络模块

获取路由地址示例

下面以获取路由地址为例,分析ET框架完成一次HTTP请求的过程。

主要包含的类有RouterAddressComponentSystem,RouterAddressComponent

其中RouterAddressComponent为数据的载体,负责填写请求参数,以及保存返回的数据

C#
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;

namespace ET.Client
{
    [ComponentOf(typeof(Scene))]
    public class RouterAddressComponent: Entity, IAwake<string, int>
    {
        public IPAddress RouterManagerIPAddress { get; set; }
        public string RouterManagerHost;
        public int RouterManagerPort;
        public HttpGetRouterResponse Info;
        public int RouterIndex;
    }
}

RouterAddressComponentSystem则是处理获取路由的逻辑

C#
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace ET.Client
{
    [FriendOf(typeof(RouterAddressComponent))]
    public static class RouterAddressComponentSystem
    {
        public class RouterAddressComponentAwakeSystem: AwakeSystem<RouterAddressComponent, string, int>
        {
            protected override void Awake(RouterAddressComponent self, string address, int port)
            {
                self.RouterManagerHost = address;
                self.RouterManagerPort = port;
            }
        }
       
        public static async ETTask Init(this RouterAddressComponent self)
        {
            self.RouterManagerIPAddress = NetworkHelper.GetHostAddress(self.RouterManagerHost);
            await self.GetAllRouter();
        }

        private static async ETTask GetAllRouter(this RouterAddressComponent self)
        {
            string url = $"http://{self.RouterManagerHost}:{self.RouterManagerPort}/get_router?v={RandomGenerator.RandUInt32()}";
            Log.Debug($"start get router info: {url}");
            string routerInfo = await HttpClientHelper.Get(url);
            Log.Debug($"recv router info: {routerInfo}");
            HttpGetRouterResponse httpGetRouterResponse = JsonHelper.FromJson<HttpGetRouterResponse>(routerInfo);
            self.Info = httpGetRouterResponse;
            Log.Debug($"start get router info finish: {JsonHelper.ToJson(httpGetRouterResponse)}");
           
            // 打乱顺序
            RandomGenerator.BreakRank(self.Info.Routers);
           
            self.WaitTenMinGetAllRouter().Coroutine();
        }
       
        // 等10分钟再获取一次
        public static async ETTask WaitTenMinGetAllRouter(this RouterAddressComponent self)
        {
            await TimerComponent.Instance.WaitAsync(5 * 60 * 1000);
            if (self.IsDisposed)
            {
                return;
            }
            await self.GetAllRouter();
        }

        public static IPEndPoint GetAddress(this RouterAddressComponent self)
        {
            if (self.Info.Routers.Count == 0)
            {
                return null;
            }

            string address = self.Info.Routers[self.RouterIndex++ % self.Info.Routers.Count];
            string[] ss = address.Split(':');
            IPAddress ipAddress = IPAddress.Parse(ss[0]);
            if (self.RouterManagerIPAddress.AddressFamily == AddressFamily.InterNetworkV6)
            {
                ipAddress = ipAddress.MapToIPv6();
            }
            return new IPEndPoint(ipAddress, int.Parse(ss[1]));
        }
       
        public static IPEndPoint GetRealmAddress(this RouterAddressComponent self, string account)
        {
            int v = account.Mode(self.Info.Realms.Count);
            string address = self.Info.Realms[v];
            string[] ss = address.Split(':');
            IPAddress ipAddress = IPAddress.Parse(ss[0]);
            //if (self.IPAddress.AddressFamily == AddressFamily.InterNetworkV6)
            //{
            //    ipAddress = ipAddress.MapToIPv6();
            //}
            return new IPEndPoint(ipAddress, int.Parse(ss[1]));
        }
    }
}

请求过程,是先添加RouterAddressComponent组件,在添加时填入对应的HTTP请求地址和端口号

之后调用routerAddressComponent的Init方法。

C#
//获取RouterAddressComponent
RouterAddressComponent routerAddressComponent = clientScene.GetComponent<RouterAddressComponent>();
if (routerAddressComponent == null)
{
    //如果RouterAddressComponent不存在,就添加RouterAddressComponent组件,并且填入HTTP请求的地址和端口号
    routerAddressComponent = clientScene.AddComponent<RouterAddressComponent, string, int>(ConstValue.RouterHttpHost, ConstValue.RouterHttpPort);
    await routerAddressComponent.Init();
    clientScene.AddComponent<NetClientComponent, AddressFamily>(routerAddressComponent.RouterManagerIPAddress.AddressFamily);
}

C#
  public static async ETTask Init(this RouterAddressComponent self)
        {
            self.RouterManagerIPAddress = NetworkHelper.GetHostAddress(self.RouterManagerHost);
            await self.GetAllRouter();
        }
       

Protobuf的使用

proto文件

Proto文件放在Unity/Assets/Config/Proto之下

并且文件名有特定的命名规范,以InnerMessage_S_20001.proto为例,以"_"为分割符,第一个字符串"InnerMessage"是文件名,第二个字符串"S"是用于区分Server还是Client,第三个字符串"20001"为协议起始的编号

生成Protoc#文件

点击菜单栏ET->Build Tool->Proto2CS,生成成功之后会在Scripts/Codes/Model/Generate

需要注意的是et使用的是protobuf-net

https://github.com/protobuf-net/protobuf-net

区别于google的protocolbuffers的c#版本

https://github.com/protocolbuffers/protobuf

问题解决

1.当前 .NET SDK 不支持将 .NET 6.0 设置为目标。请将 .NET 5.0 或更低版本设置为目标,或使用支持 .NET 6.0 .NET SDK 版本。    C:\Program Files\dotnet\sdk\5.0.414\Sdks\Microsoft.NET.Sdk\targets\Microsoft.NET.TargetFrameworkInference.targets        141       

解决方案:这个问题是由于visiual studio 2019 不支持.NET 6.0。需要将开发软件升级到visual studio 2022

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/654314.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1743_MATLAB 2-D绘图小结

全部学习汇总&#xff1a; GreyZhang/g_matlab: MATLAB once used to be my daily tool. After many years when I go back and read my old learning notes I felt maybe I still need it in the future. So, start this repo to keep some of my old learning notes servral …

【网络协议详解】——知识点复习(期末不挂科版)

课本&#xff1a; 目录 &#x1f552; 1. 概述&#x1f558; 1.1 GNS3&#x1f558; 1.2 Wireshark &#x1f552; 2. PPP协议&#x1f552; 3. VLAN技术&#x1f552; 4. STP技术&#x1f552; 5. IPV6&#x1f552; 6. 路由表&#x1f552; 7. RIP协议&#x1f552; 8. OSPF…

Jenkins pipeline 中 checkout 代码

pipeline 中 具有checkout 功能的脚本命令如下 git branch: "master", url: "https://gitee.com/liuboliu/******.git"完整的脚本命令如下 pipeline {agent anystages {stage(checkout) {steps {git branch: "master", url: "https://gite…

I2C中为什么线与?为什么要有上拉电阻?

1、为什么采用漏极开路&#xff1f; 首先&#xff0c;连接到 I2C 上的设备是开漏输出的。以漏极开漏输出&#xff08;OD&#xff09;为例&#xff0c;是指将输出级电路结构改为一个漏极开路输出的 MOS 管。这样做的好处在于&#xff1a; 防止短路。可以实现“线与”逻辑&#…

移动DICT项目是什么?

DICT项目 我们运营商的伙伴&#xff0c;很多人都知道我们的DICT&#xff0c;但是大家知不知道什么是DICT。你想一想&#xff0c;所谓的DICT&#xff0c;就是指的大数据技术与IT和CT的深度融合。 实际上&#xff0c;DICT的可以拆分成三个词&#xff0c; 第一个DT&#xff0c…

腾讯服务器CentOS Stream 8安装redis详情的步骤

tencent服务器安装的系统版本创建一个新的文件夹 /athena/redis mkdir /athena cd /athena mkdir redis1、切换到 “redis” 目录&#xff1a; cd /athena/redis2、使用 YUM 包管理器安装 GCC、C 和 Make 软件包&#xff1a; yum install gcc-c make -y这条命令将使用 YUM …

C++【set 和 map 学习及使用】

✨个人主页&#xff1a; 北 海 &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f383;操作环境&#xff1a; Visual Studio 2019 版本 16.11.17 文章目录 &#x1f307;前言&#x1f3d9;️正文1、预备知识1.1、关联式容器1.2、键值对1.3、树型结构的关联式容器 2、set2.…

网工内推 | 网络运维专场,弹性工作,14薪

01 南凌科技股份有限公司 招聘岗位&#xff1a;网络运维工程师 职责描述&#xff1a; 1、负责及时响应客户需求、做好客户报障接收&#xff0c;受理与记录工作&#xff0c;及时做好值班记录与故障交接&#xff1b; 2、通过网管平台实时监控客户线路及机房设备的运行状态、性能…

mNGS 02:SnakeMake流程简介

<~生~信~交~流~与~合~作~请~关~注~公~众~号生信探索> 流程代码在&#xff1a;https://jihulab.com/BioQuest/SnakeMake-mNGS 或https://github.com/BioQuestX/SnakeMake-mNGS 教程链接在&#xff1a;https://doc.bioquest.cn/mngs mNGS Pipeline summary Metagenomic nex…

我的世界(MC) Forge 1.20.1 服务端搭建教程

Debian系统使用MCSManager9面板搭建Minecraft Java版MOD服务器的教程&#xff0c;本教程用的Forge1.20.1服务端&#xff0c;用其他服务端的也可以参考一下。 本教程使用Docker来运行mc服&#xff0c;可以方便切换不同Java版本&#xff0c;方便安装多个mc服版本。 视频教程&am…

【网络技术】什么是DNS及常见问题

序言 域名服务器&#xff08;Domain Name Server&#xff0c;DNS&#xff09;是一种用于存储和管理域名解析信息的服务器。它们负责将易于记忆的域名&#xff08;例如 www.example.com&#xff09;转换为与之关联的 IP 地址&#xff08;例如 192.0.2.1&#xff09;&#xff0c;…

基于M300仿地飞行,D2Pros “房地一体”免像控验证

引言 目前&#xff0c;倾斜摄影技术被广泛运用于“房地一体”项目。但在云贵川某些地形起伏较大的地区&#xff0c;运用这项技术还存在一些困难。地形高差太大导致建模精度不够&#xff0c;这是让很多客户感到头疼的问题。 同时&#xff0c;采用分层飞行或者参考最高点加大重…

2023全球数字经济大会——开放原子全球开源峰会观后感及总结

目录 前言 什么是开源&#xff1f; 主会场院士及企业领导的主要观点 展台街采环节互动&#xff08;仅代表个人观点&#xff09; 软硬协同开源分论坛精华观点 1.加速迈入云原生时代-英特尔携手合作伙伴的技术创新与实践 2.英特尔Linux操作系统及12种解决方案示例 3.英特尔基础软…

深度学习--常见激活函数的实现

常见激活函数 简介激活函数的初衷激活函数必须是非线性函数 常见的激活函数与实现Step跃阶函数公式优点缺点应用场景代码实现效果图 Sigmoid函数与代码实现公式Sigmoid函数优点Sigmoid函数缺点代码实现效果图 ReLu公式优点缺点代码效果图 LeakyReLU公式优点缺点代码效果图 tanh…

快速构建机器学习Web应用的神器:Gradio

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

FreeRTOS实时操作系统(一)RTOS的基本概念

文章目录 前言操作系统分类编程风格纠正FreeRTOS介绍任务调度方式任务状态 总结 前言 最近买了把75系列的机械键盘&#xff0c;没有数字区域&#xff0c;想起来稚辉君曾经做过一把客制化键盘&#xff0c;于是下载了资料准备学一学&#xff0c;网上很多开源的都是用的ATMEGA32U…

面试专题:Redis

1.redis简介 简单来说 redis 就是一个数据库&#xff0c;不过与传统数据库不同的是 redis 的数据是存在内存中的&#xff0c;所以存写速度非常快&#xff0c; 因此 redis 被广泛应用于缓存方向。另外&#xff0c;redis 也经常用来做分布式锁。redis 提供了多种数据类型来支持不…

Redis入门 - Redis Stream

原文首更地址&#xff0c;阅读效果更佳&#xff01; Redis入门 - Redis Stream | CoderMast编程桅杆Redis入门 - Redis Stream Redis Stream 是 Redis 5.0 版本新增加的数据结构。 Redis Stream 主要用于消息队列&#xff08;MQ&#xff0c;Message Queue&#xff09;&#xf…

【Spring】— Spring MVC入门

目录 Spring MVC入门1.Spring MVC概述2.案例——第一个Spring MVC应用1.创建项目&#xff0c;引入JAR包2.配置前端控制器3.创建Controller类4.创建Spring MVC的配置文件&#xff0c;配置控制器映射信息5.创建视图&#xff08;View&#xff09;页面6.启动项目&#xff0c;测试应…

Axios异步调用

promise 主要解决异步深层嵌套的问题 promise 提供了简洁的API 使得异步操作更加容易 1. Promise 基本API //实例方法 .then() //得到异步任务正确的结果 .catch() //获取异常信息 .finally() //成功与否都会执行&#xff08;不是正式标准&#xff09; 2. axios基本使用 …