Godot 4 FPS游戏开发实战:5小时搭建状态机与武器系统

Godot 4 FPS游戏开发实战:5小时搭建状态机与武器系统
这次我们来看一个完整的 Godot 4 FPS 游戏开发实战项目。这个项目重点不是概念讲解而是如何在 5 小时内搭建一个包含状态机、武器系统、交互系统和门系统的可运行 FPS 游戏原型。如果你正在寻找一个能快速上手的 Godot 4 实战案例或者想了解 FPS 游戏核心系统的实现思路这篇文章会直接带你完成从零到可玩版本的完整流程。我们将重点关注状态机的设计选择、武器切换的逻辑、交互系统的实现方式以及如何让这些系统协同工作。1. 核心能力速览能力项说明项目类型Godot 4 FPS 游戏原型开发引擎Godot 4.x (推荐 4.2)核心系统状态机、武器系统、交互系统、门系统、射击机制代码语言GDScript硬件要求集成显卡即可运行无需独立显卡开发时间5小时完成核心功能输出成果可运行的 FPS 游戏原型适合场景Godot 初学者进阶、FPS 游戏开发学习、状态机实践2. 适用场景与使用边界这个 FPS 实战项目最适合以下几类开发者适合人群已经掌握 Godot 基础操作想进阶实战项目的学习者需要快速验证 FPS 游戏核心机制的独立开发者想深入学习状态机在游戏开发中实际应用的编程爱好者需要参考完整项目结构的 Godot 自学者能解决的问题FPS 游戏中角色状态管理行走、奔跑、瞄准、射击等多武器切换和射击逻辑的实现游戏内交互系统开门、拾取物品等各个系统之间的通信和数据流转不适合的场景需要高级图形效果或3A级画质的项目大型多人在线FPS游戏开发已经精通Godot的高级开发者技术边界提醒这是一个教学原型不是商业级游戏网络同步、高级AI、复杂物理效果需要额外扩展美术资源需要遵循相关版权规定3. 环境准备与前置条件开始之前确保你的开发环境满足以下要求3.1 软件环境要求Godot 引擎4.2 或更高版本推荐最新稳定版操作系统Windows 10/11, macOS 10.14, 或 Linux Ubuntu 18.04代码编辑器Godot 内置编辑器或 VS Code with Godot 扩展3.2 硬件配置建议内存至少 4GB RAM显卡集成显卡即可独立显卡可获得更好体验存储空间2GB 可用空间包含引擎和项目文件3.3 技能准备基本了解 Godot 场景树和节点概念熟悉 GDScript 基础语法了解面向对象编程的基本概念3.4 项目结构规划在开始编码前先规划好项目目录结构fps_project/ ├── scenes/ # 场景文件 │ ├── player/ # 玩家相关场景 │ ├── weapons/ # 武器系统 │ ├── enemies/ # 敌人系统如有 │ └── environment/ # 环境物体 ├── scripts/ # GDScript 脚本 ├── assets/ # 资源文件 │ ├── textures/ # 贴图 │ ├── sounds/ # 音效 │ └── models/ # 模型 └── levels/ # 关卡文件4. 状态机系统设计与实现状态机是 FPS 游戏的核心它管理着玩家的各种行为状态。我们将采用基于枚举的状态机设计这种方案在 Godot 中既清晰又高效。4.1 状态枚举定义首先创建玩家的状态枚举覆盖 FPS 游戏的基本行为# player_states.gd enum PlayerState { IDLE, # 待机状态 WALKING, # 行走 RUNNING, # 奔跑 JUMPING, # 跳跃 CROUCHING, # 下蹲 AIMING, # 瞄准 SHOOTING, # 射击 RELOADING, # 换弹 INTERACTING # 交互 }4.2 状态机基类实现创建一个可复用的状态机基类# state_machine.gd class_name StateMachine extends Node var current_state: int -1 var previous_state: int -1 var states: Dictionary {} func _ready(): # 初始化所有状态 initialize_states() # 设置初始状态 if states.size() 0: current_state states.keys()[0] enter_state(current_state) func initialize_states(): # 子类需要重写这个方法 pass func change_state(new_state: int): if new_state current_state: return if states.has(new_state): exit_state(current_state) previous_state current_state current_state new_state enter_state(current_state) func enter_state(state: int): if states.has(state): states[state].enter() func exit_state(state: int): if states.has(state): states[state].exit() func update_state(delta: float): if states.has(current_state): states[current_state].update(delta) func physics_update_state(delta: float): if states.has(current_state): states[current_state].physics_update(delta)4.3 玩家状态机具体实现基于状态机基类实现玩家的具体状态逻辑# player_state_machine.gd extends StateMachine onready var player: CharacterBody3D get_parent() func initialize_states(): states { PlayerState.IDLE: IdleState.new(player), PlayerState.WALKING: WalkingState.new(player), PlayerState.RUNNING: RunningState.new(player), PlayerState.AIMING: AimingState.new(player), PlayerState.SHOOTING: ShootingState.new(player), PlayerState.RELOADING: ReloadingState.new(player) } # 空闲状态 class IdleState: var player: CharacterBody3D func _init(_player: CharacterBody3D): player _player func enter(): player.animation_player.play(idle) func update(delta: float): # 检测输入决定是否切换到其他状态 if Input.is_action_pressed(move_forward) or Input.is_action_pressed(move_backward): player.state_machine.change_state(PlayerState.WALKING) if Input.is_action_pressed(aim): player.state_machine.change_state(PlayerState.AIMING) func physics_update(delta: float): pass func exit(): pass # 行走状态 class WalkingState: var player: CharacterBody3D func _init(_player: CharacterBody3D): player _player func enter(): player.animation_player.play(walk) func update(delta: float): if Input.is_action_pressed(sprint): player.state_machine.change_state(PlayerState.RUNNING) elif not (Input.is_action_pressed(move_forward) or Input.is_action_pressed(move_backward)): player.state_machine.change_state(PlayerState.IDLE) func physics_update(delta: float): # 处理移动逻辑 var input_dir Input.get_vector(move_left, move_right, move_forward, move_backward) var direction (player.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() if direction: player.velocity.x direction.x * player.walk_speed player.velocity.z direction.z * player.walk_speed else: player.velocity.x movef(player.velocity.x, 0, player.acceleration * delta) player.velocity.z movef(player.velocity.z, 0, player.acceleration * delta) func exit(): pass5. 武器系统实现武器系统需要管理当前装备的武器、切换逻辑、射击机制和弹药系统。5.1 武器基类设计# weapon_base.gd class_name WeaponBase extends Node3D # 武器属性 var weapon_name: String Weapon var damage: int 10 var max_ammo: int 30 var current_ammo: int 30 var fire_rate: float 0.1 # 射击间隔秒 var can_shoot: bool true # 节点引用 onready var animation_player: AnimationPlayer $AnimationPlayer onready var muzzle: Node3D $Muzzle func _ready(): current_ammo max_ammo # 射击方法 func shoot(): if not can_shoot or current_ammo 0: return false can_shoot false current_ammo - 1 # 播放射击动画 animation_player.play(shoot) # 执行射线检测 perform_raycast() # 设置射击冷却 get_tree().create_timer(fire_rate).timeout.connect(_on_shoot_cooldown) return true func _on_shoot_cooldown(): can_shoot true # 射线检测实现 func perform_raycast(): var space_state get_world_3d().direct_space_state var origin muzzle.global_position var forward -global_transform.basis.z var end origin forward * 100.0 var query PhysicsRayQueryParameters3D.create(origin, end) query.exclude [get_parent().get_parent()] # 排除玩家自身 var result space_state.intersect_ray(query) if result: # 命中处理 handle_hit(result) func handle_hit(hit_result: Dictionary): var hit_object hit_result.get(collider) if hit_object and hit_object.has_method(take_damage): hit_object.take_damage(damage) # 可以在这里添加命中效果如火花、弹孔等 # 重新装弹 func reload(): if current_ammo max_ammo: return false animation_player.play(reload) # 装弹动画完成后补充弹药 await animation_player.animation_finished current_ammo max_ammo return true5.2 具体武器实现# pistol.gd extends WeaponBase func _ready(): weapon_name Pistol damage 15 max_ammo 12 current_ammo max_ammo fire_rate 0.5 # rifle.gd extends WeaponBase func _ready(): weapon_name Rifle damage 25 max_ammo 30 current_ammo max_ammo fire_rate 0.15.3 武器管理器# weapon_manager.gd class_name WeaponManager extends Node var weapons: Array[WeaponBase] [] var current_weapon_index: int 0 var current_weapon: WeaponBase signal weapon_changed(weapon: WeaponBase) signal ammo_changed(current: int, max_ int) func _ready(): # 查找所有武器节点 for child in get_children(): if child is WeaponBase: weapons.append(child) child.hide() # 初始隐藏所有武器 if weapons.size() 0: equip_weapon(0) func equip_weapon(index: int): if index 0 or index weapons.size(): return # 隐藏当前武器 if current_weapon: current_weapon.hide() current_weapon_index index current_weapon weapons[index] current_weapon.show() weapon_changed.emit(current_weapon) ammo_changed.emit(current_weapon.current_ammo, current_weapon.max_ammo) func next_weapon(): var next_index (current_weapon_index 1) % weapons.size() equip_weapon(next_index) func previous_weapon(): var prev_index (current_weapon_index - 1 weapons.size()) % weapons.size() equip_weapon(prev_index) func shoot(): if current_weapon and current_weapon.shoot(): ammo_changed.emit(current_weapon.current_ammo, current_weapon.max_ammo) return true return false func reload(): if current_weapon and current_weapon.reload(): ammo_changed.emit(current_weapon.current_ammo, current_weapon.max_ammo) return true return false6. 交互系统实现交互系统处理玩家与游戏世界中物体的互动如开门、拾取物品等。6.1 交互检测机制# interaction_system.gd class_name InteractionSystem extends Node export var interaction_range: float 3.0 export var interaction_layer: int 1 var raycast: RayCast3D var current_interactable: Interactable func _ready(): # 创建射线检测 raycast RayCast3D.new() raycast.enabled true raycast.collision_mask interaction_layer raycast.target_position Vector3(0, 0, -interaction_range) add_child(raycast) func _process(_delta): check_interaction() func check_interaction(): if raycast.is_colliding(): var collider raycast.get_collider() if collider and collider is Interactable: current_interactable collider # 显示交互提示 show_interaction_prompt(current_interactable.get_interaction_text()) else: current_interactable null hide_interaction_prompt() else: current_interactable null hide_interaction_prompt() func interact(): if current_interactable: current_interactable.interact() return true return false func show_interaction_prompt(text: String): # 在UI上显示交互提示 if has_node(/root/Main/UI/InteractionPrompt): get_node(/root/Main/UI/InteractionPrompt).show_prompt(text) func hide_interaction_prompt(): if has_node(/root/Main/UI/InteractionPrompt): get_node(/root/Main/UI/InteractionPrompt).hide_prompt()6.2 可交互接口# interactable.gd class_name Interactable extends Area3D signal interacted func _ready(): # 设置碰撞层为交互层 collision_layer 2 collision_mask 0 # 获取交互提示文本 func get_interaction_text() - String: return 按 E 交互 # 执行交互 func interact(): interacted.emit() _on_interact() # 子类需要重写这个方法 func _on_interact(): pass6.3 门系统实现# door.gd extends Interactable export var open_angle: float 90.0 export var open_speed: float 2.0 var is_open: bool false var target_rotation: float 0.0 func _ready(): target_rotation rotation_degrees.y func get_interaction_text() - String: return 按 E (关闭 if is_open else 打开) 门 func _on_interact(): is_open !is_open target_rotation rotation_degrees.y (open_angle if not is_open else -open_angle) func _process(delta): # 平滑旋转门 if abs(rotation_degrees.y - target_rotation) 0.1: var new_rotation lerp(rotation_degrees.y, target_rotation, open_speed * delta) rotation_degrees.y new_rotation7. 玩家控制器整合将各个系统整合到玩家控制器中# player_controller.gd extends CharacterBody3D # 移动参数 export var walk_speed: float 5.0 export var run_speed: float 8.0 export var jump_velocity: float 4.5 export var mouse_sensitivity: float 0.002 # 组件引用 onready var camera: Camera3D $Camera3D onready var state_machine: StateMachine $StateMachine onready var weapon_manager: WeaponManager $WeaponManager onready var interaction_system: InteractionSystem $InteractionSystem var current_speed: float walk_speed func _ready(): # 锁定鼠标 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED) func _input(event): # 鼠标视角控制 if event is InputEventMouseMotion and Input.get_mouse_mode() Input.MOUSE_MODE_CAPTURED: rotate_y(-event.relative.x * mouse_sensitivity) camera.rotate_x(-event.relative.y * mouse_sensitivity) camera.rotation.x clamp(camera.rotation.x, -PI/2, PI/2) # 交互输入 if event.is_action_pressed(interact): interaction_system.interact() # 武器切换 if event.is_action_pressed(next_weapon): weapon_manager.next_weapon() if event.is_action_pressed(previous_weapon): weapon_manager.previous_weapon() # 装弹 if event.is_action_pressed(reload): weapon_manager.reload() func _physics_process(delta): # 处理移动输入 var input_dir Input.get_vector(move_left, move_right, move_forward, move_backward) var direction (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() if direction: velocity.x direction.x * current_speed velocity.z direction.z * current_speed else: velocity.x move_toward(velocity.x, 0, current_speed) velocity.z move_toward(velocity.z, 0, current_speed) # 跳跃处理 if is_on_floor() and Input.is_action_pressed(jump): velocity.y jump_velocity # 应用重力 if not is_on_floor(): velocity.y - 9.8 * delta move_and_slide() # 更新状态机 state_machine.physics_update_state(delta) func _process(delta): # 射击输入 if Input.is_action_pressed(shoot): weapon_manager.shoot() # 奔跑切换 if Input.is_action_pressed(sprint): current_speed run_speed else: current_speed walk_speed # 更新状态机 state_machine.update_state(delta)8. UI 系统实现创建基本的游戏UI来显示状态信息# game_ui.gd extends CanvasLayer onready var crosshair: TextureRect $Crosshair onready var ammo_label: Label $AmmoLabel onready var interaction_prompt: Label $InteractionPrompt func _ready(): # 连接武器管理器的信号 var weapon_manager get_node(/root/Main/Player/WeaponManager) if weapon_manager: weapon_manager.ammo_changed.connect(_on_ammo_changed) weapon_manager.weapon_changed.connect(_on_weapon_changed) func _on_ammo_changed(current: int, max_ammo: int): ammo_label.text %d / %d % [current, max_ammo] func _on_weapon_changed(weapon: WeaponBase): ammo_label.text %d / %d % [weapon.current_ammo, weapon.max_ammo] func show_interaction_prompt(text: String): interaction_prompt.text text interaction_prompt.visible true func hide_interaction_prompt(): interaction_prompt.visible false9. 场景搭建与测试9.1 基础场景设置创建一个简单的地图用于测试# main.gd extends Node3D func _ready(): # 生成测试环境 generate_test_environment() func generate_test_environment(): # 创建地面 var ground MeshInstance3D.new() ground.mesh BoxMesh.new() ground.mesh.size Vector3(20, 1, 20) add_child(ground) # 创建一些测试墙壁 for i in range(5): var wall MeshInstance3D.new() wall.mesh BoxMesh.new() wall.mesh.size Vector3(2, 3, 0.2) wall.position Vector3(i * 4 - 8, 1.5, 5) add_child(wall) # 创建测试门 var door preload(res://scenes/door.tscn).instantiate() door.position Vector3(0, 1.5, 3) add_child(door)9.2 输入映射配置在项目设置中配置输入映射# input_map.cfg # 移动控制 move_forward: W move_backward: S move_left: A move_right: D # 动作控制 jump: Space sprint: Shift interact: E # 武器控制 shoot: Mouse Left reload: R next_weapon: Mouse Wheel Up previous_weapon: Mouse Wheel Down10. 性能优化与调试10.1 性能监控添加性能监控脚本来观察游戏运行状态# performance_monitor.gd extends CanvasLayer onready var fps_label: Label $FPSLabel onready var memory_label: Label $MemoryLabel func _process(_delta): fps_label.text FPS: %d % Engine.get_frames_per_second() # 内存使用情况近似值 var memory Performance.get_monitor(Performance.MEMORY_STATIC) memory_label.text 内存: %.1f MB % (memory / 1024.0 / 1024.0)10.2 调试工具创建调试工具来帮助开发# debug_tools.gd extends Node var debug_enabled: bool true func _input(event): if event.is_action_pressed(toggle_debug): debug_enabled !debug_enabled get_tree().debug_collisions_hint debug_enabled func log_state_change(old_state: String, new_state: String): if debug_enabled: print(状态切换: %s - %s % [old_state, new_state]) func log_interaction(object: String): if debug_enabled: print(交互: %s % object)11. 常见问题与排查方法问题现象可能原因排查方式解决方案角色无法移动输入映射未正确配置检查项目设置中的输入映射确保所有必要的输入动作都已定义鼠标视角控制不灵敏鼠标灵敏度设置不当检查 mouse_sensitivity 参数调整 mouse_sensitivity 值0.001-0.005武器无法射击射线检测未命中或弹药为0检查弹药数量和射线检测逻辑确保弹药充足检查碰撞层设置门无法交互交互系统未正确连接检查门的碰撞层和交互系统设置确保交互层匹配检查信号连接状态切换异常状态机逻辑错误添加调试日志检查状态切换验证状态转换条件检查动画资源性能下降场景过于复杂或脚本效率低使用性能监控工具优化场景复杂度减少实时计算12. 最佳实践与使用建议12.1 开发流程建议分阶段开发先完成核心移动系统再逐步添加武器、交互等功能频繁测试每完成一个功能模块就进行测试确保基础功能稳定版本控制使用 Git 等版本控制工具定期提交可运行版本12.2 代码组织建议# 良好的代码组织结构示例 # 使用清晰的命名约定 class_name PlayerStateMachine # 而不是 PSMMgr # 保持函数短小专注 func handle_movement_input() - void: # 只处理移动输入逻辑 func update_animation() - void: # 只更新动画状态 # 使用信号进行松耦合通信 signal health_changed(new_health: int) signal weapon_equipped(weapon: WeaponBase)12.3 资源管理建议将相似功能的脚本放在同一目录下使用有意义的文件命名如player_state_machine.gd而不是state1.gd为重要节点添加组Groups以便批量操作使用场景实例化而不是在代码中硬编码节点结构这个 Godot 4 FPS 实战项目展示了如何用 5 小时搭建一个功能完整的游戏原型。重点在于理解状态机如何管理游戏逻辑、各个系统如何协同工作以及如何组织代码结构。实际开发中可以根据需要扩展更多功能如敌人AI、任务系统、存档功能等。建议先运行基础版本确保所有核心功能正常工作然后再根据自己的需求进行定制化开发。记得在添加新功能时保持代码的模块化这样后续维护和扩展会更加容易。