use bevy::prelude::*;
use leafwing_input_manager::prelude::*;
use super::cone::Cone;
#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
pub struct DollyPosCtrlMoveSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
pub struct DollyPosCtrlInputSetupSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
pub struct DollyPosCtrlEntitySetupSet;
pub struct DollyPosCtrl;
impl Plugin for DollyPosCtrl {
fn build(&self, app: &mut App) {
app.add_plugins(InputManagerPlugin::<MoveAction>::default());
app.init_resource::<DollyPosCtrlConfig>();
app.add_systems(
Startup,
(
dolly_pos_ctrl_config_input_setup.in_set(DollyPosCtrlInputSetupSet),
dolly_pos_ctrl_config_entity_setup.in_set(DollyPosCtrlEntitySetupSet),
),
);
app.add_systems(
Update,
dolly_pos_ctrl_move_update
.in_set(DollyPosCtrlMoveSet)
.run_if(use_dolly_pos_ctrl_config),
);
}
}
#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)]
pub enum MoveAction {
Forward,
Backward,
StrafeLeft,
StrafeRight,
Up,
Down,
RotateLeft,
RotateRight,
None,
}
#[derive(Resource)]
pub struct DollyPosCtrlConfig {
pub enabled: bool,
pub move_speed: f32,
pub rot_speed: f32,
pub pin: bool,
pub transform: Transform,
pub player: DollyCameraPlayer,
}
#[derive(Default, PartialEq, Eq, Debug, Clone, Copy, Hash)]
pub enum DollyCameraPlayer {
#[default]
DefaultPlayer,
Entity(Entity),
None,
}
impl Default for DollyPosCtrlConfig {
fn default() -> Self {
DollyPosCtrlConfig {
enabled: true,
move_speed: 1.2,
rot_speed: 0.05,
pin: true,
transform: Transform::from_translation(Vec3::new(0., 0.5, 0.))
.with_rotation(Quat::IDENTITY),
player: DollyCameraPlayer::default(),
}
}
}
fn use_dolly_pos_ctrl_config(config: Res<DollyPosCtrlConfig>) -> bool {
config.enabled
}
#[derive(Component)]
struct DollyPosCtrlAction;
fn dolly_pos_ctrl_config_input_setup(mut commands: Commands) {
commands.spawn((DollyPosCtrlAction, DollyPosCtrlInputBundle::default()));
}
#[derive(Component)]
pub struct DollyPosCtrlMove;
#[derive(Bundle)]
struct DollyPosCtrlInputBundle {
input_manager: InputManagerBundle<MoveAction>,
}
impl Default for DollyPosCtrlInputBundle {
fn default() -> Self {
use MoveAction::*;
let input_map = InputMap::default()
.with(Forward, KeyCode::KeyW)
.with(Forward, KeyCode::ArrowUp)
.with(Forward, GamepadButton::DPadUp)
.with(Backward, KeyCode::KeyS)
.with(Backward, KeyCode::ArrowDown)
.with(Backward, GamepadButton::DPadDown)
.with(StrafeLeft, KeyCode::KeyA)
.with(StrafeLeft, KeyCode::ArrowLeft)
.with(StrafeLeft, GamepadButton::DPadLeft)
.with(StrafeRight, KeyCode::KeyD)
.with(StrafeRight, KeyCode::ArrowRight)
.with(StrafeRight, GamepadButton::DPadRight)
;
let input_manager = InputManagerBundle {
input_map,
action_state: ActionState::default(),
};
Self { input_manager }
}
}
fn spawn_default_player(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
config: Res<DollyPosCtrlConfig>,
) {
let cone_mesh = meshes.add(Mesh::from(Cone {
height: 0.2,
radius: 0.1,
subdivisions: 5,
}));
let player_mat = materials.add(StandardMaterial {
base_color: Color::srgba(1.0, 0.0, 0.0, 0.5),
unlit: true,
..default()
});
commands
.spawn(config.transform)
.with_children(|cell| {
cell.spawn((
Mesh3d(cone_mesh.clone()),
MeshMaterial3d(player_mat.clone()),
Transform::from_rotation(Quat::from_rotation_x(std::f32::consts::FRAC_PI_2)),
));
})
.insert(DollyPosCtrlMove);
}
#[allow(unused_mut)]
fn dolly_pos_ctrl_config_entity_setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
config: Res<DollyPosCtrlConfig>,
) {
match config.player {
DollyCameraPlayer::DefaultPlayer => {
spawn_default_player(commands, meshes, materials, config);
}
DollyCameraPlayer::Entity(e) => {
commands.entity(e).insert(DollyPosCtrlMove);
}
DollyCameraPlayer::None => (),
}
}
fn dolly_pos_ctrl_move_update(
time: Res<Time>,
config: Res<DollyPosCtrlConfig>,
mut transforms: Query<&mut Transform, With<DollyPosCtrlMove>>,
act_query: Query<&ActionState<MoveAction>, With<DollyPosCtrlAction>>,
) {
let action_state = act_query.single();
for mut transform in transforms.iter_mut() {
let (_, mut rotation) = transform.rotation.to_axis_angle();
let mut velocity = Vec3::ZERO;
let local_z = transform.local_z();
let forward = Vec3::new(local_z.x, 0., local_z.z);
let right = transform.rotation * -Vec3::X;
velocity += forward * action_state.clamped_value(&MoveAction::Forward);
velocity += forward * -action_state.clamped_value(&MoveAction::Backward);
velocity += right * action_state.clamped_value(&MoveAction::StrafeRight);
velocity += right * -action_state.clamped_value(&MoveAction::StrafeLeft);
velocity += Vec3::Y * action_state.clamped_value(&MoveAction::Up);
velocity += Vec3::Y * -action_state.clamped_value(&MoveAction::Down);
if action_state.pressed(&MoveAction::RotateRight) {
if rotation > std::f32::consts::FRAC_PI_2 * 4.0 - config.rot_speed {
rotation = 0.0;
}
rotation += action_state.clamped_value(&MoveAction::RotateRight) * config.rot_speed;
} else if action_state.pressed(&MoveAction::RotateLeft) {
if rotation < config.rot_speed {
rotation = std::f32::consts::FRAC_PI_2 * 4.0;
}
let mut delta_value = action_state.clamped_value(&MoveAction::RotateLeft);
if delta_value.is_sign_positive() {
delta_value *= -1.;
}
rotation += delta_value * config.rot_speed;
}
transform.rotation = Quat::from_rotation_y(rotation * -1.);
velocity = velocity.normalize();
if !velocity.is_nan() {
transform.translation += velocity * time.delta_secs() * config.move_speed;
}
}
}