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?
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
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