2. 项目搭建与2.5D摄像机
项目搭建与2.5D摄像机
上一章我们设计了核心玩法,这一章开始动手——创建 Godot 项目、配置2.5D摄像机、设置输入映射。
创建 Godot 4 项目
步骤一:新建项目
- 打开 Godot 4 项目管理器
- 点击 新建 按钮
- 填写项目信息:
| 设置项 | 值 | 说明 |
|---|---|---|
| 项目名称 | Jackal25D | 项目名用英文,避免路径问题 |
| 项目路径 | 选择一个你喜欢的位置 | 建议路径中不要有中文 |
| 渲染器 | Forward+ | 3D 项目推荐使用 Forward+ |
渲染器怎么选?
Godot 4 提供三种渲染器:
- Forward+:画质最好,适合桌面平台(PC、Mac)——我们就选这个
- Mobile:画质略低,适合手机和平板
- Compatibility:兼容旧设备,画质最低
如果你将来想移植到手机,可以在导出时切换为 Mobile 渲染器。
步骤二:创建目录结构
项目创建好后,在 Godot 的文件系统面板中创建以下目录:
jackal25d/
├── scenes/ # 场景文件
│ ├── player/ # 玩家相关场景
│ ├── enemies/ # 敌人场景
│ ├── bullets/ # 子弹和弹药
│ ├── maps/ # 地图和关卡
│ └── ui/ # UI 界面
├── scripts/ # 脚本文件
│ ├── player/ # 玩家脚本
│ ├── enemies/ # 敌人脚本
│ ├── weapons/ # 武器脚本
│ └── managers/ # 管理器脚本
├── assets/ # 资源文件
│ ├── models/ # 3D 模型
│ ├── textures/ # 贴图
│ ├── sounds/ # 音效
│ └── data/ # 数据文件(JSON等)
└── project.godot # 项目配置文件为什么要提前建好目录?
就像搬家之前先把柜子摆好——虽然你可以以后再建,但一开始就规划好,后面找文件就不用到处翻了。特别是团队协作时,统一的目录结构非常重要。
配置 2.5D 摄像机
这是整个教程最关键的一步——2.5D 的"2.5"就来自摄像机的设置。
什么是正交摄像机?
普通3D游戏用的是"透视摄像机"(Perspective Camera),效果是近处的东西大、远处的东西小。就像你站在铁路上往远处看,两条铁轨会在远处交汇。
正交摄像机(Orthogonal Camera)没有这种近大远小的效果。所有东西不管远近,看起来一样大。就像用望远镜看远处——没有透视变形。
对于赤色要塞这种俯视角游戏,正交摄像机能保持画面的一致性,看起来更像经典的2D游戏。
摄像机设置步骤
- 创建一个 Node3D 作为场景根节点,命名为
Main - 给
Main添加子节点 Camera3D - 在 Camera3D 的属性面板中设置以下参数:
| 属性 | 值 | 说明 |
|---|---|---|
| Projection | Orthogonal | 正交投影,没有透视变形 |
| Size | 15 | 视野大小,值越大看到的范围越广 |
| Rotation | X: -60° | 绕X轴旋转,从上方斜着往下看 |
| Position | Y: 10 | 摄像机离地面的高度 |
| Near | 0.05 | 最近可见距离 |
| Far | 100 | 最远可见距离 |
摄像机场景树结构
Main (Node3D)
├── Ground (StaticBody3D) ← 地面
│ └── MeshInstance3D ← 地面网格(临时用一个平面)
├── DirectionalLight3D ← 主光源(太阳光)
└── CameraRig (Node3D) ← 摄像机挂架(方便后续跟随玩家)
└── Camera3D ← 主摄像机用代码配置摄像机
虽然你可以在编辑器里手动设置摄像机参数,但用代码设置更灵活,方便后续调整:
using Godot;
/// <summary>
/// 2.5D 俯视角摄像机
/// 挂在 CameraRig 节点上,Camera3D 作为子节点
/// </summary>
public partial class GameCamera : Node3D
{
[Export] public float CameraHeight { get; set; } = 12.0f; // 摄像机高度
[Export] public float CameraAngle { get; set; } = -60.0f; // 俯视角度(度)
[Export] public float OrthoSize { get; set; } = 15.0f; // 正交视野大小
[Export] public float FollowSpeed { get; set; } = 5.0f; // 跟随速度
private Camera3D _camera;
private Node3D _target; // 跟随目标(玩家)
public override void _Ready()
{
// 获取子节点 Camera3D
_camera = GetNode<Camera3D>("Camera3D");
// 配置正交投影
_camera.Projection = Camera3D.ProjectionType.Orthogonal;
_camera.Size = OrthoSize;
// 设置摄像机位置和旋转
_camera.Position = new Vector3(0, CameraHeight, 0);
_camera.RotationDegrees = new Vector3(CameraAngle, 0, 0);
}
public override void _PhysicsProcess(double delta)
{
if (_target == null) return;
// 平滑跟随目标
Vector3 targetPos = new Vector3(
_target.Position.X,
Position.Y,
_target.Position.Z
);
Position = Position.Lerp(targetPos, (float)(FollowSpeed * delta));
}
/// <summary>
/// 设置跟随目标
/// </summary>
public void SetTarget(Node3D target)
{
_target = target;
}
}# 2.5D 俯视角摄像机
# 挂在 CameraRig 节点上,Camera3D 作为子节点
extends Node3D
@export var camera_height: float = 12.0 # 摄像机高度
@export var camera_angle: float = -60.0 # 俯视角度(度)
@export var ortho_size: float = 15.0 # 正交视野大小
@export var follow_speed: float = 5.0 # 跟随速度
var _camera: Camera3D
var _target: Node3D # 跟随目标(玩家)
func _ready():
# 获取子节点 Camera3D
_camera = $Camera3D
# 配置正交投影
_camera.projection = Camera3D.PROJECTION_ORTHOGONAL
_camera.size = ortho_size
# 设置摄像机位置和旋转
_camera.position = Vector3(0, camera_height, 0)
_camera.rotation_degrees = Vector3(camera_angle, 0, 0)
func _physics_process(delta):
if _target == null:
return
# 平滑跟随目标
var target_pos = Vector3(
_target.position.x,
position.y,
_target.position.z
)
position = position.lerp(target_pos, follow_speed * delta)
## 设置跟随目标
func set_target(target: Node3D):
_target = target摄像机角度对比
不同的俯视角度会带来完全不同的观感:
| 角度(X轴旋转) | 效果 | 适合 |
|---|---|---|
| -90° | 完全正上方俯视 | 纯2D感觉,看不到建筑高度 |
| -75° | 略微倾斜 | 接近原版赤色要塞的感觉 |
| -60° | 中等倾斜 | 推荐,兼顾2D感和3D层次 |
| -45° | 较大倾斜 | 3D感很强,类似暗黑破坏神 |
| -30° | 接近平视 | 太"3D"了,不像俯视角游戏 |
角度影响操控
俯视角度越倾斜(越接近0度),3D感越强,但上下方向的移动在屏幕上看起来会越"短"。这意味着玩家按"上"键时,角色移动距离在屏幕上看起来比按"左右"时短,操控感会变奇怪。
推荐 -60°,这是在"2D感"和"3D层次感"之间的最佳平衡点。
项目设置
窗口大小
打开 项目 → 项目设置:
| 设置项 | 值 | 说明 |
|---|---|---|
| Display → Window → Size → Viewport Width | 1280 | 游戏画面宽度 |
| Display → Window → Size → Viewport Height | 720 | 游戏画面高度(16:9比例) |
| Display → Window → Size → Mode | viewport | 窗口模式 |
| Display → Window → Stretch → Mode | canvas_items | 画面缩放模式 |
| Display → Window → Stretch → Aspect | keep | 保持比例不拉伸 |
输入映射(Input Map)
赤色要塞需要以下按键配置。打开 项目 → 项目设置 → Input Map:
先添加一个叫 player 的动作组,然后添加以下动作:
| 动作名称 | 键盘按键 | 手柄按键 | 说明 |
|---|---|---|---|
move_up | W、上箭头 | 左摇杆上 | 向上移动 |
move_down | S、下箭头 | 左摇杆下 | 向下移动 |
move_left | A、左箭头 | 左摇杆左 | 向左移动 |
move_right | D、右箭头 | 左摇杆右 | 向右移动 |
shoot | J、Space | R2/RT | 射击 |
special | K、Shift | L2/LT | 特殊攻击(手雷) |
pause | Escape、P | Start | 暂停 |
用代码注册输入映射
你也可以在代码中动态注册输入映射,这样不用手动在编辑器里一个个添加。但新手建议先在编辑器里手动设置,理解更直观。
物理设置
| 设置项 | 推荐值 | 说明 |
|---|---|---|
| Physics → Ticks Per Second | 60 | 物理帧率,60帧足够流畅 |
| Physics → Common → Physics Jitter Fix | 0.5 | 减少物理抖动 |
基本场景搭建
创建主场景
现在我们来搭建游戏的最基本场景——一块地面 + 一个测试方块 + 摄像机。
- 创建新场景,根节点选 Node3D,命名为
Main - 添加以下子节点:
Main (Node3D)
├── Environment
│ ├── DirectionalLight3D ← 主光源
│ └── WorldEnvironment ← 环境设置
├── Ground
│ └── StaticBody3D
│ ├── CollisionShape3D ← 碰撞形状(BoxShape3D)
│ └── MeshInstance3D ← 地面网格
├── TestCube ← 临时测试方块(用来验证摄像机效果)
│ └── MeshInstance3D ← BoxMesh
└── CameraRig ← 摄像机挂架
└── Camera3D ← 主摄像机地面设置
地面临时用一个大的平面来代替:
- MeshInstance3D:使用
PlaneMesh,大小设为 50 x 50 - CollisionShape3D:使用
BoxShape3D,大小设为 50 x 1 x 50
光照设置
添加 DirectionalLight3D,参数如下:
| 属性 | 值 | 说明 |
|---|---|---|
| Rotation | X: -45°, Y: -30° | 从斜上方照射 |
| Shadow | Enabled | 开启阴影 |
| Light Color | 暖白色(略偏黄) | 模拟阳光 |
为什么要设置光照?
即使是开发阶段,一个好的光照设置也能让你更直观地看到3D场景的深度。没有光照的话,3D模型看起来就像一张平面的彩色图片,看不出立体感。
测试摄像机效果
- 把 TestCube 放在地面中央(Position: 0, 0.5, 0)
- 按 F6 运行当前场景
- 你应该看到一个方块从斜上方被俯视的效果
如果看起来像这样,说明摄像机设置正确:
┌─────────────────────────┐
│ │
│ ■ 测试方块 │ ← 方块略有变形(因为倾斜角)
│ │
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓ │ ← 地面
│ │
└─────────────────────────┘在 _ProjectSettings 中用代码配置输入
如果你希望项目更干净,可以用脚本自动注册输入映射:
/// <summary>
/// 输入映射自动注册工具
/// 放在一个 AutoLoad 脚本中,游戏启动时自动执行
/// </summary>
public partial class InputRegistry : Node
{
public override void _Ready()
{
RegisterAction("move_up", Key.W, Key.Up);
RegisterAction("move_down", Key.S, Key.Down);
RegisterAction("move_left", Key.A, Key.Left);
RegisterAction("move_right", Key.D, Key.Right);
RegisterAction("shoot", Key.Space, Key.J);
RegisterAction("special", Key.Shift, Key.K);
RegisterAction("pause", Key.Escape, Key.P);
}
private void RegisterAction(string actionName, Key key1, Key key2)
{
// 如果动作已存在,不重复注册
if (InputMap.HasAction(actionName)) return;
InputMap.AddAction(actionName);
var event1 = new InputEventKey { Keycode = key1 };
InputMap.ActionAddEvent(actionName, event1);
var event2 = new InputEventKey { Keycode = key2 };
InputMap.ActionAddEvent(actionName, event2);
}
}## 输入映射自动注册工具
## 放在一个 AutoLoad 脚本中,游戏启动时自动执行
extends Node
func _ready():
_register_action("move_up", KEY_W, KEY_UP)
_register_action("move_down", KEY_S, KEY_DOWN)
_register_action("move_left", KEY_A, KEY_LEFT)
_register_action("move_right", KEY_D, KEY_RIGHT)
_register_action("shoot", KEY_SPACE, KEY_J)
_register_action("special", KEY_SHIFT, KEY_K)
_register_action("pause", KEY_ESCAPE, KEY_P)
func _register_action(action_name: String, key1: Key, key2: Key):
# 如果动作已存在,不重复注册
if InputMap.has_action(action_name):
return
InputMap.add_action(action_name)
var event1 = InputEventKey.new()
event1.keycode = key1
InputMap.action_add_event(action_name, event1)
var event2 = InputEventKey.new()
event2.keycode = key2
InputMap.action_add_event(action_name, event2)设置 AutoLoad(全局管理器)
为了让一些管理器在整个游戏中都可用(不需要每个场景都手动添加),我们使用 Godot 的 AutoLoad 功能。
- 创建
scripts/managers/GameManager.cs(或.gd) - 在 项目 → 项目设置 → AutoLoad 中添加:
- 路径:
res://scripts/managers/GameManager - 名称:
GameManager
- 路径:
本章完成的检查清单
完成本章后,你应该有以下内容:
遇到问题?
如果运行场景后什么都没看到:
- 确认 Camera3D 的 Current 属性已勾选
- 确认 Camera3D 的 Rotation X 是负数(比如 -60)
- 确认测试方块的 Y 坐标大于 0(否则可能在地面下面)
下一章
项目搭好了,摄像机配好了,下一步就是让吉普车动起来!
→ 3. 载具控制
