Godot Version
Godot 4.1.1
Question
I need help figuring out a solution to the following problem:
The Setup:
I have a Character that is a CharacterBody3D with a Sprite3D node attached to it, as well as an AnimationPlayer node.
The AnimationPlayer node has animations for 8 directions - (sprite facing forward, facing backwards, facing left, facing forward-left, etc.)
In the Level scene, I have a camera that attaches itself to the character and can rotate around it along Y axis.
I am using the camera.global_transform.basis for calculating the forward vector of movement, so that when the player presses “move_up”, the character moves in the same direction the camera is looking.
Inside the Character script, I also have a a couple of functions that update the character sprite’s animation based on movement direction relative to camera, so that when the player moves, for example, to the left of where the camera is looking, the AnimationPlayer plays frames associated with the Sprite3D looking to the right. When the character moves towards the camera, the Sprite looks into the camera, etc.
And there’s also another function that handles directional animations of Sprite3D based on where the camera is looking. It uses the dot product between the character’s basis and camera basis to update the animation so when, for example, the camera rotates to the left, the sprite, instead of rotating itself with the camera, updates its animation with different directional animation to simulate the change of perspective.
The Problem:
The last two functions work separately from each other. When the character moves in any direction, the animation updates according to the direction, making the sprite show its back to the camera no matter what direction the camera is facing, but should the character stop, it will return to animation that is based on the dot_product calculation.
For the past couple of days, I have been trying to figure out how to make the animation update for both of these cases, so that when the player stops moving, the sprite’s facing direction remains unchanged and the camera rotation updates the directional animation based on the sprite’s front that is relative to the camera.
I’ve tried a bunch of different methods, but most of them, were, admittedly, shots in the dark that have failed.
I realize my explanation of the problem might be a bit all over the place, so here’s a video to demonstrate the problem to make it clearer:
And here’s a code of the function that is responsible for calculating the camera’s spatial relation to the sprite:
func update_anim_to_camera():
func update_anim_to_camera():
var camera_fwd = main_camera.global_transform.basis.z
print("camera_fwd = ", camera_fwd)
var char_forward = global_transform.basis.z #Vector3(input_dir.x,0,1)
print("char_fwd = ", char_forward)
var char_left = global_transform.basis.x #Vector3(0,0,input_dir.y)
print("char_left = ", char_left)
var l_dot = char_left.dot(camera_fwd)
print("l_dot = ", l_dot)
var f_dot = char_forward.dot(camera_fwd)
print("f_dot = ", f_dot)
if f_dot < -0.65:
sprite_facing = "front"
elif f_dot > 0.65:
sprite_facing = "back"
else:
if abs(f_dot) < 0.3 and l_dot > 0:
sprite_facing = "right" # left sprite
elif abs(f_dot) < 0.3 and l_dot < 0:
sprite_facing = "left" # left sprite
elif f_dot < 0 and l_dot > 0:
sprite_facing = "frontright" # forward left sprite
elif f_dot < 0 and l_dot < 0:
sprite_facing = "frontleft" # forward left sprite
else:
if l_dot < 0:
sprite_facing = "backleft" # back left sprite
else:
sprite_facing = "backright"
Everything else in the code works as intended… I think. The problem is specifically with this function. I pretty much copy-pasted it from a youtube tutorial and I admit I don’t fully understand the calculations happening here, so I am not sure what I need to do and how to do it to achieve the desired behavior.
But just in case, here’s the full code I have inside the CharacterBody3D for context:
CharacterBody3D Code
extends CharacterBody3D
var main_camera = null
var front = Vector3 (0,0,1)
@export var move_speed := 200.0
var input_dir = Vector2(0,0)
@onready var anim_player = $AnimationPlayer
var sprite_facing := "back"
var anim_rows : Dictionary = {
"right":0,
"backright":1,
"back":2,
"backleft":3,
"left":4,
"frontleft":5,
"front":6,
"frontright":7
}
func _ready():
anim_player.play("idle")
func set_camera(camera):
main_camera = camera
func _physics_process(delta):
velocity.x = get_movement_direction().x * move_speed * delta
velocity.z = get_movement_direction().z * move_speed * delta
move_and_slide()
if get_movement_direction():
anim_player.play("run")
update_front_direction(input_dir)
else:
anim_player.play('idle')
update_anim_to_camera()
update_sprite_facing()
animation_change()
func get_movement_direction():
input_dir.x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
input_dir.y = Input.get_action_strength("move_down") - Input.get_action_strength("move_up")
var direction = (main_camera.transform.basis * Vector3 (input_dir.x, 0, input_dir.y)).normalized()
# var rotation = Vector3(input_dir.x, 0, input_dir.y).normalized()
print ("input dir = ", input_dir)
return direction
func animation_change():
var animation = anim_player.get_animation(anim_player.current_animation)
if animation:
animation.track_set_key_value(2, 0, anim_rows[sprite_facing])
else:
printerr("Animation " + animation + " not found. " + get_parent().name)
return
func update_anim_to_camera():
var camera_fwd = main_camera.global_transform.basis.z
print("camera_fwd = ", camera_fwd)
var char_forward = global_transform.basis.z #Vector3(input_dir.x,0,1)
print("char_fwd = ", char_forward)
var char_left = global_transform.basis.x #Vector3(0,0,input_dir.y)
print("char_left = ", char_left)
var l_dot = char_left.dot(camera_fwd)
print("l_dot = ", l_dot)
var f_dot = char_forward.dot(camera_fwd)
print("f_dot = ", f_dot)
if f_dot < -0.65:
sprite_facing = "front"
elif f_dot > 0.65:
sprite_facing = "back"
else:
if abs(f_dot) < 0.3 and l_dot > 0:
sprite_facing = "right" # left sprite
elif abs(f_dot) < 0.3 and l_dot < 0:
sprite_facing = "left" # left sprite
elif f_dot < 0 and l_dot > 0:
sprite_facing = "frontright" # forward left sprite
elif f_dot < 0 and l_dot < 0:
sprite_facing = "frontleft" # forward left sprite
else:
if l_dot < 0:
sprite_facing = "backleft" # back left sprite
else:
sprite_facing = "backright"
func update_sprite_facing():
if input_dir.y < 0 and input_dir.x == 0:
sprite_facing = "back"
elif input_dir.y > 0 and input_dir.x == 0:
sprite_facing = "front"
elif input_dir.x < 0 and input_dir.y == 0:
sprite_facing = "left"
elif input_dir.x > 0 and input_dir.y == 0:
sprite_facing = "right"
elif input_dir.y < 0 and input_dir.x < 0:
sprite_facing = "backleft"
elif input_dir.y > 0 and input_dir.x > 0:
sprite_facing = "frontright"
elif input_dir.y > 0 and input_dir.x < 0:
sprite_facing = "frontleft"
elif input_dir.y < 0 and input_dir.x > 0:
sprite_facing = "backright"
func update_front_direction(input_dir):
if input_dir != Vector2.ZERO:
front = Vector3(input_dir.x, 0, input_dir.y)
front = front.normalized()