欢迎光临散文网 会员登陆 & 注册

pvzclass是如何实现的?pvzclass源代码初步分析(3) PVZ.h 概览 & Enums

2021-06-15 10:57 作者:__W1thoutD0ubt  | 我要投稿

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。

PROPERTY宏及其变形

(接下来的十个宏代码很长,全部截下来会被压缩到失真,建议读者自己打开文件查看相应位置的完整代码)

READONLY_PROPERTY宏和WRITEONLY_PROPERTY宏还算好理解,它们分别是PROPERTY宏的只读和只写版本。

而PROPERTY宏对于不很了解C++宏定义的人可以说是非常复杂的。

不过,我们可以结合具体的例子来看:

游戏内BGM声音大小

这是一个double类型的变量。尝试读取这个变量时,返回的是__get_MusicVolume(即getmethod)的返回值;而尝试对这个变量赋值时,这个值也会作为__set_MusicVolume(即setmethod)的参数。

__get_MusicVolume()和__set_MusicVolume()都在PVZ.cpp中有所定义。

说白了,通过PROPERTY宏定义的变量在赋值/取值的形式上仍与一般变量一样,但实际赋值/取值的函数与一般变量不同,需要另行定义。

PROPERTY_BINDING宏及其变形

类似于PROPERTY宏,PROPERTY_BINDING宏也具有只读和只写版本。

我们比对以下PROPERTY宏和PROPERTY_BINDING宏的定义:

可以看出,两者除了是否直接定义了getmethod()和setmethod()以外,可以说没有别的差别

PROPERTY_BINDING宏的作用也和PROPERTY宏基本一致,不同的是,PROPERTY_BINDING宏将相应的函数直接完成了定义。

因此,PROPERTY_BINDING宏也可以看成是PROPERTY宏的简化版本。

不过也是因此,接下来的宏才会长得超过200字符:

T_PROPERTY宏及其变形

T_PROPERTY宏具有整数版本和只读版本,是PVZ.h中定义变量的重要的宏。

实际上,T_PROPERTY宏只是PROPERTY_BINDING宏的一个应用:

展开后的T_PROPERTY宏

T_PROPERTY宏的四个参数分别为变量的类型、变量的名称、getmethod名称、setmethod名称和偏移量。

虽然getmethod和setmethod几乎用不到,但还是得在定义时写出。

整数版本和只读版本类似,只是相应地调整了变量类型,或是换用了只读版本的宏。

4.PVZ类

PVZ类虽然占用了最多函数的代码,但是收起大部分代码后,可以看到PVZ主要包含四个部分,即Memory类、属性、附属类和直属方法:

构造函数和析构函数的定义在PVZ.cpp中,这里先跳过。

Memory类中定义了读取、写入PVZ主程序的变量需要的函数和变量。

附属类中定义的各种类,实际上都是PVZ的关卡中直接或间接出现的各种对象。植物对象、僵尸对象,等等,都在附属类中有所对应。

而properties中定义的,则是附属类中没有包含的其他PVZ内部变量。

直属方法中包含的,则是一些常用函数和获取PVZ内部对象使用的函数。

以后的篇章将对上述内容详细解读。

按照顺序,下一篇将从Memory类开始解读。

前面丢下的Asmfuntion.h也会一并补上。


pvzclass是如何实现的?pvzclass源代码初步分析(3) PVZ.h 概览 & Enums的评论 (共 条)

分享到微博请遵守国家法律