How to move a KinematicBody to the clicked object (3D)?

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

I’m making a 3D point and click game with very little knowledge of coding, however, i’ve already managed to find a code from one of the answers here that moves the character using a navmesh, and works with the scene I created in Blender.
The next objective is now being able to interact with the objects on the scene by clicking on them.
For now i want to move the character to a specific point in front of the object so I can play an animation and do some other things.
To this end, I’ve theorised that the easiest way is to “shoot” coordinates from the object’s script onto the player’s script and, using the movement script there, override it’s target with the aforementioned coordinates. But I have not managed to make this work at all.

This is the Player’s script:

extends KinematicBody

var maxspeed = 400
var movedirection
var acceleration = 120
var moving = false
var destination = Vector3()
var movement = Vector3()
var position = Vector3(0, 0, 0) # ?
var Object_override = 0
var Override_pos = Vector3(-3.991247, -1.828135, 3.415715)


onready var anim = $AnimationPlayer

func _ready():
	navmesh = get_parent().get_node("Ground/Navigation")

func _input(event):
	if Input.is_action_pressed("LMBClick"):
		var from =  get_viewport().get_camera().project_ray_origin(event.position)
		var to = from +  get_viewport().get_camera().project_ray_normal(event.position)*10
		
		print ("From : ", from, " and To : ", to)
		var space_state = get_world().direct_space_state
		var result = space_state.intersect_ray(from, to, [self], collision_mask)
		if Object_override == 1:
			result = Override_pos
		print ("RESULT : ", result.position)
		
		
		
		var p = navmesh.get_closest_point_to_segment(from, to)
		
		
		begin = navmesh.get_closest_point(self.get_translation())#.get_translation()
		end = p
		
		moving = true
		destination = result.position
		if Object_override == 1:
			destination = result
		target = destination
		
			
		calculate_path()





export var speed = 1.5
var target = null
var navmesh
var path = []
var begin = Vector3()
var end = Vector3()

func _process(delta):
	if (path.size() > 1):
		var to_walk = delta*speed
		var to_watch = Vector3(0, 1, 0)
		while(to_walk > 0 and path.size() >= 2):
			var pfrom = path[path.size() - 1]
			var pto = path[path.size() - 2]
			to_watch = (pto - pfrom).normalized()
			var d = pfrom.distance_to(pto)
			if (d <= to_walk):
				path.remove(path.size() - 1)
				to_walk -= d
			else:
				path[path.size() - 1] = pfrom.linear_interpolate(pto, to_walk/d)
				to_walk = 0
				
			
			var atpos = path[path.size() - 1]
			var atdir = to_watch
			atdir.y = 0
			
			var t = Transform()
			t.origin = atpos
			t=t.looking_at(atpos + atdir, Vector3(0, 1, 0))
			set_transform(t)
			if (path.size() < 2):
				path = []
			
			if moving == true:
				play_anim("walk")
				
	else:
		set_process(false)
		play_anim("idle")

func calculate_path():
	begin = navmesh.get_closest_point(self.get_translation())
	end = navmesh.get_closest_point(target)
	var p = navmesh.get_simple_path(begin, end, true) 
	path = Array(p)
	path.invert()
	set_process(true)

func play_anim(name):
	anim.play(name)

And this is the object’s script: (in this case a door)

extends Spatial


onready var David = $"../David"

func _on_Area_input_event(camera, event, position, normal, shape_idx):
	var just_pressed =  event.is_pressed() and not event.is_echo()
	if Input.is_action_pressed("LMBClick") and just_pressed:
			David.Object_override = 1
			print("on")
	else:
		David.Object_override = 0

Thank you for your time.

:bust_in_silhouette: Reply From: Wakatta

Okay so you have essentially everything you need just to organize it to work as intended.
Using functions and function returns

# Player.gd
extends KinematicBody

export var speed = 1.5

onready var navmesh = get_parent().get_node("Ground/Navigation")
onready var anim = $AnimationPlayer

