本文涉及到的与游戏内部实现有关的名称,来源于PVZ 0.9.9版本中的pdb文件
DataArray是原版一代游戏中用于管理一类对象所广泛使用的结构。
对于一种特定的类型,DataArray会先在这种类型后面追加一个mID属性(新的类型称之为DataArrayItem),这个属性在游戏内广泛用于储存对象、寻找对象(例如蹦极僵尸记录抱起植物的过程)。
下文中的描述顺序并不是按照逻辑上最佳的顺序书写,而是按照这个结构在内存中的排布顺序来书写的。
DataArray的核心,为一个指向DataArrayItem类型的对象的指针。
实际游戏中,指向一个长为mMaxSize(下文叙述)的DataArrayItem数组的起始内存。
通俗来讲,这个属性即储存着所有的这类游戏对象。
DataArray曾经被使用的最大编号(不一定是其同一时间内拥有的最大对象数量),也等价于目前存在过的编号最大的对象的编号。
例如DataArray<Plant>在游戏开始时为0,种植两个植物后为2,即使铲除掉一个,这个值仍然为2
DataArray的最大容积。对于游戏内的绝大部分对象为1024.
DataArray上一个释放的对象的编号,与下一个对象在DataArray里所属的位置有关,初始为0,具体在下文中详细描述。
DataArray当前的对象数量。
例如DataArray<Zombie>的mSize属性即为僵尸数
与ID的决定有关,具体在下文中详细描述
为char*,指向DataArray的名称
给定DataArray的大小、名称,进行相关的配置操作 此操作进行后,会将mNextKey的值更改为一个由DataArray名称的第二个字符,第三个字符的ASCII码决定的初始值。
在DataArray里申请一个新的对象,位置为mFreeListHead所对应位置,同时更新mFreeListHead,如果其大于mMaxUsedCount,mMaxUsedCount和mFreeListHead均自增一,否则改为此位置下对象的ID(结合下文Free部分,即为上上个释放的对象的位置),然后构造该对象并赋予该对象新ID。 ID占32位,其中16位为其在DataArray数组中的位置顺序,另16位为mNextKey的值。 完成此操作后,mNextKey的值自增,到达65536后重置为1.
Free操作会释放对象,并且更新当前的mFreeListHead为所释放对象的对应位置。
同时,也会将对象的ID改为当前的mFreeListHead(这导致了其用于校验的Key部分始终为0,而mNextKey不会为0,于是该部分为0的对象始终为处于释放状态)
需要注意的是,游戏内的植物、僵尸等对象死亡时,会先运行Die方法,而Free方法在一轮循环的某时刻统一释放掉所有处于Die状态的对象。这也导致了mMaxUsedCount有时会比场上对象的历史最大数量还要大。
释放所有对象
重置所有状态为初始化前
获取DataArray中对象的ID。
由ID获取到其在DataArray中的位置,从而获取到对象
会事先校验给予的ID值是否为0,给予的ID的对应编号是否超过了mMaxSize 在Get的基础上,还会校验获得的对象的ID是否与给予的ID完全一致,如果不一致则返回空。
此判断被广泛使用,如果没有该判断,会出现如舞王僵尸召唤的伴舞僵尸死亡后,舞王僵尸立刻和新出现的僵尸建立连接的BUG,在有这个校验的情况下,需要在期间内刷新六万五千多次mKey才有可能达到类似的效果,这对于游戏是不可能的(但原版仍有达到现象的方法,见下文“游戏漏洞”)。
尽管不是DataArray的操作,但是几乎所有的DataArray都有对应的迭代所有元素的方法(不会迭代到mNextKey位置为0,即已经释放掉的对象)。
mMaxUsedCount在此使用,用于确定迭代的范围。顺序为从序号0遍历到序号最大,原版大部分遍历按照此顺序遍历,在无尽有关的交流中,由此产生的现象常被解释为“栈位”,这可能是由于其后释放的对象位置先重复利用,其符合栈后入先出的特点。不过DataArray本身结构更贴近数组而非栈。
对于植物、僵尸、子弹、物品,也会排除掉已经死亡但是没有调用过Free方法的对象。
原版对于以下对象的管理均使用了DataArray:
游戏内在保存DataArray时,只保存了mBlock,mSize,mMaxUsedCount,mFreeListHead。
这导致可以利用退出重进的方法刷新mNextKey,从而在游戏内建立错误的联系。可以用此方法实现缠绕海草抓取飞贼僵尸、舞王僵尸绑定非伴舞僵尸等效果。