Issues with global transformation for player movement with tween/interpolated camera change

Godot Version

4.6.2

Question

I’ve been looking through this forum to see if I could apply anyone else’s solutions to having it so that when the direction of the camera changes, left, right, up and down are applied correctly so that they move relative to where the camera is facing. I notice its a pretty common issue people get, so I apologize for making another post asking about this when it’s probably been done many times. I seem to have a unique problem however; I’m using tweens to change the orientation of the camera. I’m not sure if this is the best solution for the effect I’m trying to do, but its what I started with, and am unsure how to modify it. The tween moves the transform of the player which moves the camera_3d child.

One thing I should mention: Before making finally this post, I decided to try and use linear interpolation to remake the system I had with tweens and instead rotate the camera to a certain position, which did in some ways, work (I might have to check for the Area3D without signals now?) With my current system of organization, I am still unsure how to change the global direction though after the camera is rotated.

Anyways though, these are all the scripts that are used for movement and cameras:

class_name Player extends CharacterBody3D

@onready var input_component: InputComponent = %InputComponent
@onready var movement_component: MovementComponent = %MovementComponent
@onready var climbing: Climbing = %Climbing
@onready var camera_3d: Camera3D = $Camera3D

var tween: Tween

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta: float) -> void:
	input_component.update()
	
	movement_component.direction = input_component.move_dir
	movement_component.jump_counter = input_component.jump_pressed
	movement_component.tick(delta)

	climbing._climbing(delta)

	#camera_3d.transform = camera_3d.transform.interpolate_with($position2.transform, 1.0)

func _on_area_3d_body_entered(body: Node3D) -> void:
		#tween = create_tween()
		#tween.tween_property(self,"global_transform:basis", global_transform.basis.rotated(Vector3.UP, PI/2),0.5)
	pass
	
func _on_area_3d_2_body_entered(body: Node3D) -> void:
	tween = create_tween()
	tween.tween_property(self,"transform:basis", global_transform.basis.rotated(Vector3.UP, -PI/2),0.5)

I decided to keep some tags and examples of what I tried. The signal is the system I used before trying to use linear interpolation. The interpolation I tried applying to the signal is tagged in physics_process at the bottom.

class_name InputComponent extends Node

var jump_pressed : bool = false
var move_dir := Vector2.ZERO
@onready var camera_3d: Camera3D = $"../Camera3D"


func update() -> void:
	move_dir = Input.get_vector("up", "down", "right", "left")
	jump_pressed = Input.is_action_just_pressed("jump")

Currently when trying to do change the global direction of the player, I am aware I keep getting an error due to me trying to apply Vector3 to Vector2 ? I know what the error means, but I’m unsure how to improvise.

class_name MovementComponent extends Node

@onready var body : CharacterBody3D = get_parent()
@onready var input_component: InputComponent = %InputComponent
@onready var camera: Camera3D = $"../Camera3D"


#variables for movement and physics

const RUN_SPEED : float = 30.0
const SPEED : float = 20.0
@export var current_speed : float = SPEED
@export var jump_velocity : float = 12.0
@export var gravity_multiplier : float = 3.0
@onready var climb_ray: RayCast3D = $"../ClimbRay"

var direction: Vector2 = Vector2.ZERO
var jump_counter := false

func tick(delta: float) -> void:
	
	
	#Move
	body.velocity.x = direction.x*current_speed
	body.velocity.z = direction.y*current_speed

	#Gravity
	if not body.is_on_floor():
		body.velocity += body.get_gravity() * delta * gravity_multiplier
		
		#Jump
	if jump_counter and body.is_on_floor():
		body.velocity.y = jump_velocity
		jump_counter = false
	
	body.move_and_slide()

This is all.

A screen shot of what your game looks like while being played would help. My gut feelings is you are way overcomplicating this, but I’m not sure what you’re going for. A video of some existing game with the same or similar camera might also help.

In the mean time, take a look at my Camera3D Plugin which handles half a dozen third-person cameras and a first person camera. You can use it, or take the code from it.

Perhaps. I’ll look at your plugin as well. But to answer your question, this is what I’m aiming to achieve, but with a lerp in between the camera switches. Note that the movement does not work correctly once I go through the trigger area (the Area3D collision area)

You forgot to explain what needs to happen. You want camera to do… what?

1 Like

So I watched your video and re-read your initial post. There is currently a thread going on about Input Components and Movement Components. I recommend it to your attention.

Based on your video, why can’t you just attach it to the Player on a SpringArm3D and let it sit there following the Player? What effect do you hope to get by tweening or lerping the camera? Do you want it to lag behind the player?

Basically, for each of the 4 sides of the map, I want the camera to face the player as you transition/go between each side. I tried using a lerp (previously a tween) so that there was a gradual transition as you go to a different side, the video was (hopefully) meant to demonstrate the intended effect of facing the player depending on which side you’re on but without any transition.

In that case, try just tweening the location and rotation properties instead of the transform_basis.

Alright. I think part of my thought process with that was to account for the directions swapping due to the input direction not being relative to the camera. However at least for moving the camera itself that seems more rational. Thank you.

I looked at the post you sent, Ive realized maybe I’m not using the most efficient method; I stuck with composition initially to learn how to add onto a system like that, some parts necessity as Ive been struggling to figure out state machines, and some just for practice.

