I need help to resolve anti-gravity issue

Godot Version

Godot 4.5 dev4

Question

Hi. I’m trying to make a 2d kart game prototype, where vehicle (CharacterBody 2D) have their own gravity, which is perpendicular to ground normal (a bit similar to Mario Kart 8 anti-gravity). The problem is that, at some point, if the gravity force is set too low or the speed is higher , the vehicle will start flying when encountering a corner. Moreover, some bumps are noticeable, even with a snap of 64 px.

I’ve provided a video that shows the issue (the raycast represents the gravity direction) :

Here is my player code :

extends CharacterBody2D


const SPEED = 300.0
const JUMP_VELOCITY = -400.0
const GRAVITY := 2000.0

var gravity_direction: Vector2
@onready var ray_cast_2d: RayCast2D = %RayCast2D


func _ready() -> void:
	gravity_direction = Vector2(0.0, -1.0)

	# Set floor snap length
	set_floor_snap_length(64.0)


func _physics_process(delta: float) -> void:

	if is_on_floor():
		gravity_direction = get_floor_normal()
		gravity_direction = gravity_direction.normalized()
		up_direction = gravity_direction

	# Add the gravity.
	if not is_on_floor():
		velocity -= GRAVITY * gravity_direction * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity += -gravity_direction * JUMP_VELOCITY

	# Update raycast rotation
	ray_cast_2d.rotation = gravity_direction.angle()

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var right_vector := gravity_direction.orthogonal()
	#var direction := Input.get_axis("ui_left", "ui_right")
	var direction := 1.0

	if direction:
		velocity = velocity.slide(right_vector) + direction * -SPEED * right_vector
	else:
		velocity = velocity.slide(right_vector)

	move_and_slide()

Any help would be highly appreciated :slight_smile:

Hi, I don’t know whether this helps or not, but try setting this
set_floor_max_angle
to something higher; maybe it fixes your problem.

2 Likes

Unfortunately, no matter what value I set for set_floor_max_angle, the player acts the same.
I’ve set physics_ticks_per_second to 240 and max_physics_steps_per_frame to 32, in my Project Settings.

And sorry for the late reply :sweat_smile:

1 Like

I’m going to try working with transform, maybe it’ll help …

it seems to me this happens because of the gravity vector changes only when the vehicle is on the ground and for some reason there is no snapping (perhaps due to the fact that the turns are too sharp). So the vehicle just flies over the turn

I would try the following:

  1. put StaticBody2D with CircleShape2D at the turning points. iirc, CircleShape2D is calculated as a mathematical circle, not as a set of polygons.

  2. to get the gravity vector, I would use ShapeCast2D.get_collision_normal() instead of get_floor_normal()

  3. my best guess. In the tick when the transport lifts off the ground, I would use apply_floor_snap() to make the transport stick to the ground:

var was_on_floor_prev_tick : bool
var jumped : bool

func _physics_process(delta: float) -> void:
	#...

	if was_on_floor_prev_tick && !is_on_floor() && !jumped:
		apply_floor_snap()
	
	#...

	was_on_floor_prev_tick = is_on_floor()
1 Like

Should I rotate the Shapecast2D ? I haven’t got a lot of experience with it.

It should repeat vehicle collision, so i think no. Unless your vehicle is rotated

1 Like

Here is my new code :

extends CharacterBody2D


const SPEED = 768.0
const JUMP_VELOCITY = -400.0
const GRAVITY := 3000.0

var gravity_direction: Vector2
var was_on_floor_prev_tick: bool
var jumped: bool

@onready var shape_cast: ShapeCast2D = %ShapeCast2D



func _ready() -> void:
	gravity_direction = Vector2(0.0, -1.0)

	# Set floor snap length
	set_floor_snap_length(64)


func _physics_process(delta: float) -> void:
	if shape_cast.get_collision_count() > 0:
		gravity_direction = shape_cast.get_collision_normal(0)

	if is_on_floor():
		gravity_direction = gravity_direction.normalized()
		up_direction = gravity_direction
		jumped = false

	# Add the gravity.
	if not is_on_floor():
		velocity -= GRAVITY * gravity_direction * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity += -gravity_direction * JUMP_VELOCITY
		jumped = true

	_move()



