简介这是一份使用Python与Pygame库实现的超级马里奥游戏完整源码面向具备一定Python基础、希望进阶学习游戏开发的同学。项目以马里奥第一关为蓝本将角色、敌人、平台、道具等抽象为类完整展示了面向对象设计、事件监听、碰撞检测、地图数据结构和音画渲染等游戏开发核心环节。资源包为RAR压缩格式大小9.52MB共91个文件其中含29个.py源码文件、24个.pyc编译文件以及18个.ogg和5个.wav音频、9个.png图片源码、音效、图像与字体资源一应俱全。目前已有3860人学习下载。通过阅读和修改这份代码读者既能巩固Python语法和类设计能力也能系统了解2D游戏从框架搭建到资源管理的完整流程是实战性很强的学习案例。1. Python 编写超级马里奥游戏这不是复刻是横版引擎的一次热身想用 Python 写出超级马里奥这个标题第一反应通常是一堆问号图形库用哪个、角色怎么跳才有那个手感、地图怎么拼、碰撞会不会把角色卡进砖里。我的回答是用 Pygame 按横版平台游戏的套路做能在几百行内得到一个可以跑、可以跳、可以踩敌人、可以通关的 demo而且这套代码稍作扩展就能变成自己的横版引擎。它真正解决的不是“复刻一个商业大作”而是让你把游戏主循环、精灵管理、矩形碰撞、状态机这些概念全部用代码验证一遍。适合已经有 Python 基础、想离开脚本式编程的人也适合想做毕设 Demo 或给朋友做个可玩小游戏的开发者。下面我会直接给出能跑的代码和调参记录不绕弯子。2. 主循环、精灵与窗口先让角色在屏幕上动起来2.1 为什么选 Pygame别的库为什么不行写 2D 平台游戏Python 生态里常见选择有 Tkinter、Pygame Zero、Pyglet 和 Pygame。Tkinter 自带窗口组件可它不是为游戏设计的update 频率稍高就出现闪烁键盘事件也拿不到稳定的连续按键状态做菜单可以做动作游戏是自讨苦吃。Pygame Zero 是教学向封装把主循环和碰撞都藏起来了写起来很快但你要调跳跃手感、改碰撞细节时会发现它的接口在限制你最后还是要回头读 Pygame 源码。Pyglet 底层走 OpenGL性能上限更高可它的文档密度和安装复杂度对新手不友好而且打包成单文件时对资源目录更敏感。Pygame 是中间那个最平衡的选项它提供sprite模块管理游戏对象image和transform处理贴图mixer放音效还有现成的Rect做矩形碰撞这些正好覆盖横版平台游戏 80% 的日常需求。社区里能找到大量同类项目做参考踩坑时检索到的解决方案基本都针对 Pygame 生态这个优势在调试阶段非常值钱。我的建议是不要纠结“哪个库更高级”先让一个方块在窗口里动起来再谈别的。2.2 最小可运行框架窗口、时钟、事件循环三件套任何 Pygame 游戏都逃不开三件套初始化窗口、创建时钟、进入事件循环。下面这段是最小的能跑框架角色只是一个会左右移动的方块但“游戏”的基本骨架已经成立。import pygame # 初始化所有 Pygame 模块窗口和声音都靠它 pygame.init() # 窗口大小横版游戏常用 800x600比例接近 4:3 SCREEN_WIDTH 800 SCREEN_HEIGHT 600 screen pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT)) pygame.display.set_caption(Python 横版平台 Demo) # 时钟用于控制帧率游戏逻辑的更新节奏全部由它驱动 clock pygame.time.Clock() FPS 60 # 主角用一个矩形表示位置靠 left, top 控制 player pygame.Rect(100, 400, 32, 48) player_color (255, 0, 0) running True while running: # 事件循环关闭窗口的瞬间会收到 QUIT 事件 for event in pygame.event.get(): if event.type pygame.QUIT: running False # 键盘状态检测按住右键就一直向右移动而不是按一下走一步 keys pygame.key.get_pressed() if keys[pygame.K_LEFT]: player.x - 5 if keys[pygame.K_RIGHT]: player.x 5 # 先清空画面再绘制场景最后翻转缓冲区 screen.fill((135, 206, 235)) pygame.draw.rect(screen, player_color, player) pygame.display.flip() clock.tick(FPS) pygame.quit()这段代码里值得注意的细节有三个。第一pygame.key.get_pressed()返回的是整帧键盘状态的快照按住方向键时每帧都会触发移动才能实现“持续奔跑”而不是“点一下动一下”这和event.type KEYDOWN的触发式行为有本质区别。第二pygame.display.flip()是双缓冲机制的一部分先画完再整体刷新能避免画面闪烁如果看到撕裂感或闪烁多数情况是这里写成了pygame.display.update()且参数不对。第三clock.tick(FPS)把循环限制在每秒 60 帧不同性能的电脑上游戏速度才会一致这一点在后面的物理部分特别关键。2.3 精灵与矩形坐标游戏对象统一看成 Rect很多刚接触 Pygame 的人会把“贴图”和“位置”分开想结果代码里到处都是变量存坐标写两三个对象就开始乱。Pygame 给出了一个更好的抽象所有可视对象都可以挂在pygame.sprite.Sprite子类上而每个精灵内部都有一个self.rect作为它的位置和碰撞体积。角色、砖块、敌人、金币全部统一成“带 rect 的精灵”后续的碰撞检测、遍历更新、批量绘制都会简单很多。import pygame class Player(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() # Surface 是贴图载体convert_alpha 让 PNG 透明通道能正常显示 self.image pygame.Surface((32, 48)).convert_alpha() self.image.fill((255, 0, 0)) # rect 既是绘制位置又是碰撞体积 self.rect self.image.get_rect() self.rect.x x self.rect.y y self.speed_x 0 self.speed_y 0 def update(self, keys): # 这里只做移动碰撞逻辑放到外部统一处理 if keys[pygame.K_LEFT]: self.speed_x -4 elif keys[pygame.K_RIGHT]: self.speed_x 4 else: self.speed_x 0 self.rect.x self.speed_x self.rect.y self.speed_y逻辑说明self.image是真正被绘制到屏幕上的 Surfaceself.rect是它所在的矩形区域image.get_rect()会从贴图尺寸生成一个初始位置为零的矩形再手动赋值到出生点。后续判断碰撞时不需要比较像素只需要比较两个 rect 是否相交pygame.sprite.collide_rect内部就是做的矩形重叠判断这比自己写嵌套循环去遍历像素高效得多。参数说明speed_x在这里是每帧像素位移单位是“像素/帧”而不是秒。在锁帧 60 的前提下速度 4 意味着每秒移动 240 像素在 800 宽的窗口里大约 3.3 秒横穿屏幕这个速度对平台跳跃游戏偏慢实际开发中我会把这个值放到玩家类外面作为可调常量PLAYER_SPEED 4方便后面统一调参。矩形尺寸 32x48 是刻意设计的宽 32 像素对应一个标准瓦片宽度高 48 像素比宽多一些视觉上更接近角色站立比例这个尺寸也会影响后面碰撞检测时角色站立的稳定性。3. 跳跃与移动手感重力、加速度和“马里奥味”的三个参数3.1 横版平台跳跃的物理模型水平运动和垂直运动完全分开平台跳跃游戏的手感本质是一套非常简单的假物理水平方向用加速度和摩擦力控制顶风时的手感垂直方向用重力和跳跃初速度控制抛物线的形状。核心原则是把水平速度和垂直速度当成两个独立的量每帧先根据按键修改速度再把速度加到坐标上最后统一处理碰撞。如果你用真实物理引擎那种“给物体施加一个力”的思路反而会让角色像泥鳅一样滑因为平台跳跃游戏本来就不追求真实只追求“我能精准踩到那个砖块”。马里奥类游戏里有个关键细节跳跃不是“按一下就固定跳一个高度”而是按住跳跃键跳得更高、轻点按键跳得更矮。这个叫可变跳跃高度variable jump height实现方式是在角色上升阶段判断跳跃键是否仍被按住如果松开了就给垂直速度施加一个额外的向下加速度让上升很快刹车。这一条是“手感”和“普通方块游戏”最明显的分水岭新手最容易忽略。# 垂直方向物理简图 GRAVITY 0.6 # 每帧增加向下的速度 JUMP_SPEED -13 # 跳跃瞬间向上的初速度负值代表向上 JUMP_CUT 0.35 # 松开跳跃键后额外追加的向下加速度系数3.2 手感三参数重力、跳跃初速度、可变跳跃高度这三个参数决定了游戏是“黏手”还是“发飘”。重力GRAVITY越大角色下落越快跳跃到顶的时间越短跳跃曲线越陡峭跳越初速度JUMP_SPEED的绝对值决定跳跃最高点能到达多少像素JUMP_CUT决定松开键之后角色多快开始下落。它们本质上共同决定了一个抛物线。看几个具体数值你就知道怎么调了。假设窗口高度 600角色站在 y400 的平台目标是“跳到刚好能踩到 y300 高度的砖块”也就是跳跃高度需要至少 100 像素。用h (v^2) / (2g)估算v13, g0.6时最大跳跃高度约 141 像素够用。如果把重力调到 1.0跳跃初速度不变最高点只有约 84 像素就要把 JUMP_SPEED 加大到 -15 才能维持原高度但代价是下落更快空中操控时间变短手感更“压手”。反过来说重力 0.4、跳跃初速度 -12 时跳跃高度约 180 像素下落轻飘飘主角像在月亮上跳踩敌人时不干脆。最好的做法是先定一个目标跳跃高度再用上面的公式反推参数而不是凭感觉乱调。# 经验参数起点 GRAVITY 0.6 JUMP_SPEED -13 MAX_FALL_SPEED 15 # 下落速度上限防止穿墙参数说明MAX_FALL_SPEED很容易被忽略但没有它角色从高处落下时速度会无限累加撞击地面瞬间的位移可能超过一个砖块厚度下一帧直接穿到地面下方。把它限制在 15 像素/帧即每秒最多下落 900 像素既能保证下落速度感又不至于穿透。3.3 代码实现用键盘状态驱动移动下面是一段带上跳跃和落地的玩家更新逻辑重点在落地检测和可变跳跃。class Player(pygame.sprite.Sprite): def __init__(self, x, y): super().__init__() self.image pygame.Surface((32, 48)).convert_alpha() self.image.fill((255, 0, 0)) self.rect self.image.get_rect(topleft(x, y)) self.speed_x 0 self.speed_y 0 self.on_ground False self.jump_pressed False def handle_keys(self, keys): # 水平方向改成加速 摩擦避免一松手就停得僵硬 accel 0.4 friction 0.85 if keys[pygame.K_LEFT]: self.speed_x - accel elif keys[pygame.K_RIGHT]: self.speed_x accel else: self.speed_x * friction # 速度限幅防止按键累积导致速度爆炸 max_speed 6 self.speed_x max(-max_speed, min(max_speed, self.speed_x)) if keys[pygame.K_SPACE] and self.on_ground: self.speed_y JUMP_SPEED self.on_ground False self.jump_pressed True elif not keys[pygame.K_SPACE] and self.jump_pressed and self.speed_y 0: # 松开跳跃键后削减上升速度实现可变跳跃高度 self.speed_y JUMP_CUT * GRAVITY * 3 self.jump_pressed False def apply_physics(self, tiles): # 垂直方向先加重力再分轴移动稍后碰撞部分详解 self.speed_y min(self.speed_y GRAVITY, MAX_FALL_SPEED) self.rect.y self.speed_y self.check_collision_y(tiles) self.rect.x self.speed_x self.check_collision_x(tiles)逻辑说明水平方向我用了“加速度 摩擦”而不是直接给定速度这样按一下方向键角色会有一个短暂的加速过程松键后有滑行感和马里奥系列那种可以中途回头微调的操控感更接近。垂直方向严格按重力累加速度再判断与瓦片的碰撞。参数说明accel0.4意味着从静止到最大速度需要约 15 帧即 0.25 秒左右这个加速过程玩家能明显感到但又不闷。friction0.85是每帧速度乘以 0.85松手后速度会指数衰减大约 10 帧内停下来像结冰一样滑调到 0.9 会好很多。jump_cut的实现细节是在上升阶段松键直接给 speed_y 加上相当于跳跃初速度相当比例的额外向下速度立刻打断上升这就是“轻点跳跃键只跳半高”的由来。整套逻辑里最容易漏的是self.on_ground的维护它必须由碰撞检测的结果来更新如果你只在按键处判断按空格就跳会出现空中二段跳的问题。4. 瓦片地图与碰撞检测从字符数组拼出完整关卡4.1 用二维数组描述关卡字符即瓦片做关卡地图不要用图像编辑器手动画整个场景而是用文本标记的方式。每一个字符对应一种瓦片类型比如#是砖块P是玩家出生点E是敌人出生点空格是空气。这个做法不管在游戏开发还是地图编辑工具里都很常见好处是关卡数据可以和程序逻辑彻底分离改关卡只是改字符串不用动代码。LEVEL_MAP [ ############################################################, # #, # #, # ## ## #, # ## E #, # E ## #, # ## #, # ## ## P #, ############################################################, ] TILE_SIZE 32 def load_level(level_lines): bricks [] enemies [] player_start None for row_idx, line in enumerate(level_lines): for col_idx, char in enumerate(line): x col_idx * TILE_SIZE y row_idx * TILE_SIZE if char #: bricks.append(pygame.Rect(x, y, TILE_SIZE, TILE_SIZE)) elif char E: enemies.append((x, y)) elif char P: player_start (x, y) return bricks, enemies, player_start逻辑说明这个函数把字符串逐行逐字扫描根据每个字符生成对应的矩形对象。row_idx和col_idx乘上瓦片尺寸就得到像素坐标角色在关卡里的位置单位是像素在数组里用的索引只是地图编辑时的坐标这两套坐标在运行时必须分清楚。参数说明TILE_SIZE32是全局常量它不仅决定地图瓦片的像素宽度也决定角色宽高32 像素宽和碰撞网格的分辨率。把瓦片设成 16 会让地图更细腻但碰撞检测量大增设成 64 则角色会显得很小32 是 2D 像素风游戏最常见的选择。地图数组每一行的长度必须一致如果某行少了一个字符会导致这行后面所有砖块向左偏移下面排错时会专门讲。4.2 按轴分离的碰撞检测先横向再纵向矩形碰撞最容易犯的错误是“一次把 x 和 y 同时移动再和所有砖块做重叠判断”。这个做法会让角色斜着插进砖块然后你不知道该从哪个方向弹出来。正确的姿势是把移动拆成两步先只移动 x 方向检测碰撞如果有碰撞就把 x 推回原位再只移动 y 方向检测碰撞有碰撞就把 y 推回原位。每一步只处理一个轴永远不会出现“角色斜插进砖块”的鬼畜效果。def check_collision_x(self, bricks): # 横向移动后检测自身矩形与所有砖块的碰撞 for brick in bricks: if self.rect.colliderect(brick): if self.speed_x 0: # 向右撞墙把自己贴到砖块左边缘 self.rect.right brick.left elif self.speed_x 0: # 向左撞墙贴到砖块右边缘 self.rect.left brick.right self.speed_x 0 def check_collision_y(self, bricks): self.on_ground False for brick in bricks: if self.rect.colliderect(brick): if self.speed_y 0: # 下落撞到砖块顶部站在上面 self.rect.bottom brick.top self.on_ground True elif self.speed_y 0: # 上升撞到砖块底部提醒玩家顶到东西了 self.rect.top brick.bottom self.speed_y 0逻辑说明check_collision_x是在移动 x 之后做的碰撞后用rect.right brick.left直接把角色贴到砖块边缘而不是“退一步再试一次”这个数学做法每个碰到的砖都精确到位。check_collision_y里区分了speed_y 0和 0下落撞到砖顶站住上升撞到砖底停住注意speed_y 0时一定要把on_ground置 True否则判定会丢。参数说明碰撞检测里最容易被忽略的是on_ground的更新位置。如果写在外部按键逻辑里也就是判断“按空格时才检查地面”那么在角色还没有接触到任何基础砖块时空中按空格会触发二段跳。正确做法是每一帧的垂直碰撞都重置on_ground再重新检查保证它反映的是“这一刻是否踩在东西上”。还有碰撞后speed_y 0很重要不然下一帧重力又把角色往砖里推看起来就像角色在砖块里轻微抖动这个是避坑章节的头号问题。4.3 相机跟随让镜头平滑地追踪角色窗口大小只有 800x600而关卡地图可以比窗口大很多因此必须有一个相机偏移量来决定“这次画哪一段地图”。最常见的做法是让角色保持在屏幕中间偏左一点相机慢慢跟随而不是直接锁定到正中央这样玩家能多看到前方一点路。class Camera: def __init__(self, width, height): self.camera pygame.Rect(0, 0, width, height) self.tile_size TILE_SIZE self.map_width width def apply(self, rect): # 返回一个偏移后的矩形所有绘制都通过这个函数 return rect.move(self.camera.topleft) def update(self, target): # 让角色保持在屏幕左侧约 1/3 的位置镜头移动到角色坐标 x -target.rect.x SCREEN_WIDTH * 0.35 y -target.rect.y SCREEN_HEIGHT * 0.5 # 限制镜头不能超出地图边界 x min(0, x) y min(0, y) self.camera.x x self.camera.y y逻辑说明Camera 的核心是一个rect它的坐标是负值因为它是“世界坐标到屏幕坐标”的偏移量。比如角色在世界坐标 x1000相机 x-300那么绘制时角色在屏幕上的 x 就是 700。apply方法把所有精灵的 rect 都加上这个偏移绘制时统一调用。update里让目标点永远在屏幕左侧 35% 处这样右边会露出更多未来场景符合横版游戏的视觉习惯。参数说明SCREEN_WIDTH * 0.35是镜头焦点位置数值越小角色越靠屏幕左侧能看到的未来场景越多数值越大角色越靠中间视野越对称但前方可见距离越短。min(0, x)是防止镜头带出地图左侧的黑边如果你想做迷宫类地图右侧和下方也需要加类似的边界限制。相机这个模块如果和碰撞检测耦合在一起就会发生“角色明明站在砖上画面里却在飘”的 bug所以相机的 update 要等玩家物理更新完成后再调用。5. Pygame 平台游戏避坑指南五个最容易翻车的点5.1 角色卡墙抖动两个轴一起检测的恶果现象角色撞墙时会在墙边快速左右摇摆像癫痫一样甚至直接穿进砖块里。原因移动代码里把self.rect.x speed_x和self.rect.y speed_y同时执行然后才做一次碰撞检测。如果角色同时碰到墙和地面系统分不清是横向碰撞还是纵向碰撞会先把角色往某个方向推下一帧又推进墙里反复横跳。解决严格按第 4 章的按轴分离流程写先移动 x、检测 x、再移动 y、检测 y。这个顺序不是玄学而是保证每一帧的碰撞响应都基于“上一个轴已经稳定”的状态。我一般会再做一层防御当发生碰撞时把对应的速度直接清零而不是只调整位置防止残留速度把角色再次推进墙体。5.2 跳跃高度时高时低物理更新与帧率耦合现象游戏主循环没有锁帧时角色在性能好的机器上跳得高在老旧机器上跳得矮跳跃最高点不稳定通关难度时大时小。原因所有speed_y GRAVITY的更新都假设每帧间隔相同。如果帧率是 30那么每帧的物理时间翻倍重力累计速度也会翻倍但跳跃初速度还是那个常量在地重力主导的下落段速度翻倍会让跳跃轨迹变得更平更矮。解决锁帧clock.tick(60)是最廉价的方案。更严谨的做法是引入dtdelta time把每帧物理更新乘以时间增量但这会让碰撞检测的步长变大需要配合子步迭代否则反而出现穿透。我的经验是平台跳跃这种简单物理锁帧足够不要为了“严谨”去引入dt然后给自己增加碰撞穿透的麻烦。锁帧的同时要保证MAX_FALL_SPEED生效否则高帧率机器上仍然会出问题。5.3 Surface 莫名消失局部变量被垃圾回收现象角色、金币、背景的贴图加载出来时正常加载完成后绘制一段时间某个贴图突然变成黑块或消失报错信息提示TypeError: expected Surface, got NoneType。原因很多新手把image pygame.image.load(xx.png)写在某个函数里而没有把返回值挂到self.image或全局变量上函数执行完局部变量被回收Surface 对象被销毁绘制时拿到的是None。更隐蔽的是用convert()转换格式后忘记赋值。解决所有 Surface 一律在精灵的__init__里创建并赋给self.image绝不让它成为临时变量。如果是动态生成的贴图要统一放到一个字典self.images {}里缓存。排查这类问题时先在报错前打断点看对象类型十次有八次是变量生命周期的问题而不是图片文件本身坏了。5.4 打包后没声音没图像资源路径与工作目录问题现象在开发环境里跑图片、音效、关卡文件都正常用 PyInstaller 打包成单个 exe 后程序启动就报找不到assets/music/bgm.mp3或者窗口黑屏只显示默认颜色。原因打包后的 exe 运行时会解压到一个临时目录而程序里的相对路径assets/...是相对于“当前工作目录”找的用户双击 exe 时工作目录通常是 exe 所在目录和你开发时的项目目录不一样相对路径全部失效。解决用sys._MEIPASS判断打包环境把所有资源路径都基于这个变量拼出来。打成一个全路径函数任何地方获取资源都走这个函数不要在其他代码里裸写相对路径。这也是为什么第 6 章专门强调资源路径统一管理早做这件事打包时才不会手忙脚乱。import sys import os def resource_path(relative_path): # 打包成 exe 后资源会被解压到 _MEIPASS开发环境用当前目录 if hasattr(sys, _MEIPASS): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.dirname(__file__), relative_path)逻辑说明resource_path是打包场景下最经典的解决方案。在开发阶段os.path.dirname(__file__)指向代码文件所在目录所以只要你的资源文件夹和代码在同一层级就能稳定找到。打包阶段PyInstaller 会把assets目录里的文件复制到_MEIPASS程序通过这个路径访问不会因为双击位置不同而找不到资源。这个函数写好后所有image.load、Sound、open操作都换用它不要跳过。5.5 敌人卡在墙边不回头移动方向没有随碰撞反转现象敌人碰到砖墙后不再移动或者卡在墙内玩家轻轻一碰就判定胜利游戏流程乱套。原因敌人的简单 AI 通常是“走到墙边掉头”但掉头的触发写在了玩家与敌人的碰撞里忽略了敌人与砖块的碰撞。敌人每帧向前移动碰到砖后没有检测和响应坐标被堵在墙边但方向没反转于是原地静止或一直按一个方向推墙。解决给敌人也做和玩家一样的碰撞检测只是响应方式不同。敌人碰到横向碰撞后直接把direction * -1把方向反转回来。下方没有地面时还要掉头否则敌人会直直地走出悬崖这一步可以根据游戏类型决定。我见过不少人把敌人做成了“只会走直线”的橡皮图章碰撞一概不处理这是平台游戏敌人最基本的逻辑缺口。6. 从“能玩”到“能交付”状态机、资源组织与打包验证6.1 用状态机收拾混乱的流程当你有“开始界面、游戏运行、关卡结束、胜利结算”这几层时最忌讳在update里用一个大 if 把所有状态混在一起。用状态机把每一帧的逻辑切干净游戏主循环只做一件事根据当前状态调用对应模块。菜单、关卡、结算三个状态各写一个函数或一个类状态之间通过返回结果切换这样你的代码结构会清晰很多也方便后面加暂停功能。class Game: def __init__(self): self.state MENU def run_state(self): if self.state MENU: self.draw_menu() if key_pressed(START): self.state PLAYING elif self.state PLAYING: self.update_game() if self.player.dead: self.state GAME_OVER这个模式虽然简单但能阻止你的游戏逻辑膨胀成一团乱麻。菜单、关卡、结束的画面渲染和输入处理彻底分家调试哪个部分就只看那个函数效率高得多。6.2 交付前验证两分钟测出“露馅”的地方开发时你自己玩一百遍都可能觉得没问题交付给别人之前至少要跑三关看这几个点第一角色手持宽画面边缘时镜头是否露底第二跳跃到屏幕顶部时角色是否能看到自己的头部相机要不要加垂直限制第三在低端机器上以 30 帧模拟运行跳跃高度是否明显异常第四逐一删除资源文件测试程序是否给出明确的报错信息而不是闪退。这些是玩家最容易感知到粗糙感的地方。我自己的习惯是把调参常量全部集中到一个config.py里重力、跳跃力、速度上限、瓦片大小都在文件顶部列成常量调手感时只改这一处而不是在代码里翻找。做这个 Python 超级马里奥 demo最大的收获不是复刻了多少关而是明白了游戏手感是靠一帧一帧调出来的不是写出来就自动完美的。把主循环、精灵、碰撞、状态机这套骨架搭稳后面换题材、换美术、加道具都只是填内容的事。希望帮到你。本文还有配套的精品资源点击获取