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()
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.
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:
put StaticBody2D with CircleShape2D at the turning points. iirc, CircleShape2D is calculated as a mathematical circle, not as a set of polygons.
to get the gravity vector, I would use ShapeCast2D.get_collision_normal() instead of get_floor_normal()
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()
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.
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.
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.