func _move() -> void:
	# Update raycast rotation
	#shape_cast.rotation = gravity_direction.angle() + PI/2

	if was_on_floor_prev_tick && !is_on_floor() && !jumped:
		apply_floor_snap()

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var right_vector := gravity_direction.orthogonal()
	var direction := 1.0

	if direction:
		velocity = velocity.slide(right_vector) + direction * right_vector * -SPEED
	else:
		velocity = velocity.slide(right_vector)

	
	var y_speed = Vector2(0.0, 1.0).dot(velocity)
	if y_speed > 768:
		velocity += (768 - y_speed) * Vector2(0.0, 1.0)


	was_on_floor_prev_tick = is_on_floor()

	move_and_slide()


The resulted behaviour is by far better compared to the previous one. At low speed and with a reduced gravity, everything works amazingly. Nonetheless i still have some issues if I increase my velocity while my gravity at the same value. Have I made a mistake in my code order, or should I work with transforms too ?
(The huge shapecast is for test purposes)

it seems to me that these slippages may occur due to the large ShapeCast2D and large snap_distance. I’m not sure how ShapeCast behaves in situations like this, so I would make it into a vehicle size, and set the target_point in the direction of gravity

although on very narrow turns, this may give an incorrect normal to the surface.

It might also be worth making snap_distance shorter so that the transport can’t slip too far.

1 Like

Sorry for not replying to you directly … :face_with_hand_over_mouth:

So here is my new code, after aligning Shapecast2D to gravity_direction, reducing the snap_length from 64 to 16. And I have increased the radius of the corners :
extends CharacterBody2D

const SPEED = 768.0
const JUMP_VELOCITY = -400.0
const GRAVITY := 3000.0

var gravity_direction: Vector2
var was_on_floor_prev_tick: bool
var jumped: bool

@onready var shape_cast: ShapeCast2D = %ShapeCast2D



func _ready() -> void:
	gravity_direction = Vector2(0.0, -1.0)

	# Set floor snap length
	set_floor_snap_length(16.0)


func _physics_process(delta: float) -> void:
	if shape_cast.get_collision_count() > 0:
		gravity_direction = shape_cast.get_collision_normal(0)
		shape_cast.target_position = -shape_cast.get_collision_normal(0).normalized() * 64.0

	if is_on_floor():
		gravity_direction = gravity_direction.normalized()
		up_direction = gravity_direction
		jumped = false

	# Add the gravity.
	if not is_on_floor():
		velocity -= GRAVITY * gravity_direction * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity += -gravity_direction * JUMP_VELOCITY
		jumped = true

	_move()


func _move() -> void:
	# Update raycast rotation
	#shape_cast.rotation = gravity_direction.angle() + PI/2

	if was_on_floor_prev_tick && !is_on_floor() && !jumped:
		apply_floor_snap()

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var right_vector := gravity_direction.orthogonal()
	var direction := 1.0

	if direction:
		velocity = velocity.slide(right_vector) + direction * right_vector * -SPEED
	else:
		velocity = velocity.slide(right_vector)

	#
	var y_speed = Vector2(0.0, 1.0).dot(velocity)
	if y_speed > 768:
		velocity += (768 - y_speed) * Vector2(0.0, 1.0)


	was_on_floor_prev_tick = is_on_floor()

	move_and_slide()

Although there’s still some slippage, it is much better

Sorry for late reply. Looking at the code, I couldn’t think of anything else that could make movement better. I think next you will need to tweek the best settings yourself. But I remembered that Area2D has a gravity override setting. I think it can be useful in your case.

1 Like

Thank you very much @Haika, I really appreciated your help.
I’m going to experiment everything I can, search, and I soon as I find what I want to achieve, I’ll communicate it with you.

Again, huge thanks !

1 Like