implement slope sliding

This commit is contained in:
2025-08-22 17:34:39 +10:00
parent 889680830a
commit c5f0fd697f
4 changed files with 117 additions and 46 deletions

View File

@@ -12,7 +12,7 @@ size = Vector2(18, 26)
[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("_trajectory")]
collision_layer = 16
floor_max_angle = 0.767945
floor_max_angle = 1.55334
floor_snap_length = 0.0
script = ExtResource("1_g2els")
_trajectory = NodePath("Trajectory")

View File

@@ -1,4 +1,4 @@
[gd_scene load_steps=138 format=3 uid="uid://cfvb33kf48sga"]
[gd_scene load_steps=140 format=3 uid="uid://cfvb33kf48sga"]
[ext_resource type="PackedScene" uid="uid://db7d5pwp4gds" path="res://scenes/player.tscn" id="1_bl13t"]
[ext_resource type="Texture2D" uid="uid://j3xt1tbjcu2r" path="res://assets/textures/reference/jumpking_0.png" id="1_iyx0m"]
@@ -315,6 +315,12 @@ size = Vector2(104, 50)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_5205d"]
size = Vector2(56, 150)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_hj8ru"]
size = Vector2(48, 168)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_wf13j"]
size = Vector2(47, 32)
[sub_resource type="RectangleShape2D" id="RectangleShape2D_32c8b"]
size = Vector2(78.489, 37.4766)
@@ -928,13 +934,21 @@ shape = SubResource("RectangleShape2D_2npwi")
position = Vector2(-212, -3161)
shape = SubResource("RectangleShape2D_5205d")
[node name="CollisionShape2D96" type="CollisionShape2D" parent="Geometry"]
position = Vector2(-64, -3456)
shape = SubResource("RectangleShape2D_hj8ru")
[node name="CollisionShape2D97" type="CollisionShape2D" parent="Geometry"]
position = Vector2(-64.5, -3300)
shape = SubResource("RectangleShape2D_wf13j")
[node name="CollisionShape2D95" type="CollisionShape2D" parent="Geometry"]
position = Vector2(-177, -3107.5)
rotation = 0.785397
shape = SubResource("RectangleShape2D_32c8b")
[node name="Player" parent="." instance=ExtResource("1_bl13t")]
position = Vector2(240, 328)
position = Vector2(271, -2253)
[node name="MainCamera" type="Camera2D" parent="."]
position = Vector2(240, 180)
@@ -1053,8 +1067,9 @@ _steps = 79
metadata/_custom_type_script = "uid://ceyu5der8j8gq"
[node name="Trajectory16" type="Marker2D" parent="Trajectories"]
position = Vector2(464, -2296)
position = Vector2(271, -2253)
script = ExtResource("4_74lek")
flip = true
_steps = 79
_steps = 104
_jump = false
metadata/_custom_type_script = "uid://ceyu5der8j8gq"

View File

