+1 vote

I tried putting it in sleep and then changing pos. Also tried this inside integrateforces(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?

in Engine by (329 points)

2 Answers

0 votes

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.

by (629 points)
0 votes

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
by (1,350 points)

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
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.