ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Godot 4 平台跳跃实战:瓦片地图、敌人AI与角色手感

2026/9/1 18:01:10 拓冰建站 浏览量
Godot 4 平台跳跃实战:瓦片地图、敌人AI与角色手感 这次我们直接看一个很实际的选题用 Godot 4 从零做一个平台跳跃小游戏并且把瓦片地图、敌人 AI、角色手感这三块一次打通。很多新手学 Godot 时会发现单看每一个功能的 Demo 都能跑通但把它们组合到一个项目里就会出现各种问题瓦片碰撞没生效、敌人撞墙不回头、跳跃手感发飘。这篇文章的定位就是“组合实战”按两小时左右的节奏把一条完整流程走完最后得到一个可以实际玩的小关卡而不是停留在碎片化测试。Godot 4.x 是这次使用的引擎版本。它是开源免费的 2D/3D 游戏引擎支持 Windows、Linux、macOS导出目标覆盖 PC、Web、Android、iOS。平台跳跃属于 2D 横版玩法对硬件门槛很低普通办公电脑即可完成编辑和测试。整个项目使用 GDScript 编写语法接近 Python没有 C 基础也能看懂。核心节点集中在 CharacterBody2D角色物理、TileMapLayer瓦片地图、Area2D敌人检测与玩家受击。本文会带大家完成这些内容创建 Godot 项目、配置输入映射用 TileSet 搭建瓦片地图并添加物理碰撞实现玩家移动、跳跃、落地判定和跳跃手感实现敌人 AI 的巡逻、追击与返回加入简单的死亡判定和重开逻辑最后测试运行和导出。如果你已经会 Godot 基础界面可以直接跳到瓦片地图和敌人 AI 部分如果你是从零开始建议按顺序完整执行。1. Godot 平台跳跃实战核心能力速览项目说明项目类型2D 横版平台跳跃游戏开发实战引擎版本Godot 4.x示例按 Godot 4 语法编写脚本语言GDScript核心功能瓦片地图绘制、TileMap 碰撞、角色移动与跳跃、敌人 AI 巡逻追击硬件要求普通办公电脑即可2D 场景编辑对 GPU 要求很低资源占用2D 项目整体占用低具体取决于素材分辨率与场景复杂度启动方式Godot 编辑器打开项目运行当前场景导出平台Windows / Linux / macOS / Web / Android / iOSAPI 能力项目本身不涉及接口服务Godot 支持 HTTP 请求可按需扩展批量任务不涉及批量任务素材批量导入和重命名可以通过文件系统批量处理适合场景独立游戏原型、横版闯关、游戏开发教学、角色手感实验这一张表可以帮助你在动手前快速判断这个项目的重点不是复杂系统而是把 2D 平台跳跃最常见的技术点串成完整闭环。如果你只是想学习单个功能可以只看对应章节如果你希望做出一个能玩的小 Demo建议按文章顺序操作。2. 平台跳跃游戏适用场景与使用边界先说清楚这个项目适合谁。独立游戏开发者想快速验证横版玩法学生需要用不超过两个小时的流程理解游戏状态机与物理系统或者从零开始做毕设、作品集都很适合用这套流程。平台跳跃这个品类核心体验是角色移动、跳跃手感、关卡设计和敌人行为工程量比 RPG、动作冒险要小很多作为游戏开发的第一款完整 Demo 非常合适。同时也要说清楚不适合什么场景。如果你要做的是一款大型 3D 动作游戏、多人联机游戏或者以复杂剧情和 UI 交互为核心的产品平台跳跃这套流程只能作为入门基础架构上还需要大量扩展。教程里的敌人 AI 是简单的状态机不包含寻路、避障、Boss 战等高级行为瓦片地图也以手工绘制为主没有接入自动生成算法。使用边界方面最需要留意的是素材合规问题。做游戏开发练习时很容易从网上下载瓦片素材、音效、字体如果只是本地学习问题不大但一旦要发布到 Steam、Itch.io 或参加游戏比赛就必须检查素材的授权协议。使用 AI 生成的素材时也要看对应平台的商用条款。教程本身的代码可以随意修改使用素材则要单独确认来源。3. Godot 平台跳跃开发环境准备3.1 下载与安装 Godot平台跳跃项目不依赖复杂的编译环境只需要一个 Godot 编辑器。从 Godot 官方网站下载对应操作系统的标准版即可Windows 下解压后直接双击编辑器程序启动不需要安装也不需要配置环境变量。建议下载 4.x 分支的稳定版本。如果后续要做导出还需要到编辑器内部下载对应平台的导出模板这个可以等做到导出环节再处理。3.2 创建项目打开 Godot 后点击项目管理器中的“新建项目”项目名称PlatformerTutorial项目路径选择本地任意目录渲染器选择 Forward、Mobile 或 Compatibility 均可。2D 项目建议使用 Compatibility兼容性更好内存占用也更低。创建完成后项目会自动生成 project.godot 文件和空目录。后续所有场景、脚本、素材都放在这个目录里。由于平台跳跃的核心是 2D 物理不需要额外启用 3D 设置在项目创建阶段保持默认项即可。3.3 配置输入映射平台跳跃需要三个输入动作move_left、move_right、jump。在“项目设置 → 输入映射”中手动添加move_left绑定 A 键和左方向键move_right绑定 D 键和右方向键jump绑定空格键和 W 键添加完之后代码中就可以直接通过Input.get_axis(move_left, move_right)读取水平输入通过Input.is_action_just_pressed(jump)读取跳跃键。这里不推荐直接在 project.godot 里手写事件序列化文本因为格式很容易写错编辑器界面更可靠。配置完成这一步后面所有脚本才能正常控制角色。3.4 准备瓦片素材瓦片地图需要一个素材图。可以使用 Godot 官方示例素材也可以自己用像素编辑器画一张。建议选择 16x16 或 32x32 的地面、砖块、平台素材保存为 PNG。不需要额外安装工具Godot 的 TileSet 编辑器可以直接从单张图片切割瓦片。素材本身可以是整个关卡共用的一张图集只要你在导入时设置好切片大小Godot 会自动把图片分割成多个独立瓦片。4. 玩家角色平台跳跃的物理与手感实现4.1 创建玩家场景在场景面板中新建一个 CharacterBody2D命名为 Player添加 Sprite2D 作为视觉显示添加 CollisionShape2D使用 RectangleShape2D 或 CapsuleShape2D添加脚本 player.gdPlayer 节点结构Player (CharacterBody2D) ├── Sprite2D └── CollisionShape2DCharacterBody2D 是 Godot 中处理移动角色的标准节点它不会自动产生碰撞反应而是通过move_and_slide()方法让角色沿墙壁和地板滑动。平台跳跃的物理逻辑都写在_physics_process里。这个节点自带is_on_floor()方法可以判断角色是否站在地面这是跳跃判定最核心的依据。4.2 核心移动代码player.gdextends CharacterBody2D export var speed: float 300.0 export var jump_velocity: float -400.0 export var gravity: float 980.0 export var coyote_time: float 0.1 export var jump_buffer_time: float 0.1 var coyote_timer: float 0.0 var jump_buffer_timer: float 0.0 func _physics_process(delta: float) - void: # 重力不在空中时加速下落 if not is_on_floor(): velocity.y gravity * delta # 土狼时间离开平台后短时间内仍可跳跃 if is_on_floor(): coyote_timer coyote_time else: coyote_timer - delta # 水平移动 var direction : Input.get_axis(move_left, move_right) velocity.x direction * speed # 跳跃缓冲按下跳跃键后短时间内仍可生效 if Input.is_action_just_pressed(jump): jump_buffer_timer jump_buffer_time else: jump_buffer_timer - delta # 执行跳跃 if jump_buffer_timer 0.0 and coyote_timer 0.0: velocity.y jump_velocity jump_buffer_timer 0.0 coyote_timer 0.0 move_and_slide()这段代码的关键点有三个重力累加、水平方向乘以速度、move_and_slide()负责实际移动。后面两个 Timer 分别实现了土狼时间和跳跃缓冲这两项对平台跳跃手感影响非常大。没有它们角色每次落地都必须像素级准确才跳得起来玩起来会非常僵。这里的核心逻辑量已经覆盖了平台跳跃最常见的物理问题后续扩展二段跳、冲刺、爬坡都可以在此基础上叠加。4.3 动画朝向与简单状态如果不想用动画至少要让角色根据移动方向转头。最简单的方式是翻转 Sprite2D 的 scale.x。这里把朝向逻辑单独抽成一个函数方便在移动后调用func _flip_sprite(direction: float) - void: if direction ! 0.0: $Sprite2D.scale.x abs($Sprite2D.scale.x) * sign(direction)这个函数只做视觉翻转不影响碰撞体。在_physics_process中调用时注意要在速度更新之后再执行否则可能出现方向已经改变但视觉还没跟上的情况。如果需要跑步动画建议把 AnimationPlayer 加入 Player 节点用速度和is_on_floor()作为状态条件切换 idle、run、jump 三个动画。4.4 调整手感的参数顺序第一次调手感时建议按这个顺序来先调 speed让水平移动速度符合预期。再调 jump_velocity跳跃高度太高会导致关卡和敌人都要重新设计。然后调 gravity重力越小跳跃越飘重力越大跳跃越干脆。最后调 coyote_time 和 jump_buffer_time这两个值通常在 0.08 到 0.15 秒之间。每次修改参数后按 F6 运行当前场景反复跳跃十几次再继续调。不要一次改多个参数否则很难定位是哪一项影响最大。平台跳跃的手感没有标准答案不同像素游戏、不同角色大小、不同平台宽度需要的参数组合都不一样。最稳的做法是先把基础数值跑通再根据自己的手感需要微调。5. 瓦片地图实战从 TileSet 到关卡绘制5.1 创建 TileSet 并导入瓦片在文件系统面板选中准备好的瓦片素材右键“新建 → 资源 → TileSet”然后在 Inspector 中打开 TileSet 编辑器。Godot 4 中 TileSet 的资源类型是 TileSet在资源面板中创建后指定 source 为素材图片再设置切片大小例如 16x16 或 32x32。瓦片创建完成后编辑器会自动把图片切割成多个瓦片。在这里可以给每个瓦片添加物理层选择某个地面瓦片在 Inspector 中展开“物理层”点击“添加多边形”在瓦片上绘制一个矩形碰撞形状这里建议给所有会阻挡角色通过的瓦片都添加物理层例如地面、砖块、柱子而不是只给部分瓦片添加。否则角色可能在某个看起来很实的砖块上直接穿模掉下去。物理多边形可以画得比瓦片边缘略小一点这样角色踩在几块瓦片接缝处时更不容易被卡住。5.2 添加 TileMapLayer 节点Godot 4 中推荐使用 TileMapLayer 节点来放置瓦片地图。如果你使用的是 Godot 4.3 或更新版本可以直接创建 TileMapLayer旧版本中使用的 TileMap 节点仍然可以打开但新项目建议直接用 TileMapLayer。操作步骤在场景根节点下添加一个 TileMapLayer 节点命名为 GroundLayer。在 Inspector 的 Tile Set 属性里选择刚才创建的 TileSet。进入瓦片绘制模式选择对应瓦片在场景视图中点击绘制地图。图层设计建议GroundLayer地面与实体砖块包含碰撞BackgroundLayer背景装饰可以放在最底层不包含碰撞ForegroundLayer前景挡板或装饰放在角色前面显示用如果只需要一张平台跳跃地图用 GroundLayer 加 BackgroundLayer 两层就够。这里的关键思路是“碰撞层和装饰层分离”。装饰层不参与碰撞可以放心画各种花花草草和远景碰撞层只保留真正能站上去的实体瓦片。这样后期调整外观时不用重新配碰撞出现碰撞问题也更好排查。5.3 碰撞边界与出生点在场景中放置一个 StaticBody2D 作为场景边界或者在 GroundLayer 外画一圈深坑。平台跳跃通常不需要物理墙直接让角色掉落到边界外即可这样反而省事。为了让玩家掉落时不会无限下坠可以在场景最下方放一个不可见的 Area2D作为死亡检测区域。出生点可以是一个 Node2D标记为 SpawnPoint。玩家角色在游戏开始时被实例化到该位置。如果后续要做多关卡这个出生点也可以作为关卡检查点使用角色死亡后从最近检查点重新开始。5.4 瓦片地图绘制细节绘制时注意几个细节平台宽度至少要有 2-3 个瓦片否则角色跳跃落点太难掌握。敌人活动区域尽量平整避免 AI 反复卡在台阶边缘。高空敌人路线需要预留足够的检测空间。绘制完地图后打开调试面板开启“可见碰撞体”检查每块地面是否有碰撞框。检查碰撞是瓦片地图最容易出错的一步。如果绘制完发现角色直接穿模大概率是瓦片没有加物理层或者 CollisionShape2D 的位置发生偏移。瓦片地图本身不限制 TileSet 里可以放多少瓦片一个关卡里几十块瓦片足够覆盖常见的平台结构重点是物理层要配得干净不要出现细碎的碰撞缝隙。6. 敌人 AI巡逻、追击与状态切换6.1 敌人节点结构创建一个敌人类使用 CharacterBody2D 作为根节点这样敌人也能使用move_and_slide()和物理碰撞Enemy (CharacterBody2D) ├── Sprite2D ├── CollisionShape2D ├── RayCast2D检测前方墙壁或悬崖 └── Area2D检测玩家Area2D 用于检测玩家是否进入追击范围生成一个“看到玩家”的信号RayCast2D 用于防止敌人走到墙壁或悬崖外。也可以用简单的距离判断代替 RayCast2D看具体关卡复杂度。如果关卡地形简单的巡逻路径距离判断已经够用如果敌人需要翻越台阶或者绕开障碍就要加 RayCast2D 甚至寻路系统。6.2 敌人状态机代码enemy.gdextends CharacterBody2D enum State { IDLE, PATROL, CHASE } export var patrol_speed: float 80.0 export var chase_speed: float 160.0 export var patrol_range: float 120.0 export var detection_range: float 200.0 var state: State State.PATROL var start_x: float 0.0 var direction: int 1 func _ready() - void: start_x global_position.x func _physics_process(delta: float) - void: match state: State.IDLE: pass State.PATROL: _patrol(delta) State.CHASE: _chase(delta) move_and_slide() func _patrol(delta: float) - void: if global_position.x start_x - patrol_range: direction 1 elif global_position.x start_x patrol_range: direction -1 velocity.x direction * patrol_speed _update_facing() func _chase(delta: float) - void: var player : get_tree().get_first_node_in_group(player) if player null: return var diff : player.global_position.x - global_position.x if abs(diff) 8.0: velocity.x sign(diff) * chase_speed else: velocity.x 0.0 _update_facing() func _on_detection_area_body_entered(body: Node2D) - void: if body.is_in_group(player): state State.CHASE func _on_detection_area_body_exited(body: Node2D) - void: if body.is_in_group(player): state State.PATROL func _update_facing() - void: if direction ! 0: $Sprite2D.scale.x abs($Sprite2D.scale.x) * direction这段代码描述了一个常见敌人 AI 行为在起点附近巡逻发现玩家后追击玩家离开检测范围后恢复巡逻。状态迁移由 Area2D 信号触发。需要说明的是这个 AI 没有处理障碍寻路追击时如果敌人和玩家之间隔着墙壁敌人会直接撞墙。对于平台跳跃关卡通常建议在敌人前方加一个 RayCast2D检测到墙壁时沿墙向上或向下移动或者干脆限制敌人的追击区域。用状态机管理 AI 的好处是后续加新行为时只需要在枚举里增加一个状态再补一个处理函数不需要改动整个控制流。6.3 敌人与玩家碰撞敌人碰撞分两种情况玩家踩到敌人头顶敌人死亡玩家反弹。玩家从侧面碰到敌人玩家受伤或死亡。最简单实现在玩家脚本中判断is_on_floor()和玩家与敌人的相对位置如果是下压则调用敌人死亡否则玩家死亡。也可以用 Area2D 区分攻击区域和伤害区域。如果要做更精确的判定建议给敌人头顶单独加一个 Area2D 作为攻击判定其他地方作为伤害判定。这样玩家踩到头顶正上方时触发敌人死亡碰到敌人侧面时触发玩家死亡逻辑更清晰。7. 游戏流程与胜负判定7.1 玩家信号与主场景把主场景分为三层Main根节点控制游戏流程World包含瓦片地图、敌人生成点与背景Player玩家角色在 Main 中写一个简单的流程控制extends Node2D onready var player: CharacterBody2D $Player func _ready() - void: player.add_to_group(player) player.died.connect(_on_player_died) func _on_player_died() - void: get_tree().call_deferred(reload_current_scene)玩家脚本中需要发射 died 信号。当玩家血量归零或掉落地图时调用emit_signal(died)。这种“信号驱动”的写法比在敌人脚本里直接操作玩家节点更清晰后续加 UI、加商店都比较方便。主场景只关心“玩家死了要重新加载”至于玩家怎么死的由玩家自己通过信号通知职责划分比较明确。7.2 胜利与终点在有多个关卡时可以增加一个 Area2D 作为终点检测区玩家进入后切换到下一关。切关代码func _on_finish_area_body_entered(body: Node2D) - void: if body.is_in_group(player): get_tree().change_scene_to_file(res://level_2.tscn)如果只做一个