Move child towards specified location when not at location

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

Currently, I am trying to set up a kinemeticbody2d (I’ll call it teleporter) with a collision2d as a child of the kinematicbody2d player. The plan is, this teleporter is supposed to always be in front of the player so that when they teleport, they can move to the teleporter. When this teleporter collides with a wall, it would stop so that the player can’t teleport into/through walls, then when you move away from the wall, the teleporter would move back. This is my current code for it:

extends KinematicBody2D

const speed = 5000

var velocity = Vector2.ZERO
var location = position

func _physics_process(delta):
	if location != position:
		velocity = position.direction_to(location.position) * speed
	velocity = move_and_slide(velocity)

Currently, all that happens is a “Invalid get index ‘position’ (on base: ‘Vector2’).” If anyone has any advice on how to get this working, or if there’s a better way of setting up this sort of detection, I would greatly appreciate the advice.

:bust_in_silhouette: Reply From: timothybrentwood

Create a RayCast2D as a child of your Player KinematicBody2D and set it to cast to a teleport’s length in front of your player. This takes the place of the Teleporter KinematicBody2D that you were attempting to create.

onready var teleporting_ray_check : RayCast2D = get_node("ray_cast")

func _ready():
    teleporting_ray_check.enabled = true

func _physics_process(_delta: float) -> void:
	var teleporting = Input.is_action_just_pressed("ui_accept")
	if not teleporting:
		pass
	elif teleporting_ray_check.is_colliding():
		self.global_position =  teleporting_ray_check.get_collision_point()
	else:
		self.position += teleporting_ray_check.get_cast_to()

Add a cooldown timer as you see fit.

I had to change some things around to work with how my project worked currently, which uses a state machine between movement and teleportation. I’ll put in the relevant code:

func _ready():
	teleport.enabled = true

#State machine
func _physics_process(delta):
	match state:
		MOVE:
			move_state(delta)
		
		BLINK:
			blink_state(delta)

(In MOVE state)

if Input.is_action_just_pressed("blink"):
	state = BLINK

(Then)

func blink_state(delta):
	if teleport.is_colliding():
		self.global_position =  teleport.get_collision_point()
	else:
		self.position += teleport.get_cast_to()
	state = MOVE

In this current state, it works correctly when the raycast is colliding with a wall (albeit, with some visual jitter), however, when it isn’t, it instead teleports the player downwards into the floor. Additionally, I am attempting to rotate it in the code using rotation_degrees, which does not change it.

darkdestiny1010 | 2021-05-11 16:01

What kind of movement are you using? 2D side scroller or top down?

timothybrentwood | 2021-05-11 16:46

2D side scroller.

darkdestiny1010 | 2021-05-11 17:22

Make sure your RayCast2D’s position is the center point of your KinematicBody2D. You probably want to give yourself a buffer for when you’re colliding (a little over half the character sprite’s width is my suggestion). Make sure you’re changing where your RayCast2D is casting to based on the direction your character is facing.

const BUFFER := Vector2(8,0)
const TELEPORT_LENGTH := 64
var CAST_LEFT := Vector2.LEFT * TELEPORT_LENGTH
var CAST_RIGHT: = Vector2.RIGHT * TELEPORT_LENGTH

func _physics_process(delta):
    if teleport.is_colliding():
        self.global_position =  teleport.get_collision_point()  + (sign(teleport.get_cast_to().x) * BUFFER)
    ...
    if facing_right:
        teleport.cast_to(CAST_RIGHT)
    else:
        teleport.cast_to(CAST_LEFT)

timothybrentwood | 2021-05-11 17:45

This seems to give me the error, “Invalid call. Non existant function ‘cast_to’ in base RayCast2d.”

darkdestiny1010 | 2021-05-11 18:03

My bad it should be set_cast_to() not cast_to(). cast_to is the property.

timothybrentwood | 2021-05-11 18:57

It’s definitely now making the RayCast move and is teleporting, however it causes the raycast to go up or down, and only actually teleports when colliding. Currently it’s:

func blink_state(delta):
	if teleport.is_colliding():
		self.global_position =  teleport.get_collision_point()  + (sign(teleport.get_cast_to().x) * BUFFER)
	if arm.global_position.x - get_global_mouse_position().x < 0:
		teleport.set_cast_to(teleportRight)
	else:
		teleport.set_cast_to(teleportLeft)
	state = MOVE

darkdestiny1010 | 2021-05-11 19:09

This is going to be fighting with the physics engine a bit since you change directions with your mouse.

func blink_state(delta):
    if arm.global_position.x - get_global_mouse_position().x < 0:
        teleport.set_cast_to(teleportRight)
    else:
        teleport.set_cast_to(teleportLeft)

    teleport.force_raycast_update()

    if teleport.is_colliding():
        self.global_position =  teleport.get_collision_point()  + (sign(teleport.get_cast_to().x) * BUFFER)
    else:
        self.position += teleport.get_cast_to()

    state = MOVE

timothybrentwood | 2021-05-11 19:46

“however it causes the raycast to go up or down”
That has to deal with the values of your teleportRight and teleportLeft - which should be treated as constants. You should never be adjusting the position of the RayCast2D it will follow the center of your KinematicBody2D since the RayCast2D is a child of it.

timothybrentwood | 2021-05-11 19:50

Works almost perfectly! The issue with the raycast pointing up and down was because I had already rotated it to face to the right, and the code made it rotate again, I’ve fixed that. The only thing that currently doesn’t work is collision, as it still teleports into walls.

darkdestiny1010 | 2021-05-11 19:53

If you’re standing next to a wall do you teleport the entire length of the RayCast2D?

if teleport.is_colliding():
    print("colliding")
    self.global_position =  teleport.get_collision_point()  + (sign(teleport.get_cast_to().x) * BUFFER)
else:
    print("not colliding")
    self.position += teleport.get_cast_to()

add those print statements. If you’re hitting the “colliding” and still going into the wall it has to deal with what you’re setting your teleportLeft and teleportRight to , they should be set to Vector2.LEFT * some length and Vector2.RIGHT * some length respectively. Or your buffer is too small.

You shouldn’t be hitting the not colliding branch. If you are that’s very strange.

timothybrentwood | 2021-05-11 20:20

Ahh I finally found where it went wrong. I was setting the buffer to half the height, not half the width. Works perfectly now, thank you ever so much!

darkdestiny1010 | 2021-05-11 20:59

Hahaha I was very confused! No problem man!

timothybrentwood | 2021-05-11 21:12