Body not in control of camera (camera can swap bodies)

Godot Version

4.2.1

Question

I am seeking advice on camera control. Right now I have it setup that the camera moves on its own and its working very well, but I want the camera to potentially focus on a target and when I move the camera the target will also move.
I have tried using making the camera turn off its movement inputs while its focus on a body and that seems to work sometimes? but if I detach from the target or lose the target somehow then camera will move independently of the target and not reset. (I know this is coding stuff and all but my question isnt about coding)

What I would like to ask is what would be the proper way for the camera to move based on the target and can swap to a different target?

example code is here


func _ready():
	camera_target()
#set camera conditions
	var body=get_parent()
	if body!=null:
		target_body=true
	if target_body== true:
		lock_local_camera()
		var body_spawn=body
		var body_position=body_spawn.global_transform.origin
		player_camera.global_transform.origin=body_position #set camera start point
		spring_arm.global_transform.origin=body_position+Vector3(0,1,0) #set camera y to body
		spring_arm.spring_length=2
		gimble.rotation.x=-.20 #set camera x-rotation
		camera_zoom_direction=camera_zoom_start
		print("initial conditions set")
	else:
		print("Failed to find body")

It works as intended if the body has its own script but sometimes the target body doesnt have a script so it gets weird. I have tried making the camera give the body a script for when it doesnt have one but cant seem to get the script to function? It will work if I manually add it in on the node, but I am curious if its possible to force a script on something that the camera can then follow, or If I have to have everything the camera can potentially follow have the script on it ready to go?

func camera_target()->void:
	var body_target = get_parent()
	body_target.set_script(load("res://assets/player_body_script.gd"))

the code above doesnt seem to add the script I. So a little bit at a loss. If thiers any ideas on this I am all ears.
(I tried signals before this to some success but was running into the problem where the camera would continue to move when the body would get stuck on things, trying to make it so the camera switches to “follow” rather then be the thing in control). Any advice is appreciated.

I don’t think you gave us enough info related to the problem.

One suggestion, use preload over load if the resource is small and the file path is static.

Another suggestion is dynamically switching scripts imo is not a good practice. As you will lose state if not done correctly. (And you aren’t really handling state loss…)

The alternative for switching bodies is to have the camera not be part of the body as a child, and more like an observer that tracks bodies somewhere independent in the scene tree. You will need to setup a system to communicate to the camera, like an auto-load.

So how would the autoloader work in this way? Would it handle all the inputs as both the camera and the body communicate to the auto loader? my experience with cameras has always been to attach them as a child to something so using them this way is new.

extends Node3D

var target_body:CharacterBody3D=null

##camera dynamics
@export_range(0.1,10,.1) var camera_smoothing:float=1.0

func _ready():
	BodyCameraTarget.connect("set_camera_target", _camera_target)

func _process(delta):
	follow_target(delta)

func _camera_target(entity:CharacterBody3D)->void:
	print("i am working")
	print(entity)
	target_body=entity
	print(target_body)

func follow_target(_delta)->void:
	if target_body==null:
		return
	
	#position=lerp(position,target_body.position,camera_smoothing*delta)
	position=target_body.position #this is where it glues to the screen

I have the signal set up and did some prints and confirm that the signal is indeed working, but I cant seem to get the .connect to properly relay the signal to the func.
none of the prints give any information.

I think the signal isnt emitted by the time the _ready() is set up looking for it, I think that is my problem, how do I deal with timing mismatch issues like this? my default has been doing .call but I have been struggling to deal with signals on my own and figure might as well ask to see what others have for ideas

Cache the target in the autoload so it can be retrieved in the ready function, then use the signal for the changes as you have done. If target hasn’t been set yet just need to wait until it is set so you should probably have checks when using the body or have a dummy default so it won’t throw an error on a null value in the camera.

the autoload was an interesting attempt but didnt seem to work, same problem as other attempts. I tried setting it up so that it wont try to check for the signal until a call function is set from the singal receiver when it finishes (receiver->call->sender) and (sender->emit->receiver) even though the receiver is fully setup and ready the signal still is not listening to the .connect. I have plenty of other signals that work perfectly fine, I am wondering if im missing something?

Yeah you need the signal, but signals won’t queue. So your assumption about timing is right. my suggestion is to have a separate function on the autoload. Get_target. That you call once from the camera during ready.

We don’t have to go this route,

The other option is to use remove_child add_child. Passing the camera.

Another option is to have multiple cameras and just change which one is active. (I do this for a spectator camera in my game. And I have a seamless transition from a player camera to the spectator camera on death. Use autoloads to communicate the transition)

I got it working~


func follow_target(_delta)->void:
	if target_body==null:
		BodyCameraTarget.connect("set_camera_target", set_target)
		print("target not found")
		return
	
	#position=lerp(position,target_body.position,camera_smoothing*delta)
	position=target_body.position #this is where it glues to the screen

I just had it look for the signal in different place and it found it properly, the timing is a bit weird, I also think it was set up in a way that the signal was emitted after it did the .connect so If I have it looking for a signal if in “null” and return once it has a signal then that might fix my timing issues. but it is working now~ thank you for the help.