refactor!: Split everything into submodules

- Bump version to 0.2.0
- Split every module to systems, observers, plugins, etc
- Renamed Crate to Container
- Removed Wall component
- Removed try_insert_item system
- Removed inventory::ui module
This commit is contained in:
Alexey 2026-03-19 14:23:26 +03:00
commit 7c386d4128
20 changed files with 830 additions and 772 deletions

41
src/input/mod.rs Normal file
View file

@ -0,0 +1,41 @@
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;
use serde::{
Serialize,
Deserialize
};
pub mod plugin;
#[derive(Actionlike, PartialEq, Eq, Hash, Debug, Clone, Reflect, Serialize, Deserialize)]
pub enum InputAction {
#[actionlike(Axis)]
Move,
ToggleInventory,
Interact,
}
impl InputAction {
pub fn default_input_map() -> InputMap<Self> {
InputMap::default()
.with_axis(Self::Move, VirtualAxis::ad())
.with_axis(Self::Move, GamepadAxis::LeftStickX)
.with(Self::ToggleInventory, KeyCode::KeyI)
.with(Self::ToggleInventory, GamepadButton::Select)
.with(Self::Interact, KeyCode::KeyE)
.with(Self::Interact, GamepadButton::East)
}
}
#[derive(Actionlike, PartialEq, Eq, Hash, Debug, Clone, Reflect, Serialize, Deserialize)]
pub enum UiAction {
Rotate,
}
impl UiAction {
pub fn default_input_map() -> InputMap<Self> {
InputMap::default()
.with(Self::Rotate, KeyCode::KeyR)
.with(Self::Rotate, GamepadButton::West)
}
}

246
src/input/plugin.rs Normal file
View file

@ -0,0 +1,246 @@
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)]
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)]
#[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)]
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, Deref)]
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 + GetTypeRegistration> {
_phantom: PhantomData<T>,
extensions: &'static [&'static str],
}
impl<T> InputAssetPlugin<T>
where T: Sized + Hash + Eq + Reflect + TypePath + Actionlike + DeserializeOwned + GetTypeRegistration {
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 + GetTypeRegistration {
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((
TomlAssetPlugin::<InputAsset<T>>::new(&self.extensions),
InputManagerPlugin::<T>::default()
));
}
}