Nodes
April 17, 2020

The idea of the contents of the directory (keh_nodes) is to hold addons that are pre-made nodes with attached scripts. Those can be directly used by simply adding a scene instance wherever the functionality is desired.

For the moment there is a single node:

  • cam3d.gd: this is meant to be a "generic" 3D camera which contains a bunch of functionality.

Node: cam3d.gd

The main objective of this node is to provide a "camera arm" that can be attached into a different node (the target) and keep the camera at an specified distance. The available features are:

  • Collision response when there is something between the target object and the camera itself. The behavior can be defined through a property.
  • Configurable (through exported property) distance between the camera and the target object.
  • Ability to individually lock each of the 3 orientation components (yaw, pitch and roll).
  • Interpolate the target position so it can smoothly follow objects that are moving through interpolated positions.
  • Incorporate some lag into the camera's motion.
  • Camera shake using simplex noise in order to smooth its perturbation, which can be set to do only rotation, only translation or both.
  • When changing the arm length within the editor, the camera should update its distance from the target so, in a way, previewed when editing.
How to Use
How it Works

To start using the Cam3D all that is necessary is add a scene instance into the desired target object. Normally this will be attached into the player character. After that, the properties can be configured within the editor itself.

Cam3D Properties

In this how to use you will find explanation for those properties up to the Max Shake Offset, which is the last Cam3D actual property. The rest is actually exposing the original Camera properties so, if needed, you can change them.

Also, every single GDScript snippet will assume you have an instance of the Cam3D named cam3d. Maybe retrieved like this:

# some properties....
var cam3d: Cam3D

func _ready() -> void:
    # Some code...
    cam3d = $player_character/Cam3D

Arm Length

This should be an obvious property and it determines how far (or how close) the camera will be from the target object. This is in the Godot's 3D unit.

If you want to manipulate this value from GDScript, you can directly access the property arm_length.

# Get the arm length of the Cam3D
var length1: float = cam3d.arm_length
# Set the arm length of the Cam3D
cam3d.arm_length = 10.5

Lock Rotation

This property is meant to prevent the camera from changing its orientation with the target. So, if you only keep Roll enabled, the camera will change its pitch and its yaw when the target does so, however the camera roll will not change. Some example use cases:

  • 3rd person character: camera directly attached into the character's node. Both roll and pitch are locked. Yaw will be directly controlled by the direction the character is facing. The pitch then get its value manually changed by some script, as a result of the player input and roll never change.
  • "arpg style": camera is placed at the character with the pivot rotated around X. Then all 3 angles get locked. The camera will still follow the target but will never change its orientation with character, only if manually done through script.
  • Space Ship: camera is attached into the space ship node and none of the locks is enabled. In this way the orientation of the camera will directly follow the space ship.

To manually change those values through script, you have to keep in mind that the property itself, lock_rotation, is meant to be a bit mask, controlled by the flags FLR_ROLL, FLR_PITCH and FLR_YAW. To facilitate here and avoid the necessity of having to deal with bit masking operations, there are several functions meant to deal with this property:

  • set_lock_pitch(bool): this allow you to enable or disable the pitch locking.
  • set_lock_yaw(bool): this allow you to enable or disable the yaw locking.
  • set_locK_roll(bool): this allow you to enable or disable the roll locking.
  • is_pitch_locked(): returns true if the pitch is locked.
  • is_yaw_locked(): returns true if the yaw is locked.
  • is_roll_locked(): returns true if the roll is locked.

Interpolate Pivot/Orientation

The camera can be configured to interpolate its transform when following the target. This is most useful when the target's visual representation is actually smoothed by interpolation. While you can individually enable/disable rotation and translation, ideally both should be at the same state. The reason those are separated goes from the fact that there was one bug on a different addon (network) but I believed the culprit was caused by the interpolation method. Once the actual bug got fixed the camera's interpolation remained here.

To directly manipulate those settings from GDScript:

# Get the state of pivot interpolation
var is_interpolating_pivot: bool = cam3d.interpolate_pivot
# Get the state of orientation interpolation
var is_interpolating_orient: bool = cam3d.interpolate_orientation
# Enable interpolation on both translation and orientation
cam3d.interpolate_pivot = true
cam3d.interpolate_orientation = true

Camera Lag/Lag Speed

These two properties are directly connected, so dealing with them in a single topic.

The lag speed will be completely ignored if the lag is set to None. From the drop down menu you can change the lag mode to Smooth Stop or Smooth Start. In either case the camera will have a certain delay behind the target, controlled by the Lag Speed property. The lower this number, the more it will lag behind a moving target.

From GDScript you can change (or retrieve) those properties:

# Get current camera lag speed
var lag_speed: float = cam3d.lag_speed
# Set lag speed
cam3d.lag_speed = 0.01
# Get current lag mode
var lag_mode: int = cam3d.camera_lag
# Change to smooth start
cam3d.camera_lag = Cam3D.CameraLag.SmoothStart
# Or to smooth stop
cam3d.camera_lag = Cam3D.CameraLag.SmoothStop
# Or disable the camera lag
cam3d.camera_lag = Cam3D.CameraLag.None

