Unity后端Server实现方案

引言

在Unity与NetCore使用方案中

https://blog.csdn.net/FeiBin2013/article/details/86574687

讨论过如何在NetCore中使用UnityEngine 库的问题,本文进行更加详细的调查。

先还是尝试使用注入的方式替换部分Native底层

Unity注入GameObject实践

本文以CreatePrimitive方法为例,实现注入把其修改为通过DllImport的方式,实现替换其Native实现

        [MethodImpl (MethodImplOptions.InternalCall)]
        [FreeFunction ("GameObjectBindings::CreatePrimitive")]
        public static extern GameObject CreatePrimitive (PrimitiveType type);
第一步是需要先定义GameObject的C++版本,即Native代码部分,

需要先整理并收集其成员和属性

先看本Class中的一些属性,目前只有一个scene。

其它的已经标注为Deprecated,通过GetComponent的方式获取。

但由于如果C++中不定义完整的话就会出现Marshaling异常。

需要先实现对Scene和Component的类的Marshaling。当然可以只返回个无用的对象。

public Scene scene {
            [FreeFunction ("GameObjectBindings::GetScene")]
            get;
        }

        public GameObject gameObject => this;

        [EditorBrowsable (EditorBrowsableState.Never)]
        [Obsolete ("Property rigidbody has been deprecated. Use GetComponent<Rigidbody>() instead. (UnityUpgradable)"true)]
        public Component rigidbody {
            get;
        }

先看以上Scene的定义只有两个m_Handle和name属性

再看以下Component的定义,Tag之外,也都是Deprecated的属性,且都是其自身Component

其基类Object

一个方案就是删除所有属性,但是还不能都进行删除,因为有一些会使用到的。

目前看,虽然删除了一些属性,会有一些运行时出错的问题,但是不影响整体的调用,比如Vector2的使用。

但发现一个问题就是一定要使用CoreModule这个名,不能修改名字,不然说找不到。

还有这个版本的一定要同时加载UnityEngine.SharedInternalsModule.dll才能运行,之前在Web Core App中试用没有这个问题啊。或者是因为使用的是Unity系统的版本,其Root目录下本身就有这个Dll,不然显式加载了。

UnityEngine中非Native方法整理

矩阵与向量

Matrix4x4的大部分方法是不可以用的,比如以下Rotate,以及通过的TRS方法。

Matrix4x4 m = Matrix4x4.Rotate(Quaternion.AngleAxis(a, Vector3.forward));

与点计算是可以的

Vector3 temp = m.MultiplyVector(new Vector3(Pos.Value.Direction.Value.x, Pos.Value.Direction.Value.y));

Quaternion中有关Euler角的计算是Native的,需要另外调查方法

            Quaternion q = new Quaternion();
            q.eulerAngles = new Vector3(30, 30, 30); 

Inverse方法是不可用的。

目前的替换方案使用的System.Numerics库。

它的一些问题:不支持Euler角

Euler角计算

四元数、Euler角之前的关系。

https://blog.csdn.net/candycat1992/article/details/41254799

 以下有四元数的相互转换,其中四无数的乘法表示两个旋转的合成

https://www.cnblogs.com/21207-iHome/p/6894128.html

Unity的Euler顺序是啥?一共有12种组合,不同组合得到的转换也是不一样的。

这个文章 有人实验证明使用的是ZXY

https://blog.csdn.net/andrewfan/article/details/60866636

其中有关自由度丢失的问题,比如把Rotation的X设置为-90,这时修改YZ是相同的效果。

父子关系时,放大还需要考虑的。

旋转也是需要考虑的。

说明是要使用矩阵进行对应。

狸猫换太子

根据实验看,通过DllImport替换Native实现的方案,工作量巨大。而从根本上,就是使用Unity的方式,目前推测Unity是使用定制化mono .Net runtime的方式实现的Native方法,这个工作量也是很大的。所有考虑使用替换Unity关键类的方式,把CoreModule中的一些类使用注入的方式删除掉或者修改名字,然后自定义相应的类。大概整理参考以下类图

另外,还有Resources、Debug等。

主要包括几大部分,其它ZP大部分功能是可以实现前后端复用的,包括一些上层应用逻辑层也是可以复用的,大大减少了后端开发的周期,不不需要重写很多代码。

引擎驱动:大体工作流程就是资源场景加载、GameObject对象初始化(包括调用Awake/Start)、Update驱动。

