-
Notifications
You must be signed in to change notification settings - Fork 22
Jump fall Through Platforms
Jump/fall through platforms are Rapier colliders that:
- Have their solver groups (not collision groups) set to not resolve the contact with the character entity.
- Have the
TnuaGhostPlatform
component.
By default, the player character will treat these platforms as thin air. To be able to stand on them, special intervention is required.
The jump/fall through platforms will need to have TnuaGhostPlatform
and SolverGroups
configured as mentioned above:
cmd.insert(SolverGroups {
// Or pick some other configuration - as long as it excludes the character
// that needs to jump or fall through this platform.
memberships: Group::empty(),
filters: Group::empty(),
});
cmd.insert(TnuaGhostPlatform);
The character entity needs, in addition to its TnuaProximitySensor
, to also have TnuaGhostSensor
. This sensor will contain a list of all the ghost sensor.
The user system that operates it needs to run inside the TnuaUserControlsSystemSet
set:
app.add_system(apply_tnua_fall_through_controls.in_set(TnuaUserControlsSystemSet));
Manually check if any ghost platforms were detected, and move the first one to override the proximity sensor's output:
fn apply_tnua_fall_through_controls(
mut query: Query<
&mut TnuaProximitySensor,
&TnuaGhostSensor,
>,
) {
for (mut proximity_sensor, ghost_sensor) in query.iter_mut() {
for ghost_platform in ghost_sensor.iter() {
if MIN_PROXIMITY <= ghost_platform.proximity {
proximity_sensor.output = Some(ghost_platform.clone());
break;
}
}
}
}
The proximity sensor terminates once it detects a non-ghost object, so all the ghost platforms are guaranteed to be higher (assuming the sensor faces down) and even if the is an original input - it's okay to overwrite it. The list is in order of proximity, so the first item will be the closest.
We are checking MIN_PROXIMITY <= ghost_platform.proximity
because by default the sensor starts detecting entities from the origin of the character entity. Since the character phases through the platform when jumping through it, we don't want to consider that character as standing on the platform when it is only halfway through it. Typically we'd want MAX_PROXIMITY
to be the distance from the character's origin to the bottom of its collider, so that we get the same climbing behavior we get with fully solid platforms.
Peek.2023-06-05.19-44.mp4
Since standing on a ghost platform requires actively overriding the proximity sensor's output
, falling through can be as simple as not overriding it:
if fall_through_input_invoked() {
for ghost_platform in ghost_sensor.iter() {
if MIN_PROXIMITY <= ghost_platform.proximity {
proximity_sensor.output = Some(ghost_platform.clone());
break;
}
}
}
fall_through_input_invoked()
is placeholder for actual user input the system needs to check.
The drawback of this approach is that if the player leaves the button too soon, the fall through will be cancelled and visibly "reverted":
Peek.2023-06-05.19-46.mp4
This may not be an issue if the character floats very low above the ground (in which case many advantages of the floating character model are lost). In cases it is an issue, TnuaSimpleFallThroughPlatformsHelper
can be used to solve it.
TnuaSimpleFallThroughPlatformsHelper
is a component that needs to be added to the character entity:
cmd.insert(TnuaPlatformerBundle {
config: TnuaPlatformerConfig {
// ...
},
..Default::default()
});
cmd.insert(TnuaGhostSensor::default()); // we already needed this one for the previous methods
cmd.insert(TnuaSimpleFallThroughPlatformsHelper::default());
In the control system, instead of operating over the proximity and ghost sensors directly, we pass them to the helper (which we need to get mutably):
let mut handle = fall_through_helper.with(proximity_sensor.as_mut(), ghost_sensor, MIN_PROXIMITY);
We can then use the handle to control the fall through:
if fall_through_input_invoked() {
handle.try_falling(that_input_was_just_pressed())
} else {
handle.dont_fall();
}
Again - fall_through_input_invoked()
and that_input_was_just_pressed()
are placeholders for actual user input the system needs to check.
This will cause the character to fall through a single layer of ghost platforms. When the just_pressed
argument is passed as true
to try_falling
, the helper will make a list of all the ghost platforms it currently detects. Typically that would be everything up to cling_distance
below the ground. In the next frames, where just_pressed
is false
, the character will only fall through these platforms.
When the player releases that button, and dont_fall
is getting called each frame instead, the platforms remain in that list until the character finishes falling through them. This ensures the fall is not visibly reverted.
Peek.2023-06-05.19-48.mp4
By always passing true
to just_pressed
, we can update the list of ghost platforms to fall through on each and every frame, making the character fall down until the player releases the button or until the character reaches a solid object.
if fall_through_input_invoked() {
handle.try_falling(that_input_was_just_pressed())
} else {
handle.dont_fall();
}