home / skills / hhhh124hhhh / godot-mcp / godot-camera-system

This skill configures and optimizes Godot 2D camera systems for multi-character games, delivering smooth follow, dynamic framing, and adaptive view controls.

npx playbooks add skill hhhh124hhhh/godot-mcp --skill godot-camera-system

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
14.6 KB
---
name: godot-camera-system
description: Godot 2D相机系统专家,专门处理多角色相机管理、视野控制、平滑跟随和动态视角系统
---

# Godot 2D相机系统专家技能

> **重要提示**: 本技能专门解决Godot 2D游戏中的相机配置和角色显示问题,基于 **Godot 4.5** 最佳实践。
> 特别适用于格斗游戏、平台游戏、多人游戏等需要复杂相机管理的场景。

## 技能概述

`godot-camera-system` 是专门用于Godot 2D游戏相机系统配置和优化的智能化技能,能够根据用户需求自动实现多角色相机管理、视野控制、平滑跟随、动态视角切换等完整的相机系统解决方案。

## 核心功能

### 📷 多角色相机管理
- **智能跟随**: 自动计算多个角色的最佳视野
- **焦点切换**: 动态切换相机焦点目标
- **边界适应**: 智能调整相机边界以适应所有角色
- **群组管理**: 支持角色群组的统一相机控制

### 🎬 平滑跟随系统
- **插值算法**: 使用lerp实现平滑的相机移动
- **速度控制**: 可配置的跟随速度和响应性
- **预测跟随**: 基于角色速度的前瞻性相机调整
- **弹性效果**: 自然的弹性跟随效果

### 🖼️ 视野控制
- **缩放管理**: 动态调整相机缩放以适应场景
- **边界限制**: 设置相机移动范围防止越界
- **视口适配**: 自动适配不同屏幕分辨率
- **安全区域**: 确保重要内容始终在视野内

### 🎯 动态视角系统
- **状态驱动**: 基于游戏状态的相机行为切换
- **动画集成**: 与角色动画系统深度集成
- **特效支持**: 震动、缩放、旋转等相机特效
- **场景切换**: 流畅的场景间相机过渡

## 使用方法

### 格斗游戏相机系统
```
用户: "我需要为格斗游戏配置双人相机系统"
系统: 自动实现:
- 双角色中点跟随算法
- 动态视野调整
- 攻击时的镜头特效
- 角色分离时的智能处理
```

### 平台游戏相机
```
用户: "为平台跳跃游戏创建跟随相机"
系统: 智能生成:
- 垂直跟随系统
- 平台边界限制
- 跳跃时的预测跟随
- 掉落检测和救援机制
```

### 多人游戏相机
```
用户: "为4人合作游戏配置相机系统"
系统: 全面开发:
- 4人位置的最优视野计算
- 动态缩放以包含所有玩家
- 玩家分散时的处理策略
- 领导者跟随模式
```

## 相机系统实现

### 🎮 格斗游戏相机管理器
```gdscript
# 格斗游戏专用相机系统
class_name FightingGameCameraSystem extends Node
@export var camera: Camera2D
@export var player1: Node2D
@export var player2: Node2D

# 相机参数
@export var follow_speed: float = 5.0
@export var base_zoom: float = 1.0
@export var max_distance: float = 400.0
@export var min_zoom: float = 0.8
@export var max_zoom: float = 1.5

# 动画特效参数
@export var shake_intensity: float = 5.0
@export var shake_duration: float = 0.3

func _ready():
    _setup_camera()
    _connect_player_signals()

func _setup_camera():
    if not camera:
        camera = Camera2D.new()
        add_child(camera)

    # 配置基础属性
    camera.position_smoothing_enabled = true
    camera.position_smoothing_speed = follow_speed

    # 设置边界限制
    camera.limit_left = -1024
    camera.limit_right = 1024
    camera.limit_top = -600
    camera.limit_bottom = 600

func _process(delta):
    if player1 and player2:
        _update_camera_follow(delta)

func _update_camera_follow(delta: float) -> void:
    # 计算两个角色的中点
    var center_point = (player1.global_position + player2.global_position) * 0.5
    var distance = player1.global_position.distance_to(player2.global_position)

    # 动态调整缩放
    var target_zoom = _calculate_dynamic_zoom(distance)
    camera.zoom = camera.zoom.lerp(Vector2(target_zoom, target_zoom), follow_speed * delta * 0.5)

    # 平滑跟随中点
    var target_position = center_point + Vector2(0, -100)  # 向上偏移
    camera.global_position = camera.global_position.lerp(target_position, follow_speed * delta)

func _calculate_dynamic_zoom(distance: float) -> float:
    # 根据角色距离动态调整缩放
    var normalized_distance = clamp(distance / max_distance, 0.0, 1.0)
    return lerp(min_zoom, max_zoom, normalized_distance)

# 特效系统
func play_hit_effect(hit_position: Vector2):
    # 攻击命中时的相机震动
    _start_camera_shake(shake_intensity, shake_duration)

func _start_camera_shake(intensity: float, duration: float):
    var tween = create_tween()
    var original_position = camera.offset

    # 震动效果
    for i in range(5):
        var random_offset = Vector2(
            randf_range(-intensity, intensity),
            randf_range(-intensity, intensity)
        )
        tween.tween_property(camera, "offset", random_offset, duration * 0.1)
        tween.tween_property(camera, "offset", original_position, duration * 0.1)
```

