第一次把V01版本的恐龙跳跃跑通时,我发了条朋友圈,配文是“游戏开发入门,其实也没那么难”。现在回头看,那句Flag立得挺打脸的。V01确实能跑,能跳、能撞仙人掌、能Game Over,但代码就是一团麻花:恐龙的位置、速度、跳跃状态全是全局变量,障碍物用一个固定数组硬扛,主函数的while循环里塞了快两百行逻辑。最讽刺的是,我想给恐龙加一个“二段跳”,搜遍整个文件才发现跳跃状态藏在第三个if分支里,改完以后连着戳坏了两次碰撞判定。
所以当V02要求我用面向对象或者结构体重写这版游戏时,我反而有点庆幸。这不只是“让代码变好看”,这是把一个已经能玩的游戏从“一次性脚本”升级成“真正能被后续维护的项目”的关键一步。这篇文章不聊什么高深的设计模式,就把我在EGE19.01上重构恐龙跳跃游戏的真实过程记录下来:先讲为什么V01必须重构,然后分别给出结构体版本和类版本的完整思路与代码,最后聊一聊两版跑完后的取舍判断,以及重构中踩过的那些让人拍桌子的坑。
1. 为什么V01那一版让我下决心重写:面条代码与全局变量的代价
V01刚写完的时候,功能上其实没有太大缺陷。游戏无非就那几件事:恐龙跳跃、障碍物左移、碰撞检测、分数累加、Game Over重开。但问题在于,这些逻辑“散装”得太厉害了。
我举个例子。V01里跳跃状态是用一个int jumpState全局变量控制的,0表示在地面,1表示起跳,2表示下落。听起来很简单对吧?但跳跃状态的修改分散在两个地方:main循环里读取空格键时把它改成1;恐龙更新函数里根据当前y坐标把它改回0或者改成2。而碰撞检测函数又需要读取这个变量来决定要不要缩小碰撞框。然后有一天我想调整跳跃高度,把初速度从-12改成-15,结果发现恐龙落地判定出了问题——它在落地的一瞬间又弹了一小段。为什么呢?因为落地判定里写的是y >= GROUND_Y - 10,这个-10是我当时为了让恐龙看起来更“贴地”随手加的偏移,它和跳跃初速度耦合在了一起。
这就是典型的全局变量造成的“状态污染”。数据没有归属,任何函数都能改;操作也没有边界,改一处不留意就影响到另一处。更头疼的是,V01里障碍物的生成和回收都是通过遍历数组完成的,数组下标一多,代码读起来真的像在解谜。
到了V02,我的核心目标很明确:把恐龙、障碍物、游戏状态这三块数据重新组织起来。数据要么用结构体打包,要么用类封装,让每一个操作都作用在一个明确的对象上,而不是漫山遍野地找全局变量。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 重构前先做数据建模:恐龙、仙人掌和全局游戏状态
真正动手写结构体和类之前,我先花了一些时间把游戏里到底有哪些“实体”想清楚。这一步非常重要,直接决定后面代码长什么样。
恐龙需要管理的数据有:位置(x,y)、垂直速度vy、宽高w和h、跳跃状态。注意我这里把y定义为“脚底坐标”,也就是恐龙脚掌贴地的位置。很多EGE新手容易把y理解成“头顶坐标”或者“中心坐标”,导致画图和碰撞检测对不上号。我习惯用脚底坐标,是因为障碍物的y也同样定义为“底部坐标”,这样两个物体碰撞检测时统一从底部往上减高度,逻辑会清晰很多。
仙人掌/障碍物的数据有:位置x和y、宽高w和h、类型type、是否活跃active。类型我定成0和1,0是小仙人掌,1是大仙人掌,后续如果要加翼龙,可以再加2。
全局游戏状态包括:恐龙对象、障碍物数组、当前分数、当前移动速度、障碍物生成计时器、游戏是否结束。
把这些数据列出来以后,结构体版本其实就已经呼之欲出了:用三个结构体分别描述恐龙、障碍物、游戏状态,再用一组函数去操作它们。这也是C语言开发者最熟悉的一套思路。
cpp复制typedef struct {
float x, y; // x是左上角,y是脚底坐标
float vy; // 垂直速度,正数向下
float w, h;
int jumping; // 0=地面 1=跳跃
} Dino;
typedef struct {
float x,
