引言
在Unity与NetCore使用方案中
讨论过如何在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角之前的关系。
以下有四元数的相互转换,其中四无数的乘法表示两个旋转的合成
Unity的Euler顺序是啥?一共有12种组合,不同组合得到的转换也是不一样的。
这个文章 有人实验证明使用的是ZXY
其中有关自由度丢失的问题,比如把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实现。
这里有一个还不错的。
不需要对应的类,这样可以与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的代码不能在后端环境下使用,参考以下相关的设置。
另外,需要引入一些对Server自定义引擎的支持,比如NextFrame等Observable的支持,其实原理很简单就是自定义一个MainThreadDispatcher类,定义Update等相关的Subject,然后在Update流程中进行触发。
由于没有UI,比如transform.OnPointerClickAsObservable 等Unity支持,需要不需要对应了, 只支持物理事件就可了。
2. StartCoroutine ,目前暂时还未使用,Server端的线程管理,目前还只使用Timer方式,比如Update啥的应该是在一个线程中进行调用的。优先还是使用单线程,因为Unity也是单线程的,还未考虑Job的支持。