### 🏃 平台游戏相机跟随
```gdscript
# 平台游戏相机系统
class_name PlatformerCameraSystem extends Node
@export var camera: Camera2D
@export var target: CharacterBody2D
@export var follow_ahead_distance: float = 100.0

# 跟随参数
@export var vertical_follow_speed: float = 8.0
@export var horizontal_follow_speed: float = 5.0
@export var look_ahead_strength: float = 0.1

# 边界参数
@export var level_bounds: Rect2 = Rect2(-1000, -1000, 2000, 2000)

var _look_ahead_position: Vector2
var _target_bounds: Rect2

func _ready():
    _setup_camera()
    _target_bounds = level_bounds

func _setup_camera():
    if not camera:
        camera = Camera2D.new()
        add_child(camera)

    camera.position_smoothing_enabled = true

func _process(delta):
    if target:
        _update_platformer_camera(delta)

func _update_platformer_camera(delta: float) -> void:
    var target_position = target.global_position

    # 水平方向的前瞻跟随
    var look_ahead = Vector2.ZERO
    if target.velocity.x > 0:  # 向右移动
        look_ahead.x = follow_ahead_distance
    elif target.velocity.x < 0:  # 向左移动
        look_ahead.x = -follow_ahead_distance

    _look_ahead_position = _look_ahead_position.lerp(look_ahead, look_ahead_strength)
    target_position += _look_ahead_position

    # 限制在关卡边界内
    target_position = _clamp_to_bounds(target_position)

    # 平滑跟随
    camera.global_position = camera.global_position.lerp(target_position, horizontal_follow_speed * delta)

func _clamp_to_bounds(position: Vector2) -> Vector2:
    var viewport_size = get_viewport().get_visible_rect().size / camera.zoom
    var half_viewport = viewport_size / 2

    # 限制相机位置,确保视口不超出关卡边界
    position.x = clamp(position.x, _target_bounds.position.x + half_viewport.x,
                      _target_bounds.position.x + _target_bounds.size.x - half_viewport.x)
    position.y = clamp(position.y, _target_bounds.position.y + half_viewport.y,
                      _target_bounds.position.y + _target_bounds.size.y - half_viewport.y)

    return position

# 处理角色掉落
func handle_fall_off_level():
    # 角色掉出关卡边界时的救援处理
    if target.global_position.y > _target_bounds.position.y + _target_bounds.size.y + 200:
        # 重置角色到安全位置
        target.global_position = _find_safe_spawn_point()
        _play_fall_recovery_effect()

func _find_safe_spawn_point() -> Vector2:
    # 寻找安全的重生点
    return Vector2(_target_bounds.position.x + _target_bounds.size.x / 2,
                   _target_bounds.position.y)
```

