mcpField
P
phantom-camera

phantom-camera

github.com/jame581开发889117 次安装4 天前更新
代码仓库

关于 phantom-camera

Phantom Camera 是面向 Godot 开发者的相机控制插件,用多个候选镜头描述跟随、构图和视角。真实相机上的 Host 会依优先级切换镜头,并支持基于资源的平滑过渡。

主要功能

  1. 01按优先级切换镜头
  2. 022D 与 3D 跟随模式
  3. 033D 注视目标模式
  4. 04资源驱动的镜头过渡
  5. 05Host 控制真实相机
  6. 06提供 C# 包装 API

适用场景

  1. 01在游戏区域切换跟随镜头
  2. 02为过场配置独立镜头
  3. 03让镜头跟随多个目标
  4. 04平滑过渡到高优先级镜头

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/jame581/godotprompter --skill phantom-camera

安装后在 Claude Code 中输入/phantom-camera确认已加载。

附带文件references/follow-modes.md

详细介绍

Phantom Camera

相关技能: camera-system 适用于手写相机模式,tween-animation 适用于这些过渡所基于的缓动概念。

插件: Phantom Camera · 版本 v0.11.0.2 · Godot 4.4+ · MIT · 源码:https://github.com/ramokz/phantom-camera · 节点使用 GDScript 编写,此外还提供官方 C# 封装 API(namespace PhantomCamera),以源代码 .cs 文件的形式随插件发布。1.0 之前: 次版本可能会破坏 API 兼容性。


1. 何时使用 Phantom Camera,何时使用 camera-system

方案最适合
手写(camera-system 技能)单个相机、简单跟随/抖动、不依赖插件
Phantom Camera需要基于优先级切换的多个相机“镜头”、死区/群组/路径/第三人称跟随逻辑,以及通过资源驱动的平滑补间在镜头间过渡——类似 Cinemachine 的工作流

当场景需要多种不同的相机行为(游戏跟随、过场画面、Boss 战群组镜头),并希望它们根据优先级自动切换,而不是用一个脚本处理所有情况时,可以使用 Phantom Camera。它取代的是 Camera2D/Camera3D 本身的位置控制逻辑——每个视口仍然只保留一个真正的 Camera2D/Camera3D;Phantom Camera 节点本身不会渲染任何内容。


2. 安装与设置

Asset Library(推荐): Godot AssetLib → 搜索“Phantom Camera” → 下载(只选择 phantom_camera 目录)→ 项目 → 项目设置 → 插件 → 启用插件。

GitHub 压缩包: 将 addons/phantom_camera/ 解压到项目根目录,然后以同样的方式启用插件。

启用插件会自动注册一个 PhantomCameraManager 自动加载单例,并且每次运行 _enable_plugin() 时都会重启编辑器(不只是第一次)——这是预期行为,并非 Bug。 无需手动设置自动加载。六种自定义节点类型会出现在“创建新节点”对话框中:PhantomCamera2D、PhantomCamera3D、PhantomCameraHost、PhantomCameraNoiseEmitter2D、 PhantomCameraNoiseEmitter3D 和 PhantomCameraTweenDirector。

C# 项目: 插件在 addons/phantom_camera/scripts/** 下以纯 .cs 源文件形式提供官方封装(namespace PhantomCamera)——无需添加 NuGet 包。启用 C# 的 Godot 项目(具有自己的生成 .csproj、Godot.NET.Sdk)会在插件目录存在后自动引入这些文件;接着只需使用 using PhantomCamera;(§3–§7)。


3. Host + 相机模型

两种节点协同工作:

  • PhantomCameraHost ——将其添加为真正的 Camera2D/Camera3D 的子节点(而不是反过来)。它会读取场景中优先级最高的 PhantomCamera2D/3D,并逐帧驱动真实相机的变换。每个相机只会使用其第一个 PhantomCameraHost 子节点。
  • PhantomCamera2D/PhantomCamera3D ——放置在场景树中的其他位置(与玩家同级、置于触发区域内或过场动画装置中)。可以存在任意多个;每个节点都通过 priority、跟随模式以及(仅限 3D)注视模式来描述一个候选“镜头”。它们本身不会渲染任何内容。
# Scene tree:
# Camera2D (or Camera3D)
#   └─ PhantomCameraHost
# Player (CharacterBody2D)
#   └─ PhantomCamera2D   (priority 0, follow_mode = SIMPLE, follow_target = Player)
# BossArena
#   └─ PhantomCamera2D   (priority 10, follow_mode = GROUP, follow_targets = [Player, Boss])
# CameraRig.gd — on the Camera2D/Camera3D
extends Camera2D

@onready var host: PhantomCameraHost = $PhantomCameraHost

func _ready() -> void:
    # host.camera_2d / host.camera_3d are populated automatically from get_parent()
    var active := host.get_active_pcam()
    print("Active PCam: ", active.name if active else "none")
// CameraRig.cs — on the Camera2D/Camera3D
using PhantomCamera;

