2. 项目搭建与2.5D摄像机
项目搭建与2.5D摄像机
创建 Godot 4 项目
第一步:打开 Godot 项目管理器
当你安装好 Godot 4 之后,打开它会看到一个项目管理器界面。点击右上角的 "新建" 按钮。
Godot 4 去哪下载?
去 Godot 官网 https://godotengine.org/download 下载。如果你要用 C#,需要下载 .NET 版本(文件名里带 "mono" 或 ".net" 的)。如果只用 GDScript,下载标准版就行。
第二步:填写项目信息
| 字段 | 填什么 | 说明 |
|---|---|---|
| 项目名称 | Contra25D | 随便取,英文,别用中文和空格 |
| 项目路径 | 你喜欢的任何位置 | 建议用英文路径,别有中文 |
| 渲染器 | Forward+ | Godot 4 默认的高质量渲染器 |
第三步:选择渲染器
Godot 4 提供三种渲染器:
| 渲染器 | 适用场景 | 我们用不用 |
|---|---|---|
| Forward+ | 桌面端,画面质量最好 | 用这个 |
| Mobile | 移动端,性能优先 | 以后导出手机时再说 |
| Compatibility | 兼容旧设备 | 不需要 |
选 Forward+,点击"创建并编辑"。
项目目录结构
创建好项目后,我们需要组织好文件结构。就像整理衣柜一样——衣服分类放好,找起来才方便。
Contra25D/
├── Scenes/ # 场景文件(.tscn)
│ ├── Player/ # 玩家相关场景
│ ├── Enemies/ # 敌人相关场景
│ ├── Bullets/ # 子弹相关场景
│ ├── Levels/ # 关卡场景
│ ├── Props/ # 道具场景
│ └── UI/ # 界面场景
├── Scripts/ # 脚本文件(.cs 或 .gd)
│ ├── Player/ # 玩家脚本
│ ├── Enemies/ # 敌人脚本
│ ├── Bullets/ # 子弹脚本
│ ├── Systems/ # 系统脚本(对象池、波次等)
│ └── UI/ # 界面脚本
├── Assets/ # 资源文件
│ ├── Models/ # 3D 模型
│ ├── Textures/ # 贴图
│ ├── Audio/ # 音效
│ ├── Fonts/ # 字体
│ └── Particles/ # 粒子特效
└── project.godot # 项目配置文件为什么要这样分?
当你做了10个、20个场景之后,如果都堆在根目录,你会找不到东西。分好文件夹,就像图书馆分好书架一样——想找什么一找就到。
配置正交摄像机(2.5D 的关键)
这是整个项目最关键的一步。我们要创建一个从侧面看的正交摄像机。
正交摄像机是什么?
普通相机拍照时,近的东西大、远的东西小,这叫"透视"。而正交投影就像建筑图纸一样,不管远近,东西看起来一样大。这就是为什么我们用正交摄像机——它能让3D场景看起来像2D的。
创建场景结构
- 新建一个场景,根节点选择 Node3D,命名为
GameWorld - 在
GameWorld下添加以下子节点:
GameWorld (Node3D)
├── CameraRig (Node3D) # 摄像机支架
│ └── Camera3D # 主摄像机
├── Player (CharacterBody3D) # 玩家角色
│ └── MeshInstance3D # 占位用的模型
├── Ground (StaticBody3D) # 地面
│ ├── MeshInstance3D # 地面模型
│ └── CollisionShape3D # 碰撞形状
└── DirectionalLight3D # 灯光摄像机配置
选中 Camera3D,在右侧检查器面板中做以下设置:
| 属性 | 值 | 说明 |
|---|---|---|
| Projection | Orthographic | 正交投影(关键!) |
| Size | 10 | 视野大小,数值越大看到越多 |
| Position | (0, 5, 15) | 放在角色侧面偏上方 |
| Rotation | (-15, 0, 0) | 稍微向下看一点 |
| Current | On | 设为主摄像机 |
Size 参数很重要
正交摄像机的 Size 不是"距离",而是"视野高度"。Size = 10 意味着摄像机能看到上下共 10 米的范围。你可以把它想象成"窗口的大小"——窗口越大,看到的东西越多,但每个东西看起来越小。
摄像机配置代码
如果你想用代码来设置摄像机,也可以:
using Godot;
public partial class GameCamera : Camera3D
{
public override void _Ready()
{
// 设置为正交投影
Projection = Camera3D.ProjectionType.Orthogonal;
// 视野高度(单位:米)
Size = 10.0f;
// 放在角色侧面,稍微偏上
Position = new Vector3(0, 5, 15);
// 稍微向下看
RotationDegrees = new Vector3(-15, 0, 0);
// 设为当前摄像机
Current = true;
}
}extends Camera3D
func _ready():
# 设置为正交投影
projection = Camera3D.PROJECTION_ORTHOGONAL
# 视野高度(单位:米)
size = 10.0
# 放在角色侧面,稍微偏上
position = Vector3(0, 5, 15)
# 稍微向下看
rotation_degrees = Vector3(-15, 0, 0)
# 设为当前摄像机
current = true输入映射配置
我们需要定义游戏的按键操作。在 Godot 中,这叫"输入映射(Input Map)"。
什么是输入映射?
输入映射就是把"按哪个键"和"做什么动作"对应起来。比如你把"A键"映射到"向左移动"这个动作上,那在代码里你只需要问"玩家想不想向左移?",而不用管他具体按了哪个键。这样做的好处是:以后想改按键,只改映射就行,不用改代码。
打开 项目 → 项目设置 → 输入映射,添加以下动作:
| 动作名称 | 按键绑定 | 说明 |
|---|---|---|
move_left | A、左箭头 | 向左移动 |
move_right | D、右箭头 | 向右移动 |
jump | W、上箭头、空格 | 跳跃 |
shoot | J、鼠标左键 | 射击 |
duck | S、下箭头 | 蹲下/卧倒 |
pause | Escape、P | 暂停游戏 |
用代码读取输入
// 检测玩家是否按了某个键
bool wantsLeft = Input.IsActionPressed("move_left");
bool wantsRight = Input.IsActionPressed("move_right");
bool wantsJump = Input.IsActionJustPressed("jump");
bool wantsShoot = Input.IsActionPressed("shoot");
bool wantsDuck = Input.IsActionPressed("duck");# 检测玩家是否按了某个键
var wants_left = Input.is_action_pressed("move_left")
var wants_right = Input.is_action_pressed("move_right")
var wants_jump = Input.is_action_just_pressed("jump")
var wants_shoot = Input.is_action_pressed("shoot")
var wants_duck = Input.is_action_pressed("duck")IsActionPressed 和 IsActionJustPressed 的区别
IsActionPressed:只要按着就一直返回true。适合持续动作(移动、蹲下)IsActionJustPressed:只在按下的一瞬间返回true。适合单次动作(跳跃、开枪)
创建基础场景
地面场景
我们先创建一个简单的地面,让角色有地方站:
using Godot;
/// <summary>
/// 地面节点 - 提供角色站立的平台
/// </summary>
public partial class Ground : StaticBody3D
{
public override void _Ready()
{
// 创建地面模型(一个扁平的长方体)
var mesh = new MeshInstance3D();
var boxMesh = new BoxMesh();
boxMesh.Size = new Vector3(100, 1, 10); // 宽100米,高1米,深10米
mesh.Mesh = boxMesh;
// 给地面上个颜色
var material = new StandardMaterial3D();
material.AlbedoColor = new Color(0.3f, 0.5f, 0.2f); // 草绿色
mesh.MaterialOverride = material;
AddChild(mesh);
// 创建碰撞形状
var collision = new CollisionShape3D();
var shape = new BoxShape3D();
shape.Size = new Vector3(100, 1, 10);
collision.Shape = shape;
AddChild(collision);
}
}extends StaticBody3D
## 地面节点 - 提供角色站立的平台
func _ready():
# 创建地面模型(一个扁平的长方体)
var mesh = MeshInstance3D.new()
var box_mesh = BoxMesh.new()
box_mesh.size = Vector3(100, 1, 10) # 宽100米,高1米,深10米
mesh.mesh = box_mesh
# 给地面上个颜色
var material = StandardMaterial3D.new()
material.albedo_color = Color(0.3, 0.5, 0.2) # 草绿色
mesh.material_override = material
add_child(mesh)
# 创建碰撞形状
var collision = CollisionShape3D.new()
var shape = BoxShape3D.new()
shape.size = Vector3(100, 1, 10)
collision.shape = shape
add_child(collision)灯光设置
场景里需要一盏灯,不然什么都看不见:
using Godot;
/// <summary>
/// 场景灯光 - 模拟太阳光
/// </summary>
public partial class GameLight : DirectionalLight3D
{
public override void _Ready()
{
// 灯光方向:从右上方照过来
RotationDegrees = new Vector3(-45, -30, 0);
// 灯光颜色:暖白色
LightColor = new Color(1.0f, 0.95f, 0.8f);
// 开启阴影
ShadowEnabled = true;
// 阴影质量
DirectionalShadowMaxDistance = 50.0f;
}
}extends DirectionalLight3D
## 场景灯光 - 模拟太阳光
func _ready():
# 灯光方向:从右上方照过来
rotation_degrees = Vector3(-45, -30, 0)
# 灯光颜色:暖白色
light_color = Color(1.0, 0.95, 0.8)
# 开启阴影
shadow_enabled = true
# 阴影质量
directional_shadow_max_distance = 50.0窗口比例设置
魂斗罗是横版游戏,窗口比例应该是 16:9(宽比高 = 16比9)。打开 项目 → 项目设置 → 显示 → 窗口,设置:
| 属性 | 值 | 说明 |
|---|---|---|
| Viewport Width | 1280 | 窗口宽度(像素) |
| Viewport Height | 720 | 窗口高度(像素) |
| Stretch Mode | canvas_items | 拉伸模式 |
| Stretch Aspect | keep | 保持比例 |
为什么是1280x720?
这是720p的分辨率,是PC和网页游戏最常用的开发分辨率。最终发布时可以根据目标平台调整。但开发阶段用这个最方便,因为大多数显示器都支持。
项目设置检查清单
在开始写代码之前,确认以下设置都正确:
| 检查项 | 位置 | 期望值 |
|---|---|---|
| 渲染器 | 项目设置 → General → Rendering | Forward+ |
| 视口大小 | 项目设置 → General → Display → Window | 1280 x 720 |
| 拉伸模式 | 项目设置 → General → Display → Window → Stretch | canvas_items |
| 拉伸比例 | 同上 | keep |
| 物理帧率 | 项目设置 → General → Physics → Common | 60 |
| 输入映射 | 项目设置 → Input Map | 6个动作已添加 |
| 摄像机 | Camera3D 节点 | 正交投影,Size=10 |
| 灯光 | DirectionalLight3D 节点 | 已添加并开启阴影 |
完整的场景配置脚本
最后,让我们写一个完整的场景初始化脚本,把所有东西组合起来:
using Godot;
/// <summary>
/// 游戏世界 - 管理整个游戏场景
/// </summary>
public partial class GameWorld : Node3D
{
[Export] public float CameraSize = 10.0f;
[Export] public Vector2 ViewportSize = new Vector2(1280, 720);
private Camera3D _camera;
private DirectionalLight3D _light;
private Player _player;
public override void _Ready()
{
SetupCamera();
SetupLighting();
SetupGround();
GD.Print("游戏世界初始化完成!");
}
private void SetupCamera()
{
// 创建摄像机支架
var cameraRig = new Node3D { Name = "CameraRig" };
AddChild(cameraRig);
// 创建摄像机
_camera = new Camera3D { Name = "MainCamera" };
_camera.Projection = Camera3D.ProjectionType.Orthogonal;
_camera.Size = CameraSize;
_camera.Position = new Vector3(0, 5, 15);
_camera.RotationDegrees = new Vector3(-15, 0, 0);
_camera.Current = true;
cameraRig.AddChild(_camera);
}
private void SetupLighting()
{
_light = new DirectionalLight3D { Name = "SunLight" };
_light.RotationDegrees = new Vector3(-45, -30, 0);
_light.LightColor = new Color(1.0f, 0.95f, 0.8f);
_light.ShadowEnabled = true;
_light.DirectionalShadowMaxDistance = 50.0f;
AddChild(_light);
}
private void SetupGround()
{
var ground = new StaticBody3D { Name = "Ground" };
// 地面模型
var mesh = new MeshInstance3D();
var boxMesh = new BoxMesh();
boxMesh.Size = new Vector3(100, 1, 10);
mesh.Mesh = boxMesh;
var material = new StandardMaterial3D();
material.AlbedoColor = new Color(0.3f, 0.5f, 0.2f);
mesh.MaterialOverride = material;
ground.AddChild(mesh);
// 地面碰撞
var collision = new CollisionShape3D();
var shape = new BoxShape3D();
shape.Size = new Vector3(100, 1, 10);
collision.Shape = shape;
ground.AddChild(collision);
AddChild(ground);
}
}extends Node3D
## 游戏世界 - 管理整个游戏场景
@export var camera_size: float = 10.0
@export var viewport_size: Vector2 = Vector2(1280, 720)
var _camera: Camera3D
var _light: DirectionalLight3D
var _player: Node # Player 节点
func _ready():
_setup_camera()
_setup_lighting()
_setup_ground()
print("游戏世界初始化完成!")
func _setup_camera():
# 创建摄像机支架
var camera_rig = Node3D.new()
camera_rig.name = "CameraRig"
add_child(camera_rig)
# 创建摄像机
_camera = Camera3D.new()
_camera.name = "MainCamera"
_camera.projection = Camera3D.PROJECTION_ORTHOGONAL
_camera.size = camera_size
_camera.position = Vector3(0, 5, 15)
_camera.rotation_degrees = Vector3(-15, 0, 0)
_camera.current = true
camera_rig.add_child(_camera)
func _setup_lighting():
_light = DirectionalLight3D.new()
_light.name = "SunLight"
_light.rotation_degrees = Vector3(-45, -30, 0)
_light.light_color = Color(1.0, 0.95, 0.8)
_light.shadow_enabled = true
_light.directional_shadow_max_distance = 50.0
add_child(_light)
func _setup_ground():
var ground = StaticBody3D.new()
ground.name = "Ground"
# 地面模型
var mesh = MeshInstance3D.new()
var box_mesh = BoxMesh.new()
box_mesh.size = Vector3(100, 1, 10)
mesh.mesh = box_mesh
var material = StandardMaterial3D.new()
material.albedo_color = Color(0.3, 0.5, 0.2)
mesh.material_override = material
ground.add_child(mesh)
# 地面碰撞
var collision = CollisionShape3D.new()
var shape = BoxShape3D.new()
shape.size = Vector3(100, 1, 10)
collision.shape = shape
ground.add_child(collision)
add_child(ground)常见问题排查
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 场景全黑 | 没有灯光 | 添加 DirectionalLight3D |
| 角色看不到 | 没有模型或太小 | 给角色添加 MeshInstance3D |
| 画面有透视感 | 摄像机没设正交 | 把 Projection 改为 Orthographic |
| 角色掉出屏幕 | 没有碰撞体 | 给地面添加 CollisionShape3D |
| 窗口比例不对 | 没设置拉伸模式 | 设置 Stretch Mode 为 canvas_items |
本章小结
这一章我们完成了:
- 创建 Godot 4 项目,选择 Forward+ 渲染器
- 配置正交摄像机,实现 2.5D 侧视角效果
- 设置输入映射,定义了6个操作动作
- 搭建基础场景结构,包含地面和灯光
- 确认项目设置,确保一切就绪
下一步
现在项目框架已经搭好了,接下来我们要让角色动起来!下一章将实现角色的左右移动和跳跃功能。