### 👥 多人游戏相机系统
```gdscript
# 多人游戏相机管理器
class_name MultiplayerCameraSystem extends Node
@export var camera: Camera2D
@export var players: Array[Node2D] = []

# 相机参数
@export var padding: float = 50.0
@export var min_zoom: float = 0.5
@export var max_zoom: float = 2.0
@export var follow_speed: float = 3.0

enum CameraMode {
    ALL_PLAYERS,    # 显示所有玩家
    LEADER_FOLLOW,  # 跟随领导者
    SPLIT_SCREEN    # 分屏模式
}

var current_mode: CameraMode = CameraMode.ALL_PLAYERS
var leader_index: int = 0

func _ready():
    _setup_camera()

func _process(delta):
    match current_mode:
        CameraMode.ALL_PLAYERS:
            _update_all_players_camera(delta)
        CameraMode.LEADER_FOLLOW:
            _update_leader_follow_camera(delta)
        CameraMode.SPLIT_SCREEN:
            _update_split_screen_camera(delta)

func _update_all_players_camera(delta: float) -> void:
    if players.size() == 0:
        return

    # 计算所有玩家的边界
    var bounds = _calculate_players_bounds()

    # 计算需要的缩放
    var viewport_size = get_viewport().get_visible_rect().size
    var required_zoom = _calculate_zoom_for_bounds(bounds, viewport_size)
    required_zoom = clamp(required_zoom, min_zoom, max_zoom)

    # 更新相机
    camera.zoom = camera.zoom.lerp(Vector2(required_zoom, required_zoom), follow_speed * delta * 0.5)

    var center = bounds.get_center()
    camera.global_position = camera.global_position.lerp(center, follow_speed * delta)

func _calculate_players_bounds() -> Rect2:
    if players.size() == 0:
        return Rect2.ZERO

    var min_pos = players[0].global_position
    var max_pos = players[0].global_position

    for player in players:
        min_pos.x = min(min_pos.x, player.global_position.x)
        min_pos.y = min(min_pos.y, player.global_position.y)
        max_pos.x = max(max_pos.x, player.global_position.x)
        max_pos.y = max(max_pos.y, player.global_position.y)

    # 添加填充
    min_pos -= Vector2(padding, padding)
    max_pos += Vector2(padding, padding)

    return Rect2(min_pos, max_pos - min_pos)

func _calculate_zoom_for_bounds(bounds: Rect2, viewport_size: Vector2) -> float:
    var zoom_x = viewport_size.x / bounds.size.x
    var zoom_y = viewport_size.y / bounds.size.y
    return min(zoom_x, zoom_y)

# 切换相机模式
func set_camera_mode(mode: CameraMode):
    current_mode = mode
    print("相机模式切换到: ", CameraMode.keys()[mode])
```

## 相机特效系统

### 📳 震动效果
```gdscript
# 相机震动系统
class_name CameraShakeSystem extends Node
@export var camera: Camera2D
@export var default_shake_intensity: float = 5.0
@export var default_shake_duration: float = 0.3

var _shake_tween: Tween
var _original_offset: Vector2

func _ready():
    _original_offset = camera.offset

func play_shake(intensity: float = -1, duration: float = -1):
    if intensity < 0:
        intensity = default_shake_intensity
    if duration < 0:
        duration = default_shake_duration

    # 停止之前的震动
    if _shake_tween:
        _shake_tween.kill()

    _shake_tween = create_tween()
    _shake_tween.set_loops(5)  # 震动5次

    for i in range(5):
        var random_offset = Vector2(
            randf_range(-intensity, intensity),
            randf_range(-intensity, intensity)
        )
        _shake_tween.tween_property(camera, "offset", random_offset, duration * 0.1)
        _shake_tween.tween_property(camera, "offset", _original_offset, duration * 0.1)

    _shake_tween.finished.connect(_on_shake_finished)

func _on_shake_finished():
    camera.offset = _original_offset
```

### 🔍 缩放特效
```gdscript
# 相机缩放特效
class_name CameraZoomEffect extends Node
@export var camera: Camera2D

func play_zoom_in(target_zoom: float, duration: float = 0.5):
    var tween = create_tween()
    tween.tween_property(camera, "zoom", Vector2(target_zoom, target_zoom), duration)

func play_zoom_out(target_zoom: float, duration: float = 0.5):
    var tween = create_tween()
    tween.tween_property(camera, "zoom", Vector2(target_zoom, target_zoom), duration)

func play_zoom_pulse(intensity: float = 1.2, duration: float = 0.3):
    var original_zoom = camera.zoom.x
    var tween = create_tween()
    tween.tween_property(camera, "zoom", Vector2(intensity, intensity), duration * 0.5)
    tween.tween_property(camera, "zoom", Vector2(original_zoom, original_zoom), duration * 0.5)
```

## 故障排除

### 常见相机问题

#### 问题1: 角色不在相机视野内
```gdscript
# 诊断和修复
func fix_character_not_visible(camera: Camera2D, character: Node2D):
    print("诊断角色可见性问题...")

    # 计算角色在相机坐标系中的位置
    var camera_space_pos = camera.to_local(character.global_position)
    var viewport_size = get_viewport().get_visible_rect().size

    print("角色相机坐标: ", camera_space_pos)
    print("视口大小: ", viewport_size)

    # 如果角色不在视野内,调整相机位置
    if abs(camera_space_pos.x) > viewport_size.x / 2 or abs(camera_space_pos.y) > viewport_size.y / 2:
        print("角色不在视野内,调整相机位置...")
        camera.global_position = character.global_position
```

#### 问题2: 相机跟随不流畅
```gdscript
# 优化相机跟随
func optimize_camera_following(camera: Camera2D, follow_speed: float):
    # 调整平滑参数
    camera.position_smoothing_enabled = true

    # 根据帧率动态调整速度
    var fps = Engine.get_frames_per_second()
    var adjusted_speed = follow_speed * (60.0 / max(fps, 30.0))

    camera.position_smoothing_speed = adjusted_speed
    print("相机跟随速度调整为: ", adjusted_speed)
```

