2. 项目搭建
2026/4/14大约 3 分钟
2. 项目搭建
2.1 创建 Godot 项目
打开 Godot 4,新建项目:
- 项目名称:
DungeonAndWarriors - 渲染器:选 Compatibility(兼容性最好,适合2D游戏)
- 语言:C# 或 GDScript 均可
2.2 目录结构
DungeonAndWarriors/
├── scenes/
│ ├── main.tscn # 主场景
│ ├── ui/
│ │ ├── main_menu.tscn # 主菜单
│ │ ├── char_select.tscn # 角色选择
│ │ └── hud.tscn # 游戏HUD
│ ├── stages/
│ │ ├── stage_01.tscn # 第一关
│ │ └── stage_02.tscn # 第二关
│ ├── players/
│ │ ├── warrior.tscn # 战士
│ │ ├── mage.tscn # 法师
│ │ ├── rogue.tscn # 盗贼
│ │ └── paladin.tscn # 圣骑士
│ ├── enemies/
│ │ ├── goblin.tscn # 哥布林
│ │ ├── orc.tscn # 兽人
│ │ └── boss_01.tscn # 第一关Boss
│ └── items/
│ ├── health_potion.tscn # 红药水
│ └── gold_coin.tscn # 金币
├── scripts/
│ ├── managers/
│ │ ├── GameManager.cs # 游戏管理器
│ │ └── DifficultyManager.cs # 难度管理器
│ ├── players/
│ │ ├── PlayerBase.cs # 玩家基类
│ │ └── PlayerInput.cs # 输入处理
│ └── enemies/
│ └── EnemyBase.cs # 敌人基类
├── resources/
│ ├── player_data/ # 职业数据资源
│ └── enemy_data/ # 敌人数据资源
└── assets/
├── sprites/ # 图片资源
├── audio/ # 音效资源
└── fonts/ # 字体资源2.3 配置多人输入
这是本游戏的关键配置。Godot 支持多个手柄同时接入,我们需要为4个玩家分别配置输入动作。
打开 项目 → 项目设置 → 输入映射,添加以下动作:
玩家1(键盘)
| 动作名 | 按键 |
|---|---|
p1_left | A |
p1_right | D |
p1_up | W |
p1_down | S |
p1_attack | J |
p1_skill | K |
p1_roll | Shift(左) |
p1_interact | F |
玩家2(键盘或手柄1)
| 动作名 | 按键 |
|---|---|
p2_left | ← 方向键 |
p2_right | → 方向键 |
p2_up | ↑ 方向键 |
p2_down | ↓ 方向键 |
p2_attack | 小键盘1 |
p2_skill | 小键盘2 |
p2_roll | 小键盘0 |
p2_interact | 小键盘3 |
玩家3、4(手柄)
玩家3和4使用手柄,在输入映射中为每个动作添加手柄按钮,并指定设备编号(Device 0 = 手柄1,Device 1 = 手柄2)。
2.4 输入管理器
为了让代码更整洁,我们创建一个输入管理器,统一处理4个玩家的输入:
C
using Godot;
/// <summary>
/// 玩家输入管理器——统一处理4个玩家的输入
/// </summary>
public static class PlayerInput
{
/// <summary>
/// 获取玩家的移动方向
/// </summary>
public static Vector2 GetMovement(int playerId)
{
string prefix = $"p{playerId}_";
float x = 0f;
float y = 0f;
if (Input.IsActionPressed(prefix + "left")) x -= 1f;
if (Input.IsActionPressed(prefix + "right")) x += 1f;
if (Input.IsActionPressed(prefix + "up")) y -= 1f;
if (Input.IsActionPressed(prefix + "down")) y += 1f;
return new Vector2(x, y);
}
public static bool IsAttackJustPressed(int playerId)
=> Input.IsActionJustPressed($"p{playerId}_attack");
public static bool IsSkillJustPressed(int playerId)
=> Input.IsActionJustPressed($"p{playerId}_skill");
public static bool IsRollJustPressed(int playerId)
=> Input.IsActionJustPressed($"p{playerId}_roll");
public static bool IsInteractJustPressed(int playerId)
=> Input.IsActionJustPressed($"p{playerId}_interact");
}GDScript
## 玩家输入管理器——统一处理4个玩家的输入
class_name PlayerInput
## 获取玩家的移动方向
static func get_movement(player_id: int) -> Vector2:
var prefix: String = "p%d_" % player_id
var x: float = 0.0
var y: float = 0.0
if Input.is_action_pressed(prefix + "left"): x -= 1.0
if Input.is_action_pressed(prefix + "right"): x += 1.0
if Input.is_action_pressed(prefix + "up"): y -= 1.0
if Input.is_action_pressed(prefix + "down"): y += 1.0
return Vector2(x, y)
static func is_attack_just_pressed(player_id: int) -> bool:
return Input.is_action_just_pressed("p%d_attack" % player_id)
static func is_skill_just_pressed(player_id: int) -> bool:
return Input.is_action_just_pressed("p%d_skill" % player_id)
static func is_roll_just_pressed(player_id: int) -> bool:
return Input.is_action_just_pressed("p%d_roll" % player_id)
static func is_interact_just_pressed(player_id: int) -> bool:
return Input.is_action_just_pressed("p%d_interact" % player_id)2.5 游戏管理器
C
using Godot;
using System.Collections.Generic;
/// <summary>
/// 游戏管理器——管理玩家人数、状态和难度
/// </summary>
public partial class GameManager : Node
{
public static GameManager Instance { get; private set; }
[Signal] public delegate void PlayerCountChangedEventHandler(int count);
[Signal] public delegate void GameStateChangedEventHandler(int state);
private readonly List<int> _activePlayers = new();
private DnfGameState _state = DnfGameState.MainMenu;
public int PlayerCount => _activePlayers.Count;
public DnfGameState State => _state;
public override void _EnterTree() => Instance = this;
public override void _ExitTree() { if (Instance == this) Instance = null; }
/// <summary>
/// 玩家加入游戏(playerId: 1-4)
/// </summary>
public void AddPlayer(int playerId)
{
if (_activePlayers.Contains(playerId) || _activePlayers.Count >= 4) return;
_activePlayers.Add(playerId);
EmitSignal(SignalName.PlayerCountChanged, _activePlayers.Count);
}
/// <summary>
/// 玩家离开游戏
/// </summary>
public void RemovePlayer(int playerId)
{
if (!_activePlayers.Remove(playerId)) return;
EmitSignal(SignalName.PlayerCountChanged, _activePlayers.Count);
}
public void ChangeState(DnfGameState newState)
{
_state = newState;
EmitSignal(SignalName.GameStateChanged, (int)newState);
}
}GDScript
extends Node
class_name GameManager
## 游戏管理器——管理玩家人数、状态和难度
static var instance: GameManager
signal player_count_changed(count: int)
signal game_state_changed(state: int)
var _active_players: Array[int] = []
var _state: int = DnfGameState.MAIN_MENU
var player_count: int:
get: return _active_players.size()
func _enter_tree() -> void:
instance = self
func _exit_tree() -> void:
if instance == self:
instance = null
## 玩家加入游戏(player_id: 1-4)
func add_player(player_id: int) -> void:
if player_id in _active_players or _active_players.size() >= 4:
return
_active_players.append(player_id)
player_count_changed.emit(_active_players.size())
## 玩家离开游戏
func remove_player(player_id: int) -> void:
var idx: int = _active_players.find(player_id)
if idx == -1:
return
_active_players.remove_at(idx)
player_count_changed.emit(_active_players.size())
func change_state(new_state: int) -> void:
_state = new_state
game_state_changed.emit(new_state)2.6 本章小结
| 内容 | 说明 |
|---|---|
| 目录结构 | 按场景、脚本、资源分类组织 |
| 多人输入 | 4套输入动作,支持键盘+手柄混用 |
| PlayerInput | 静态工具类,统一查询各玩家输入 |
| GameManager | 单例,管理玩家列表和游戏状态 |
下一章我们将实现玩家角色的移动、跳跃和基础攻击。
