Rotate smoothly with look_at

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Frenggie

Hey. I am moving and rotating my Player with the following lines:

func physics_process(delta):
	player.motion = player.position.direction_to(player.get_global_mouse_position()) * 600
	
	player.look_at(player.get_global_mouse_position())

The look_at method makes it turn almost instantly. I want to make that more smooth. Can anyone help me with this please? (Maybe with a Tween?)

Thanks.

Edit: If you’re wondering why I am using a reference to the player and why I don’t use _physics_process(delta). This script is from a subclass of a State machine which controls Player movement. The actual _physics_process method is in the Base Class.

:bust_in_silhouette: Reply From: Samomba

I am currently looking for the same thing.

After searching the web this code was pointed to me as a possible solution, but I am not very familiar with tweens. This code is also really fast and I can’t seem to make it turn slower.

Hope it helps anyway.

extends Node2D

onready var TweenNode = get_node(“Tween”)

func _process(delta):

# target to look at
var target = get_global_mouse_position()

# initial and final x-vector of basis
var initial_transform_x = self.transform.x
var final_transform_x = (target - self.global_position).normalized()

# interpolate
TweenNode.interpolate_method(self, '_set_rotation', initial_transform_x, final_transform_x, 0.1, Tween.TRANS_LINEAR, Tween.EASE_OUT)
TweenNode.start()

apply rotation

func _set_rotation(new_transform):

# apply tweened x-vector of basis
self.transform.x = new_transform

# make x and y orthogonal and normalized
self.transform = self.transform.orthonormalized()
:bust_in_silhouette: Reply From: Andrea

look_at is not the function you need, you need looking_at, which creates a new transform.
i basically create a new transform that look at the target, and then i used lerp to interpolate the x,y,z basis of the camera transform with the one of the created transform

func _process(delta):  #inside camera process
		var T=global_transform.looking_at(target.global_transform.origin, Vector3(0,1,0))
		global_transform.basis.y=lerp(global_transform.basis.y, T.basis.y, delta*camera_speed)
		global_transform.basis.x=lerp(global_transform.basis.x, T.basis.x, delta*camera_speed)
		global_transform.basis.z=lerp(global_transform.basis.z, T.basis.z, delta*camera_speed)

I previously commented that this works perfectly but actually it doesn’t. It works in one direction but it scales the model in a negative direction sometimes. Especially if it is a 180 degree turn.

Dileeep | 2022-10-17 03:41

You are right, above solution does not work with 180° turn.
Rotation happens when at least 1 basis vector changes direction (the others follow automatically using an orthonormalization process), but in case of a 180° turn, you are just changing the magnitude of one of the vectors and keep the other 2 as the same. Therefore the lerping just shrink the part and then it flips it.

I suggest to add a line of “if” code to intercept those situation, and rotate the part toward a 90° direction first, before continuing toward the 180° one

Andrea | 2022-10-17 21:17

:bust_in_silhouette: Reply From: Afely

This solution definitely isn’t the best, but it’s very simple and easy to grasp, so this is how I do it:

var weight = 0.1

func physics_process(delta):
    player.motion = player.position.direction_to(player.get_global_mouse_position()) * 600

    player.rotation = lerp_angle(player.rotation, (player.get_global_mouse_position() - player.global_position).normalized().angle(), weight)
2 Likes
:bust_in_silhouette: Reply From: jeudyx

This is the right solution:

(sample code 3d Rotate Direct Constant Smooth - Godot Asset Library)

I tried it and works perfectly.

2 Likes

This definitely works, but when applied to a 3d character, it causes them to “bob” up and down as they change direction, even between say, 90 and 105 degrees.

Not sure if it’s due to the way the character is changing direction or some kind of animation reset.

The key line in the above solution (called ‘smooth’) is this one, in the _physics_process, in my case for a 3D character looking in a direction (Vector3):

rotation.y = lerp_angle( rotation.y, atan2( -direction.x, -direction.z ), delta * SMOOTH_SPEED ) # SMOOTH_SPEED being a float, e.g. 10.0

Although the atan2 looks a bit cumbersome, this one line really solved the puzzle for me!!

2 Likes

Hey if you like using look_at still here’s a nice hacky solution I just found!

Just keep track of the old transform and grab the new one provided by look_at then lerp:

var old = transform.basis
look_at(target_pos)
var new = transform.basis
transform.basis = lerp(old, new, .1)

4 Likes

Very nice man!

Works to me!

What’s been working well for me so far is a helper function that is based on the C++ source implementation of look_at(). At the step where setting the desired global transform, I instead interpolate to it (framerate independently). The decay value specifies the amount of smoothing (lower decay is more smoothing).

func look_at_lerp(target_position: Vector3, up: Vector3, use_model_front: bool, decay: float, delta: float) -> void:
	if global_position.is_equal_approx(target_position):
		return
	if up.is_zero_approx():
		push_error("up vector can't be zero")
		return
	if decay < 0.0:
		push_error("decay must be positive")
		return
	var forward = target_position - global_position
	var lookat_basis = Basis.looking_at(forward, up, use_model_front)
	var lookat_transform = Transform3D(lookat_basis, global_position)
	global_transform = global_transform.interpolate_with(lookat_transform, 1.0 - exp(-decay * delta))

I left out the scaling related code so this might not play well with nodes that are scaled. See Node3D::look_at and Node3D::look_at_from_position.

3 Likes

This is gold thanks!