var maxspeed = 400
var movedirection
var acceleration = 120
var moving = false
var destination = Vector3()
var movement = Vector3()

var target = Vector3.ZERO
var path = []


func get_ray_position(event):
	var from =  get_viewport().get_camera().project_ray_origin(event.position)
	var to = from +  get_viewport().get_camera().project_ray_normal(event.position) * 1000

	print ("From : ", from, " and To : ", to)
	var space_state = get_world().direct_space_state
	var result = space_state.intersect_ray(from, to, [self], collision_mask)

	if not result.empty():
		print ("RESULT : ", result.position)
		return result.position

	return Vector3.ZERO

func _input(event):
	if Input.is_action_pressed("LMBClick"):
		target = get_ray_position(event)
		if target:
			calculate_path()
			moving = true


func _process(delta):
	if (path.size() > 1):
		var to_walk = delta*speed
		var to_watch = Vector3(0, 1, 0)
		while(to_walk > 0 and path.size() >= 2):
			var pfrom = path[path.size() - 1]
			var pto = path[path.size() - 2]
			to_watch = (pto - pfrom).normalized()
			var d = pfrom.distance_to(pto)
			if (d <= to_walk):
				path.remove(path.size() - 1)
				to_walk -= d
			else:
				path[path.size() - 1] = pfrom.linear_interpolate(pto, to_walk/d)
				to_walk = 0
				
				
			var atpos = path[path.size() - 1]
			var atdir = to_watch
			atdir.y = 0

			var t = Transform()
			t.origin = atpos
			t=t.looking_at(atpos + atdir, Vector3(0, 1, 0))
			set_transform(t)
			if (path.size() < 2):
				path = []

			if moving == true:
				play_anim("walk")

	else:
		moving = false
		target = Vector3.ZERO
		set_process(false)
		play_anim("idle")

func calculate_path():
	var begin = navmesh.get_closest_point(global_transform.origin)
	var end = navmesh.get_closest_point(target)
	path = navmesh.get_simple_path(begin, end, true) 
	path.invert()
	set_process(true)

func play_anim(name):
	anim.play(name)

10 Cent Advice

You use way too many variables
Did not modify your movement code as a result but it can be majorly simplified

Couldn’t for the life of me understand what Object_override does

And it seems that you’re trying to do the same thing in multiple places, don’t, keep your code centralized

Thank you for the reply. The override thing was my attempt at creating a sort of switch that overrides the coordinates to where I want the character to go, because I dont want to make it impossible to move somewhere else once the player has clicked an object. Like I said, I don’t really know much about how this works but I’m studying as I go.

DiAlEx | 2022-12-06 02:14

So what I do on the object scripts is change the “target” var? Or another one?

DiAlEx | 2022-12-06 02:23

Correct change the target var to where ever you want to go. And that can replace your override var

func _on_Area_input_event(camera, event, position, normal, shape_idx):
    var just_pressed =  event.is_pressed() and not event.is_echo()
    if Input.is_action_pressed("LMBClick") and just_pressed:
            David.target = Vector3(-3.991247, -1.828135, 3.415715)
            print("on")
    else:
        David.target = Vector3.ZERO

Wakatta | 2022-12-06 04:59

It’s giving me the error “Invalid set index ‘target’ (on base: ‘null instance’) with value of type ‘Vector3’.” when I click or hover over the object
Nevermind it looks like I hadn’t properly referenced the player node.

DiAlEx | 2022-12-06 12:24

Apologies for the delayed reply and

Nevermind it looks like I hadn’t properly referenced the player node

was thinking exactly that…so everything works now?

Wakatta | 2022-12-07 00:16

Yes thank you for your replies. I am able to place the character in a specific spot when I click the door but that was only step 1 and I’m already having trouble with the next part. This is probably not the place to ask, but would you be willing to help me with my project on the coding side of things? I’m pretty sure I’m only going to have more doubts moving forward and unfortunately I don’t have the necessary time to learn this in detail.

DiAlEx | 2022-12-07 21:55