315 lines
13 KiB
Markdown
315 lines
13 KiB
Markdown
|
|
# SesothoLine
|
|||
|
|
|
|||
|
|
## 简介
|
|||
|
|
|
|||
|
|
unity通用库插件,现已转为unity 6000版本。
|
|||
|
|
|
|||
|
|
## 软件架构
|
|||
|
|
|
|||
|
|
* CentralizeLog: 小日志,unity本身的日志在调用时开销巨大,所以提供一个小日志的类型保存临时的日志内容。
|
|||
|
|
* Deconstruction: 通过数学公式对线进行描述,进行标准的CAD线路参数计算,并支持在线路上进行导航,合理性计算等
|
|||
|
|
* RescissionRework: 撤销重做 🕑
|
|||
|
|
* SerializerHelper: 序列化库
|
|||
|
|
* XericLibrary: 实用性unity宏库,包含一些实用扩展用法。
|
|||
|
|
|
|||
|
|
## Deconstruction
|
|||
|
|
|
|||
|
|
Deconstruction 部分由两个dll组成,一个是 Deconstruction,另一个是 Deconstruction Style 。
|
|||
|
|
|
|||
|
|
### 线路绘制
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
线路绘制组件支持二维,或三维的线条计算,线条继承自“可放置物”类,为了能够显示线条,需要向其提供渲染器和,同理,为了能够算出构成线的点集合,需要提供线计算器。
|
|||
|
|
为此插件内默认提供了一个使用lineRenderer组件的线渲染器,以及二维的直线和圆弧的线计算器。
|
|||
|
|
在计算平面上的CAD线路时,通过实现二维的线计算器,并实现它的坐标转换方法,即可在任意表面上实现线的绘制与保存。
|
|||
|
|
|
|||
|
|
另外如果希望快速构建具有特制绘制规则的对象,则需要另外的可放置物管理器 PlaceElementManager ,
|
|||
|
|
它继承于 SingleMonoBase ,所以可以通过单例的方式访问它。
|
|||
|
|
|
|||
|
|
可放置物管理器管理的是所有的可放置物对象,和决策工具类 DecisionMakerToolBase ,以及它们的生命周期。
|
|||
|
|
|
|||
|
|
工具类则提供了细致的针对如何放置对象的操作规则,并提供了操作栈表结构,
|
|||
|
|
可以通过操作栈来实现具有链表关系的行为树,栈中的每个节点是行为树上的一个节点,
|
|||
|
|
每个节点可以访问到上一个对象和下一个对象成员的节点。
|
|||
|
|
|
|||
|
|
每个节点可以是正在绘制的线,正在计算寻路的ai,或其他任何行为,
|
|||
|
|
在节点内可以直接通过 ReplaceThis(new Exit_OpSlot()); 方法类来切换行为树的节点到一个新的节点。
|
|||
|
|
|
|||
|
|
* 基础规则
|
|||
|
|
建议在工具类中进行创建等操作,管理类中只进行管理操作,比如在插件的sesotho部分的非dll文件中,提供了SesothoPeManager和SesothoArrangementWiresTool,他们就是管理器与工具类的派生部分。
|
|||
|
|
|
|||
|
|
* 按键规则
|
|||
|
|
在没有任何工具启动的情况下,管理器可以接收按键输入,直到被工具输入抢占。
|
|||
|
|
不过,管理器到工具中并没有任何抢占逻辑,所以请根据工具激活逻辑适时关闭按键处理。
|
|||
|
|
另外,在没有任何工具激活的情况下,工具本身的按键获取机制也不会激活,记得使用原生的按键获取逻辑。
|
|||
|
|
|
|||
|
|
* 线路规则
|
|||
|
|
|
|||
|
|
|
|||
|
|
* 链路规则
|
|||
|
|
链路是指实现了IConterminousness接口的对象,这个接口提供了一个获取ICoterminous接口的访问器;
|
|||
|
|
ICoterminous接口通常是用于实现链接关系的抽象层,简单的通过输入和输出来定义链接的线路,在接口中可以通过GetOpposite在当前对象的链接结构上下文中访问另一个与当前访问者相对应的链接对象,通常这是与访问者同类的对象,但是会经过接口进行抽象;而GetIdentical则可以获取与访问者同类链接的对象成员。
|
|||
|
|
|
|||
|
|
链路方向使用LinkType进行标记,也可以直接使用LinkType.SwapType()来快速反转这个链接标记;
|
|||
|
|
LinkType可以直接和LinkedPortType相互进行转换,也可以使用LinkType.ConvertType()来快速转换类型。
|
|||
|
|
链路方向通常只用在内部作为方向标识,外部无法获取,一般只在设置时进行一次标识设置即可。
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 线路结构
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
线路绘制组件提供了线路连接引用关系,可以将一个线连接到另一个线上,也可以用点将它们连接起来,
|
|||
|
|
默认线
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 生命周期
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
由于所有的成员都是通过批处理器完成生命周期的定义,批处理器的调用时机对于后续的流程来说至关重要。
|
|||
|
|
为了便于使用,所有的批处理对象的生命周期都遵循以下规则:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
开始和结束时间在LateUpdate阶段触发;
|
|||
|
|
主线程更新事件在Update阶段触发;
|
|||
|
|
异步更新事件在LateUpdate阶段触发;
|
|||
|
|
|
|||
|
|
主线程事件在所有事件之后才执行;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
* 安全检查
|
|||
|
|
在批处理期间是允许销毁的,目前的安全限制仅限在更新前会检查一遍批处理的key值是否为空,在其他时刻,可以通过其他的接口传递的安全检查来辅助更新检查。
|
|||
|
|
比如在PromptLine2中,如果太早或太迟调用UpdateRender会导致在更新期间捕获到一个空引用的报错,来源是linerenderer组件在此时还不存在,具体原理不清楚,但是只会提示一次,所以此时的渲染操作可以直接跳过。
|
|||
|
|
|
|||
|
|
### 获取工具
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
如果希望获取放置物管理器中的工具:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
// 获取当前激活的工具目标
|
|||
|
|
DecisionMakerToolBase.ActiveDecisionMaker
|
|||
|
|
// 以及这个工具目标是否有效
|
|||
|
|
DecisionMakerToolBase.HasAciveDecisionMaker
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 示例文件
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
在Runtime/HorizonLineOrbit中已经提供了基本的用例,将SesothoPeManager挂到任意一个节点上就可以了。
|
|||
|
|
运行后按下键盘L键,此时点击世界中layer为defualt的物体,就会点击处y轴为0的位置上生成一个线,移动光标,线也会跟着移动。
|
|||
|
|
|
|||
|
|
这些脚本提供了一个基本的示例,并且几乎将所有可以进行配置修改的要素都呈现了出来,可以在后续的扩展中通过参考这些文件来进行实现。
|
|||
|
|
|
|||
|
|
在示例脚本中主要分为对元素的实现,以及对过程的实现;
|
|||
|
|
* terminalPoint, TerminalLine2T3, TerminalIland是后续创建的实例所挂载的具有确定空间,绘制方式的实现。
|
|||
|
|
* 其他的都是管理器,工具,操作栈的实现。
|
|||
|
|
|
|||
|
|
其中管理器,工具,操作栈类都是层层嵌套的引用结构,尽量只让相邻的两个过程进行相互的引用,比如工具应该引用管理器的实现,开放给操作栈。
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 使用须知
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
现在已知的线路绘制问题:
|
|||
|
|
* 如果同时创建大量的操作栈(>500),并在之后的某个时刻中将它们全部隐式地释放掉,有可能会在之后的某个时刻带来庞大的GC占用(>90ms)
|
|||
|
|
* 逆时针绘制的曲线线路没法计算最近点。
|
|||
|
|
|
|||
|
|
## XericLibrary
|
|||
|
|
|
|||
|
|
XericLibrary 部分由两个dll组成,一个是 Xeric Library,另一个是 Xeric Library Editor 。
|
|||
|
|
|
|||
|
|
### 宏库
|
|||
|
|
|
|||
|
|
#### 特性库
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
* CanFind$
|
|||
|
|
其中包含多个特性类,用于查找类,字段,属性,方法等项目。
|
|||
|
|
|
|||
|
|
* ReName
|
|||
|
|
重命名特性,在没有安装odin插件的旧版本引擎上可以直接替换细节面板中的参数名称,不过由于无法和odin兼容,所以odin的功能会覆盖掉这个特性。
|
|||
|
|
主要作用是提供属性标签查找功能。
|
|||
|
|
|
|||
|
|
#### 协程宏
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
#### 调试宏
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### 模型网格宏
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### 机器标识宏
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### 宏库
|
|||
|
|
----
|
|||
|
|
* 摄像机宏
|
|||
|
|
* 颜色宏
|
|||
|
|
* 常数宏
|
|||
|
|
* 曲线宏
|
|||
|
|
* 枚举宏
|
|||
|
|
* 方程宏
|
|||
|
|
* 事件宏
|
|||
|
|
* 文件宏
|
|||
|
|
* 步进宏
|
|||
|
|
* 按键宏
|
|||
|
|
* 集合宏
|
|||
|
|
* 数学宏
|
|||
|
|
* 对象宏
|
|||
|
|
* PID宏
|
|||
|
|
* 对象池宏
|
|||
|
|
* 矩形变换宏
|
|||
|
|
* 超驰宏
|
|||
|
|
* 仓库宏
|
|||
|
|
* 材质宏
|
|||
|
|
* 平滑宏
|
|||
|
|
* 排序宏
|
|||
|
|
* 文本宏
|
|||
|
|
* 时间宏
|
|||
|
|
* 轴变换宏
|
|||
|
|
* 类型扩展
|
|||
|
|
* 矢量扩展
|
|||
|
|
|
|||
|
|
#### 导航宏
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
#### 安全宏
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### 转换宏
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
#### 独特类型
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 简易批处理器
|
|||
|
|
|
|||
|
|
在代码中引用 XericLibrary.Runtime.Type.BatchProcess ,
|
|||
|
|
并将需要进行批处理的对象实现 ICanBatchProcess<T> 接口,
|
|||
|
|
其中提供了所有额外支持的生命周期事件,可以直接实现或显式实现。
|
|||
|
|
|
|||
|
|
缺点是只有异步方法的生命周期约束,没有提供线程安全的数据类型。
|
|||
|
|
|
|||
|
|
和官方的相比没有什么优势,只是为了此处使用的方便。
|
|||
|
|
|
|||
|
|
|
|||
|
|
## DataBaseControl
|
|||
|
|
|
|||
|
|
数据库控制器,当前主要工作区域是SqlClient部分
|
|||
|
|
|
|||
|
|
----
|
|||
|
|
|
|||
|
|
### 基础使用
|
|||
|
|
|
|||
|
|
数据库操作的指令被抽象到Order类了(我在注释里会称为解释器),比如sqlClient就是sqlcOrder,其中sqlc代表sqlclient。
|
|||
|
|
order类本质上是一个文本拼接器,为了方便管理,你可以使用对应类的order.GetOrder来获取池中的一条指令,比如:
|
|||
|
|
```
|
|||
|
|
var orderValues = SQLCHelper.GetOrder();
|
|||
|
|
```
|
|||
|
|
这个orderValues必须手动释放才会回到池中,比如:
|
|||
|
|
```
|
|||
|
|
orderValues.Dispost();
|
|||
|
|
```
|
|||
|
|
GetOrder指令可以在编译时静态调用,用作静态参数,不去释放,这样的话指令的生命周期会跟随程序释放而释放。
|
|||
|
|
使用指令前建议先清空,避免指令污染:
|
|||
|
|
```
|
|||
|
|
orderValues.CleanOrder();
|
|||
|
|
```
|
|||
|
|
然后,可以在指令上调用对应的函数,里面预制了数据库连接增删改查,表增删改查,项增删改查,以及插入判断,筛选条件命令。
|
|||
|
|
对于连接功能,可以参考下面的示例:
|
|||
|
|
```
|
|||
|
|
public void InitDataBase(string hostName, string databaseName, string tableName,
|
|||
|
|
IEnumerable<MyData> valueData)
|
|||
|
|
{
|
|||
|
|
SQLCHelper.connectOrder.OrderSetServer(hostName, null);
|
|||
|
|
// 数据库不存在,就创建一个
|
|||
|
|
if ((int)orderValues.OrderIsDataBaseExist(databaseName)
|
|||
|
|
.ExecuteScalar() <= 0)
|
|||
|
|
orderValues.OrderCreatDataBase(databaseName)
|
|||
|
|
.ExecuteNonQuery();
|
|||
|
|
|
|||
|
|
SQLCHelper.connectOrder.OrderSetServer(hostName, databaseName);
|
|||
|
|
// 数据表不存在,就创建一个
|
|||
|
|
if ((int)orderValues.OrderIsTableExist(tableName)
|
|||
|
|
.ExecuteScalar() <= 0)
|
|||
|
|
orderValues.OrderCreatTable(tableName)
|
|||
|
|
.ExecuteNonQuery();
|
|||
|
|
|
|||
|
|
// 构建数据项目
|
|||
|
|
orderValues.CleanValue();
|
|||
|
|
orderValues.Values.AddRange(valueData.Select(a => new SqlCOrder.SqlCValue(a.name, a.value)));
|
|||
|
|
|
|||
|
|
// 数据项目不存在,就创建一个
|
|||
|
|
if (valueData != null)
|
|||
|
|
{
|
|||
|
|
orderValues.OrderInsetTableItem(tableName);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
可以看到前面会使用同一个order指令来执行命令构建,然后通过Execute方法来执行指令。
|
|||
|
|
指令在构建时会将文本压入stringBuilder中,直到遇到Execute指令后,指令才会被拼接成具体的文本,存入历史指令中。
|
|||
|
|
当这个指令中stringBuilder里没有待输出的指令时,会优先使用之前拼接过的文本,也就是说一条已经构建的指令可以被重复执行。
|
|||
|
|
|
|||
|
|
刚刚的示例代码也可以通过调用预制的扩展方法完成,比如:
|
|||
|
|
```
|
|||
|
|
orderValues.InitializationDataBase("localhost", "DEFAULT_DATABASE", "DEFAULT_TABLE");
|
|||
|
|
```
|
|||
|
|
上面这段指令会自动连接到地址localhost下,名为DEFAULT_DATABASE的DEFAULT_TABLE表中。
|
|||
|
|
如果数据库中没有库或表,将会自动创建。这对于仅仅为了保存数据的情况来说会比较方便。
|
|||
|
|
这种扩展指令根据其使用情景可能会相对于普通的方法有所区别,就比方说这个扩展方法会立刻执行,不需要手动调用Execute方法。
|
|||
|
|
一般指令都仅仅只是将命令填入拼接池中,然后在Execute中执行order里的SubmitOrder()来持久化指令内容。
|
|||
|
|
|
|||
|
|
如果希望在内存中保留一个与数据库对应的数值,则可以使用order的Values属性;
|
|||
|
|
一个order里的Values对应一个数据库表里的一列下的一个数据。
|
|||
|
|
Value的name对应列名,value对应实际值。
|
|||
|
|
|
|||
|
|
还是上面那段完整的数据库创建函数实例中,最后的OrderInsetTableItem就是将values插入到表的指令,包括InitializationDataBase扩展中也会自动将当前执行的order里的values插入到表,所以这个指令会建议手动管理生命周期。
|
|||
|
|
|
|||
|
|
这里说明一下,指令里的方法名称是有特定规律的:
|
|||
|
|
* 比如用于单段执行的指令方法,会使用Order开头,使用<code>.Order...</code>可以搜索到很多相关的指令。他们都会返回自身,用以支持链式的调用。
|
|||
|
|
* 如果某个指令的后面可以继续添加指令,比如条件指令之类的,可以使用<code>.OrderJoin...</code>搜索到一些相关的预制指令。
|
|||
|
|
* 而其他的指令则可以在扩展库中找到,扩展库中会专门针对特定的应用情景开放专门的方法。
|
|||
|
|
|
|||
|
|
#### 数据读写
|
|||
|
|
|
|||
|
|
在上面已经说过如何新建表,建表的时候会默认写入一次数据,而如果希望手动定义表格里的内容的话可以这么写:
|
|||
|
|
```
|
|||
|
|
order.OrderJoinValuesDefinitinOnly(orderValues.Values);
|
|||
|
|
```
|
|||
|
|
如果希望填入当前order里的Values的话,可以这么写:
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
读取可以使用:
|
|||
|
|
```
|
|||
|
|
orderValues.OrderSelect("DEFAULT_TABLE", null, null, true, true);
|
|||
|
|
```
|
|||
|
|
这段指令将orderValues里的value作为项目进行查询,由于两个null没有指定条件和排序,所以这会返回所有value的值。
|
|||
|
|
记得使用ExecuteReader执行,并返回阅读器进行处理,在阅读器中,需要手动根据值名称一一对应地将值填回orderValues里。
|
|||
|
|
|
|||
|
|
不过在扩展类中也提供了自动地读取逻辑:
|
|||
|
|
```
|
|||
|
|
orderValues.ReadLatestValue("DEFAULT_TABLE");
|
|||
|
|
```
|
|||
|
|
这会读取数据库中的,orderValues中的值,并直接写在orderValues的每一项中。
|
|||
|
|
然后直接对orderValues中的values进行处理姐可以了。
|
|||
|
|
|
|||
|
|
需要注意的是,这些指令虽然没有明确要求数据库连接,但还是至少需要连接过一次数据库,比如至少调用过一次InitializationDataBase方法,这样,后续的所有指令都可以不需要主动指定connectOrder项目。
|
|||
|
|
|
|||
|
|
|