0%

参考Game Programming Patterns完成的设计模式总结笔记。

命令模式

命令模式本质上希望把一个硬编码的函数解耦成可配置的函数命令,尽可能将功能触发具体功能分离。

如下有一个玩家对象的输入控制功能,其中按下X键对应jump动作。如果我们想要将X键绑定到fireGun功能,那还得去代码里修改对应的硬编码if语句,然后重新编译,重新链接。

1
2
3
4
5
6
7
void InputHandler::handleInput()
{
if (isPressed(BUTTON_X)) jump();
else if (isPressed(BUTTON_Y)) fireGun();
else if (isPressed(BUTTON_A)) swapWeapon();
else if (isPressed(BUTTON_B)) lurchIneffectively();
}
阅读全文 »

点云空间学习

PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation

  • Qi C R, Su H, Mo K, et al. Pointnet: Deep learning on point sets for 3d classification and segmentation[C]//Proceedings of the IEEE conference on computer vision and pattern recognition. 2017: 652-660.

PointNet首次基于原始点云进行深度学习,其提出了点云深度学习的三大原则: 无序性、点间联系、变换一致性。基于此, PointNet在点云上逐点运用了MLP进行变换, 并且构造了T-Net进行对抗点云的仿射变换, 最终使用max pool进行对称聚合。

缺少对局部结构的特征学习

PointNet++: Deep Hierarchical Feature Learning on Point Sets in a Metric Space

  • Qi C R, Yi L, Su H, et al. Pointnet++: Deep hierarchical feature learning on point sets in a metric space[J]. Advances in neural information processing systems, 2017, 30.

PointNet没有捕捉到点的局部结构特征,限制了细粒度和复杂场景的识别、泛化能力。PointNet++则引出了一个set abstraction层对点云进行多级学习。set abstraction定义了多级多块的局部邻域结构, 其在每一个局部邻域中都使用了mini-PointNet来进行特征抽取。然而由于点云是非均匀分布的, 不同的局部邻域的密度不一样, 因此PointNet++提出了两种自适应密度的特征融合模块: Multi-scale grouping(MSG)Multi-resolution grouping(MRG)

另外由于部位分割等任务最终需要输出逐点的特征标签, 因此在set abstraction之后, Pointnet++一方面在同一级内进行反距离的插值传播, 另一方面自顶向下进行反向逐级的特征传播。在同一层内对两种传播特征进行拼接, 即得到该层的逐点特征。

阅读全文 »

机制

委托

委托类似于函数指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//定义委托类型 / 定义函数指针类型
delegate void MyDelegateType(int a);

//根据类型创建实例对象
MyDelegateType my_delegate_instance=new ();
//注册指向函数
my_delegate_instance+=Func1;
my_delegate_instance+=Func2;
my_delegate_instance+=Func3;
//删除函数
my_delegate_instance-=Func3;
//唤醒函数
my_delegate_instance.Invoke(int_variable);
my_delegate_instance(int_variable);

在我们定义委托类型时,本质上编译器会生成一个继承自标准库的类,如下所示。这个类实际上会形成一个委托的闭包,其会包含调用所需的所有信息,如调用实例、待调用方法的位置。

1
2
3
4
5
6
7
8
9
10
11
12
class MyDelegateType :System.MulticastDelegate
{

//一些继承的重要字段
internal Object _target;//当委托注册了实例方法时,这个字段会填充实例对象,以便调用实例方法。
internal IntPtr _methodPtr;//指针,单播时使用,指向那个注册的方法。
private Object _invocationList;//方法链,多播时使用,指向多个单播委托实例。

//唤醒操作
public virtual void Invoke(int a);
//...其余字段和方法
}

对于单播委托,其会在_methodPtr中直接存储方法地址。然而正如上文所示,一个委托可以注册多个方法。实际上在注册多个方法后,C#会生成一个独立的委托实例,用来指向多个单播委托:

阅读全文 »

概念讨论

内存对齐是一个很基础的概念。虽然内存地址的单位是字节,但是CPU访存指令大部分都是以机器字长(64位的CPU机器字长即8字节)为单位,并且由于内存提供的访存电路接口限制,CPU只能从机器字长的倍数的地址开始访问。即CPU能够在一条指令中访存0000-0007,但是不能够从0003-0010,而必须拆分成两条访存指令,分别从0000和0008开始。

CPU按机器字长访存容易理解,但是只能从倍数地址开始访问难以让人理解。据参考,倍数地址的限制更多来源于内存访问器的硬件连线限制。

在没有内存对齐的情况下,假设有两个连续数据INT8,INT64,分别占据了地址0000和0001-0008。此时64位CPU需要几次访存才能拿到INT64数据呢?

  1. 首先访存0000-0007,拿到0001-0007的部分。
  2. 再次访存0008-0015,拿到0008的部分。

即需要访存两次才能取到数据。因此,通常程序会对数据的布局作出内存对齐,以优化访存效率。例如还是INT8和INT64,对齐后地址分别为0000和0008-0015。这样取INT64就只需要访存一次。

据此,业界给出了一个内存对齐的基本原则:将X字节的数据放在X倍数的起始地址上。这样即可在尽可能节约内存空间的情况下,减少访存指令。

阅读全文 »

UGUI

动静分离

如果每个UI元素都独立一次drawcall的话,那显然会极大浪费渲染队列,因此UGUI在绘制UI时会将UI元素进行网格合并的操作,以减少DC一次绘制。

合并的出发点没有问题,但因此只要任何一个UI元素一变,网格就需要重新绘制,这样节省下来的DC全用在合并顶点上了,因此就需要UI元素的动静分离。UGUI中以Canvas为合并网格的基础,因此我们需要尽可能把动态UI元素放在单独的Canvas中,把静止常态的UI放在另一个Canvas中。

嵌套子Canvas也不会和父Canvas合并

重UI的分解

随着UI嵌套的越来越厚、越来越多,项目后期的一个UI对象可能会套着好几层UI面板,并且用的时候只显示某一个,隐藏其他的。因此虽然看起来没什么,但在对这个重UI实例化和销毁的时候会变得很慢,因此可以考虑把不必要的二级UI拆分成单独的对象,在需要时才实例化生成,而不是激活和隐藏。

不管分不分解,加载这些对象的CPU时间都是需要的,但本质上我们把集中的大段CPU作业拆解成了多个小段,以降低卡顿。当然,也要注意如果拆分不当,导致小元素频繁实例化和销毁也会造成性能浪费。

加载优化

UI加载并实例化需要做完:加载GameObject对象、网格合并、组件初始化、素材资源加载等任务,并不算轻松。因此可以考虑在CPU缓和的情况下预加载UI:例如提前加载AB包素材。或者像上一节中对UI进行激活和隐藏处理,而不是生成和销毁。激活和隐藏不用再改变内存,但是组件的重新enable也会带来一定开销,因此再极端一点可以考虑直接把UI移出屏幕并且关闭部分更新,以形成隐藏。

字体拆分

字体图集也是一项比较重的资源,而UI中如果同时有几种不同的字体,也会对内存造成一定负担。因此可以针对于特定使用场景,提取出特定词汇单独生成一个字体图集使用,比较常见的有登录场景字体、数值字体、常用字字体。

阅读全文 »