场景是以JSON进行定义的,其结构与Unity 场景的结构是一致 ,另外还包括了RES定义、prefab定义。整体Asset工程目录前端与后端是统一复用的。可以使用工具把unity场景转换为Server端场景。Server主要处理的GameObject上绑定的Component组件。以GMain为例,大概定义如下,其主要绑定了以一些Json。

            "GameObject.InComponents":[
                {
                    "BindComponent.InType":"Gege.GMain",
                    "BindComponent.Params":[
                        {
                            "BindComponentParam.Name":"CardsConfigText",
                            "BindComponentParam.Data": "Resources/Jsons/Config/Cards.json"
                        },
                        {
                            "BindComponentParam.Name":"ShipsConfigText",
                            "BindComponentParam.Data": "Resources/Jsons/Config/Ships.json"
                        },
                        {
                            "BindComponentParam.Name":"StarsConfigText",
                            "BindComponentParam.Data": "Resources/Jsons/Config/Stars.json"
                        },
                        {
                            "BindComponentParam.Name":"MinesConfigText",
                            "BindComponentParam.Data": "Resources/Jsons/Config/Mines.json"
                        }
                    ]
                },

如上图,资源使用的TextAsset进行定义的,Server端也所其进行了重写。其实现也就是通过绑定的字符串路径,加载对应的文件。后续支持LayerMask 暂时未支持。

物理引擎:先使用最简单的AABB算法判断Trigger和Collision,目前只支持BoxCollider,后续再扩展。

其中一个是刚体的限制。目前还不支持。还有BoxCollider中的碰撞的 List<ContactPoint> contacts 这个还未进行支持。

网络引擎:需要支持HTTP和TCP,HTTP直接使用ASP.net Core就可以,TCP使用MQTT框架。HTTPClient代码如下:

        static async Task httpGet(string url, KeyValuePair<string, Subject<string>>  k)
        {
            using (var client = new HttpClient())
            {
                var result = await client.GetAsync(url);
                //Console.WriteLine(result.StatusCode);

                string result111 = await result.Content.ReadAsStringAsync();

                k.Value.OnNext(result111);
                Console.WriteLine(result111);
            }

            //await Task.Run(async () => { await httpGet(url, k); });
        }

数据引擎:对数据库的支持,由于前端Unity是不需要数据库的,使用JSON就可以了。后端需要进行大量的数据操作。目前ZP框架只支持Mysql。Mysql使用Nuget包 Mysql.Data实现。

https://blog.csdn.net/zhongyanfu0/article/details/71063773

这里有一个还不错的。

不需要对应的类,这样可以与ZP进行对接。

主要代码如下:

        public List<T> Query<T>(string sql)// where T : IZProperty
        {
            List<T> list = new List<T>();

            //查找数据库里面的表
            MySqlCommand mscommand = new MySqlCommand(sql, mySqlConnection);
            using (MySqlDataReader reader = mscommand.ExecuteReader())
            {
                //读取数据
                while (reader.Read())
                {
                    var obj = ZPropertyMesh.CreateObject<T>();
                    ConvertObject(obj, reader);

                    list.Add(obj);

                    ZPropertyMesh.InvokeLoadMethod(obj);
                }
            }
                
            return list;
        }

这里ZP的处理是,Query后返回对应的ZP类List,同时从Sql中获取后,也需要触发Load操作,这里与JSON是一样的。

其中ConvertObject就是通过ZP类的属性名即PropertyID,从Reader中获取对应的SQL字段。

SQL返回 的字段尽量与ZP属性保持一致,这样性能最好。

各种数据类型的与数据库字段的映射关系参考ZPropertyMysql ConvertObject函数。

如果返回的是单体问题,可以使用Clone。

DO的ZPropertyInterfaceRef和List都不会加入到SQL的查询中

这里引入一些属性方便与数据库进行对接,比如MysqlColNameAttribute可以定义字段的别名,DBExcludeAttribute、DBIncludeAttribute可以排除一些属性的定义。DBJsonStringAttribute可以把字符串字段,直接转换为ZP对应的对象。

数据库定义参考,这里注意对于ZP的共通Object比如ZExp、ZTransform也需要在引擎层进行支持,如下ZTransform会参拆分为四个字段。以下的问题就是字段的名字比较长,可以使用自动生成工具进行生成。

CREATE TABLE `Stars` (
  `sid` bigint(20) NOT NULL AUTO_INCREMENT,
  `StarUnit.UnitID` bigint(20) DEFAULT NULL,
  `StarUnit.BrainID` int(11) DEFAULT NULL,
  `StarUnit.CardID` int(11) DEFAULT NULL,
  `StarUnit.GalaxyAreaID` int(11) DEFAULT NULL,
  `StarUnit.Pos.Position.x` float DEFAULT NULL,
  `StarUnit.Pos.Position.y` float DEFAULT NULL,
  `StarUnit.Pos.Direction.x` float DEFAULT NULL,
  `StarUnit.Pos.Direction.y` float DEFAULT NULL,
  PRIMARY KEY (`sid`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

其它:

1. UniRX它一定要使用,非Unity版本,不然,其中还是有很多Native的代码不能在后端环境下使用,参考以下相关的设置。

https://blog.csdn.net/FeiBin2013/article/details/86574687

另外,需要引入一些对Server自定义引擎的支持,比如NextFrame等Observable的支持,其实原理很简单就是自定义一个MainThreadDispatcher类,定义Update等相关的Subject,然后在Update流程中进行触发。

由于没有UI,比如transform.OnPointerClickAsObservable 等Unity支持,需要不需要对应了, 只支持物理事件就可了。

2. StartCoroutine ,目前暂时还未使用,Server端的线程管理,目前还只使用Timer方式,比如Update啥的应该是在一个线程中进行调用的。优先还是使用单线程,因为Unity也是单线程的,还未考虑Job的支持。

参考:

NetCore安装

https://dotnet.microsoft.com/learn/dotnet/hello-world-tutorial#ubuntu

https://github.com/neuecc

还有几个库很牛的,看看

发布了51 篇原创文章 · 获赞 10 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/FeiBin2013/article/details/86753204