Orbital physics with CharacterBody2D

Godot Version

v4.2.2.stable.official [15073afe3]

Question

How do I make orbital physics for a CharacterBody2D node, allowing player to jump between planets and rotate according to their gravity, while being out of orbit results in default rotation angle and default gravity?

Player’s script:

extends CharacterBody2D

const PLAYER_SPEED : float = 10
const PLAYER_JUMP_HEIGHT : float = 10
const PLAYER_ROTATION_SPEED : float = 1

var planets : Array[Planet]
var gravity_velocity : Vector2
var player_input_velocity : Vector2

func _physics_process(delta):
	# add all gravity of all planets together
	var gravity := Vector2.ZERO
	for planet : Planet in planets:
		gravity += planet.get_gravity_at(global_position)
	gravity_velocity += gravity * delta
	
	# rotate the player according to the gravitytion
	var rx = atan2(gravity.y ,0)
	var ry = atan2(gravity.x * cos(rx), 0)
	global_rotation = lerp(global_rotation,  Vector2(rx, ry), delta * PLAYER_ROTATION_SPEED)
	
	# add player input (you player input as you would use it in a normal platformer / top down, in this case just right and left input + jump)
	player_input_velocity = Vector2(Input.get_axis("left", "right"), 0).normalized() * PLAYER_SPEED
	if Input.is_action_just_pressed("jump"):
		player_input_velocity.y = -sqrt(PLAYER_JUMP_HEIGHT * 2 * gravity.length())
	# rotate the input velocity to match player rotation
	player_input_velocity = player_input_velocity.rotated(rotation)
	
	# add velocities
	velocity = gravity_velocity + player_input_velocity
	
	
	move_and_slide()

Planet’s script:

# make sure you Area2D's collision shape is a sphere collider with a radius of 'max_gravity_radius'

class_name Planet
extends Node2D

@export var gravitation_area : Area2D
@export var max_gravity_radius : float
@export var gravity_at_center : float

func _ready():
	gravitation_area.connect("body_entered", Callable(self, "on_body_entered"))
	gravitation_area.connect("body_exited", Callable(self, "on_body_exited"))

func get_gravity_at(pos : Vector2) -> Vector2:
	var distance : float = (global_position - pos).length()
	
	# if pos outside of radius return no gravity
	if distance > max_gravity_radius:
		return Vector2.ZERO
	
	# return calculated gravity vector
	return gravity_at_center * (global_position - pos).normalized() * (1 -(distance / max_gravity_radius))

func on_body_entered(body : PhysicsBody2D):
	if body.is_in_group("player"):
		body.planets.append(self)

func on_body_exited(body : PhysicsBody2D):
	if body.is_in_group("player"):
		body.planets.erase(self)


The gravity of the planets applies linear: if the players position is the planets position the gravity is the maximum, if the player’s distance to the planet is the max gravity distance the gravity is 0

1 Like

May I ask for a quick look at the node tree?

I have not tried the code but this is the intended node tree:

Node2D (Root)
|- Node2D (Planets)
   |- Node2D (Planet1) [if you want collision: StaticBody with collisionshape as child, else Node2D]
      |- Area2D
          |- CollisionShape (use circle collider)
|- CharacterBody2D (Player)
   |- CollisionShape

You probably have to play around with the script and change the constants to your liking. I did not include vertical gravity if there is no planet. You can easily add it though by just adding a Vector2(0, downwards_gravity) to the gravity variable. (when you create the variable or after the for planet in … Part

Thank you so much for such an insightful answer! Have a good day.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.