C#与.Net

最近用C#开发一套独立的编辑器,使用WPF界面框架,引入了Telerik界面库。后来为了让处理数据的逻辑能跨平台运行又创建了一个.Net Core的console app来完成这部分独立的功能。做Unity开发的时候也知道mono可以支持C#代码的跨平台运行,在此打算把C#生态涉及到的各种概念梳理一下。包括C#、.Net、.Net Framework、.NetCore、.Net Standard、mono、MAUI、WPF、Telerik、CIL、CLR、ECMA标准等等。

最初C#是微软推出的基于.Net Framewok的编程语言。C#与Java一样是托管语言,通过编译器生成中间语言 CIL( Common Intermediate Language ),再在运行时由中间语言运行时库CLR( Common Language Runtime)翻译执行并调用对应系统的API来工作。因为C#语法比较接近C/C++,但具有托管语言的优势(内存管理,异常机制等),很快流行起来。但当初的.Net Framework只支持Windows平台,就有公司及开源社区希望能让C#跨平台运行,这就是mono的出现。此后C#语言就不再是微软自家的产品了,需要有统一的标准。关于C#语言及C#编译出来的中间语言CIL的标准由欧洲计算机协会ECMA(European Computer Manufacturers Association)制定。

Continue reading

帧同步技术(二)

第一篇文章中我们讲了帧同步的核心:如何保证各个客户端的一致性。保证不同客户端各自计算的结果都相同就能确保帧同步玩法的正确有效,但如果服务端消息没下来前整个客户端画面一动不动,体验是非常不好的。在确保一致性的前提下做一些预表现以及降低网络传输的延迟能给到玩家更好的体验。这篇文章主要讲这方面的一些优化,另外还有关于不同步问题的定位排查、防止客户端修改作弊、断线重连与网络消息堆积时的一些处理方案。

UDP传输:TCP传输因为在底层做了各种关于可靠性及稳定性的处理(详细可见《计算机网络——传输层:TCP与UDP》),所以延迟会比较高。而在帧同步战斗中,操作需要上行到服务器然后经过转发到达到各个客户端才能推进真正的逻辑,低延迟对玩家操作体验非常重要。而帧同步战斗这种频繁(1秒30帧)的传输小量(只转发操作)的数据,用UDP也是很有优势的。在相同的网络环境下,UDP可以在一个逻辑帧内完成来回的,延迟在33ms以内,而使用TCP至少要3-4个逻辑帧才能完成来回,延迟在130ms以上。

使用UDP传输需要自己处理乱序和丢包的问题。帧同步战斗中,服务器定时派发的帧数据本来就是有序号的,所以乱序到达的问题只要缓存起来按顺序使用就可以了。而丢包的问题我们可以采用冗余传输来尽量避免。服务器一次下行中不仅带上当前第n帧的信息,还可以带上n-1,n-2或更多的冗余信息,这样只要不是连续丢多个包,都能保证客户端最终收到连续的帧信息。如果客户端当前在等第t帧, 但已经收到第m帧的信息,m-t的差值大于一定程度,就认为已经连续丢了多个包,这个时候靠冗余信息已经拼凑不回来完整的序列了,就需要利用TCP重新请求缺失的帧,服务端用TCP把客户端请求的帧信息重新补发。利用TCP来做请求重传虽然速度慢,但可以保证一定能收到。具体流程图如下:

Continue reading

帧同步技术(一)

关于帧同步技术原理及实现方案网上已经有非常多的文章介绍了,但做《月夜狂想曲》的帧同步战斗时还是有遇到很多具体的细节问题,也做了很多相关处理,在这打算分两篇文章记录一下。第一篇主要讲客户端如何保持一致性,会遇到哪些问题以及怎么处理。第二篇主要讲通过哪些改进可以让帧同步有更好的游戏体验。

帧同步的原理是,服务器只负责收集指令,然后以恒定帧率(例如30帧1秒)转发指令,真正的游戏逻辑由各个客户端各自计算,客户端要等到服务器派发的指令才能推进逻辑,没有收到指令时逻辑是暂停的(LockStep)。帧同步最重要的是确保各个客户端在不同硬件不同平台下各自计算游戏逻辑结果都是一致的。在Unity中为了达到这个目标,我们需要解决以下问题:

随机数:Unity自带的随机数因为有可能会被物理系统,粒子系统,UI系统,第三方组件等各种模块使用,我们是无法保证他在不同客户端下每次都能取到统一的随机数的。这就需要我们自己实现一套自己控制的随机数发生器,关于如何实现随机数的算法,之前写过一篇文章介绍《随机数生成算法》。在有可控的随机数发生器后,我们需要查找项目中所有用到随机数的地方,判断该流程是逻辑相关需要被统一接管的,还是表现相关可以由各个客户端自由决定的,从而使用不同的随机数发生器。 目标就是确保逻辑相关的流程下在各个客户端下都能取到同样的随机数。

Continue reading

游戏开发安全问题

这几年开发Unity手游时,陆续会针对安全问题做一些处理,在这里分享一下我们的一些做法。我将安全问题分为四大块,每一块都有很多可以深入挖掘的点,也有专门做安全的部门或第三方支持。这里更多介绍的是一些项目组会用到的安全策略。

—— 数据安全

数据安全首先需要处理的是内存安全,面对大量的内存扫描,内存修改工具,如果项目内的核心数据是裸放在内存中的话,肯定避免不了被改属性或者锁血这些问题。 内存安全我们的具体做法是把关键数据int或float的4字节随机映射到一块36字节的空间内。

Continue reading

浮点数二进制表示

我们都知道浮点数会有精度问题,但为什么会有精度问题?用来表示整数是否会有精度问题?最大可以准确描述的整数可以有多大?这些问题在我们平常开发中也会遇到,想要弄明白这些问题的答案,需要从浮点数的具体表示方式入手。后续会写的一些技术文章也有涉及到这部分知识,所以先开一篇文章从最简单的浮点数表示开始,把这些问题讲清楚。以下均以32位浮点数举例说明,64位同理。

首先我们需要知道浮点数表示由三部分组成,最高位是符号位,表示正负。中间8位是指数位,用来表示后23位有效位的小数点位移。后23位就是用来存储浮点数有效表示的01。

我们先来看看6.75这个数怎么用二进制表示,整数部分6是110,小数部分0.75是11,整体是110.11。整数部分 6是110 是与整型表示是一致的,而小数部分第一个1表示2的1次幂的倒数即1/2,第二个1表示 2的2次幂的倒数即 1/4,如此类推。0.75小数部分11(1/2+1/4),0.625小数部分是101(1/2+1/8)。

Continue reading