带弹簧臂的第三人称相机

前言

3D 游戏经常会用到第三人称相机,相机会跟随玩家角色、车辆等物体,还能够围绕目标旋转。

在 Godot 中将 Camera3D 设置为某个节点的子节点就可以实现这种相机。不过如果直接这样做、不进行额外处理,你就会发现相机会穿过几何体,导致场景隐藏。

这就需要用到弹簧臂节点 SpringArm3D 了。

弹簧臂是什么?

弹簧臂有两个主要组件,都会影响其行为。

弹簧臂的“长度”是指从弹簧臂的全局位置开始检测碰撞的距离:

../../_images/spring_arm_position_length.webp

弹簧臂的“形状”则是用于碰撞检测的形状。弹簧臂会用这个形状从它的原点一路“扫”到长度的位置。

../../_images/spring_arm_shape.webp

弹簧臂会尝试将所有子节点保持在其长度的末端。当形状与其他物体发生碰撞时,就会将子节点放置在碰撞点处或其附近:

../../_images/spring_arm_children.webp

带相机的弹簧臂

When a camera is placed as a child of a spring arm, a pyramid representing the camera will be used as the shape.

This pyramid represents the near plane of the camera:

../../_images/spring_arm_camera_shape.webp

备注

如果给弹簧臂指定了特定形状,那么该形状将**始终**被使用。

The camera's shape is only used if the camera is a direct child of the spring arm.

如果没有提供形状并且摄像机不是直接子节点,弹簧臂将回退使用射线检测,这对于摄像机碰撞来说不准确,因此不推荐使用。

每个物理处理帧中,弹簧臂都会执行一次运动投射,以检查是否有物体发生碰撞:

../../_images/spring_arm_camera_motion_cast.webp

当形状碰到物体时,摄像机将被放置在碰撞点或附近:

../../_images/spring_arm_camera_collision.webp

设置弹簧臂和相机

让我们在平台跳跃演示中添加弹簧臂和相机吧。

备注

你可以在 GitHub 上下载 Platformer 3D 演示,也可以使用资产库

In general, for a third-person camera setup, you will have three nodes as children of the node that you're following:

  • Node3D(相机的“轴心点”)

    • SpringArm3D

      • Camera3D

打开 player/player.tscn 场景。将这些节点设置为玩家节点的子节点,并为它们设置唯一的名称,以便我们可以在脚本中找到它们。请确保删除现有的摄像机节点!

../../_images/spring_arm_editor_setup.webp

Let's move the pivot point up by 2 on the Y-axis so that it's not on the ground:

../../_images/spring_arm_pivot_setup.webp

Give the spring arm a length of 3 so that it is placed behind the character:

../../_images/spring_arm_length_setup.webp

备注

将弹簧臂的 Shape 保持为 <空>。这样就会使用相机的视锥形状。

如果你愿意,也可以尝试其他形状——球体是一个常见的选择,因为它可以沿着边缘平滑滑动。

更新 player/player.gd 的顶部代码,通过其唯一名称获取相机和枢轴点:

player/player.gd
# Comment out this existing camera line.
# @onready var _camera := $Target/Camera3D as Camera3D

@onready var _camera := %Camera3D as Camera3D
@onready var _camera_pivot := %CameraPivot as Node3D

添加一个 _unhandled_input 函数来检测相机移动,然后相应地旋转轴心:

player/player.gd
@export_range(0.0, 1.0) var mouse_sensitivity = 0.01
@export var tilt_limit = deg_to_rad(75)


func _unhandled_input(event: InputEvent) -> void:
    # Mouselook implemented using `screen_relative` for resolution-independent sensitivity.
    if event is InputEventMouseMotion:
        _camera_pivot.rotation.x -= event.screen_relative.y * mouse_sensitivity
        # Prevent the camera from rotating too far up or down.
        _camera_pivot.rotation.x = clampf(_camera_pivot.rotation.x, -tilt_limit, tilt_limit)
        _camera_pivot.rotation.y += -event.screen_relative.x * mouse_sensitivity

弹簧臂会随轴心旋转,改变相机的位置。运行游戏,你会发现现在移动鼠标移动就可以使相机围绕角色旋转。如果相机向墙壁移动,就会与墙壁发生碰撞。