Godot Version
v4.1.3
Question
Hello, I want to move a character like in Diablo 4 or TorchLight 2, but only teleportation works (if I want to change something, it either doesn’t work or gives an error). Tell me what to do.
Working code:
extends CharacterBody3D
func _physics_process(delta):
if Input.is_action_just_pressed("mouse_left"):
var viewport := get_viewport()
var mouse_position := viewport.get_mouse_position()
var camera := viewport.get_camera_3d()
var origin := camera.project_ray_origin(mouse_position)
var direction := camera.project_ray_normal(mouse_position)
var ray_length := camera.far
var end := origin + direction * ray_length
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(origin, end)
var result := space_state.intersect_ray(query)
var mouse_position_3D:Vector3 = result["position"]
position += mouse_position_3D - position
position.y = 0
move_and_slide()
Try moving towards the mouse, you successfully get the difference in position now it needs to be normalized to a speed. Finally using velocity instead of position directly.
extends CharacterBody3D
const SPEED = 1
func _physics_process(delta):
if Input.is_action_just_pressed("mouse_left"):
var viewport := get_viewport()
var mouse_position := viewport.get_mouse_position()
var camera := viewport.get_camera_3d()
var origin := camera.project_ray_origin(mouse_position)
var direction := camera.project_ray_normal(mouse_position)
var ray_length := camera.far
var end := origin + direction * ray_length
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(origin, end)
var result := space_state.intersect_ray(query)
var mouse_position_3D:Vector3 = result["position"]
var direction := mouse_position_3D - position
velocity = direction.normalized() * SPEED
velocity.y = 0
move_and_slide()
1 Like
Thanks! And you can tell me how to make sure that when the specified point is reached with the mouse, the character stops. Thank you in advance.
That wil be a distance check, continuing from the last code we can use the direction again. A vector can be broken down into two components, the direction and length. We got the direction by normalizing and we can get the length with aptly named .length()
.
var mouse_position_3D:Vector3 = result["position"]
var direction := mouse_position_3D - position
if direction.length() < 0.1:
velocity = Vector3.ZERO
else:
velocity = direction.normalized() * SPEED
However the underlying math for length()
is quite expensive, especially for scripts that happen each frame. It’s the Pythagorean theorem! The big expense here is a square root.
func length():
return sqrt(x*x + y*y)
Godot, Unity, Unreal, and many many more game engines recognize this and add a sqrt
free version. Then we can compare squared numbers instead. It’s a small code change but could be 2x faster.
const min_character_distance = 0.1 ** 2 # squared
var mouse_position_3D:Vector3 = result["position"]
var direction := mouse_position_3D - position
if direction.length_squared() < min_character_distance:
velocity = Vector3.ZERO
else:
velocity = direction.normalized() * SPEED
2 Likes
I’m probably just dumb, but if this code is supposed to look like this:
extends CharacterBody3D
const SPEED = 1
const min_character_distance = 0.1 ** 2 # squared
func _physics_process(delta):
if Input.is_action_just_pressed("mouse_left"):
var viewport := get_viewport()
var mouse_position := viewport.get_mouse_position()
var camera := viewport.get_camera_3d()
var origin := camera.project_ray_origin(mouse_position)
var direction := camera.project_ray_normal(mouse_position)
var ray_length := camera.far
var end := origin + direction * ray_length
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(origin, end)
var result := space_state.intersect_ray(query)
var mouse_position_3D:Vector3 = result["position"]
direction = mouse_position_3D - position
if direction.length_squared() < min_character_distance:
velocity = Vector3.ZERO
else:
velocity = direction.normalized() * SPEED
velocity.y = 0
move_and_slide()
Then, won’t the check be performed only when “mouse_left” is triggered. And it turns out that the check does not pass, and the character goes into the void (I have not completed the map yet).
And I apologize in advance for such a possibly stupid question.
Ah I though it it was a hold click to move, that’s how I play Torchlight 2; you are absolutely correct!
You would have to store the target position in a variable and continously do this distance check, or calculate how long it takes to reach that position and set velocity to zero once complete, minding to interrupt if another click is placed. I would recommend the first since it makes hold-to-move easier to program in addition to click-to-move
1 Like
I know I’ve finished the discussion, but I still want to share with you the working code where the character moves smoothly to the click position. Thanks gertkeno, it wouldn’t have happened without you!
Code:
extends CharacterBody3D
const SPEED = 1
var target_position = null
func _physics_process(delta):
if Input.is_action_just_pressed("mouse_left"):
var viewport := get_viewport()
var mouse_position := viewport.get_mouse_position()
var camera := viewport.get_camera_3d()
var origin := camera.project_ray_origin(mouse_position)
var direction := camera.project_ray_normal(mouse_position)
var ray_length := camera.far
var end := origin + direction * ray_length
var space_state := get_world_3d().direct_space_state
var query := PhysicsRayQueryParameters3D.create(origin, end)
var result := space_state.intersect_ray(query)
target_position = result["position"]
if target_position != null:
var direction = target_position - position
velocity = direction.normalized() * SPEED
velocity.y = 0
if position.distance_to(target_position) <= 0.1:
target_position = null
velocity.y = 0
velocity.x = 0
velocity.z = 0
move_and_slide()
Thanks for showing the final code, I always appreciate that!
There is also a distance_squared_to()
function that implements that optimization I wrote about.
1 Like