pvzclass是如何实现的?pvzclass源代码初步分析(3) PVZ.h 概览 & Enums
PVZ.h算是把pvzclass中几乎所有的对象(除了后来加上的events相关)都定义了出来,这也使得它是pvzclass核心文件中最主要的,也是最大的文件。
本篇主要把PVZ.h包含的内容概括一下,顺带将Enums.h和Classes文件夹下的东西说一说。

1.PVZ.h概览
个人认为,大部分人是不会喜欢看PVZ.h的代码的。
因为一打开PVZ.h,映入眼帘的将是:

(因为PVZ.h含有#pragma region和#pragma endregion这种只有Visual Studio才能使用的编译命令,本篇使用Visual Studio呈现代码)
这种看上去就很难分析的代码很容易给人留下极不友好的印象。
再加上下面的定义使用的是不同于常见代码的方式,这就足以筛掉大部分人了。
既然先细致地看会招致混乱,我们就先粗略得看,对PVZ.h的内容形成一个大致的认识。
我们将大部分代码先收起来,就可以看出PVZ.h可以大致分为三部分:

可以看到,PVZ类的定义占用了最多行数的代码。
而实际上,PVZ.h还没有包含完整的定义。其中附属的方法由其他的源代码文件完成。
这些源代码大部分都在Classes文件夹下的文件之中,剩余的代码则包含在PVZ.cpp之中。
接下来按照顺序,先把Enums.h说一下。
2. Enums
Enum,本来是一种特殊变量类型,在C/C++中也有。
这种变量在中文语境下一般称为“枚举”类型变量。
顾名思义,Enums.h自然是包括了pvzclass中用到的所有enum类的头文件。
然而它实际上长这样:

没错,Enums.h和pvzclass.h一样,也是一个头文件索引。
那么Enums文件夹的作用也很显然了,就是包含上面提到的各个文件,以及它们对应的源代码文件。
所幸的是,这些头文件都是有规律的,它们都可以划分为两个部分(以下以ProjectileType.h为例):

enum部分为该头文件下的枚举变量。ProjectileType.h存储的当然是Projectile Type(子弹类型)了。
顺便说一句,其中Puff是孢子子弹(就是小喷菇等的子弹),Kernel是玉米粒,ZombiePea就是豌豆射手僵尸等的子弹。
ToString函数的目的很简单,就是将相应的枚举变量转换为描述性的字符串,以ProjectileType.cpp为例:

借助Enums,我们在写基于pvzclass的代码时,就没有必要背过PVZ中各种类型在PVZ内的存储情况了。需要的时候,直接去Enums文件夹对应的头文件查询即可。
3.PVZ.h中主要的宏定义
回到PVZ.h。开头的宏定义虽然长得令人发指,但是接下来的代码几乎完全基于这些宏。
搞明白宏的定义,对于理解代码是有很大帮助的。
我们从头开始:

STRING宏很好理解,就是将str和它的字符数同时罗列。
这个宏在pvzclass的代码中主要作为参数出现。
SETARG宏和SETARGFLOAT宏对于不熟悉“指针”概念的人而言可能不好理解。实际上它们分别相当于如下代码中的第一、二行:
不过,在pvzclass中,这两个宏主要是用于修改汇编指令中的部分参数,而pvzclass中汇编指令都是用byte数组存储的。
不过不用担心,这两个宏依然可以像往常一样使用,不过要注意的是这会同时修改数组中的4个量。

PAGE_SIZE宏……跳过。
PVZ_BASE宏的作用是获取PVZ中绝大部分变量的基址,即MemoryAddressList中第一行表示的基址。
当然,你不知道什么是基址也无妨因为你一般用不到。
(这里出现了Memory类的函数,以后会讲)
PVZBASEADDRESS宏的作用,则是获取PVZ关卡中相关变量的基址。
如果你不熟悉这个东西,至少需要知道一点:没有进入关卡时这个宏对应的值为0,在关卡内时则相反,不会为0。

(接下来的十个宏代码很长,全部截下来会被压缩到失真,建议读者自己打开文件查看相应位置的完整代码)
READONLY_PROPERTY宏和WRITEONLY_PROPERTY宏还算好理解,它们分别是PROPERTY宏的只读和只写版本。
而PROPERTY宏对于不很了解C++宏定义的人可以说是非常复杂的。
不过,我们可以结合具体的例子来看:

这是一个double类型的变量。尝试读取这个变量时,返回的是__get_MusicVolume(即getmethod)的返回值;而尝试对这个变量赋值时,这个值也会作为__set_MusicVolume(即setmethod)的参数。
__get_MusicVolume()和__set_MusicVolume()都在PVZ.cpp中有所定义。
说白了,通过PROPERTY宏定义的变量在赋值/取值的形式上仍与一般变量一样,但实际赋值/取值的函数与一般变量不同,需要另行定义。

类似于PROPERTY宏,PROPERTY_BINDING宏也具有只读和只写版本。
我们比对以下PROPERTY宏和PROPERTY_BINDING宏的定义:

可以看出,两者除了是否直接定义了getmethod()和setmethod()以外,可以说没有别的差别!
PROPERTY_BINDING宏的作用也和PROPERTY宏基本一致,不同的是,PROPERTY_BINDING宏将相应的函数直接完成了定义。
因此,PROPERTY_BINDING宏也可以看成是PROPERTY宏的简化版本。
不过也是因此,接下来的宏才会长得超过200字符:

T_PROPERTY宏具有整数版本和只读版本,是PVZ.h中定义变量的重要的宏。
实际上,T_PROPERTY宏只是PROPERTY_BINDING宏的一个应用:

T_PROPERTY宏的四个参数分别为变量的类型、变量的名称、getmethod名称、setmethod名称和偏移量。
虽然getmethod和setmethod几乎用不到,但还是得在定义时写出。
整数版本和只读版本类似,只是相应地调整了变量类型,或是换用了只读版本的宏。
4.PVZ类
PVZ类虽然占用了最多函数的代码,但是收起大部分代码后,可以看到PVZ主要包含四个部分,即Memory类、属性、附属类和直属方法:

构造函数和析构函数的定义在PVZ.cpp中,这里先跳过。
Memory类中定义了读取、写入PVZ主程序的变量需要的函数和变量。
附属类中定义的各种类,实际上都是PVZ的关卡中直接或间接出现的各种对象。植物对象、僵尸对象,等等,都在附属类中有所对应。
而properties中定义的,则是附属类中没有包含的其他PVZ内部变量。
直属方法中包含的,则是一些常用函数和获取PVZ内部对象使用的函数。
以后的篇章将对上述内容详细解读。

按照顺序,下一篇将从Memory类开始解读。
前面丢下的Asmfuntion.h也会一并补上。