Collision Mode

This property determines how the camera will behave when something is detected between the camera and the target object. Note that currently this detection is done by a single ray cast and depending on the geometry of the objects it may miss something. Nevertheless, the available methods are:

  • None: nothing will be done and objects will keep blocking the view.
  • ShrinkArm: the arm length will reduce its size so it's placed right in front of the blocking object. The length will be restored to its desired value when no object is blocking the path.
  • CullObstructing: from the first detected object until the camera, everything will be culled. This has the effect that some objects (ground for instance) will probably get partially culled.
  • HideObstructing: every detected object between the camera and the target will be hidden. Be careful with this mode in multiplayer games because hidden objects will not result in collisions for other objects.

Which method you will use largely depends on the project and the desired effect. Nevertheless, to deal with that from GDScript:

# Get current collision mode
var coll_mode: int = cam3d.collision_mode
# Change collision mode to none
cam3d.collision_mode = Cam3D.CollisionMode.None
# Or to Shrink Arm
cam3d.collision_mode = Cam3D.CollisionMode.ShrinkArm
# Or to cull obstructing
cam3d.collision_mode = Cam3D.CollisionMode.CullObstructing
# Or to hide obstructing
cam3d.collision_mode = Cam3D.CollisionMode.HideObstructing

Collision Layers

This property works exactly like the mask in the various collision shape objects. In other words, the enabled layers will be used to match objects. So, if you want to have some objects to be completely ignored by the collision checking, make sure those objects are part of a layer that is not enabled in this property.

In order to avoid having to deal with bit mask operations from code, two functions have been provided to deal with this aspect of the property, set_collision_layer() and is_collision_layer_enabled().

The set_collision_layer() allows one to enable or disable an specified collision layer index, matching the labels shown in the inspector. In other words, the index in here begins from 1 rather than 0.

Similarly, the is_collision_layer_enabled() will return true if the specified layer index (again, starting from 1 rather than 0) is enabled.

The following snippet performs some silly manipulations:

# Disable collision Layer 2
cam3d.set_collision_layer(false, 2)
# Check if collision layer 5 is enabled and, if not, enabled it
if (!cam3d.is_collision_layer_enabled(5)):
    cam3d.set_collision_layer(true, 5)

Shake Mode

This is another bit mask property, that can be used to individually enable/disable the possible traumas that can be applied to the camera when shaking it. Specifically, the shake can change the rotation, the orientation or both. As with the rotation lock, there are some functions to avoid having to directly deal with the masks:

  • set_shake_rotate(bool): enable or disable the rotation of the camera when shaking
  • set_shake_translate(bool): enable or disable the translation of the camera when shaking
  • is_shake_rotate_enabled(): returns true if the rotation of the camera when shaking is enabled
  • is_shake_translate_enabled(): returns true if the translation of the camera when shaking is enabled

Trauma Decay/Shake Frequency

These two properties determine how the shake will behave. The trauma decay indicate how fast it will stop shaking while the frequency how fast it will shake. Ok, things will probably get clearer with the explanation of how to use the shake system.

To make the camera shake you basically call the function add_trauma(amount). By calling this with a positive number the camera will then have some amount of trauma which will then cause it to shake during the update. At that point, trauma_decay will be removed from the camera's trauma and when it reaches 0, it stops shaking. But the shake_frequency determines how fast the camera will actually shake. So, one thing to keep in mind here is that the trauma is always kept in range [0 .. 1].

From GDScript, this snippet shows how to change those properties and then cause the camera to shake:

# Get current trauma decay
var current_decay: float = cam3d.trauma_decay
# Get current shake frequency
var current_frequency: float = cam3d.shake_frequency
# Change decay rate
cam3d.trauma_decay = 0.5
# Change frequency
cam3d.shake_frequency = 3.5
# Add maximum trauma
cam3d.add_trauma(1.0)

Max Shake Rotation/Max Shake Offset

Each of these two properties is a Vector3, giving more control over the shaking behavior. Each one specifies the maximum amount that each component can be changed while shaking. So, for example, if the MaxShakeOffset.x is set to 2, then the final X coordinate of the camera will be changed by something in between -2 and +2. The same applies to any other component.

Manipulating those from GDScript:

# Get current maximum shake rotation
var shake_rot: Vector3 = cam3d.max_shake_rotation
# Get current maximum shake translation
var shake_translation: Vector3 = cam3d.max_shake_offset
# Change the maximum rotation
cam3d.max_shake_rotation = Vector3(1.0, 1.5, 1.2)
# Change the maximum offset
cam3d.max_shake_offset = Vector3(2.0, 2.2, 2.5)

Obviously you can access each vector's component:

# Change just the maximum offset X when shaking
cam3d.max_shake_offset.x = 2.1