fix(controls): combine multiple action bindings
This commit is contained in:
+1
@@ -428,6 +428,7 @@ dependencies = [
|
||||
"bevy_derive",
|
||||
"bevy_ecs",
|
||||
"bevy_input",
|
||||
"bevy_platform",
|
||||
"bevy_reflect",
|
||||
"bevy_state",
|
||||
"env_logger",
|
||||
|
||||
Regular → Executable
+16
-33
@@ -159,7 +159,6 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
|
||||
collected_inputs
|
||||
}
|
||||
|
||||
// TODO: ! Zero out values that did not arrive
|
||||
/// Fills the player's inputs based on the collected inputs and the current control bindings.
|
||||
///
|
||||
/// This function takes in the current inputs, updates them according to the specified control
|
||||
@@ -174,49 +173,33 @@ impl<A: Action, Ic: InputsContainer<A>, Gs: GameState> ControlsPlugin<A, Ic, Gs>
|
||||
) {
|
||||
// Check if the input container is accessible
|
||||
if let Some(inputs) = inputs_container.me_mut() {
|
||||
// Initialize a vector to hold tuples of actions and their corresponding bindings
|
||||
// TODO: only on controls change
|
||||
let mut action_binding_pairs: Vec<(&A, &BindingConfig<Gs>, &Binding<Gs>)> = Vec::new();
|
||||
|
||||
// Iterate over the control bindings and collect all action-binding pairs
|
||||
for (action, config) in controls.iter() {
|
||||
let mut action_value = InputValue::Empty;
|
||||
|
||||
// Iterate over the control bindings and collect all action-binding pairs
|
||||
for binding in config.bindings.iter() {
|
||||
action_binding_pairs.push((action, config, binding));
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the action-binding pairs by the length of the input sequence, in descending order
|
||||
action_binding_pairs.sort_by(|a, b| b.2.input.len().cmp(&a.2.input.len()));
|
||||
// Skip any binding with mismatched condition
|
||||
if binding
|
||||
.conditions
|
||||
.iter()
|
||||
.any(|condition| matches!(condition, BindingCondition::InGameState(state) if *state != *game_state.get()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Loop over each binding to apply the corresponding input actions
|
||||
'bindings_loop: for (action, _config, binding) in action_binding_pairs.iter() {
|
||||
// Check the binding conditions before applying
|
||||
for condition in &binding.conditions {
|
||||
match condition {
|
||||
// Skip this binding if the game state does not match the condition
|
||||
BindingCondition::InGameState(state) => {
|
||||
if *state != *game_state.get() {
|
||||
continue 'bindings_loop;
|
||||
match &binding.input {
|
||||
ButtonCombination::Single(input_type) => {
|
||||
if let Some(&value) = collected_inputs.get(&*input_type) {
|
||||
action_value = action_value.merge(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match the binding input type to update the player's inputs
|
||||
match &binding.input {
|
||||
ButtonCombination::Single(input_type) => {
|
||||
// TODO: Exclude this input if it has already been used
|
||||
if let Some(value) = collected_inputs.get(&*input_type) {
|
||||
inputs.forced_set(**action, *value);
|
||||
} else {
|
||||
inputs.forced_set(**action, InputValue::Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
inputs.forced_set(*action, action_value);
|
||||
}
|
||||
} else {
|
||||
// Log a warning if the inputs container cannot be accessed
|
||||
// TODO: Ensure this warning is only printed once
|
||||
log::warn!("cannot find me in inputs container");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ common_traits_conditions! {
|
||||
Mouse(MouseInput),
|
||||
// TODO: Gamepad(GamepadButtonType),
|
||||
// TODO: Touch screen
|
||||
// TODO: TouchPad inputs
|
||||
// TODO: TouchPad inputs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,6 +431,36 @@ impl InputValue {
|
||||
InputValue::Empty => 0.,
|
||||
}
|
||||
}
|
||||
|
||||
/// Merges another input value into this one.
|
||||
///
|
||||
/// `Empty` acts as an identity value. Boolean inputs are combined using
|
||||
/// logical OR. For other non-empty values, the existing value is preserved.
|
||||
///
|
||||
/// This is used when multiple control bindings contribute to the same action.
|
||||
pub(crate) fn merge(self, other: Self) -> Self {
|
||||
match (self, other) {
|
||||
// Empty doesn't affect an existing value
|
||||
(Self::Empty, rhs) => rhs,
|
||||
(lhs, Self::Empty) => lhs,
|
||||
|
||||
// Multiple boolean bindings combine with OR
|
||||
(Self::Boolean(lhs), Self::Boolean(rhs)) => Self::Boolean(lhs || rhs),
|
||||
|
||||
// For floats choose max
|
||||
// TODO: maybe user want to chose multiply same inputs of chose max
|
||||
(Self::Float(lhs), Self::Float(rhs)) => {
|
||||
if rhs.abs() > lhs.abs() {
|
||||
Self::Float(rhs)
|
||||
} else {
|
||||
Self::Float(lhs)
|
||||
}
|
||||
}
|
||||
|
||||
(lhs, _) if !lhs.is_empty() => lhs,
|
||||
(_, rhs) => rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
common_traits_conditions! {
|
||||
|
||||
Reference in New Issue
Block a user