游戏逻辑思想
上QQ阅读APP看书,第一时间看更新

第一章
基础内容交流

1. 代码规范,编写可读懂的代码

代码有许多种命名的方法,比如驼峰命名法、匈牙利命名法、帕斯卡命名法和下划线命名法。这些方法是语言级别的命名方法。

驼峰命名法除第一个单词之外,其他单词首字母大写。比如下面的代码:

int myStudentCount;

我们在其他地方看到myStudentCount的时候,我们可以很快的通过它的英文或者拼音理解它的含义。但是重要的是,我们无法一眼知道它的类型。

下面我们讨论动态语言的代码规范,因为动态语言的类型灵活性,它的代码比其他语言要严格的多。我们以lua为例,讨论命名的规范性。

首先,是变量的命名。

字符串统一以s开头,比如 sText,s意味着string

整形或者浮点统一以n开头,比如nCount,n意味着number

布尔类型统一以b开头,比如bShowMsg,b意味着boolean

数组或者map统一用t开头,比如tAward,t意味着table类型或者map类型

函数对象统一用f开头,比如fCallBack,f意味着function

对象统一用o开头,比如oButton,常用在更为底层的对象上,o意味着object

从上面可以看到,我们的命名应该带上足够的信息,这些信息应该包含了它是什么类型。在类型方面,又存在2种不同的类型条目,程序类型以及逻辑类型。我们前面已经定义了程序类型,接下来我们可以对逻辑的命名进行拓展。比如对于某个库的ui对象的命名。

假设这个ui的类型在该库中是Label。那么我们可以把它命名开头定位oLbXXXXX。最后,我们给按照功能给这个对象进行命名。比如这是一个标题栏的Label,那么我们可以命名为oLbTitle。这里就会有一个疑问,为什么要把类型放在前面而不是把作用放在前面,比如说命名为oTitleLb。这是因为逻辑功能的命名通常长于类型命名,类型放在后面不容易快速识别是什么类型,而放在前面就很容易可以识别出来了。所以我更建议把类型放在前面。

以get开头+名词的函数,在函数内不能对对象或者现有环境产生副作用。经常遇到的一种情况:就是为了使下次的get能够查找的更快,对对象进行缓存。这种接口也不能直接使用get 开头+名词的方式。

如果在get中希望也做一些事情,那么命名的接口为getAndDoSometing。get接口的返回值应该非常清晰,直接就对应着命名,比如上面的问题可以命名为getAndCacheXXX。在get开头+名词的接口的参数中传递一个回调也是不应该的,比如getXXX(fCallaBack)。fCallBack里面本身可能会对整个环境做修改,所以也需要用getAndDoSometing。

以set开头的函数,一定是对变量进行直接的赋值,不能是加或者减。加或者减的操作应该使用更加清晰的add,sub接口。set接口的返回值可以是无类型或者是boolean,返回boolean常用在判断是否真正产生了变化。这种情况一般是这么写的:

function setFlag(bNewFlag)

if self.bFlag == bNewFlag then

return false

end

self.bFlag = bNewFlag

--做一些别的操作

return true

end

如果支持多返回值,那么返回值可以带上一些修改后的值。

注意在set接口中一般需要判断是否真的改变,只有真的发生改变了才执行后面的逻辑。如果在部分场景需要强制赋值,那么有2种方式:

1. 添加一个tOption参数,通过该参数对不同的可选参数进行不同的处理,这边的可选参数是个map类型或者说是个table,所以用t开头。

2. 新增一个直接赋值的接口,该接口内不进行比较判断。原来的set接口改为判断后调用新的接口。

我们来欣赏一段赋值代码,取自白鹭引擎的底层代码Bitmap.ts,来加速我们队赋值函数的理解。

$setTexture(value: Texture): boolean {

let self = this;

let oldTexture = self.$texture;

if (value == oldTexture) {

return false;

}

self.$texture = value;

if (value) {

self.$refreshImageData();

}

//省略其他代码

}

我们在代码中,经常也会看到以on开头的函数,这类的函数主要是用来作为事件响应的。比如关闭按钮的响应事件onClickClose,又或者是响应其他对象发出的事件,如onLevelChange,注意这类的接口外界看到是不应该直接去调用的,即使它不小心把自己设置成为了public。

在我们的一些语言中,比如lua之类的动态类型的语言。我们无法通过ide来限制外界对内部的访问。那么我们需要建立一个项目共识,比如带下划线的函数是禁止从外界调用的。带下划线或者$开头的成员变量也是不能直接去访问的。总结起来就是所有看似是私有的函数或者变量,都不应该直接去访问。当然,变量应该只通过接口去访问。

一个常见的场景,比如项目中已经有一些代码与你遵循的代码规范不一致的时候,这时候我们建议的是在原作者的文件中,遵从原作者的写法。在自己新建的文件中,按照自己的规范来写代码。还有一种场景,如果你使用的框架或者第三方库与你遵循的代码规范不一致,这时候你依然按照自己的规范书写代码,除非你是直接去修复框架或者第三方库的问题。

接下来讨论另外一个问题,成员变量的放置问题。我们通常在别人的代码中看到的是把成员变量定义在一个类最开始的地方。我们这边提出另外一种可能性,就是把成员变量放在对应的函数前面。比如下面这种:

private bFlag = false;

public getFlag(){

}

public setFlag(bFlag){

}

什么时候我们把变量放在函数前面呢?当这个变量的有效赋值以及使用只有部分函数,且这些函数都是紧紧挨在一起的时候,我们把它放在它的前面。这是遵从一个更大的原则,有关联性的东西应该放一起。