move collision handling to a separate subclass to reduce code repetition in trajectory logic

This commit is contained in:
2025-08-23 15:32:33 +10:00
parent cf3710c8c2
commit c690f309cf
2 changed files with 144 additions and 151 deletions

View File

@@ -73,61 +73,6 @@ static func process_movement(
return new_velocity
static func process_collision(
new_velocity: Vector2,
hit_slope: bool,
hit_ceiling: bool,
hit_wall: bool,
floor_angle: float,
wall_bounce_velocity_loss: float,
) -> Vector2:
if hit_slope:
new_velocity = (
new_velocity.slide(Vector2.UP.rotated(floor_angle))
* wall_bounce_velocity_loss
)
if hit_ceiling:
new_velocity.y = 0
if hit_wall:
new_velocity.x = -new_velocity.x
if hit_ceiling or hit_wall:
new_velocity.x *= wall_bounce_velocity_loss
return new_velocity
static func collide_shape(
pos: Vector2,
direction: Vector2,
shape: Shape2D,
space_state: PhysicsDirectSpaceState2D,
max_results: int = 1
) -> Array[Vector2]:
var floor_query := PhysicsShapeQueryParameters2D.new()
floor_query.collision_mask = 1
floor_query.transform = Transform2D(0, pos)
floor_query.motion = direction
floor_query.shape = shape
floor_query.margin = -0.08
return space_state.collide_shape(floor_query, max_results)
static func raycast_floor_angle(
collisions: Array[Vector2], space_state: PhysicsDirectSpaceState2D
) -> float:
var floor_angle: float = 0
for i in range(1, collisions.size(), 2):
var from := collisions[i] + Vector2.UP
var to := collisions[i] + Vector2.DOWN
var query := PhysicsRayQueryParameters2D.create(from, to)
var result := space_state.intersect_ray(query)
if result["normal"] != Vector2.ZERO:
var normal := result["normal"] as Vector2
floor_angle = -normal.angle_to(Vector2.UP)
if is_zero_approx(floor_angle):
return floor_angle
return floor_angle
func _ready() -> void:
instance = self
Debugger.add_event("collided")
@@ -176,7 +121,34 @@ func _physics_process(delta: float) -> void:
# apply velocity
move_and_collide(_velocity * delta)
_handle_collision()
# handle collisions
var handler := CollisionHandler.new()
handler.is_on_floor = _is_on_floor
handler.is_on_ceiling = _is_on_ceiling
handler.is_on_wall = _is_on_wall
handler.floor_angle = _floor_angle
handler.jump_released = _jump_released
handler.velocity = _velocity
var space_state := get_world_2d().direct_space_state
handler.handle(
space_state, _shape.global_position, _shape.shape, _wall_bounce_velocity_loss
)
_is_on_floor = handler.is_on_floor
_is_on_ceiling = handler.is_on_ceiling
_is_on_wall = handler.is_on_wall
_floor_angle = handler.floor_angle
_jump_released = handler.jump_released
_velocity = handler.velocity
if handler.hit_floor:
_trajectory.visible = false
collided.emit(SIDE_BOTTOM, _velocity)
if handler.hit_ceiling:
collided.emit(SIDE_TOP, _velocity)
if handler.hit_wall:
collided.emit(SIDE_LEFT if _velocity.x > 0 else SIDE_RIGHT, _velocity)
queue_redraw()
@@ -236,57 +208,107 @@ func _gather_input(delta: float) -> void:
_is_on_wall = false
func _handle_collision() -> void:
var is_on_floor_prev := _is_on_floor
var is_on_ceiling_prev := _is_on_ceiling
var is_on_wall_prev := _is_on_wall
class CollisionHandler:
var is_on_floor: bool
var is_on_ceiling: bool
var is_on_wall: bool
var floor_angle: float
var jump_released: bool
var velocity: Vector2
var space_state := get_world_2d().direct_space_state
var floor_collisions := collide_shape(
_shape.global_position, Vector2.DOWN, _shape.shape, space_state, 3
)
var ceiling_collisions := collide_shape(
_shape.global_position, Vector2.UP, _shape.shape, space_state
)
var wall_collisions := collide_shape(
_shape.global_position, Vector2.LEFT, _shape.shape, space_state
)
if not wall_collisions:
wall_collisions = collide_shape(
_shape.global_position, Vector2.RIGHT, _shape.shape, space_state
var hit_floor: bool = false
var hit_slope: bool = false
var hit_ceiling: bool = false
var hit_wall: bool = false
func handle(
space_state: PhysicsDirectSpaceState2D,
shape_pos: Vector2,
shape: Shape2D,
wall_bounce_velocity_loss: float
) -> void:
var is_on_floor_prev := is_on_floor
var is_on_ceiling_prev := is_on_ceiling
var is_on_wall_prev := is_on_wall
var floor_collisions := _collide_shape(
shape_pos, Vector2.DOWN, shape, space_state, 3
)
var ceiling_collisions := _collide_shape(
shape_pos, Vector2.UP, shape, space_state
)
var wall_collisions := _collide_shape(
shape_pos, Vector2.LEFT, shape, space_state
)
if not wall_collisions:
wall_collisions = _collide_shape(
shape_pos, Vector2.RIGHT, shape, space_state
)
is_on_floor = floor_collisions.size() > 0
is_on_ceiling = ceiling_collisions.size() > 0
is_on_wall = wall_collisions.size() > 0
var is_on_floor_changed := not is_on_floor_prev and is_on_floor
if is_on_floor_changed:
floor_angle = _get_floor_angle(floor_collisions, space_state)
hit_floor = is_on_floor_changed and is_zero_approx(floor_angle)
hit_slope = is_on_floor_changed and not is_zero_approx(floor_angle)
hit_ceiling = not is_on_ceiling_prev and is_on_ceiling
hit_wall = (
not is_on_wall_prev
and is_on_wall
and (not is_on_floor or jump_released)
and not is_zero_approx(velocity.x)
)
_is_on_floor = floor_collisions.size() > 0
_is_on_ceiling = ceiling_collisions.size() > 0
_is_on_wall = wall_collisions.size() > 0
_process_collision(
wall_bounce_velocity_loss,
)
var is_on_floor_changed := not is_on_floor_prev and _is_on_floor
if is_on_floor_changed:
_floor_angle = raycast_floor_angle(floor_collisions, space_state)
func _process_collision(
wall_bounce_velocity_loss: float,
) -> void:
if hit_slope:
velocity = (
velocity.slide(Vector2.UP.rotated(floor_angle))
* wall_bounce_velocity_loss
)
if hit_ceiling:
velocity.y = 0
if hit_wall:
velocity.x = -velocity.x
if hit_ceiling or hit_wall:
velocity.x *= wall_bounce_velocity_loss
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 or _jump_released)
and not is_zero_approx(_velocity.x)
)
func _collide_shape(
pos: Vector2,
direction: Vector2,
shape: Shape2D,
space_state: PhysicsDirectSpaceState2D,
max_results: int = 1
) -> Array[Vector2]:
var floor_query := PhysicsShapeQueryParameters2D.new()
floor_query.collision_mask = 1
floor_query.transform = Transform2D(0, pos)
floor_query.motion = direction
floor_query.shape = shape
floor_query.margin = -0.08
return space_state.collide_shape(floor_query, max_results)
_velocity = process_collision(
_velocity,
hit_slope,
hit_ceiling,
hit_wall,
_floor_angle,
_wall_bounce_velocity_loss,
)
if hit_floor:
_trajectory.visible = false
collided.emit(SIDE_BOTTOM, _velocity)
if hit_ceiling:
collided.emit(SIDE_TOP, _velocity)
if hit_wall:
collided.emit(SIDE_LEFT if _velocity.x > 0 else SIDE_RIGHT, _velocity)
func _get_floor_angle(
collisions: Array[Vector2], space_state: PhysicsDirectSpaceState2D
) -> float:
var angle: float = 0
for i in range(1, collisions.size(), 2):
var from := collisions[i] + Vector2.UP
var to := collisions[i] + Vector2.DOWN
var query := PhysicsRayQueryParameters2D.create(from, to)
var result := space_state.intersect_ray(query)
if result["normal"] != Vector2.ZERO:
var normal := result["normal"] as Vector2
angle = -normal.angle_to(Vector2.UP)
if is_zero_approx(angle):
return angle
return angle

