Create portals in Godot with _integrate_forces

14. January 2024

My 3D Godot game Ball2Box needs some variety, so I created some portals for new levels. This was not trivial to realize, as expected, and still needs some fine-tuning. But the proof of concept finally works now, and it revealed quite easy to realize, if using the correct Godot functions.

Note: These are not portals like you might know from the game "Portal", where you can see through the portal to the other side. These portals simply teleport a moving body from one point to another.

First attempts

The goal of the game is to get the ball into the box with only one shot. If the attempt fails, the ball gets reset to its initial position. So technically, I already had a "teleport" of the ball from its final position to the initial position.

Here, the old code of the "reset to initial position" function. It simply changes the mode of the RigidBody to static, so it can be moved by changing the translation Vector. Then it also resets all velocity and rotations, to make sure the ball remains still.

extends RigidBody
class_name Ball

# ...omitted code

func reset() -> void:
    mode = RigidBody.MODE_STATIC
    translation = initial_position
    linear_velocity = Vector3.ZERO
    angular_velocity = Vector3.ZERO
    rotation = Vector3.ZERO

Of course I ignored the warnings about changing game physics outside the physic process, because well, it worked.

_integrate_forces(), the forgotten solution

Obviously, I thought this would work also for portals, where the ball gets teleported without resting the velocity and rotation. After a few attempts for making it work, I tried searching the web, and luckily found some hint on using _integrate_forces.
Surely somehow it would have worked without it, after banging my head for some hours, but using this function was the correct way.

The documentation of the _integrate_forces function writes

Called during physics processing, allowing you to read and safely modify the simulation state for the object.

Safely is here the keyword! It means that there will be no strange glitches or non expected movements

Here the final working code, that not only teleports the ball from portal to portal, but also to its initial position.

extends RigidBody
class_name Ball

var do_teleport:bool = false
var do_reset:bool = false
var do_static:bool = false

var initial_position:Transform
var next_transform:Transform

func teleport(p_transform:Transform) -> void:
	next_transform = p_transform
	do_teleport = true

func teleport_to_initial() -> void:
	do_reset = true

func _integrate_forces(state:PhysicsDirectBodyState) -> void:
	# change the mode to static on the next iteration
	# otherwise ball doesn't move to new position
	if do_static:
		do_static = false
		mode = RigidBody.MODE_STATIC

	if do_teleport:
		state.transform = next_transform
		do_teleport = false

	if do_reset:
		state.transform = initial_position
		state.angular_velocity = Vector3.ZERO
		state.linear_velocity = Vector3.ZERO
		rotation = Vector3.ZERO
		do_reset = false
		do_static = true

An important difference here, is that instead of moving the ball directly, a flag gets set. The actual movement happens then during the next _integrate_forces step.

For completion, here the code where the teleport function gets called, with the transform variable of the corresponding portal.

func _on_Portal1_ball_entered(ball:Ball) -> void:
	ball.teleport(portal2.transform)

After some refactoring of Pocket Broomball, I found out that I already used _integrate_forces there to move the ball. So, I was already sitting on the solution, but forgot about it, as usual.

Find the full documentation of _integrate_forces here.

The full source code of Ball2box is available on Github and Codeberg.

Every feedback is welcome

Feel free to write me an email at info@simondalvai.org and comment on Mastodon or HackerNews.

github buttoncodeberg buttonmastodon buttonrss buttonemail button