7. 滚动摄像机
2026/4/14大约 6 分钟
7. 超级玛丽奥——滚动摄像机
简介
滚动摄像机就是让画面跟着玩家移动的"眼睛"。玛丽奥往右跑,画面就跟着往右滚动,让玩家始终能看到玛丽奥。
超级玛丽奥的摄像机有一个重要特点:不能往左滚动。这就像你在走一条单行道——只能往前走,不能回头。这是经典横版游戏的设计,目的是防止玩家回到已经过去的区域。
为什么限制左移?
在经典的超级玛丽奥中,玩家不能往回走(摄像机不会往左移动)。这样做有几个原因:
| 原因 | 说明 |
|---|---|
| 增加紧张感 | 你不能回头,必须一直前进 |
| 简化设计 | 设计师只需要设计"向前"的路线 |
| 防止卡关 | 玩家不能通过反复刷怪来积累分数 |
| 增加挑战 | 一旦错过道具就错过了,不能回来拿 |
Camera2D 基础
Godot 的 Camera2D 节点可以自动跟随指定的目标。只需要把它作为玩家(或跟随目标)的子节点,它就会自动跟随。
Player (CharacterBody2D)
├── AnimatedSprite2D
├── CollisionShape2D
└── Camera2D # 摄像机作为玩家的子节点
├── Position Smoothing: true # 开启平滑跟随
├── Smoothing Speed: 5 # 跟随速度
└── Limit: # 边界限制
├── Left: 0 # 最左边(不允许往左看)
├── Top: 0 # 最上面
└── Bottom: 480 # 最下面游戏摄像机脚本
// GameCamera.cs - 游戏摄像机
using Godot;
public partial class GameCamera : Camera2D
{
// ========== 摄像机配置 ==========
[Export] public float FollowSpeed { get; set; } = 5.0f;
// 限制摄像机不能往左移动
[Export] public bool LimitLeftScroll { get; set; } = true;
// 摄像机能到达的最左边界(初始化时设置)
private float _minX = 0.0f;
// 跟随目标
private Player _player;
// ========== 初始化 ==========
public override void _Ready()
{
// 设置平滑跟随
PositionSmoothingEnabled = true;
PositionSmoothingSpeed = FollowSpeed;
// 延迟获取玩家引用(等场景加载完)
CallDeferred(MethodName.SetupPlayer);
}
// 设置跟随目标
private void SetupPlayer()
{
_player = GetTree().GetFirstNodeInGroup("player") as Player;
if (_player != null)
{
// 记录初始位置作为最左边界
_minX = _player.Position.X - 200;
// 设置摄像机边界
if (LimitLeftScroll)
{
LimitLeft = _minX;
}
}
}
// ========== 每帧更新 ==========
public override void _Process(double delta)
{
if (_player == null) return;
UpdateCameraBounds();
}
// 更新摄像机边界
private void UpdateCameraBounds()
{
// 摄像机不能往左超过初始位置
if (LimitLeftScroll)
{
// 确保摄像机左边界不超过最左位置
float cameraLeft = Position.X - GetViewportRect().Size.X / 2;
if (cameraLeft < _minX)
{
Position = new Vector2(
_minX + GetViewportRect().Size.X / 2,
Position.Y
);
}
}
}
// 更新最左边界(玩家到达新位置时调用)
public void UpdateMinX(float newMinX)
{
_minX = Mathf.Max(_minX, newMinX);
LimitLeft = _minX;
}
}# game_camera.gd - 游戏摄像机
extends Camera2D
# ========== 摄像机配置 ==========
@export var follow_speed: float = 5.0
# 限制摄像机不能往左移动
@export var limit_left_scroll: bool = true
# 摄像机能到达的最左边界
var _min_x: float = 0.0
# 跟随目标
var _player: Node = null
# ========== 初始化 ==========
func _ready() -> void:
# 设置平滑跟随
position_smoothing_enabled = true
position_smoothing_speed = follow_speed
# 延迟获取玩家引用
call_deferred("setup_player")
## 设置跟随目标
func setup_player() -> void:
_player = get_tree().get_first_node_in_group("player")
if _player != null:
# 记录初始位置作为最左边界
_min_x = _player.position.x - 200
# 设置摄像机边界
if limit_left_scroll:
limit_left = _min_x
# ========== 每帧更新 ==========
func _process(delta: float) -> void:
if _player == null:
return
update_camera_bounds()
## 更新摄像机边界
func update_camera_bounds() -> void:
if not limit_left_scroll:
return
# 确保摄像机左边界不超过最左位置
var camera_left: float = position.x - get_viewport_rect().size.x / 2
if camera_left < _min_x:
position = Vector2(
_min_x + get_viewport_rect().size.x / 2,
position.y
)
## 更新最左边界
func update_min_x(new_min_x: float) -> void:
_min_x = maxf(_min_x, new_min_x)
limit_left = _min_x摄像机边界设置
摄像机边界确保画面不会显示关卡以外的区域:
| 边界 | 方向 | 说明 |
|---|---|---|
| Limit Left | 左 | 最左位置(防止往左看) |
| Limit Top | 上 | 最上位置(防止看到天空以上) |
| Limit Bottom | 下 | 最下位置(防止看到地面以下) |
| Limit Right | 右 | 最右位置(关卡终点) |
在场景中设置
可以在 Godot 编辑器中直接设置,也可以在代码中动态设置:
// 在关卡场景的脚本中设置摄像机边界
public partial class Level : Node2D
{
private GameCamera _camera;
public override void _Ready()
{
_camera = GetNode<GameCamera>("Player/Camera2D");
// 根据关卡大小设置边界
var levelWidth = 6400; // 关卡宽度(像素)
var levelHeight = 480; // 关卡高度(像素)
_camera.LimitLeft = 0;
_camera.LimitTop = 0;
_camera.LimitRight = levelWidth;
_camera.LimitBottom = levelHeight;
}
}# 在关卡场景的脚本中设置摄像机边界
extends Node2D
var _camera: Node = null
func _ready() -> void:
_camera = get_node("Player/Camera2D")
# 根据关卡大小设置边界
var level_width: int = 6400
var level_height: int = 480
_camera.limit_left = 0
_camera.limit_top = 0
_camera.limit_right = level_width
_camera.limit_bottom = level_height防止玩家往左走
除了限制摄像机,还需要防止玩家自己往左走超出摄像机范围:
// 在 Player.cs 中添加边界限制
public override void _PhysicsProcess(double delta)
{
// ... 正常的物理处理 ...
// 移动后,限制玩家不能走出屏幕左边
LimitPlayerPosition();
}
private void LimitPlayerPosition()
{
// 获取摄像机
var camera = GetNode<Camera2D>("Camera2D");
if (camera == null) return;
// 计算屏幕左边界在世界坐标中的位置
float screenLeft = camera.GlobalPosition.X - GetViewportRect().Size.X / 2;
// 玩家不能走到屏幕左边以外
if (Position.X < screenLeft + 8)
{
Position = new Vector2(screenLeft + 8, Position.Y);
Velocity = new Vector2(0, Velocity.Y); // 停止水平移动
}
}# 在 player.gd 中添加边界限制
func _physics_process(delta: float) -> void:
# ... 正常的物理处理 ...
# 移动后,限制玩家不能走出屏幕左边
limit_player_position()
func limit_player_position() -> void:
var camera = get_node_or_null("Camera2D") as Camera2D
if camera == null:
return
# 计算屏幕左边界在世界坐标中的位置
var screen_left: float = camera.global_position.x - get_viewport_rect().size.x / 2
# 玩家不能走到屏幕左边以外
if position.x < screen_left + 8:
position = Vector2(screen_left + 8, position.y)
velocity = Vector2(0, velocity.y)摄像机特效
死亡时摄像机不跟随
当玛丽奥死亡时,摄像机应该停在原地,而不是跟着玛丽奥掉下去:
// 在 Player.cs 的 Die() 方法中
private void Die()
{
SetPhysicsProcess(false); // 停止物理处理
SetProcess(false); // 停止处理
// 播放死亡动画(先向上弹再落下)
var tween = CreateTween();
tween.TweenProperty(this, "position:y", Position.Y - 64, 0.3f);
tween.TweenProperty(this, "position:y", Position.Y + 600, 1.0f);
// 摄像机停止跟随
var camera = GetNode<Camera2D>("Camera2D");
camera.PositionSmoothingEnabled = false;
// 延迟后重新开始关卡
var timer = GetTree().CreateTimer(2.0);
timer.Timeout += GameManager.Instance.PlayerDied;
}# 在 player.gd 的 die() 方法中
func die() -> void:
set_physics_process(false)
set_process(false)
# 播放死亡动画
var tween = create_tween()
tween.tween_property(self, "position:y", position.y - 64, 0.3)
tween.tween_property(self, "position:y", position.y + 600, 1.0)
# 摄像机停止跟随
var camera = get_node_or_null("Camera2D") as Camera2D
if camera != null:
camera.position_smoothing_enabled = false
# 延迟后重新开始关卡
var timer = get_tree().create_timer(2.0)
timer.timeout.connect(GameManager.player_died)关卡通过时摄像机滑动到城堡
// 关卡通过后,摄像机缓缓移动到城堡
public void CameraPanToCastle(Vector2 castlePosition)
{
var camera = GetNode<Camera2D>("Camera2D");
camera.PositionSmoothingEnabled = true;
camera.PositionSmoothingSpeed = 3.0f;
// 创建一个临时节点让摄像机跟随
var cameraTarget = new Node2D { Position = castlePosition };
AddChild(cameraTarget);
// 重新设置摄像机父节点
RemoveChild(camera);
cameraTarget.AddChild(camera);
}# 关卡通过后,摄像机缓缓移动到城堡
func camera_pan_to_castle(castle_position: Vector2) -> void:
var camera = get_node_or_null("Camera2D") as Camera2D
if camera == null:
return
camera.position_smoothing_enabled = true
camera.position_smoothing_speed = 3.0
# 创建一个临时节点让摄像机跟随
var camera_target = Node2D.new()
camera_target.position = castle_position
add_child(camera_target)
remove_child(camera)
camera_target.add_child(camera)摄像机参数总结
| 参数 | 推荐值 | 说明 |
|---|---|---|
| Position Smoothing | 开启 | 让画面移动更平滑 |
| Smoothing Speed | 5 | 跟随速度(越大越快) |
| Limit Left | 动态更新 | 玩家到达的最左位置 |
| Limit Top | 0 | 不让看到天空以上 |
| Limit Bottom | 关卡高度 | 不让看到地面以下 |
| Limit Right | 关卡宽度 | 关卡终点 |
| Anchor Mode | 底部居中 | 玛丽奥站在屏幕下方 |
下一章预告
滚动摄像机完成了!画面会跟着玛丽奥移动,且不能往左滚动。下一章我们将实现游戏界面(HUD)——显示金币、时间、生命数等信息。
