bevy_ahoy 0.1.0

A fun 3D Kinematic Character Controller for Bevy + Avian + BEI.
Documentation
use crate::{CharacterControllerState, prelude::*};

pub struct AhoyWaterPlugin;

impl Plugin for AhoyWaterPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            FixedUpdate,
            update_water.before(AhoySystems::MoveCharacters),
        );
    }
}

#[derive(Component, Default, Copy, Reflect, PartialEq, Clone, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
#[reflect(Component)]
pub struct WaterState {
    pub level: WaterLevel,
    pub speed: f32,
}

#[derive(Default, Copy, Reflect, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
pub enum WaterLevel {
    #[default]
    None,
    Feet,
    Waist,
    Head,
}

#[derive(Reflect, Component, Default)]
#[require(Sensor, Transform, GlobalTransform)]
#[reflect(Component)]
pub struct Water {
    pub speed: f32,
}

fn update_water(
    mut kccs: Query<(
        &Position,
        &CharacterController,
        &CharacterControllerState,
        &mut WaterState,
        &CollidingEntities,
    )>,
    waters: Query<(&Collider, &Position, &Rotation, &Water)>,
) {
    for (kcc_center, cfg, state, mut water_state, colliding_entities) in &mut kccs {
        water_state.level = WaterLevel::None;
        water_state.speed = f32::MAX;
        let kcc_center = kcc_center.0;
        let eye_pos = kcc_center
            + Vec3::Y
                * if state.crouching {
                    cfg.crouch_view_height
                } else {
                    cfg.standing_view_height
                };
        for (collider, position, rotation, water) in waters.iter_many(colliding_entities.iter()) {
            let level = if collider.contains_point(*position, *rotation, eye_pos) {
                WaterLevel::Head
            } else if collider.contains_point(*position, *rotation, kcc_center) {
                WaterLevel::Waist
            } else {
                WaterLevel::Feet
            };

            water_state.level = level.max(water_state.level);
            water_state.speed = water_state.speed.min(water.speed);
        }
    }
}