What is the proper way to teleport RigidBody2D?

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

I tried putting it in sleep and then changing pos. Also tried this inside _integrate_forces(s).

if teleporting:
  var new_transform = s.get_transform()
  new_transform.o = teleported_pos
  s.set_transform(new_transform)
  teleporting = false

Even creating other functions, yield(get_tree(),"idle_frame") putting these idle frames everywhere and literally every combination of things mentioned above.

The body is always teleported inaccurately. Usually going into walls. It varies around ± 100 for both x and y. I’m really depressed…

What is simple and proper way to teleport RigidBody2D in version 2.1.4?

:bust_in_silhouette: Reply From: markopolo

I don’t know of any official “proper” way. The most consistent method I’ve found is to use set_physics_process(false), wait for a small delay, then set_global_position, then wait again, then reactivate physics.

Usually the delays are not enough to make the player feel lag, especially since teleporting usually happens around a level transition or somewhere players expect it / won’t be upset by it.

You might be interested in this question, although it’s about a KinematicBody2D.

I’ve written a script for this exact thing:

extends Node

signal buffer_expired

export(NodePath) var body_path
onready var body = get_node(body_path)

export(float) var time_buffer = 0.02
var time_remaining

var teleporting = false

func _ready():
  set_process(false)

func teleport(new_destination):
  # don't teleport while teleporting!
  if teleporting:
    return
  
  teleporting = true
  
  # disable physics
  body.set_physics_process(false)
  
  # wait for physics engine to chill out
  time_remaining = time_buffer
  set_process(true)
  yield(self, "buffer_expired")
  
  # do teleport
  body.global_position = new_destination
  
  # wait again to prevent physics engine from undoing teleport
  time_remaining = time_buffer
  set_process(true)
  yield(self, "buffer_expired")
  
  # re-enable physics
  body.set_physics_process(true)
  teleporting = false

func _process(delta):
  time_remaining -= delta
  if time_remaining <= 0:
    set_process(false)
    emit_signal("buffer_expired")

Happy to receive feedback or explain more if this doesn’t help.

:bust_in_silhouette: Reply From: Xrayez

Is your object moving before teleporting using _integrate_forces()? If yes, I suppose that you also need to reset RigidBody2D’s linear and angular velocities to avoid the object going into walls that way. Setting body’s transform is usually not enough:

if teleporting:
    var new_transform = s.get_transform()
    new_transform.o = teleported_pos
    s.set_transform(new_transform)

    # Reset velocities to avoid the object going through walls
    s.set_linear_velocity(Vector2())
    s.set_angular_velocity(0.0)

    teleporting = false

The same works in 3d. To get rid of any velocity or rotation, set those using the state like in the previous comment. Weirdly, this may still cause weird bugs if the object is allowed to sleep, so for transporting objects, make sure can_sleep is false.

extends RigidBody

var warp_offset = null

func _on_Spatial_shift(offset):
	warp_offset = offset

func _integrate_forces(state):
	if warp_offset:
		var t = state.get_transform()
		t.origin += warp_offset
		state.set_transform(t)
		warp_offset = null

tcmug | 2020-11-30 20:36