// Look up a direct or indirect child control. public InputControl TryGetControl(InputControl parent, string path) { if (string.IsNullOrEmpty(path)) { throw new ArgumentException("path"); } if (m_Device == null) { return(null); } if (parent == null) { parent = m_Device; } var match = InputControlPath.TryFindChild(parent, path); if (match != null) { return(match); } if (ReferenceEquals(parent, m_Device)) { return(InputControlPath.TryFindControl(m_Device, string.Format("{0}/{1}", m_Device.name, path))); } return(null); }
////REVIEW: how does this system work in combination with actual user overrides //// (answer: we rebind based on the base path not the override path; thus user overrides are unaffected; //// and hopefully operate on more than just the path; probably action+path or something) ////TODO: add option to suppress any non-matching binding by setting its override to an empty path ////TODO: need ability to do this with a list of controls // For all bindings in the given action, if a binding matches a control in the given control // hierarchy, set an override on the binding to refer specifically to that control. // // Returns the number of overrides that have been applied. // // Use case: Say you have a local co-op game and a single action map that represents the // actions of any single player. To end up with action maps that are specific to // a certain player, you could, for example, clone the action map four times, and then // take four gamepad devices and use the methods here to have bindings be overridden // on each map to refer to a specific gamepad instance. // // Another example is having two XRControllers and two action maps can be on either hand. // At runtime you can dynamically override and re-override the bindings on the action maps // to use them with the controllers as desired. public static int ApplyBindingOverridesOnMatchingControls(this InputAction action, InputControl control) { if (action == null) { throw new ArgumentNullException("action"); } if (control == null) { throw new ArgumentNullException("control"); } var bindings = action.bindings; var bindingsCount = bindings.Count; var numMatchingControls = 0; for (var i = 0; i < bindingsCount; ++i) { var matchingControl = InputControlPath.TryFindControl(control, bindings[i].path); if (matchingControl == null) { continue; } action.ApplyBindingOverride(i, matchingControl.path); ++numMatchingControls; } return(numMatchingControls); }
public static InputControlScheme?FindControlSchemeForControl <TList>(InputDevice control, TList schemes) where TList : IEnumerable <InputControlScheme> { foreach (var scheme in schemes) { var requirements = scheme.m_DeviceRequirements; for (var i = 0; i < requirements.Length; ++i) { if (InputControlPath.TryFindControl(control, requirements[i].controlPath) != null) { return(scheme); } } } return(null); }
////REVIEW: have mode where instead of matching only the first device that matches a requirement, we match as many //// as we can get? (could be useful for single-player) /// <summary> /// Based on a list of devices, make a selection that matches the <see cref="deviceRequirements">requirements</see> /// imposed by the control scheme. /// </summary> /// <param name="devices">A list of devices to choose from.</param> /// <returns>A <see cref="MatchResult"/> structure containing the result of the pick. Note that this structure /// must be manually <see cref="MatchResult.Dispose">disposed</see> or unmanaged memory will be leaked.</returns> /// <remarks> /// Does not allocate managed memory. /// </remarks> public MatchResult PickDevicesFrom <TDevices>(TDevices devices) where TDevices : IReadOnlyList <InputDevice> { // Empty device requirements match anything while not really picking anything. if (m_DeviceRequirements == null || m_DeviceRequirements.Length == 0) { return(new MatchResult { m_Result = MatchResult.Result.AllSatisfied, }); } // Go through each requirement and match it. // NOTE: Even if `devices` is empty, we don't know yet whether we have a NoMatch. // All our devices may be optional. var haveAllRequired = true; var haveAllOptional = true; var requirementCount = m_DeviceRequirements.Length; var controls = new InputControlList <InputControl>(Allocator.Persistent, requirementCount); try { var orChainIsSatisfied = false; var orChainHasRequiredDevices = false; for (var i = 0; i < requirementCount; ++i) { var isOR = m_DeviceRequirements[i].isOR; var isOptional = m_DeviceRequirements[i].isOptional; // If this is an OR requirement and we already have a match in this OR chain, // skip this requirement. if (isOR && orChainIsSatisfied) { // Skill need to add an entry for this requirement. controls.Add(null); continue; } // Null and empty paths shouldn't make it into the list but make double // sure here. Simply ignore entries that don't have a path. var path = m_DeviceRequirements[i].controlPath; if (string.IsNullOrEmpty(path)) { controls.Add(null); continue; } // Find the first matching control among the devices we have. InputControl match = null; for (var n = 0; n < devices.Count; ++n) { var device = devices[n]; // See if we have a match. var matchedControl = InputControlPath.TryFindControl(device, path); if (matchedControl == null) { continue; // No. } // We have a match but if we've already match the same control through another requirement, // we can't use the match. if (controls.Contains(matchedControl)) { continue; } match = matchedControl; break; } // Check requirements in AND and OR chains. We look ahead here to find out whether // the next requirement is starting an OR chain. As the OR combines with the previous // requirement in the list, this affects our current requirement. var nextIsOR = i + 1 < requirementCount && m_DeviceRequirements[i + 1].isOR; if (nextIsOR) { // Shouldn't get here if the chain is already satisfied. Should be handled // at beginning of loop and we shouldn't even be looking at finding controls // in that case. Debug.Assert(!orChainIsSatisfied); // It's an OR with the next requirement. Depends on the outcome of other matches whether // we're good or not. if (match != null) { // First match in this chain. orChainIsSatisfied = true; } else { // Chain not satisfied yet. if (!isOptional) { orChainHasRequiredDevices = true; } } } else if (isOR && i == requirementCount - 1) { // It's an OR at the very end of the requirements list. Terminate // the OR chain. if (match == null) { if (orChainHasRequiredDevices) { haveAllRequired = false; } else { haveAllOptional = false; } } } else { // It's an AND. if (match == null) { if (isOptional) { haveAllOptional = false; } else { haveAllRequired = false; } } // Terminate ongoing OR chain. if (i > 0 && m_DeviceRequirements[i - 1].isOR) { if (!orChainIsSatisfied) { if (orChainHasRequiredDevices) { haveAllRequired = false; } else { haveAllOptional = false; } } orChainIsSatisfied = false; } } // Add match to list. Maybe null. controls.Add(match); } // We should have matched each of our requirements. Debug.Assert(controls.Count == requirementCount); } catch (Exception) { controls.Dispose(); throw; } return(new MatchResult { m_Result = !haveAllRequired ? MatchResult.Result.MissingRequired : !haveAllOptional ? MatchResult.Result.MissingOptional : MatchResult.Result.AllSatisfied, m_Controls = controls, m_Requirements = m_DeviceRequirements, }); }