make player use move_and_collide instead of move_and_slide and reimplement most of collision handling
This commit is contained in:
@@ -10,12 +10,13 @@
|
||||
[sub_resource type="RectangleShape2D" id="RectangleShape2D_3vyb7"]
|
||||
size = Vector2(18, 26)
|
||||
|
||||
[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("_trajectory")]
|
||||
[node name="Player" type="CharacterBody2D" node_paths=PackedStringArray("_trajectory", "_shape")]
|
||||
collision_layer = 16
|
||||
floor_max_angle = 1.55334
|
||||
floor_snap_length = 0.0
|
||||
script = ExtResource("1_g2els")
|
||||
_trajectory = NodePath("Trajectory")
|
||||
_shape = NodePath("CollisionShape2D")
|
||||
|
||||
[node name="JumpParticlesHolder" type="Node2D" parent="."]
|
||||
|
||||
|
||||
@@ -2,12 +2,13 @@ class_name Player
|
||||
extends CharacterBody2D
|
||||
|
||||
signal collided(side: Side, force: Vector2)
|
||||
signal jumped(strength: float, velocity: Vector2)
|
||||
signal jumped(strength: float, _velocity: Vector2)
|
||||
|
||||
static var instance: Player
|
||||
|
||||
@export_group("References")
|
||||
@export var _trajectory: Trajectory
|
||||
@export var _shape: CollisionShape2D
|
||||
|
||||
@export_group("Metrics")
|
||||
@export var _move_speed: float = 1.5 * 60.0
|
||||
@@ -18,12 +19,17 @@ static var instance: Player
|
||||
@export var _jump_full_charge_frames: float = 35
|
||||
@export var _wall_bounce_velocity_loss: float = 0.5
|
||||
|
||||
var _velocity: Vector2 = Vector2.ZERO
|
||||
var _direction: float = 0
|
||||
var _is_charging_jump: bool = false
|
||||
var _charge_strength: float = 0
|
||||
var _jump_released: bool = false
|
||||
var _floor_angle: float = 0
|
||||
|
||||
var _is_on_floor: bool = false
|
||||
var _is_on_wall: bool = false
|
||||
var _is_on_ceiling: bool = false
|
||||
|
||||
var _freemove_enabled: bool = false
|
||||
|
||||
@onready var _saved_state: Vector2 = global_position
|
||||
@@ -47,12 +53,12 @@ static func process_movement(
|
||||
# falling
|
||||
new_velocity.y = (
|
||||
0.0
|
||||
if on_floor and is_zero_approx(floor_angle)
|
||||
if on_floor and is_zero_approx(floor_angle) and new_velocity.y >= 0.0
|
||||
else move_toward(new_velocity.y, fall_speed, delta * fall_acceleration)
|
||||
)
|
||||
|
||||
# moving
|
||||
if on_floor and is_zero_approx(floor_angle):
|
||||
if on_floor and is_zero_approx(floor_angle) and new_velocity.y >= 0.0:
|
||||
new_velocity.x = direction * move_speed if not is_charging_jump else 0.0
|
||||
|
||||
# sliding
|
||||
@@ -68,7 +74,6 @@ static func process_movement(
|
||||
|
||||
|
||||
static func process_collision(
|
||||
prev_velocity: Vector2,
|
||||
new_velocity: Vector2,
|
||||
hit_floor: bool,
|
||||
hit_slope: bool,
|
||||
@@ -77,18 +82,63 @@ static func process_collision(
|
||||
floor_angle: float,
|
||||
wall_bounce_velocity_loss: float,
|
||||
) -> Vector2:
|
||||
if hit_floor:
|
||||
if hit_floor and not hit_slope and not hit_wall and not hit_ceiling:
|
||||
new_velocity = Vector2.ZERO
|
||||
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 hit_wall:
|
||||
new_velocity.x = -prev_velocity.x * wall_bounce_velocity_loss
|
||||
new_velocity.x = -new_velocity.x
|
||||
if hit_ceiling or hit_wall:
|
||||
new_velocity.x *= wall_bounce_velocity_loss
|
||||
return new_velocity
|
||||
|
||||
|
||||
static func shapecast(
|
||||
pos: Vector2,
|
||||
direction: Vector2,
|
||||
shape: Shape2D,
|
||||
space_state: PhysicsDirectSpaceState2D
|
||||
) -> bool:
|
||||
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.intersect_shape(floor_query).size() > 0
|
||||
|
||||
|
||||
static func raycast_down(
|
||||
pos: Vector2, shape: RectangleShape2D, space_state: PhysicsDirectSpaceState2D
|
||||
) -> Dictionary:
|
||||
var half_width := shape.size.x / 2.0
|
||||
var from := pos + Vector2.UP * (shape.size.x + 1)
|
||||
var to := pos + Vector2.DOWN * (shape.size.x + 1)
|
||||
|
||||
var query_center := PhysicsRayQueryParameters2D.create(from, to)
|
||||
var result_center := space_state.intersect_ray(query_center)
|
||||
if result_center:
|
||||
return result_center
|
||||
|
||||
var query_left := PhysicsRayQueryParameters2D.create(
|
||||
from + Vector2.LEFT * half_width, to + Vector2.LEFT * half_width
|
||||
)
|
||||
var result_left := space_state.intersect_ray(query_left)
|
||||
if result_left:
|
||||
return result_left
|
||||
|
||||
var query_right := PhysicsRayQueryParameters2D.create(
|
||||
from + Vector2.RIGHT * half_width, to + Vector2.RIGHT * half_width
|
||||
)
|
||||
var result_right := space_state.intersect_ray(query_right)
|
||||
if result_right:
|
||||
return result_right
|
||||
|
||||
return {}
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
instance = self
|
||||
Debugger.add_event("collided")
|
||||
@@ -102,28 +152,23 @@ func _ready() -> void:
|
||||
|
||||
func _physics_process(delta: float) -> void:
|
||||
if _freemove_enabled:
|
||||
velocity = (
|
||||
(
|
||||
Input
|
||||
. get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
. normalized()
|
||||
)
|
||||
* _jump_speed
|
||||
* (4.0 if Input.is_action_pressed("jump") else 1.0)
|
||||
var direction := (
|
||||
Input
|
||||
. get_vector("move_left", "move_right", "move_up", "move_down")
|
||||
. normalized()
|
||||
)
|
||||
_velocity = (
|
||||
direction * _jump_speed * (4.0 if Input.is_action_pressed("jump") else 1.0)
|
||||
)
|
||||
move_and_slide()
|
||||
return
|
||||
|
||||
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()
|
||||
_gather_input(delta)
|
||||
|
||||
_gather_input(delta, is_on_floor_prev)
|
||||
|
||||
var new_velocity := process_movement(
|
||||
velocity,
|
||||
_velocity = process_movement(
|
||||
_velocity,
|
||||
delta,
|
||||
is_on_floor_prev,
|
||||
_is_on_floor,
|
||||
_floor_angle,
|
||||
_direction,
|
||||
_is_charging_jump,
|
||||
@@ -136,59 +181,25 @@ func _physics_process(delta: float) -> void:
|
||||
_fall_acceleration,
|
||||
)
|
||||
if _jump_released:
|
||||
jumped.emit(_charge_strength, new_velocity)
|
||||
jumped.emit(_charge_strength, _velocity)
|
||||
|
||||
# apply velocity
|
||||
velocity = new_velocity
|
||||
move_and_slide()
|
||||
move_and_collide(_velocity * delta)
|
||||
|
||||
# collisions
|
||||
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()
|
||||
and not is_zero_approx(new_velocity.x)
|
||||
)
|
||||
velocity = process_collision(
|
||||
new_velocity,
|
||||
velocity,
|
||||
hit_floor,
|
||||
hit_slope,
|
||||
hit_ceiling,
|
||||
hit_wall,
|
||||
_floor_angle,
|
||||
_wall_bounce_velocity_loss,
|
||||
)
|
||||
if hit_floor:
|
||||
_trajectory.visible = false
|
||||
collided.emit(SIDE_BOTTOM, new_velocity)
|
||||
if hit_ceiling:
|
||||
collided.emit(SIDE_TOP, new_velocity)
|
||||
if hit_wall:
|
||||
collided.emit(SIDE_LEFT if velocity.x > 0 else SIDE_RIGHT, new_velocity)
|
||||
_handle_collision()
|
||||
|
||||
queue_redraw()
|
||||
|
||||
Debugger.text("position", global_position)
|
||||
Debugger.text("velocity", new_velocity)
|
||||
Debugger.text("velocity_modified", velocity)
|
||||
Debugger.text("velocity", _velocity)
|
||||
Debugger.text("floor_angle", _floor_angle)
|
||||
Debugger.text("direction", _direction)
|
||||
Debugger.text("is_charging_jump", _is_charging_jump)
|
||||
Debugger.text("charge_strength", _charge_strength)
|
||||
Debugger.text("is_on_ceiling", is_on_ceiling())
|
||||
Debugger.text("is_on_ceiling_only", is_on_ceiling_only())
|
||||
Debugger.text("is_on_floor", is_on_floor())
|
||||
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("floor_angle", _floor_angle)
|
||||
|
||||
Debugger.text("is_on_floor", _is_on_floor, 2)
|
||||
Debugger.text("is_on_wall", _is_on_wall, 2)
|
||||
Debugger.text("is_on_ceiling", _is_on_ceiling, 2)
|
||||
|
||||
|
||||
func _unhandled_input(event: InputEvent) -> void:
|
||||
@@ -198,18 +209,18 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
_saved_state = global_position
|
||||
if event.is_action_pressed("load_state"):
|
||||
global_position = _saved_state
|
||||
velocity = Vector2.ZERO
|
||||
_velocity = Vector2.ZERO
|
||||
if event.is_action_pressed("toggle_freemove"):
|
||||
_freemove_enabled = not _freemove_enabled
|
||||
|
||||
|
||||
func _gather_input(delta: float, on_floor: bool) -> void:
|
||||
func _gather_input(delta: float) -> void:
|
||||
_direction = signf(Input.get_axis("move_left", "move_right"))
|
||||
|
||||
# jump charge start
|
||||
if (
|
||||
Input.is_action_just_pressed("jump")
|
||||
and on_floor
|
||||
and _is_on_floor
|
||||
and is_zero_approx(_floor_angle)
|
||||
):
|
||||
_is_charging_jump = true
|
||||
@@ -226,9 +237,67 @@ func _gather_input(delta: float, on_floor: bool) -> void:
|
||||
# jump charge release
|
||||
_jump_released = (
|
||||
_is_charging_jump
|
||||
and on_floor
|
||||
and _is_on_floor
|
||||
and is_zero_approx(_floor_angle)
|
||||
and (Input.is_action_just_released("jump") or _charge_strength >= 1.0)
|
||||
)
|
||||
if _jump_released:
|
||||
_is_charging_jump = false
|
||||
_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
|
||||
|
||||
var space_state := get_world_2d().direct_space_state
|
||||
_is_on_floor = shapecast(
|
||||
_shape.global_position, Vector2.DOWN, _shape.shape, space_state
|
||||
)
|
||||
_is_on_wall = (
|
||||
shapecast(_shape.global_position, Vector2.LEFT, _shape.shape, space_state)
|
||||
or shapecast(_shape.global_position, Vector2.RIGHT, _shape.shape, space_state)
|
||||
)
|
||||
_is_on_ceiling = shapecast(
|
||||
_shape.global_position, Vector2.UP, _shape.shape, space_state
|
||||
)
|
||||
|
||||
var is_on_floor_changed := not is_on_floor_prev and _is_on_floor
|
||||
if is_on_floor_changed and _is_on_floor:
|
||||
var raycast_result := raycast_down(
|
||||
global_position, _shape.shape as RectangleShape2D, space_state
|
||||
)
|
||||
if raycast_result:
|
||||
var normal := raycast_result["normal"] as Vector2
|
||||
_floor_angle = -normal.angle_to(Vector2.UP)
|
||||
else:
|
||||
_floor_angle = 0
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
_velocity = process_collision(
|
||||
_velocity,
|
||||
hit_floor,
|
||||
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)
|
||||
|
||||
@@ -54,7 +54,7 @@ func _draw() -> void:
|
||||
|
||||
|
||||
func _on_player_collided(side: Side, force: Vector2) -> void:
|
||||
Debugger.text("collision force.length()", force.length())
|
||||
Debugger.text("collision force.length()", force.length(), 2)
|
||||
if force.length() < _collision_particles_min_force and side == SIDE_BOTTOM:
|
||||
return
|
||||
var particles_transform := _particles_transform_bottom
|
||||
@@ -72,7 +72,7 @@ func _on_player_collided(side: Side, force: Vector2) -> void:
|
||||
|
||||
|
||||
func _on_player_jumped(strength: float, force: Vector2) -> void:
|
||||
Debugger.text("jump force.length()", force.length())
|
||||
Debugger.text("jump force.length()", force.length(), 2)
|
||||
if force.length() < _jump_particles_min_force:
|
||||
return
|
||||
var particles := _jump_particles.instantiate() as GPUParticles2D
|
||||
|
||||
@@ -198,6 +198,7 @@ func _draw() -> void:
|
||||
var is_on_floor: bool = false
|
||||
var direction: float = -1.0 if flip else 1.0
|
||||
var floor_angle: float = 0
|
||||
var space_state := get_world_2d().direct_space_state
|
||||
|
||||
for i in range(_steps):
|
||||
pos += velocity * delta
|
||||
@@ -224,7 +225,7 @@ func _draw() -> void:
|
||||
)
|
||||
|
||||
if _do_collisions:
|
||||
var motion_proportion := _cast_motion(pos_prev, pos)
|
||||
var motion_proportion := _cast_motion(pos_prev, pos, space_state)
|
||||
if not is_equal_approx(motion_proportion, 1.0):
|
||||
var motion := (pos - pos_prev) * motion_proportion
|
||||
pos = pos_prev + motion
|
||||
@@ -232,12 +233,15 @@ func _draw() -> void:
|
||||
var hit_floor: bool = false
|
||||
var hit_ceiling: bool = false
|
||||
|
||||
var shape_pos := to_global(pos) + Vector2.UP * (_player_size.y / 2.0)
|
||||
# hitting floor
|
||||
if velocity.y >= 0:
|
||||
var result := _shapecast(pos + Vector2.UP, pos + Vector2.DOWN)
|
||||
var result := Player.shapecast(
|
||||
shape_pos, Vector2.DOWN, _player_shape, space_state
|
||||
)
|
||||
if result:
|
||||
var raycast_result := _raycast(
|
||||
pos + Vector2.UP * 10, pos + Vector2.DOWN * 10
|
||||
var raycast_result := Player.raycast_down(
|
||||
to_global(pos), _player_shape, space_state
|
||||
)
|
||||
if raycast_result:
|
||||
var normal := raycast_result["normal"] as Vector2
|
||||
@@ -248,9 +252,8 @@ func _draw() -> void:
|
||||
is_on_floor = false
|
||||
# hitting ceiling
|
||||
if velocity.y < 0:
|
||||
var result := _shapecast(
|
||||
pos - Vector2(0, _player_size.y) + Vector2.DOWN,
|
||||
pos - Vector2(0, _player_size.y) + Vector2.UP
|
||||
var result := Player.shapecast(
|
||||
shape_pos, Vector2.UP, _player_shape, space_state
|
||||
)
|
||||
if result:
|
||||
hit_ceiling = true
|
||||
@@ -260,7 +263,6 @@ func _draw() -> void:
|
||||
velocity = (
|
||||
Player
|
||||
. process_collision(
|
||||
velocity,
|
||||
velocity,
|
||||
hit_floor,
|
||||
hit_slope,
|
||||
@@ -299,39 +301,9 @@ func _draw() -> void:
|
||||
draw_polyline(points, _line_color)
|
||||
|
||||
|
||||
func _shapecast(from: Vector2, to: Vector2) -> Array[Dictionary]:
|
||||
var space_state := get_world_2d().direct_space_state
|
||||
var query := PhysicsShapeQueryParameters2D.new()
|
||||
query.collision_mask = 1
|
||||
var origin := to_global(from)
|
||||
origin.y -= _player_size.y / 2.0
|
||||
query.transform = Transform2D(0, origin)
|
||||
query.motion = to - from
|
||||
query.shape = _player_shape
|
||||
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
|
||||
func _cast_motion(
|
||||
from: Vector2, to: Vector2, space_state: PhysicsDirectSpaceState2D
|
||||
) -> float:
|
||||
var query := PhysicsShapeQueryParameters2D.new()
|
||||
query.collision_mask = 1
|
||||
var origin := to_global(from)
|
||||
|
||||
Reference in New Issue
Block a user