#### 问题3: 多人游戏相机缩放异常
```gdscript
# 修复多人相机缩放
func fix_multiplayer_zoom(camera: Camera2D, players: Array[Node2D]):
    if players.size() < 2:
        camera.zoom = Vector2.ONE
        return

    # 计算合理的缩放范围
    var max_distance = 0.0
    for i in range(players.size()):
        for j in range(i + 1, players.size()):
            var distance = players[i].global_position.distance_to(players[j].global_position)
            max_distance = max(max_distance, distance)

    # 限制最大距离,防止过度缩放
    max_distance = min(max_distance, 800.0)

    var target_zoom = 200.0 / max(max_distance, 200.0)
    target_zoom = clamp(target_zoom, 0.5, 1.5)

    camera.zoom = Vector2(target_zoom, target_zoom)
    print("多人相机缩放设置为: ", target_zoom)
```

## 智能特性

### 自动相机配置
- **场景分析**: 自动分析场景大小和角色位置
- **参数优化**: 根据游戏类型优化相机参数
- **性能监控**: 实时监控相机系统性能

### 自适应系统
- **帧率适应**: 根据性能动态调整跟随质量
- **分辨率适配**: 自动适配不同屏幕分辨率
- **设备优化**: 针对不同设备优化相机行为

### 调试工具
- **可视化调试**: 显示相机视野和跟随范围
- **性能分析**: 监控相机系统性能指标
- **参数调节**: 实时调节相机参数

---

**技能状态**: ✅ 可用
**最后更新**: 2025-11-09
**兼容性**: Godot 4.5+
**专长**: 2D相机系统、多角色跟随、视野控制
**依赖**: Godot MCP 工具集 + 相机系统知识

Overview

This skill is an expert assistant for designing and implementing 2D camera systems in Godot (optimized for Godot 4.5). It automates multi-character camera management, smooth follow behavior, dynamic zooming, and camera effects so you can deliver polished camera behavior for fighting games, platformers, and co-op titles. The goal is practical, ready-to-use patterns and troubleshooting tips to get cameras stable and responsive across devices.

How this skill works

It inspects scene layout, player positions, velocities, and game state to generate or recommend camera logic: midpoint and group bounds calculations, dynamic zoom based on player spread, look-ahead and velocity-predictive follow, and state-driven effect triggers like shake or zoom pulses. It also proposes boundary clamping, viewport-adaptive zoom, and performance-aware smoothing parameters to keep motion stable under varying frame rates.

When to use it

  • Creating a two-player fighting game camera that keeps both fighters visible with dynamic zoom and hit effects.
  • Building a platformer camera with look-ahead, vertical bias, and level-bound clamping for tight platforming feel.
  • Implementing a co-op camera that adapts zoom to include 3–4 players and switches to leader-follow or split-screen when needed.
  • Adding camera feedback like shake, pulse, or scene-driven transitions for combat or cinematic moments.
  • Diagnosing visibility, jitter, or zoom issues across different screen sizes and performance profiles.

Best practices

  • Compute camera target from player bounds or midpoint, then lerp the camera position and zoom for smooth motion.
  • Clamp camera position by calculating half-viewport in world space to avoid showing outside level bounds.
  • Adjust follow speed relative to frame rate (or use delta) to keep responsiveness consistent on low-end devices.
  • Use padded bounds and min/max zoom limits to prevent over-zooming when players are spread out.
  • Attach effects (shake, zoom pulse) as separate systems so core follow logic remains simple and testable.

Example use cases

  • Fighting game: midpoint follow, distance-based zoom, upward offset for HUD, and hit-triggered camera shake.
  • Platformer: asymmetric follow with horizontal look-ahead, different X/Y smoothing, and fall-recovery respawn centering.
  • 4-player local co-op: compute bounding rect of players, dynamic zoom to fit viewport, switch to split-screen when threshold exceeded.
  • Cinematic transitions: state-driven camera animations and zoom tweens for cutscenes or special abilities.
  • Debugging: visualize camera bounds and viewport extents to diagnose invisible characters or jitter.

FAQ

How do I prevent the camera from showing outside the level?

Clamp camera position using level bounds and half-viewport size in world units (viewport_size / zoom / 2) so edges never pass outside the defined Rect2.

Why does follow feel jittery at low FPS?

Make smoothing frame-rate aware: scale follow speed by delta or adjust position_smoothing_speed using current FPS to keep perceived responsiveness stable.