2. 项目搭建
2026/4/14大约 8 分钟
项目搭建
这一章我们从零创建 Godot 项目,搭建好基本的文件结构、全局设置和基础场景框架。完成后你会有一个可以运行的空项目骨架,随时可以开始填充具体功能。
创建 Godot 项目
第一步:新建项目
- 打开 Godot Editor(我们使用 Godot 4.6)
- 点击右上角 "新建" 按钮
- 填写项目信息:
| 设置项 | 值 | 说明 |
|---|---|---|
| 项目名称 | CSCounterStrike | 项目名 |
| 项目路径 | 你喜欢的任意位置 | 建议用英文路径,不要有中文和空格 |
| 渲染器 | Forward+ | 3D 游戏用 Forward+,画质和性能平衡最好 |
为什么要选 Forward+?
Godot 提供三种渲染器:
- Forward+:画质最好,适合桌面端 3D 游戏(我们选这个)
- Mobile:适合移动端,画质略有降低
- Compatibility:兼容性最好,老电脑也能跑,但画质一般
CS 类游戏需要精确的碰撞检测和不错的光影效果,Forward+ 是最佳选择。
第二步:配置项目设置
项目创建后,进入 项目 → 项目设置,做以下调整:
显示设置:
Display → Window
├── Size
│ ├── Viewport Width: 1920 # 视口宽度
│ ├── Viewport Height: 1080 # 视口高度
│ ├── Mode: fullscreen # 全屏模式
│ └── Stretch Mode: canvas_items # 缩放模式
└── Handled Orientation: landscape # 横屏输入映射(项目设置 → 输入映射):
我们需要提前定义好所有操作按键,这样代码里只需引用动作名称,不用关心具体是哪个键。
| 动作名称 | 按键绑定 | 说明 |
|---|---|---|
move_forward | W | 向前走 |
move_backward | S | 向后退 |
move_left | A | 向左平移 |
move_right | D | 向右平移 |
jump | Space | 跳跃 |
crouch | Ctrl | 蹲下 |
sprint | Shift | 静步(CS 里 Shift 是静步不是冲刺) |
shoot | 鼠标左键 | 射击 |
aim | 鼠标右键 | 瞄准(狙击枪开镜) |
reload | R | 换弹 |
weapon_1 | 1 | 切换主武器 |
weapon_2 | 2 | 切换副武器 |
weapon_3 | 3 | 切换刀具 |
drop_weapon | G | 丢弃武器 |
use_bomb | E | 安放/拆除炸弹 |
buy_menu | B | 打开买枪菜单 |
scoreboard | Tab | 显示计分板 |
C#
// 在代码中引用输入动作的方式
// Godot 会自动从项目设置中读取这些映射
Vector2 inputDir = Input.GetVector(
"move_left", "move_right",
"move_forward", "move_backward"
);GDScript
# 在代码中引用输入动作的方式
# Godot 会自动从项目设置中读取这些映射
var input_dir = Input.get_vector(
"move_left", "move_right",
"move_forward", "move_backward"
)文件结构设计
一个好的文件结构能让项目在变大后仍然井井有条。CS 项目的文件结构如下:
CSCounterStrike/
├── project.godot # 项目配置文件
│
├── Scenes/ # 所有场景文件
│ ├── Main.tscn # 主场景(游戏入口)
│ ├── MainMenu.tscn # 主菜单
│ │
│ ├── Characters/ # 角色相关场景
│ │ ├── Player.tscn # 玩家角色
│ │ ├── AIPlayer.tscn # AI 角色
│ │ └── Ragdoll.tscn # 死亡布娃娃(可选)
│ │
│ ├── Weapons/ # 武器相关场景
│ │ ├── Pistol.tscn # 手枪
│ │ ├── RifleAK47.tscn # AK-47 步枪
│ │ ├── RifleM4A4.tscn # M4A4 步枪
│ │ ├── SniperAWP.tscn # AWP 狙击枪
│ │ └── Knife.tscn # 匕首
│ │
│ ├── Maps/ # 地图场景
│ │ ├── Dust2.tscn # 经典地图
│ │ └── Mirage.tscn # 第二张地图
│ │
│ └── UI/ # UI 场景
│ ├── HUD.tscn # 游戏内 HUD
│ ├── BuyMenu.tscn # 买枪菜单
│ ├── Scoreboard.tscn # 计分板
│ └── Crosshair.tscn # 准星
│
├── Scripts/ # 所有脚本文件
│ ├── Characters/ # 角色脚本
│ │ ├── PlayerController.cs # 玩家控制
│ │ ├── FPSCamera.cs # 第一人称摄像机
│ │ └── AIController.cs # AI 控制
│ │
│ ├── Weapons/ # 武器脚本
│ │ ├── WeaponBase.cs # 武器基类
│ │ ├── HitscanWeapon.cs # 射线武器(所有枪)
│ │ ├── MeleeWeapon.cs # 近战武器(刀)
│ │ └── WeaponManager.cs # 武器管理器
│ │
│ ├── Systems/ # 游戏系统
│ │ ├── RoundManager.cs # 回合管理
│ │ ├── EconomyManager.cs # 经济系统
│ │ ├── DamageSystem.cs # 伤害系统
│ │ ├── BombSystem.cs # 炸弹系统
│ │ └── AudioManager.cs # 音效管理
│ │
│ └── UI/ # UI 脚本
│ ├── HUDController.cs # HUD 控制
│ └── BuyMenuController.cs # 买枪菜单控制
│
├── Assets/ # 所有美术和音效资源
│ ├── Models/ # 3D 模型
│ │ ├── Weapons/ # 武器模型
│ │ ├── Characters/ # 角色模型
│ │ └── Props/ # 道具模型(箱子、桶等)
│ │
│ ├── Textures/ # 贴图
│ │ ├── Walls/ # 墙壁贴图
│ │ ├── Floors/ # 地面贴图
│ │ └── Skyboxes/ # 天空盒
│ │
│ ├── Sounds/ # 音效
│ │ ├── Weapons/ # 枪声
│ │ ├── Footsteps/ # 脚步声
│ │ └── Ambient/ # 环境音
│ │
│ └── Materials/ # 材质文件
│
└── Resources/ # Godot 资源文件
├── WeaponData/ # 武器数据资源
│ ├── AK47.tres # AK47 数据
│ ├── M4A4.tres # M4A4 数据
│ └── AWP.tres # AWP 数据
└── RecoilPatterns/ # 后坐力模式资源创建基础场景
主场景
主场景是游戏启动后第一个加载的场景。它负责协调所有游戏系统。
C#
// Scripts/Main.cs
using Godot;
public partial class Main : Node3D
{
[Export] public PackedScene PlayerScene { get; set; }
[Export] public PackedScene MapScene { get; set; }
private RoundManager _roundManager;
private Node3D _currentMap;
public override void _Ready()
{
// 加载地图
_currentMap = MapScene.Instantiate<Node3D>();
AddChild(_currentMap);
// 初始化回合管理器
_roundManager = new RoundManager();
AddChild(_roundManager);
// 生成玩家
SpawnPlayer();
// 开始第一回合
_roundManager.StartRound();
}
private void SpawnPlayer()
{
var player = PlayerScene.Instantiate<CharacterBody3D>();
AddChild(player);
// 将玩家放在 CT 出生点
var spawnPoint = _currentMap.GetNode<Marker3D>("CTSpawn");
player.GlobalPosition = spawnPoint.GlobalPosition;
}
}GDScript
# Scripts/Main.gd
extends Node3D
@export var player_scene: PackedScene
@export var map_scene: PackedScene
var round_manager: RoundManager
var current_map: Node3D
func _ready():
# 加载地图
current_map = map_scene.instantiate()
add_child(current_map)
# 初始化回合管理器
round_manager = RoundManager.new()
add_child(round_manager)
# 生成玩家
spawn_player()
# 开始第一回合
round_manager.start_round()
func spawn_player():
var player = player_scene.instantiate()
add_child(player)
# 将玩家放在 CT 出生点
var spawn_point = current_map.get_node("CTSpawn") as Marker3D
player.global_position = spawn_point.global_position自动加载(Autoload / 单例)
CS 有一些系统需要在所有场景中都能访问,比如经济系统和音效管理器。我们可以用 Godot 的 Autoload 功能把它们变成全局单例。
在 项目 → 项目设置 → Autoload 中添加:
| 脚本路径 | 名称 | 说明 |
|---|---|---|
Scripts/Systems/AudioManager.cs | AudioManager | 全局音效管理 |
Scripts/Systems/EconomyManager.cs | EconomyManager | 经济系统 |
Scripts/Systems/GameEvents.cs | GameEvents | 全局事件总线 |
事件总线是一个很实用的模式——让不同系统之间能通信,而不需要互相引用。
C#
// Scripts/Systems/GameEvents.cs
using Godot;
/// <summary>
/// 全局事件总线,用信号让各系统之间解耦通信。
/// 比如玩家射击时,不用直接调用音效管理器,
/// 而是发射一个 "PlayerShot" 信号,音效管理器自己监听并播放音效。
/// </summary>
public partial class GameEvents : Node
{
// 声明全局信号
[Signal] public delegate void PlayerShotEventHandler(Vector3 position, Vector3 direction);
[Signal] public delegate void PlayerHitEventHandler(Node3D target, float damage, string hitZone);
[Signal] public delegate void PlayerDiedEventHandler(Node3D player, Node3D killer);
[Signal] public delegate void RoundStartedEventHandler(int roundNumber);
[Signal] public delegate void RoundEndedEventHandler(string winningTeam);
[Signal] public delegate void BombPlantedEventHandler(Vector3 position);
[Signal] public delegate void BombDefusedEventHandler();
[Signal] public delegate void BombExplodedEventHandler();
[Signal] public delegate void MoneyChangedEventHandler(int playerId, int newAmount);
[Signal] public delegate void WeaponPurchasedEventHandler(int playerId, string weaponName);
}GDScript
# Scripts/Systems/GameEvents.gd
extends Node
## 全局事件总线,用信号让各系统之间解耦通信。
## 比如玩家射击时,不用直接调用音效管理器,
## 而是发射一个 "player_shot" 信号,音效管理器自己监听并播放音效。
# 声明全局信号
signal player_shot(position: Vector3, direction: Vector3)
signal player_hit(target: Node3D, damage: float, hit_zone: String)
signal player_died(player: Node3D, killer: Node3D)
signal round_started(round_number: int)
signal round_ended(winning_team: String)
signal bomb_planted(position: Vector3)
signal bomb_defused
signal bomb_exploded
signal money_changed(player_id: int, new_amount: int)
signal weapon_purchased(player_id: int, weapon_name: String)武器数据资源
我们使用 Godot 的自定义资源(Resource)来存储武器数据。这样做的好处是:修改武器数值不需要改代码,直接在编辑器里调就行。
C#
// Scripts/Weapons/WeaponData.cs
using Godot;
/// <summary>
/// 武器数据资源。每种武器创建一个 .tres 文件,
/// 在编辑器里直接填写数值,不用改代码。
///
/// 你可以把它想象成一张"武器信息卡"——
/// 记录了这把枪叫什么名字、伤害多少、后坐力多大等所有属性。
/// </summary>
[GlobalClass]
[Resource]
public partial class WeaponData : Resource
{
[Export] public string WeaponName { get; set; } = "AK-47";
[Export] public string WeaponType { get; set; } = "Rifle"; // Rifle, Pistol, Sniper, Melee
[Export] public int Price { get; set; } = 2700;
// 伤害相关
[Export] public float BaseDamage { get; set; } = 36f;
[Export] public float HeadshotMultiplier { get; set; } = 4f;
[Export] public float ChestMultiplier { get; set; } = 1f;
[Export] public float StomachMultiplier { get; set; } = 1.25f;
[Export] public float LegMultiplier { get; set; } = 0.75f;
// 射击相关
[Export] public float FireRate { get; set; } = 0.1f; // 每发间隔(秒)
[Export] public int MagazineSize { get; set; } = 30; // 弹匣容量
[Export] public int ReserveAmmo { get; set; } = 90; // 备用弹药
[Export] public float ReloadTime { get; set; } = 2.5f; // 换弹时间(秒)
[Export] public bool IsAutomatic { get; set; } = true; // 是否全自动
// 后坐力相关
[Export] public float RecoilMagnitude { get; set; } = 2.0f; // 后坐力强度
[Export] public float RecoilRecoverySpeed { get; set; } = 5.0f; // 后坐力恢复速度
[Export] public Curve RecoilPattern { get; set; } // 后坐力曲线
// 精度相关
[Export] public float StandingSpread { get; set; } = 0.02f; // 站立散布
[Export] public float CrouchingSpread { get; set; } = 0.015f; // 蹲下散布
[Export] public float MovingSpread { get; set; } = 0.08f; // 移动散布
[Export] public float RunningSpread { get; set; } = 0.15f; // 跑步散布
// 移动速度影响
[Export] public float MovementSpeedMultiplier { get; set; } = 0.85f; // 持枪时移动速度倍率
// 资源路径
[Export] public string ModelPath { get; set; } = "";
[Export] public string ShootSoundPath { get; set; } = "";
}GDScript
# Scripts/Weapons/WeaponData.gd
class_name WeaponData
extends Resource
## 武器数据资源。每种武器创建一个 .tres 文件,
## 在编辑器里直接填写数值,不用改代码。
##
## 你可以把它想象成一张"武器信息卡"——
## 记录了这把枪叫什么名字、伤害多少、后坐力多大等所有属性。
@export var weapon_name: String = "AK-47"
@export var weapon_type: String = "Rifle" ## Rifle, Pistol, Sniper, Melee
@export var price: int = 2700
# 伤害相关
@export var base_damage: float = 36.0
@export var headshot_multiplier: float = 4.0
@export var chest_multiplier: float = 1.0
@export var stomach_multiplier: float = 1.25
@export var leg_multiplier: float = 0.75
# 射击相关
@export var fire_rate: float = 0.1 ## 每发间隔(秒)
@export var magazine_size: int = 30 ## 弹匣容量
@export var reserve_ammo: int = 90 ## 备用弹药
@export var reload_time: float = 2.5 ## 换弹时间(秒)
@export var is_automatic: bool = true ## 是否全自动
# 后坐力相关
@export var recoil_magnitude: float = 2.0 ## 后坐力强度
@export var recoil_recovery_speed: float = 5.0 ## 后坐力恢复速度
@export var recoil_pattern: Curve ## 后坐力曲线
# 精度相关
@export var standing_spread: float = 0.02 ## 站立散布
@export var crouching_spread: float = 0.015 ## 蹲下散布
@export var moving_spread: float = 0.08 ## 移动散布
@export var running_spread: float = 0.15 ## 跑步散布
# 移动速度影响
@export var movement_speed_multiplier: float = 0.85 ## 持枪时移动速度倍率
# 资源路径
@export var model_path: String = ""
@export var shoot_sound_path: String = ""小结
这一章我们完成了项目搭建:
- 创建项目:选择 Forward+ 渲染器,配置好显示和输入映射
- 文件结构:按场景/脚本/资源三类组织,每个类别再按功能分子目录
- 基础场景:Main 场景负责加载地图和玩家
- 全局系统:用 Autoload 创建事件总线和音效管理器
- 武器数据:用 Resource 存储武器参数,方便在编辑器里调整
下一章我们开始实现最核心的部分——FPS 角色控制器。
