第10章 相机系统
10.1 Camera2D 基础
extends Camera2D
@onready var target: Node2D = get_parent() # 跟随目标
func _ready() -> void:
# 设置为当前相机
make_current()
# 方式1:使用内置平滑(推荐,简单)
# 在检查器中设置:
# Position Smoothing → Enabled ✅
# Position Smoothing → Speed = 8.0
# 然后在 _physics_process 中直接设置 global_position 即可
# Camera2D 会自动平滑插值
func _physics_process(delta: float) -> void:
# 跟随目标(配合 Position Smoothing 使用)
global_position = target.global_position
# 方式2:手动平滑(更灵活,可以添加前瞻/偏移)
# func _physics_process(delta: float) -> void:
# var target_pos := target.global_position
# global_position = global_position.lerp(target_pos, 10.0 * delta)
10.2 相机边界限制
# 限制相机不超出关卡边界
func set_camera_limits(level_rect: Rect2) -> void:
limit_left = int(level_rect.position.x)
limit_top = int(level_rect.position.y)
limit_right = int(level_rect.end.x)
limit_bottom = int(level_rect.end.y)
# 也可以从 TileMap 获取边界
# var used_rect := tilemap.get_used_rect()
# var cell_size := tilemap.tile_set.tile_size
# limit_left = int(used_rect.position.x * cell_size.x)
# ...
10.3 屏幕震动
extends Camera2D
@export var shake_decay: float = 5.0
var shake_intensity: float = 0.0
var shake_offset: Vector2 = Vector2.ZERO
func shake(intensity: float, duration: float = 0.2) -> void:
shake_intensity = intensity
var tween := create_tween()
tween.tween_property(self, "shake_intensity", 0.0, duration)
func _process(delta: float) -> void:
if shake_intensity > 0:
shake_offset = Vector2(
randf_range(-1, 1) * shake_intensity,
randf_range(-1, 1) * shake_intensity
)
else:
shake_offset = Vector2.ZERO
offset = shake_offset
# 使用
# 受伤时震动
$Camera2D.shake(10.0, 0.3)
# 爆炸时强烈震动
$Camera2D.shake(20.0, 0.5)
10.4 相机 Zoom 脉冲
# 打击感增强:攻击时短暂放大再恢复
func zoom_pulse(intensity: float = 1.1, duration: float = 0.1) -> void:
var tween := create_tween()
tween.tween_property(self, "zoom", Vector2.ONE * intensity, duration * 0.3)
tween.tween_property(self, "zoom", Vector2.ONE, duration * 0.7)
# 使用
func on_attack_hit() -> void:
$Camera2D.zoom_pulse(1.15, 0.15)
$Camera2D.shake(8.0, 0.1)
10.5 相机区域切换
# 进入房间时切换相机边界
extends Area2D
@export var camera_limits: Rect2
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
var camera: Camera2D = body.get_node("Camera2D")
var tween := create_tween()
tween.tween_property(camera, "limit_left", int(camera_limits.position.x), 0.5)
tween.parallel().tween_property(camera, "limit_top", int(camera_limits.position.y), 0.5)
tween.parallel().tween_property(camera, "limit_right", int(camera_limits.end.x), 0.5)
tween.parallel().tween_property(camera, "limit_bottom", int(camera_limits.end.y), 0.5)
10.6 小地图(SubViewport 方案)
# 场景结构:
# SubViewportContainer
# SubViewport
# MiniMapCamera (Camera2D) - zoom 很大,俯瞰全局
# MiniMapPlayerDot (ColorRect) - 玩家位置标记
# MiniMapEnemyDot (ColorRect) - 敌人位置标记
extends SubViewportContainer
@onready var camera: Camera2D = $SubViewport/MiniMapCamera
@onready var player_dot: Control = $SubViewport/MiniMapPlayerDot
func _process(delta: float) -> void:
# 相机跟随玩家
camera.global_position = player.global_position
# 更新玩家点位置
player_dot.position = Vector2(50, 50) # 小地图中心
实操练习
练习:智能相机
extends Camera2D
@export var target: Node2D
@export var look_ahead: float = 50.0 # 前瞻距离
@export var vertical_offset: float = -30.0 # 垂直偏移
func _process(delta: float) -> void:
var target_pos := target.global_position
# 前瞻:根据移动方向偏移
if target is CharacterBody2D:
var vel: Vector2 = target.velocity
target_pos.x += vel.x * look_ahead / 300.0
# 垂直偏移
target_pos.y += vertical_offset
# 平滑跟随
global_position = global_position.lerp(target_pos, 10.0 * delta)