View File

@@ -244,61 +244,32 @@ func _draw() -> void:
var motion := (pos - pos_prev) * motion_proportion
pos = pos_prev + motion
var is_on_floor_prev := is_on_floor
var is_on_ceiling_prev := is_on_ceiling
var is_on_wall_prev := is_on_wall
var shape_pos := to_global(pos) + Vector2.UP * (_player_size.y / 2.0)
var floor_collisions := Player.collide_shape(
shape_pos, Vector2.DOWN, _player_shape, space_state, 3
)
var ceiling_collisions := Player.collide_shape(
shape_pos, Vector2.UP, _player_shape, space_state
)
var wall_collisions := Player.collide_shape(
shape_pos, Vector2.LEFT, _player_shape, space_state
)
if not wall_collisions:
wall_collisions = Player.collide_shape(
shape_pos, Vector2.RIGHT, _player_shape, space_state
)
var handler := Player.CollisionHandler.new()
handler.is_on_floor = is_on_floor
handler.is_on_ceiling = is_on_ceiling
handler.is_on_wall = is_on_wall
handler.floor_angle = floor_angle
handler.jump_released = jump_released
handler.velocity = velocity
is_on_floor = floor_collisions.size() > 0
is_on_ceiling = ceiling_collisions.size() > 0
is_on_wall = wall_collisions.size() > 0
var is_on_floor_changed := not is_on_floor_prev and is_on_floor
if is_on_floor_changed:
floor_angle = Player.raycast_floor_angle(floor_collisions, space_state)
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 or jump_released)
and not is_zero_approx(velocity.x)
handler.handle(
space_state, shape_pos, _player_shape, _wall_bounce_velocity_loss
)
velocity = (
Player
. process_collision(
velocity,
hit_slope,
hit_ceiling,
hit_wall,
floor_angle,
_wall_bounce_velocity_loss,
)
)
is_on_floor = handler.is_on_floor
is_on_ceiling = handler.is_on_ceiling
is_on_wall = handler.is_on_wall
floor_angle = handler.floor_angle
jump_released = handler.jump_released
velocity = handler.velocity
if hit_floor or hit_slope:
if handler.hit_floor or handler.hit_slope:
_draw_collision_hit(pos, SIDE_BOTTOM, _hit_color)
if hit_ceiling:
if handler.hit_ceiling:
_draw_collision_hit(pos, SIDE_TOP, _hit_color)
if hit_wall:
if handler.hit_wall:
_draw_collision_hit(
pos, SIDE_LEFT if velocity.x > 0 else SIDE_RIGHT, _hit_color
)