2. 项目搭建与场景配置
2026/4/13大约 9 分钟
2. 项目搭建与场景配置:搭建你的 3D 战场
2.1 创建新项目
打开 Godot 4.x,点击"新建项目",选择 Forward+ 渲染器(这是 Godot 4 默认的高质量 3D 渲染器)。
为什么选 Forward+ 而不是其他渲染器?
- Forward+:质量最高,适合桌面平台,我们象棋游戏不需要极致性能
- Mobile:性能优先,适合手机,但画质会降低
- Compatibility:兼容旧设备,一般不用选
对于象棋这种棋类游戏,Forward+ 绰绰有余。
| 项目设置 | 推荐值 | 说明 |
|---|---|---|
| 项目名称 | ChineseChessWarSoul | 项目名,随意取 |
| 渲染器 | Forward+ | 高质量 3D 渲染 |
| 项目路径 | 你喜欢的位置 | 建议路径不含中文 |
2.2 项目目录结构
项目创建好后,我们需要组织好文件。一个好的目录结构就像整洁的房间,东西好找、好管理。
chinese_chess_war_soul/
├── scenes/ # 场景文件
│ ├── main.tscn # 主场景
│ ├── board.tscn # 棋盘场景
│ ├── piece.tscn # 棋子场景(基础模板)
│ └── battle_effect.tscn # 战斗特效场景
├── scripts/ # 脚本文件
│ ├── game_manager.gd # 游戏主控脚本
│ ├── board.gd # 棋盘逻辑
│ ├── piece.gd # 棋子逻辑
│ ├── move_validator.gd # 走法验证
│ ├── ai_engine.gd # AI引擎
│ └── network_manager.gd # 网络管理
├── models/ # 3D模型
│ ├── board_mesh.tres # 棋盘网格
│ └── piece_mesh.tres # 棋子网格
├── textures/ # 纹理贴图
│ ├── board_wood.png # 棋盘木纹
│ ├── piece_red.png # 红方棋子纹理
│ └── piece_black.png # 黑方棋子纹理
├── audio/ # 音效文件
│ ├── move.wav # 走子音效
│ ├── capture.wav # 吃子音效
│ └── check.wav # 将军音效
├── particles/ # 粒子特效
│ └── explosion.tres # 爆炸粒子
└── project.godot # 项目配置2.3 主场景搭建
主场景是游戏的"舞台"。就像盖房子一样,我们先搭好框架,再往里面放家具。
创建主场景节点树
在 Godot 中,场景由节点树组成。节点就像积木块,你把它们搭在一起组成完整的场景。
我们的主场景节点树结构如下:
Main (Node3D) ← 场景根节点,3D世界的起点
├── Board (Node3D) ← 棋盘容器
│ ├── BoardMesh (MeshInstance3D) ← 棋盘的3D外观
│ ├── BoardLines (MeshInstance3D) ← 棋盘线条
│ └── BoardSurface (StaticBody3D) ← 棋盘物理体(用于鼠标点击检测)
│ └── CollisionShape3D ← 碰撞形状
├── Pieces (Node3D) ← 所有棋子的容器
│ ├── RedPieces (Node3D) ← 红方棋子组
│ └── BlackPieces (Node3D) ← 黑方棋子组
├── Camera3D ← 3D摄像机
│ └── CameraPivot (Node3D) ← 摄像机旋转轴
├── Light3D ← 灯光
│ ├── DirectionalLight3D ← 主光源(太阳光)
│ └── WorldEnvironment ← 环境光设置
├── Effects (Node3D) ← 特效容器
│ └── BattleParticles (GPUParticles3D) ← 战斗粒子
├── GameState (Node) ← 游戏状态管理
└── UI (CanvasLayer) ← UI层(覆盖在3D画面上的2D界面)
├── TopBar (HBoxContainer) ← 顶部状态栏
├── SidePanel (VBoxContainer)← 侧边面板(棋谱等)
└── MenuPanel (Control) ← 菜单面板为什么要这样分层?
想象你在搭乐高:根节点是底板,Board 是棋盘层,Pieces 是棋子层,Camera 是你的眼睛,Light 是灯,Effects 是烟花,UI 是记分牌。每一层独立,互不干扰,方便修改。
2.4 摄像机配置
摄像机就是玩家的"眼睛"。在 3D 游戏中,摄像机决定了玩家从什么角度看游戏画面。
摄像机位置与角度
对于象棋游戏,我们使用一个斜上方的俯视角度,既能看清整个棋盘,又有 3D 立体感。
C#
using Godot;
public partial class GameCamera : Camera3D
{
// 摄像机参数
[Export] public float CameraHeight { get; set; } = 15.0f; // 摄像机高度
[Export] public float CameraDistance { get; set; } = 12.0f; // 摄像机距离
[Export] public float CameraAngle { get; set; } = 55.0f; // 俯视角度(度)
private Node3D _pivot; // 摄像机旋转中心
public override void _Ready()
{
// 创建摄像机旋转中心
_pivot = new Node3D();
_pivot.Name = "CameraPivot";
GetParent().AddChild(_pivot);
// 把摄像机放在旋转中心下面
Reparent(_pivot);
// 设置摄像机位置
Position = new Vector3(0, 0, CameraDistance);
RotationDegrees = new Vector3(-CameraAngle, 0, 0);
// 将旋转中心移到棋盘上方
_pivot.Position = new Vector3(4, CameraHeight, 4.5f);
// 设置摄像机参数
Fov = 50.0f; // 视野角度
Near = 0.1f; // 近裁剪面
Far = 100.0f; // 远裁剪面
Projection = Camera3D.ProjectionType.Perspective;
}
}GDScript
extends Camera3D
## 摄像机参数
@export var camera_height: float = 15.0 ## 摄像机高度
@export var camera_distance: float = 12.0 ## 摄像机距离
@export var camera_angle: float = 55.0 ## 俯视角度(度)
var _pivot: Node3D # 摄像机旋转中心
func _ready():
# 创建摄像机旋转中心
_pivot = Node3D.new()
_pivot.name = "CameraPivot"
get_parent().add_child(_pivot)
# 把摄像机放在旋转中心下面
reparent(_pivot)
# 设置摄像机位置
position = Vector3(0, 0, camera_distance)
rotation_degrees = Vector3(-camera_angle, 0, 0)
# 将旋转中心移到棋盘上方
_pivot.position = Vector3(4, camera_height, 4.5)
# 设置摄像机参数
fov = 50.0 # 视野角度
near = 0.1 # 近裁剪面
far = 100.0 # 远裁剪面
projection = Camera3D.PROJECTION_PERSPECTIVE摄像机参数说明
| 参数 | 值 | 含义(大白话) |
|---|---|---|
| FOV | 50 度 | 摄像机的"视野宽度",越大看得越宽但变形越厉害 |
| Near | 0.1 | 离摄像机多近的东西就看不到了 |
| Far | 100 | 离摄像机多远的东西就看不到了 |
| Projection | Perspective | 透视投影(近大远小,有立体感) |
2.5 灯光配置
灯光决定了你的 3D 场景看起来是什么样。没有灯光,一切都是黑的。
三种基本灯光
| 灯光类型 | 类比 | 用途 |
|---|---|---|
| DirectionalLight3D | 太阳光 | 模拟远处的平行光,照亮整个场景 |
| OmniLight3D | 灯泡 | 从一个点向四周发光 |
| SpotLight3D | 手电筒 | 从一个点向一个方向发出锥形光 |
对于象棋游戏,我们使用一个方向光作为主光源,再加一个环境光来补充亮度。
C#
using Godot;
public partial class GameLighting : Node3D
{
public override void _Ready()
{
SetupDirectionalLight();
SetupEnvironment();
}
private void SetupDirectionalLight()
{
var light = new DirectionalLight3D();
light.Name = "MainLight";
// 光的方向:从左上方照下来
light.RotationDegrees = new Vector3(-45, 30, 0);
// 光的强度和颜色
light.LightColor = new Color(1.0f, 0.95f, 0.9f); // 暖白色
light.LightEnergy = 1.2f; // 稍亮一点
light.ShadowEnabled = true; // 开启阴影
// 阴影设置(让阴影更柔和)
light.ShadowMaxDistance = 50.0f;
light.DirectionalemShadowSplit1 = 0.25f;
light.DirectionalemShadowSplit2 = 0.5f;
light.DirectionalemShadowSplit3 = 0.75f;
AddChild(light);
}
private void SetupEnvironment()
{
var worldEnv = new WorldEnvironment();
var env = new Environment();
// 环境光(让暗处也能看到)
env.AmbientLightSource = Environment.AmbientSource.Color;
env.AmbientLightColor = new Color(0.4f, 0.4f, 0.45f);
env.AmbientLightEnergy = 0.5f;
// 背景色(深灰蓝色)
env.BackgroundMode = Environment.BgMode.Color;
env.BackgroundColor = new Color(0.15f, 0.15f, 0.2f);
worldEnv.Environment = env;
AddChild(worldEnv);
}
}GDScript
extends Node3D
func _ready():
setup_directional_light()
setup_environment()
func setup_directional_light():
var light = DirectionalLight3D.new()
light.name = "MainLight"
# 光的方向:从左上方照下来
light.rotation_degrees = Vector3(-45, 30, 0)
# 光的强度和颜色
light.light_color = Color(1.0, 0.95, 0.9) # 暖白色
light.light_energy = 1.2 # 稍亮一点
light.shadow_enabled = true # 开启阴影
# 阴影设置(让阴影更柔和)
light.directional_shadow_max_distance = 50.0
add_child(light)
func setup_environment():
var world_env = WorldEnvironment.new()
var env = Environment.new()
# 环境光(让暗处也能看到)
env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR
env.ambient_light_color = Color(0.4, 0.4, 0.45)
env.ambient_light_energy = 0.5
# 背景色(深灰蓝色)
env.background_mode = Environment.BG_COLOR
env.background_color = Color(0.15, 0.15, 0.2)
world_env.environment = env
add_child(world_env)2.6 棋盘坐标系统
在开始放棋子之前,我们需要确定棋盘的坐标系。这就像给棋盘画上网格线,每个交叉点都有一个"地址"。
棋盘坐标映射
象棋棋盘有 9 列 x 10 行 = 90 个交叉点。我们用 Godot 的 3D 坐标来表示这些点:
| 棋盘概念 | 记法 | 范围 |
|---|---|---|
| 列(横坐标) | col(或 x) | 0 到 8 |
| 行(纵坐标) | row(或 z) | 0 到 9 |
C#
using Godot;
/// <summary>
/// 棋盘坐标工具类 —— 把棋盘的"第几行第几列"转换成3D空间的位置
/// </summary>
public static class BoardCoords
{
// 每个格子的大小(3D世界中两个交叉点之间的距离)
public const float CellSize = 1.0f;
// 棋盘中心偏移(让棋盘居中)
public const float OffsetX = 4.0f; // (8 / 2) = 4
public const float OffsetZ = 4.5f; // (9 / 2) = 4.5
/// <summary>
/// 把棋盘坐标转换成3D世界坐标
/// </summary>
/// <param name="col">列 0-8</param>
/// <param name="row">行 0-9</param>
/// <returns>3D世界坐标</returns>
public static Vector3 ToWorldPosition(int col, int row)
{
float x = col * CellSize - OffsetX;
float z = row * CellSize - OffsetZ;
return new Vector3(x, 0.2f, z); // y=0.2 让棋子稍微高于棋盘
}
/// <summary>
/// 把3D世界坐标转换成棋盘坐标
/// </summary>
public static Vector2I ToBoardCoord(Vector3 worldPos)
{
int col = Mathf.RoundToInt(worldPos.X / CellSize + OffsetX);
int row = Mathf.RoundToInt(worldPos.Z / CellSize + OffsetZ);
return new Vector2I(col, row);
}
/// <summary>
/// 检查坐标是否在棋盘范围内
/// </summary>
public static bool IsValidCoord(int col, int row)
{
return col >= 0 && col <= 8 && row >= 0 && row <= 9;
}
}GDScript
class_name BoardCoords
extends RefCounted
## 棋盘坐标工具类 —— 把棋盘的"第几行第几列"转换成3D空间的位置
# 每个格子的大小(3D世界中两个交叉点之间的距离)
const CELL_SIZE: float = 1.0
# 棋盘中心偏移(让棋盘居中)
const OFFSET_X: float = 4.0 # (8 / 2) = 4
const OFFSET_Z: float = 4.5 # (9 / 2) = 4.5
## 把棋盘坐标转换成3D世界坐标
## col: 列 0-8
## row: 行 0-9
## 返回: 3D世界坐标
static func to_world_position(col: int, row: int) -> Vector3:
var x: float = col * CELL_SIZE - OFFSET_X
var z: float = row * CELL_SIZE - OFFSET_Z
return Vector3(x, 0.2, z) # y=0.2 让棋子稍微高于棋盘
## 把3D世界坐标转换成棋盘坐标
static func to_board_coord(world_pos: Vector3) -> Vector2i:
var col: int = roundi(world_pos.x / CELL_SIZE + OFFSET_X)
var row: int = roundi(world_pos.z / CELL_SIZE + OFFSET_Z)
return Vector2i(col, row)
## 检查坐标是否在棋盘范围内
static func is_valid_coord(col: int, row: int) -> bool:
return col >= 0 and col <= 8 and row >= 0 and row <= 9坐标示意图
3D世界坐标系(从上方看):
-4.5 -3.5 -2.5 -1.5 -0.5 0.5 1.5 2.5 3.5 4.5 (Z轴)
| | | | | | | | | |
-4 --+-----+-----+-----+-----+-----+-----+-----+-----+-----+-- row 0 (黑方底线)
| | | | \\ | / | | | | |
-3 --+-----+-----+-----+-----+-----+-----+-----+-----+-----+-- row 1
| | | | | | | | | |
... | | | | | | | | | |
| | | | | | | | | |
0 --+-----+-----+-----+-----+-----+-----+-----+-----+-----+-- 楚河汉界
| | | | | | | | | |
... | | | | | | | | | |
| | | | | | | | | |
4 --+-----+-----+-----+-----+-----+-----+-----+-----+-----+-- row 9 (红方底线)
| | | | | | | | | |
col0 col1 col2 col3 col4 col5 col6 col7 col8
-4 -3 -2 -1 0 1 2 3 4 (X轴)2.7 游戏管理器
游戏管理器是整个游戏的"大脑",负责协调各个部分。
C#
using Godot;
public partial class GameManager : Node
{
// 游戏状态
public enum GameState
{
Menu, // 主菜单
Playing, // 对局进行中
Paused, // 暂停
GameOver // 游戏结束
}
public enum PieceColor
{
Red, // 红方
Black // 黑方
}
// 当前状态
public GameState CurrentState { get; set; } = GameState.Menu;
public PieceColor CurrentTurn { get; set; } = PieceColor.Red;
// 单例模式(方便其他脚本访问)
public static GameManager Instance { get; private set; }
public override void _Ready()
{
Instance = this;
}
/// <summary>
/// 开始新游戏
/// </summary>
public void StartNewGame()
{
CurrentState = GameState.Playing;
CurrentTurn = PieceColor.Red; // 红方先走
GD.Print("新游戏开始!红方先走。");
}
/// <summary>
/// 切换回合
/// </summary>
public void SwitchTurn()
{
CurrentTurn = (CurrentTurn == PieceColor.Red)
? PieceColor.Black
: PieceColor.Red;
GD.Print($"轮到 {(CurrentTurn == PieceColor.Red ? "红方" : "黑方")} 走棋");
}
}GDScript
extends Node
## 游戏状态枚举
enum GameState {
MENU, # 主菜单
PLAYING, # 对局进行中
PAUSED, # 暂停
GAME_OVER # 游戏结束
}
## 棋子颜色枚举
enum PieceColor {
RED, # 红方
BLACK # 黑方
}
## 当前状态
var current_state: GameState = GameState.MENU
var current_turn: PieceColor = PieceColor.RED
## 单例模式(方便其他脚本访问)
static var instance: GameManager
func _ready():
instance = self
## 开始新游戏
func start_new_game():
current_state = GameState.PLAYING
current_turn = PieceColor.RED # 红方先走
print("新游戏开始!红方先走。")
## 切换回合
func switch_turn():
current_turn = PieceColor.BLACK if current_turn == PieceColor.RED else PieceColor.RED
print("轮到 %s 走棋" % ("红方" if current_turn == PieceColor.RED else "黑方"))2.8 项目设置检查清单
在继续之前,确认以下设置都正确:
项目设置检查
在 Godot 编辑器中,点击"项目" → "项目设置",检查以下配置:
| 设置项 | 路径 | 推荐值 |
|---|---|---|
| 窗口大小 | Application → Window → Viewport | 1280 x 720 |
| 拉伸模式 | Application → Window → Stretch | canvas_items |
| 拉伸比例 | Application → Window → Stretch → Aspect | keep |
| 主场景 | Application → Run | 选择 main.tscn |
| 渲染器 | 渲染 → 渲染器 | Forward+ |
本章小结
| 要点 | 说明 |
|---|---|
| 项目结构 | 场景、脚本、模型、纹理、音效分目录管理 |
| 主场景 | Node3D 根节点 + 棋盘 + 棋子 + 摄像机 + 灯光 + 特效 + UI |
| 摄像机 | 斜上方俯视角度,FOV 50 度,带旋转中心 |
| 灯光 | 方向光 + 环境光,暖白色,开启阴影 |
| 坐标系 | 9x10 棋盘映射到 3D 世界坐标 |
→ 3. 棋盘与棋子
