从零到一:我用Godot打造了我的第一款2D游戏《丛林探险》

每一个字节都是思想的回响,每一行代码都是成长的见证。

引言:为什么选择Godot?

在游戏开发的世界中,引擎的选择往往决定了开发的起点与边界。Unity、Unreal 固然强大,但对于初学者或独立开发者而言,Godot 以其轻量、开源、脚本友好等特性,正成为越来越多人的选择。

我在学习Godot的过程中,完成了我的第一个完整2D游戏项目——《丛林探险》。这是一个集移动、射击、敌人生成、碰撞检测、UI交互与音效于一体的像素风小游戏。本文将回顾我从零开始的开发历程,分享其中关键技术点与思考,希望能为同样走在游戏开发路上的你,提供一些启发与参考。


一、项目初始化:场景、节点与资源管理

Godot的核心设计哲学之一是“一切皆节点”。游戏中的每一个对象,无论是玩家、敌人、子弹,还是UI元素,都是一个节点,通过树状结构组织在一起。

1.1 场景与节点的关系

我首先创建了主场景 Game,并在其中逐步添加了:

  • Sprite2D 节点作为背景;

  • Camera2D 节点控制渲染范围;

  • Player 场景实例作为玩家;

  • Slime 场景实例作为敌人;

  • CanvasLayer 用于UI渲染。

这种“场景即预制体”的设计,让代码和资源的复用变得非常自然。

1.2 资源的导入与管理

我将所有素材(图片、音频)统一放在 AssetBundle 文件夹中,通过Godot的文件系统面板直接拖拽导入。Godot支持自动识别并切割 Sprite Sheet,极大提升了动画制作的效率。


二、玩家控制:GDScript与输入系统

我使用 GDScript 作为开发语言,其语法类似 Python,非常适合快速原型开发。

2.1 移动与动画控制

玩家控制的核心代码如下:

gdscript

@export var move_speed: float = 100
@onready var animator: AnimatedSprite2D = $AnimatedSprite2D

func _physics_process(delta):
    if not is_game_over:
        velocity = Input.get_vector("left", "right", "up", "down") * move_speed
        if velocity != Vector2.ZERO:
            animator.play("run")
        else:
            animator.play("idle")
        move_and_slide()

通过 Input.get_vector() 获取键盘输入,并结合 AnimatedSprite2D 播放不同状态下的动画,实现了流畅的移动与动画切换。

2.2 状态管理:游戏结束与动画切换

我为玩家引入了 is_game_over 状态变量,用于控制游戏结束后的行为:

gdscript

func game_over():
    if not is_game_over:
        is_game_over = true
        animator.play("game_over")
        $GameOverSound.play()
        $RestartTimer.start()

在 _physics_process 中通过判断该变量,禁止玩家在失败后继续移动或发射子弹。


三、敌人与碰撞系统:信号与分组

3.1 敌人的生成与移动

敌人使用 Area2D 作为根节点,通过 _physics_process 控制其向左移动:

gdscript

@export var slime_speed: float = -50

func _physics_process(delta):
    if not is_dead:
        position += Vector2(slime_speed, 0) * delta

3.2 碰撞检测与信号机制

Godot的信号系统让节点之间的通信变得非常优雅。我在史莱姆上连接了 body_entered 和 area_entered 信号,分别用于检测玩家碰撞与子弹击中:

gdscript

func _on_body_entered(body):
    if body is CharacterBody2D and not is_dead:
        body.game_over()

func _on_area_entered(area):
    if area.is_in_group("bullet") and not is_dead:
        is_dead = true
        $AnimatedSprite2D.play("death")
        $DeathSound.play()
        area.queue_free()
        await get_tree().create_timer(0.6).timeout
        queue_free()

3.3 分组管理

我将子弹节点加入 bullet 分组,便于在碰撞时快速判断对象类型:

gdscript

if area.is_in_group("bullet"):
    # 处理子弹击中逻辑

四、游戏逻辑与UI:GameManager与CanvasLayer

4.1 游戏管理器

我创建了一个 GameManager 脚本,负责生成敌人、计分、控制游戏状态等全局逻辑:

gdscript

@export var slime_scene: PackedScene
@export var score_label: Label
@export var game_over_label: Label

var score: int = 0

func _process(delta):
    score_label.text = "Score: " + str(score)

4.2 UI 系统

使用 CanvasLayer 渲染UI,通过 Label 节点显示得分与游戏结束提示。Godot的 Theme Override 功能让我可以轻松自定义字体、颜色与描边效果。


五、音效与背景音乐:AudioStreamPlayer 与 AutoLoad

我为游戏添加了发射、死亡、脚步声等多种音效,并使用 AudioStreamPlayer 节点进行播放。背景音乐通过 AutoLoad 功能设置为常驻节点,避免场景重载时中断。


六、导出与发布:从本地到Web

Godot支持多平台导出,我分别导出了:

  • Windows 平台的可执行文件;

  • Web 版本,上传至 itch.io 供浏览器游玩。

在导出设置中,我启用了“内嵌PCK”以生成单一exe文件,并设置了窗口拉伸模式为 canvas_items,确保游戏在不同分辨率下都能正常显示。


结语:代码与成长

完成《丛林探险》的整个过程,是我与Godot引擎、GDScript语言、游戏设计思维的一次深度对话。从最初的节点拖拽,到后来的信号通信、状态管理、资源优化,每一步都是对自我技术能力的挑战与提升。

如果你也想尝试游戏开发,Godot 是一个绝佳的起点。它不仅轻量、免费,更有一个活跃的社区和丰富的文档支持。每一个你写下的节点,每一行你调试通过的代码,都是你与技术对话的见证。

正如这次CSDN程序员节的主题所言:

“每一行代码都是成长的见证。”

你可以在我提供的完整项目笔记中,找到每一步的详细实现。我也已将游戏上传至 itch.io,欢迎你来试玩、反馈,甚至在此基础上继续扩展。


📁 项目地址 |https://gitcode.com/Brianna_Home/my-frist-game

🏷️Godot 官网 | 

🔗源码素材|https://pan.quark.cn/s/c23f604511b8)

🎮 游戏试玩 | https://brianna-home.itch.io/forest-adventure


作者:Briana
时间:2025年10月
声明:本文为原创内容,项目素材来源于自整理资源包,代码部分为Godot 4.3 + GDScript实现。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