Yeah, your camera doesn’t need to update its position based on player input. Another way to do it without the tween, is to just do it in _physics_process.() Something like this…

First, create an Area2D that stores the direction you want the camera to face.

class_name CameraSwitch extends Area3D

@export camera_facing: Vector3 = Vector3.LEFT #Just giving it a default for illustrative purpose, probably shouldn't have one.

func _ready() -> void:
	body_entered.connect(_on_player_entered)

func _on_body_entered(player: Player)
	player.switch_camera.emit(camera_facing)

The player needs to have its own physics layer (I use 2) - in addition to colliding with the geometry (typically on 1 - the default). You put it on that layer, with no mask. Then these switches are on no layer and only have a mask on layer 2. So they only thing they can collide with is your Player. This is why the _on_body_entered() function is looking for a Player argument. It’s the only thing it should be able to detect - and if you accidentally put an Enemy on it, this will immediately throw an error and prevent hours of debugging weird “random” camera switching whenever that enemy runs into an one of these areas.

The camera facing allows you to rotate the camera and should be enough to help you move the position as well. The signal doesn’t exist yet, but we are about to make it. We could call a function on the Player to emit the signal, but in this case we have access to the Player and we might as well just trigger what we need. This keeps the Player from needing any camera code. It becomes the link between the trigger and the Camera3D.

You could take this a step further by putting a CollisionObject3D as a child of the Camera3D, putting it on a camera physics layer and removing the Player from the equation altogether. But I leave that as an exercise to you. (If you want to do that, I recommend you take this intermediate step first or you’ll be juggling too many balls.)

Now let’s change Player a bit by removing all references to the camera and adding our signal.

class_name Player extends CharacterBody3D

signal switch_camera(camera_facing: Vector3)

@onready var input_component: InputComponent = %InputComponent
@onready var movement_component: MovementComponent = %MovementComponent
@onready var climbing: Climbing = %Climbing


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _physics_process(delta: float) -> void:
	input_component.update()
	
	movement_component.direction = input_component.move_dir
	movement_component.jump_counter = input_component.jump_pressed
	movement_component.tick(delta)

	climbing._climbing(delta)

Then our Camera3D script:

class_name OrbitingCamera3D extends Camera3D

@extport var transition_speed = 5.0

var player: Player
var new_position: Vector3 = Vector3.ZERO


func _ready() -> void:
	player = get_parent()
	player.switch_camera.connect(_on_switch_camera)
	set_physics_process(false)


func _physics_process(delta: flaot) -> void:
	position = position.slerp(new_position, delta * transition_speed)

	if position.is_equal_approx(new_position):
		position = new_position
		set_physics_process(false)


func _on_switch_camera(camera_facing: Vector3)
	var distance: float = position.distance_to(player.position)
	new_position = player.global_position + camera_facing.normalized() * distance
	set_physics_process(true)

I haven’t tested this, but it’ll get you started. (Vector math is not my strong suit.) Alternately, you can use a tween here. But since your player will be moving, this might work better. (Not sure.)

Well I talk about state machines in that post, but if you’re having trouble with them, make another post and ask.

Did you mean to specify Area2D or? Apologies, mostly asking because I’m unsure how that’d work as its on a 2D plane, I tried to do this with an Area3D though. Your code seems to work, I am however struggling to figure out how to fill out the camera_facing variable. I tested it with your placeholder Vector3, and it did work, but of course the transforms were very off.

Typo. Fixed it.

The other thing you can do is figure out exactly what coordinates you want to apply, and have the Area3D objects pass those along. As a local transformation.

Post your scene structure.

I’m not sure if this is what you intended, This is the scene with the Area3Ds that are supposed to change the transformations of the player’s camera, which then in turn, I was hoping to accurately transform the directions of the player input as well, so that I can go left and right, forward and backward with WASD accurately after the camera switches angles.

This is useful as well but I meant a snapshot of your scene tree, particularly the relation of the player and the camera nodes.

Instead of parenting the camera directly to the player, use a pivot node between them.

Add the pivot Node3D child to the player, position in the player’s center (where you want camera rotation pivot to be) and parent the camera to that pivot. Now just rotate the pivot node 90 degrees around the vertical axis when player enters the area.

1 Like

I see. I’ve been trying to get the coordinates of a object with specified transformations. I did try to input exact coordinates, but I am unsure how to also apply the rotation with the position. When attempting to put in the right coordinate for a certain transformation, the camera ends up transforming to a coordinate very high up without rotating to the desired angle.

Do what normalized suggested. Also you’re over-thinking it. Transforms are almost NEVER the solution in Godot. There are way easier ways to do things.

Here’s what I suggest:

  1. Create an AnimationPlayer.
  2. Create two animations, one for moving the camera to the left. One for moving the camera to the right. with the rotations.
  C
  |
C-P-C
  1. Trigger the animations in your code.

Once you get the camera moving where it needs to go, either take the settings to make your tweens/whatever, or add enough animations that you can move from and to all the spots you want.

Great. So far it is working. I however still am having trouble with player input changing based on the camera angle.

Then it sounds like your player input is somehow tied to your camera and the way it faces. So decouple that.