@@ -23,6 +23,7 @@ var _is_charging_jump: bool = false
var _jump_charge_frames: float = 0
var _charge_strength: float = 0
var _jump_released: bool = false
var _floor_angle: float = 0
var _freemove_enabled: bool = false
@@ -33,6 +34,7 @@ static func process_movement(
new_velocity: Vector2,
delta: float,
on_floor: bool,
floor_angle: float,
direction: float,
is_charging_jump: bool,
charge_strength: float,
@@ -45,15 +47,19 @@ static func process_movement(
) -> Vector2:
# falling
new_velocity.y = (
move_toward(new_velocity.y, fall_speed, delta * fall_acceleration)
if not on_floor
else 0.0
0.0
if on_floor and is_zero_approx(floor_angle)
else move_toward(new_velocity.y, fall_speed, delta * fall_acceleration)
)
# moving
if on_floor:
if on_floor and is_zero_approx(floor_angle):
new_velocity.x = direction * move_speed if not is_charging_jump else 0.0
# sliding
if on_floor and not is_zero_approx(floor_angle):
new_velocity.x = new_velocity.y * signf(floor_angle)
# jump charge release
if jump_released:
new_velocity.y = -jump_force * charge_strength
@@ -65,16 +71,21 @@ static func process_movement(
static func process_collision(
prev_velocity: Vector2,
new_velocity: Vector2,
on_floor: bool,
on_ceiling: bool,
on_wall: bool,
hit_floor: bool,
hit_slope: bool,
hit_ceiling: bool,
hit_wall: bool,
floor_angle: float,
wall_bounce_velocity_loss: float,
) -> Vector2:
if on_floor:
if hit_floor:
new_velocity = Vector2.ZERO
if on_ceiling:
if hit_slope:
new_velocity.x = new_velocity.y * signf(floor_angle)
if hit_ceiling:
new_velocity.y = 0
new_velocity.x *= wall_bounce_velocity_loss
if on_wall:
if hit_wall:
new_velocity.x = -prev_velocity.x * wall_bounce_velocity_loss
return new_velocity
@@ -104,7 +115,7 @@ func _physics_process(delta: float) -> void:
move_and_slide()
return
var is_on_floor_prev := is_on_floor()
var is_on_floor_prev := is_on_floor_only()
var is_on_ceiling_prev := is_on_ceiling()
var is_on_wall_prev := is_on_wall()
@@ -114,6 +125,7 @@ func _physics_process(delta: float) -> void:
velocity,
delta,
is_on_floor_prev,
_floor_angle,
_direction,
_is_charging_jump,
_charge_strength,
@@ -132,9 +144,15 @@ func _physics_process(delta: float) -> void:
move_and_slide()
# collisions
var on_floor := not is_on_floor_prev and is_on_floor()
var on_ceiling := not is_on_ceiling_prev and is_on_ceiling()
var on_wall := (
if is_on_floor() and not is_zero_approx(_floor_angle):
velocity = new_velocity
var is_on_floor_changed := not is_on_floor_prev and is_on_floor()
if is_on_floor_changed and is_on_floor():
_floor_angle = -get_floor_normal().angle_to(Vector2.UP)
var hit_floor := is_on_floor_changed and is_zero_approx(_floor_angle)
var hit_slope := is_on_floor_changed and not is_zero_approx(_floor_angle)
var hit_ceiling := not is_on_ceiling_prev and is_on_ceiling()
var hit_wall := (
not is_on_wall_prev
and is_on_wall()
and not is_on_floor()
@@ -143,17 +161,19 @@ func _physics_process(delta: float) -> void:
velocity = process_collision(
new_velocity,
velocity,
on_floor,
on_ceiling,
on_wall,
hit_floor,
hit_slope,
hit_ceiling,
hit_wall,
_floor_angle,
_wall_bounce_velocity_loss,
)
if on_floor:
if hit_floor:
_trajectory.visible = false
collided.emit(SIDE_BOTTOM, new_velocity)
if on_ceiling:
if hit_ceiling:
collided.emit(SIDE_TOP, new_velocity)
if on_wall:
if hit_wall:
collided.emit(SIDE_LEFT if velocity.x > 0 else SIDE_RIGHT, new_velocity)
queue_redraw()
@@ -170,7 +190,7 @@ func _physics_process(delta: float) -> void:
Debugger.text("is_on_floor_only", is_on_floor_only())
Debugger.text("is_on_wall", is_on_wall())
Debugger.text("is_on_wall_only", is_on_wall_only())
Debugger.text("get_slide_collision_count", get_slide_collision_count())
Debugger.text("floor_angle", _floor_angle)
func _unhandled_input(event: InputEvent) -> void:
@@ -180,6 +200,7 @@ func _unhandled_input(event: InputEvent) -> void:
_saved_state = global_position
if event.is_action_pressed("load_state"):
global_position = _saved_state
velocity = Vector2.ZERO
if event.is_action_pressed("toggle_freemove"):
_freemove_enabled = not _freemove_enabled
@@ -188,7 +209,11 @@ func _gather_input(delta: float, on_floor: bool) -> void:
_direction = signf(Input.get_axis("move_left", "move_right"))
# jump charge start
if Input.is_action_just_pressed("jump") and on_floor:
if (
Input.is_action_just_pressed("jump")
and on_floor
and is_zero_approx(_floor_angle)
):
_is_charging_jump = true
_jump_charge_frames = 0
@@ -208,6 +233,7 @@ func _gather_input(delta: float, on_floor: bool) -> void:
_jump_released = (
_is_charging_jump
and on_floor
and is_zero_approx(_floor_angle)
and (
Input.is_action_just_released("jump")
or _jump_charge_frames >= _jump_full_charge_frames

View File

@@ -197,10 +197,11 @@ func _draw() -> void:
var velocity: Vector2
var is_on_floor: bool = false
var direction: float = -1.0 if flip else 1.0
var floor_angle: float = 0
for i in range(_steps):
pos += velocity * delta
if is_on_floor and _stop_on_landing:
if is_on_floor and is_zero_approx(floor_angle) and _stop_on_landing:
break
velocity = (
@@ -209,10 +210,11 @@ func _draw() -> void:
velocity,
delta,
is_on_floor,
floor_angle,
direction,
false,
charge_strength,
i == 0,
i == 0 and _jump,
_move_speed,
_jump_speed,
_jump_force,
@@ -226,40 +228,50 @@ func _draw() -> void:
if not is_equal_approx(motion_proportion, 1.0):
var motion := (pos - pos_prev) * motion_proportion
pos = pos_prev + motion
var is_hitting_floor: bool = false
var is_hitting_ceiling: bool = false
var hit_slope: bool = false
var hit_floor: bool = false
var hit_ceiling: bool = false
# hitting floor
if velocity.y > 0 and not is_on_floor:
if velocity.y >= 0:
var result := _shapecast(pos + Vector2.UP, pos + Vector2.DOWN)
if result:
is_hitting_floor = true
var raycast_result := _raycast(
pos + Vector2.UP * 10, pos + Vector2.DOWN * 10
)
if raycast_result:
var normal := raycast_result["normal"] as Vector2
floor_angle = -normal.angle_to(Vector2.UP)
hit_floor = is_zero_approx(floor_angle)
hit_slope = not is_zero_approx(floor_angle)
else:
is_on_floor = false
# hitting ceiling
if velocity.y < 0:
var raycast_result := _shapecast(
var result := _shapecast(
pos - Vector2(0, _player_size.y) + Vector2.DOWN,
pos - Vector2(0, _player_size.y) + Vector2.UP
)
if raycast_result:
is_hitting_ceiling = true
if result:
hit_ceiling = true
var is_hitting_wall: bool = (
not is_hitting_floor and not is_hitting_ceiling
)
var hit_wall: bool = not hit_floor and not hit_slope and not hit_ceiling
velocity = (
Player
. process_collision(
velocity,
velocity,
is_hitting_floor,
is_hitting_ceiling,
is_hitting_wall,
hit_floor,
hit_slope,
hit_ceiling,
hit_wall,
floor_angle,
_wall_bounce_velocity_loss,
)
)
if is_hitting_floor:
if hit_floor or hit_slope:
is_on_floor = true
_draw_collision_hit(pos, SIDE_BOTTOM, _floor_hit_color)
draw_dashed_line(
@@ -267,14 +279,13 @@ func _draw() -> void:
pos + Vector2.RIGHT * _player_size.x / 2,
_floor_hit_color
)
if is_hitting_ceiling:
velocity.y = 0
if hit_ceiling:
draw_rect(
Rect2(_get_hitbox_pos(pos), _player_size),
Color(_ceiling_hit_color, 0.25)
)
_draw_collision_hit(pos, SIDE_TOP, _ceiling_hit_color)
if is_hitting_wall:
if hit_wall:
draw_rect(
Rect2(_get_hitbox_pos(pos), _player_size),
Color(_wall_hit_color, 0.25)
@@ -313,6 +324,25 @@ func _shapecast(from: Vector2, to: Vector2) -> Array[Dictionary]:
return space_state.intersect_shape(query)
func _raycast(from: Vector2, to: Vector2) -> Dictionary:
var space_state := get_world_2d().direct_space_state
var query_left := PhysicsRayQueryParameters2D.create(
to_global(from) + Vector2.LEFT * _player_size.x / 2,
to_global(to) + Vector2.LEFT * _player_size.x / 2
)
var result_left := space_state.intersect_ray(query_left)
if result_left:
return result_left
var query_right := PhysicsRayQueryParameters2D.create(
to_global(from) + Vector2.RIGHT * _player_size.x / 2,
to_global(to) + Vector2.RIGHT * _player_size.x / 2
)
var result_right := space_state.intersect_ray(query_right)
if result_right:
return result_right
return {}
func _cast_motion(from: Vector2, to: Vector2) -> float:
var space_state := get_world_2d().direct_space_state
var query := PhysicsShapeQueryParameters2D.new()