bevy-expedition-demo/src/input/plugin.rs

268 lines
9.5 KiB
Rust

use std::{
any::{
Any,
TypeId,
},
collections::HashMap,
hash::Hash,
marker::PhantomData,
};
use bevy::{
prelude::*,
reflect::GetTypeRegistration,
};
use bevy_common_assets::toml::TomlAssetPlugin;
use leafwing_input_manager::{
Actionlike,
plugin::InputManagerPlugin,
prelude::*,
};
use serde::{
Deserialize,
Serialize,
de::DeserializeOwned,
};
const INPUT_ASSET_EXTENSIONS: [&'static str; 1] = ["input.toml"];
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Reflect)]
#[reflect(Clone, Debug, Serialize, Deserialize, Default, PartialEq)]
pub struct MultiInput {
pub keyboard: Option<Vec<KeyCode>>,
pub mouse: Option<Vec<MouseButton>>,
pub gamepad: Option<Vec<GamepadButton>>,
}
impl From<MultiInput> for InputKind {
fn from(value: MultiInput) -> Self {
Self::Button(value)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Reflect)]
#[reflect(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum InputKind {
Button(MultiInput),
Axis(Vec<Box<dyn Axislike>>),
DualAxis(Vec<Box<dyn DualAxislike>>),
}
#[derive(Default, Deref, DerefMut, Debug, Asset, Reflect, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[reflect(Debug, Clone, PartialEq)]
pub struct InputAsset<Name>
where Name: Sized + Hash + Eq + Reflect + TypePath + Actionlike {
#[serde(flatten)]
events: HashMap<Name, InputKind>,
}
fn copy_keys<Name, Button>(input_map: &mut InputMap<Name>, name: &Name, buttons: &Vec<Button>)
where Name: Sized + Hash + Eq + Reflect + TypePath + Actionlike,
Button: Buttonlike + Clone {
let bindings: Vec<(Name, Button)> = buttons.iter()
.map(|k| (name.clone(), k.to_owned())).collect();
input_map.insert_multiple(bindings);
}
impl<Name> From<InputAsset<Name>> for InputMap<Name>
where Name: Sized + Hash + Eq + Reflect + TypePath + Actionlike {
fn from(asset: InputAsset<Name>) -> Self {
let new_asset = (*asset).clone();
let mut input_map = InputMap::default();
for (name, input_kind) in new_asset.into_iter() {
match input_kind {
InputKind::Button(input) => {
if let Some(buttons) = input.keyboard {
copy_keys(&mut input_map, &name, &buttons);
}
if let Some(buttons) = input.mouse {
copy_keys(&mut input_map, &name, &buttons);
}
if let Some(buttons) = input.gamepad {
copy_keys(&mut input_map, &name, &buttons);
}
},
InputKind::Axis(axises) => {
for axis in axises {
input_map.insert_axis_boxed(name.clone(), axis);
}
},
InputKind::DualAxis(axises) => {
for axis in axises {
input_map.insert_dual_axis_boxed(name.clone(), axis);
}
},
}
}
input_map
}
}
impl<Name> InputAsset<Name>
where Name: Sized + Hash + Eq + Reflect + TypePath + Actionlike {
/// This method does several things:
/// - Replace all actions which both are contained in the [`InputAsset`] and in the [`InputMap`]
/// and have the same "type" (e.g. [`Buttonlike`]).
/// - Insert all actions which are not contained in the map
/// Common usecase for this is to update default values with this asset
pub fn replace_input_map_actions(&self, map: &mut InputMap<Name>) {
for (name, input_kind) in self.iter() {
match input_kind {
InputKind::Button(input) => {
if map.iter_buttonlike().find(|(n, _)| n == &name).is_some() {
map.clear_action(name);
}
if let Some(buttons) = &input.keyboard {
copy_keys(map, name, &buttons);
}
if let Some(buttons) = &input.mouse {
copy_keys(map, name, &buttons);
}
if let Some(buttons) = &input.gamepad {
copy_keys(map, name, &buttons);
}
},
InputKind::Axis(axises) => {
if map.iter_axislike().find(|(n, _)| n == &name).is_some() {
map.clear_action(name);
}
for axis in axises.iter() {
map.insert_axis_boxed(name.to_owned(), axis.to_owned());
}
},
InputKind::DualAxis(axises) => {
if map.iter_dual_axislike().find(|(n, _)| n == &name).is_some() {
map.clear_action(name);
}
for axis in axises.iter() {
map.insert_dual_axis_boxed(name.to_owned(), axis.to_owned());
}
}
}
}
}
}
fn try_multi_input_insert<T: Clone>(input_type: &mut Option<Vec<T>>, new_key: T) {
match input_type {
Some(key) => key.push(new_key),
None => {
let wrapped_new_key = Some(vec![new_key]);
*input_type = wrapped_new_key;
},
}
}
impl<Name> From<InputMap<Name>> for InputAsset<Name>
where Name: Sized + Hash + Eq + Reflect + TypePath + Actionlike + Default {
fn from(map: InputMap<Name>) -> Self {
let mut asset: InputAsset<Name> = InputAsset::default();
const KC_TYPE: TypeId = TypeId::of::<KeyCode>();
const MB_TYPE: TypeId = TypeId::of::<MouseButton>();
const GP_TYPE: TypeId = TypeId::of::<GamepadButton>();
for (name, buttonlikes) in map.iter_buttonlike() {
let multi_input = {
let input_kind = match asset.get_mut(name) {
Some(input_kind) => input_kind,
None => {
asset.insert(name.to_owned(), MultiInput::default().into());
asset.get_mut(name).unwrap()
}
};
let InputKind::Button(btn) = input_kind else {
continue;
};
btn
};
for buttonlike in buttonlikes.iter() {
let buttonlike = &(**buttonlike) as &dyn Any;
let checked_type = buttonlike.type_id();
if checked_type == KC_TYPE {
let new_key = (*buttonlike).downcast_ref::<KeyCode>().unwrap().to_owned();
try_multi_input_insert(&mut multi_input.keyboard, new_key);
} else if checked_type == MB_TYPE {
let new_key = (*buttonlike).downcast_ref::<MouseButton>().unwrap().to_owned();
try_multi_input_insert(&mut multi_input.mouse, new_key);
} else if checked_type == GP_TYPE {
let new_key = (*buttonlike).downcast_ref::<GamepadButton>().unwrap().to_owned();
try_multi_input_insert(&mut multi_input.gamepad, new_key);
}
}
}
for (name, axislikes) in map.iter_axislike() {
match asset.get_mut(name) {
Some(input_kind) => {
let InputKind::Axis(axises) = input_kind else {
continue;
};
for axislike in axislikes {
axises.push(axislike.clone());
}
},
None => {
asset.insert(name.clone(), InputKind::Axis(axislikes.clone()));
}
}
}
for (name, axislikes) in map.iter_dual_axislike() {
match asset.get_mut(name) {
Some(input_kind) => {
let InputKind::DualAxis(axises) = input_kind else {
continue;
};
for axislike in axislikes {
axises.push(axislike.clone());
}
},
None => {
asset.insert(name.clone(), InputKind::DualAxis(axislikes.clone()));
}
}
}
asset
}
}
#[derive(Resource, Debug, Deref, DerefMut, Reflect, Clone, PartialEq, Eq)]
#[reflect(Resource, Debug, Clone, PartialEq)]
pub struct InputAssetHandle<T: Sized + Hash + Eq + Reflect + TypePath + Actionlike> (Option<Handle<InputAsset<T>>>);
#[derive(Debug)]
pub struct InputAssetPlugin<T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned> {
_phantom: PhantomData<T>,
extensions: &'static [&'static str],
}
impl<T> InputAssetPlugin<T>
where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned {
pub fn new(extensions: &'static [&'static str]) -> Self {
Self {
_phantom: PhantomData,
extensions,
}
}
}
impl<T> Default for InputAssetPlugin<T>
where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned {
fn default() -> Self {
Self {
_phantom: PhantomData,
extensions: &INPUT_ASSET_EXTENSIONS,
}
}
}
impl<T> Plugin for InputAssetPlugin<T>
where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned + GetTypeRegistration {
fn build(&self, app: &mut App) {
app.add_plugins((
InputManagerPlugin::<T>::default(),
TomlAssetPlugin::<InputAsset<T>>::new(&self.extensions),
));
}
}