public partial class CameraRig : Camera2D
{
    private PhantomCameraHost _host;

    public override void _Ready()
    {
        // Host.Camera2D / Host.Camera3D are populated automatically from GetParent()
        _host = GetNode<Node>("PhantomCameraHost").AsPhantomCameraHost();
        var active = _host.GetActivePhantomCamera();
        GD.Print("Active PCam: ", active is PhantomCamera2D p ? p.Node2D.Name.ToString() : "none");
    }
}

PhantomCameraHost.interpolation_mode(C#:InterpolationMode,枚举值 Auto/Idle/Physics/Manual)控制 Host 更新真实相机的时机: AUTO(默认值——根据活动 PCam 的目标选择 physics 或 idle)、IDLE、PHYSICS 或 MANUAL(每个 tick 都自行调用 host.process(delta))。

host_layers(渲染层标志:2D 设置在 Host 和 PhantomCamera2D 上,3D 设置在 PhantomCamera3D 上)用于筛选给定 Host 会考虑的 PCam——只有当 PCam 的 host_layers 位掩码与 Host 重叠时,该 PCam 才符合条件。


4. 基于优先级的切换

每个 PhantomCamera2D/3D 都有 priority: int = 0。附加到场景中实际摄像机上的 PhantomCameraHost 始终跟随优先级最高且与其共享 host_layers 位的 PCam。可在运行时通过 set_priority(value) 更改 priority,或通过 get_priority() 读取——取值会限制为 >= 0。

# TriggerArea.gd — raise priority while the player is inside, restore on exit
extends Area2D

@export var area_pcam: PhantomCamera2D

func _ready() -> void:
    area_entered.connect(_on_entered)
    area_exited.connect(_on_exited)

func _on_entered(area: Area2D) -> void:
    if area.get_parent() is CharacterBody2D:
        area_pcam.set_priority(20)

func _on_exited(area: Area2D) -> void:
    if area.get_parent() is CharacterBody2D:
        area_pcam.set_priority(0)
using PhantomCamera;

public partial class TriggerArea : Area2D
{
    [Export] private Node2D _areaPCamNode;
    private PhantomCamera2D _areaPCam;

    public override void _Ready()
    {
        _areaPCam = _areaPCamNode.AsPhantomCamera2D();
        AreaEntered += a => { if (a.GetParent() is CharacterBody2D) _areaPCam.Priority = 20; };
        AreaExited  += a => { if (a.GetParent() is CharacterBody2D) _areaPCam.Priority = 0; };
    }
}

每个 PCam 包装器上的实用事件:BecameActive、BecameInactive、TweenStarted、IsTweening(过渡期间每帧触发)、TweenInterrupted(更高优先级的 PCam 抢占了此补间——参数为发起中断的节点)、TweenCompleted——通过 += 订阅,名称与 GDScript 信号相同,采用 PascalCase。

priority_override: bool 是仅限编辑器的“强制预览”开关,可快速预览镜头,而无需触碰 priority;导出构建时会自动禁用——不要将它用于游戏逻辑。


5. 跟随模式

PhantomCamera2D/PhantomCamera3D 通过 FollowMode 枚举跟随目标:GLUED、SIMPLE、GROUP、PATH、FRAMED,以及 THIRD_PERSON(仅限 3D)。参考文档涵盖每种模式的属性、GDScript 和 C# 设置、GROUP 自动重新构图,以及跟随选项 follow_axis_lock、rotate_with_target 和 lookahead(后两者仅限 2D),还包括 is_following() 和 teleport_position()。

有关模式表、设置示例和通用跟随选项,请参阅 references/follow-modes.md。


6. 注视模式(仅限 3D)

PhantomCamera2D 没有注视系统——只有 rotate_with_target(§5)。PhantomCamera3D 还提供:

enum LookAtMode { NONE = 0, MIMIC = 1, SIMPLE = 2, GROUP = 3 }
模式行为
MIMIC直接复制目标的旋转。
SIMPLE直接注视 look_at_target(单个 Node3D)。
GROUP注视 look_at_targets: Array[Node3D] 的中心点。
extends PhantomCamera3D

func _ready() -> void:
    look_at_mode = LookAtMode.SIMPLE
    look_at_target = get_node("../Boss")
    look_at_damping = true
    look_at_damping_value = 0.25  # single scalar, not per-axis
    up_target = get_node("../GroundNormalMarker")  # overrides `up` continuously
using PhantomCamera;

public partial class BossLookAtSetup : Node
{
    [Export] private Node3D _pCamNode; // has a PhantomCamera3D node/script attached
    [Export] private Node3D _boss;
    [Export] private Node3D _groundNormalMarker;

    public override void _Ready()
    {
        // LookAtMode has no wrapper setter (getter-only) — set it on the underlying node.
        _pCamNode.Set("look_at_mode", (int)LookAtMode.Simple);

        var pCam = _pCamNode.AsPhantomCamera3D();
        pCam.LookAtTarget = _boss;
        pCam.LookAtDamping = true;
        pCam.LookAtDampingValue = 0.25f; // single scalar, not per-axis
        pCam.UpTarget = _groundNormalMarker;
    }
}

注意事项(来自插件自身的运行时警告): 在同一个 PhantomCamera3D 上同时使用非 NONE 的 follow_mode 和非 NONE 的 look_at_mode,会打印“在同一个 PCam3D 上同时使用 Look At 和 Follow Mode 尚未经过充分测试,请谨慎使用!”——请将这种组合视为实验性功能,并在发布前手动验证。


7. 摄像机之间的补间

每个 PCam 都拥有一个 tween_resource: PhantomCameraTween(一种 Resource——可在多个 PCam 之间共享同一个 .tres 以复用时序设置,也可让每个 PCam 使用各自的默认实例):

enum TransitionType {
    LINEAR = 0, SINE = 1, QUINT = 2, QUART = 3, QUAD = 4, EXPO = 5,
    ELASTIC = 6, CUBIC = 7, CIRC = 8, BOUNCE = 9, BACK = 10,
}
enum EaseType { EASE_IN = 0, EASE_OUT = 1, EASE_IN_OUT = 2, EASE_OUT_IN = 3 }

@export var duration: float = 1.0
@export var transition: TransitionType = TransitionType.LINEAR
@export var ease: EaseType = EaseType.EASE_IN_OUT

这些值与 Godot 内置的 Tween.TransitionType / Tween.EaseType 名称一一对应(去掉 TRANS_/EASE_ 前缀)——有关每条曲线的效果,请参阅 tween-animation。

# Cutscene PCam: slow, elastic-eased transition when it takes priority
extends PhantomCamera3D

func _ready() -> void:
    tween_resource = PhantomCameraTween.new()
    tween_duration = 1.5          # passthrough — writes tween_resource.duration
    # TransitionType/EaseType live on PhantomCameraTween — qualify them:
    tween_transition = PhantomCameraTween.TransitionType.ELASTIC
    tween_ease = PhantomCameraTween.EaseType.EASE_OUT
using PhantomCamera;

public partial class CutsceneCamSetup : Node
{
    [Export] private Node3D _pCamNode; // has a PhantomCamera3D node/script attached

    public override void _Ready()
    {
        var pCam = _pCamNode.AsPhantomCamera3D();
        pCam.TweenResource = PhantomCameraTween.New();
        pCam.TweenDuration = 1.5f;   // passthrough — writes TweenResource.Duration
        pCam.TweenTransition = TransitionType.Elastic;
        pCam.TweenEase = EaseType.EaseOut;
    }
}

tween_on_load: bool = true——如果此 PCam 在运行时实例化时已是优先级最高的 PCam,则会在加载时将摄像机补间到位;设为 false 则立即切换。如果 tween_resource 为 null,get_tween_duration() 会返回 0.0(立即切换)。


实现检查清单

  • 每个真实的 Camera2D/Camera3D 恰好有一个 PhantomCameraHost,并将其作为子节点添加
  • 每个 PhantomCamera2D/3D 都与应跟踪它的 Host 共享一个 host_layers 位
  • 更改优先级时使用 set_priority() 或 priority 属性(两者都会调用 setter,由它通知 Host)
  • 在依赖 is_following() 之前,先赋值 follow_target / follow_targets
  • GROUP 跟随模式使用 follow_targets(数组),而不是 follow_target(单个节点)
  • 3D 的 THIRD_PERSON 旋转 setter(set_third_person_rotation、set_third_person_rotation_degrees、set_third_person_quaternion)会检查 follow_mode == THIRD_PERSON;否则会打印错误并且不执行任何操作——set_follow_distance/set_spring_length/set_collision_mask(_value)/set_shape 没有此类检查,也不会打印任何内容
  • 手动测试同一个 PhantomCamera3D 上组合使用 follow_mode 和 look_at_mode 的情况(插件标记为未测试)
  • 如果多个 PCam 需要相同的过渡效果,有意共享 tween_resource(同一个 .tres)
  • C# 通过 AsPhantomCamera2D()/AsPhantomCamera3D()/AsPhantomCameraHost()/AsPhantomCameraTween() 获取包装器——它们是包装节点的普通类,不是 Node 子类,因此绝不要写 class MyCam : PhantomCamera2D
  • C# 枚举名称:只有 FollowMode/FollowLockAxis 带有维度后缀(FollowMode2D、FollowLockAxis3D);LookAtMode、TransitionType、EaseType、InterpolationMode 均不带后缀
  • 包装器中的 FollowMode/LookAtMode 只能读取——通过底层节点上的 .Set("follow_mode", (int)FollowMode2D.Simple) 设置它们
  • 在 plugin.cfg/版本控制中锁定插件版本——1.0 之前的小版本升级可能会导致 API 不兼容

评论

支持 Markdown