Maze / Library /PackageCache /com.unity.inputsystem@1.6.1 /Samples~ /CustomComposite /CustomComposite.cs
| using System; | |
| using UnityEngine; | |
| using UnityEngine.InputSystem; | |
| using UnityEngine.InputSystem.Layouts; | |
| using UnityEngine.InputSystem.Utilities; | |
| using UnityEditor; | |
| using UnityEngine.InputSystem.Editor; | |
| using UnityEngine.UIElements; | |
| // Let's say we want to have a composite that takes an axis and uses | |
| // it's value to multiply the length of a vector from a stick. This could | |
| // be used, for example, to have the right trigger on the gamepad act as | |
| // a strength multiplier on the value of the left stick. | |
| // | |
| // We start by creating a class that is based on InputBindingComposite<>. | |
| // The type we give it is the type of value that we will compute. In this | |
| // case, we will consume a Vector2 from the stick so that is the type | |
| // of value we return. | |
| // | |
| // NOTE: By advertising the type of value we return, we also allow the | |
| // input system to filter out our composite if it is not applicable | |
| // to a specific type of action. For example, if an action is set | |
| // to "Value" as its type and its "Control Type" is set to "Axis", | |
| // our composite will not be shown as our value type (Vector2) is | |
| // incompatible with the value type of Axis (float). | |
| // | |
| // Also, we need to register our composite with the input system. And we | |
| // want to do it in a way that makes the composite visible in the action | |
| // editor of the input system. | |
| // | |
| // For that to happen, we need to call InputSystem.RegisterBindingComposite | |
| // sometime during startup. We make that happen by using [InitializeOnLoad] | |
| // in the editor and [RuntimeInitializeOnLoadMethod] in the player. | |
| [] | |
| // We can customize the way display strings are formed for our composite by | |
| // annotating it with DisplayStringFormatAttribute. The string is simply a | |
| // list with elements to be replaced enclosed in curly braces. Everything | |
| // outside those will taken verbatim. The fragments inside the curly braces | |
| // in this case refer to the binding composite parts by name. Each such | |
| // instance is replaced with the display text for the corresponding | |
| // part binding. | |
| [] | |
| public class CustomComposite : InputBindingComposite<Vector2> | |
| { | |
| // In the editor, the static class constructor will be called on startup | |
| // because of [InitializeOnLoad]. | |
| static CustomComposite() | |
| { | |
| // Trigger our RegisterBindingComposite code in the editor. | |
| Initialize(); | |
| } | |
| // In the player, [RuntimeInitializeOnLoadMethod] will make sure our | |
| // initialization code gets called during startup. | |
| [] | |
| private static void Initialize() | |
| { | |
| // This registers the composite with the input system. After calling this | |
| // method, we can have bindings reference the composite. Also, the | |
| // composite will show up in the action editor. | |
| // | |
| // NOTE: We don't supply a name for the composite here. The default logic | |
| // will take the name of the type ("CustomComposite" in our case) | |
| // and snip off "Composite" if used as a suffix (which is the case | |
| // for us) and then use that as the name. So in our case, we are | |
| // registering a composite called "Custom" here. | |
| // | |
| // If we were to use our composite with the AddCompositeBinding API, | |
| // for example, it would look like this: | |
| // | |
| // myAction.AddCompositeBinding("Custom") | |
| // .With("Stick", "<Gamepad>/leftStick") | |
| // .With("Multiplier", "<Gamepad>/rightTrigger"); | |
| InputSystem.RegisterBindingComposite<CustomComposite>(); | |
| } | |
| // So, we need two parts for our composite. The part that delivers the stick | |
| // value and the part that delivers the axis multiplier. Note that each part | |
| // may be bound to multiple controls. The input system handles that for us | |
| // by giving us an integer identifier for each part that reads a single value | |
| // from however many controls are bound to the part. | |
| // | |
| // In our case, this could be used, for example, to bind the "multiplier" part | |
| // to both the left and the right trigger on the gamepad. | |
| // To tell the input system of a "part" binding that we need for a composite, | |
| // we add a public field with an "int" type and annotated with an [InputControl] | |
| // attribute. We set the "layout" property on the attribute to tell the system | |
| // what kind of control we expect to be bound to the part. | |
| // | |
| // NOTE: These part binding need to be *public fields* for the input system | |
| // to find them. | |
| // | |
| // So this is introduces a part to the composite called "multiplier" and | |
| // expecting an "Axis" control. The value of the field will be set by the | |
| // input system. It will be some internal, unique numeric ID for the part | |
| // which we can then use with InputBindingCompositeContext.ReadValue to | |
| // read out the value of just that part. | |
| [] | |
| public int multiplier; | |
| // The other part we need is for the stick. | |
| // | |
| // NOTE: We could use "Stick" here but "Vector2" is a little less restrictive. | |
| [] | |
| public int stick; | |
| // We may also expose "parameters" on our composite. These can be configured | |
| // graphically in the action editor and also through AddCompositeBinding. | |
| // | |
| // Let's say we want to allow the user to specify an additional scale factor | |
| // to apply to the value of "multiplier". We can do so by simply adding a | |
| // public field of type float. Any public field that is not annotated with | |
| // [InputControl] will be treated as a possible parameter. | |
| // | |
| // If we added a composite with AddCompositeBinding, we could configure the | |
| // parameter like so: | |
| // | |
| // myAction.AddCompositeBinding("Custom(scaleFactor=0.5)" | |
| // .With("Multiplier", "<Gamepad>/rightTrigger") | |
| // .With("Stick", "<Gamepad>/leftStick"); | |
| public float scaleFactor = 1; | |
| // Ok, so now we have all the configuration in place. The final piece we | |
| // need is the actual logic that reads input from "multiplier" and "stick" | |
| // and computes a final input value. | |
| // | |
| // We can do that by defining a ReadValue method which is the actual workhorse | |
| // for our composite. | |
| public override Vector2 ReadValue(ref InputBindingCompositeContext context) | |
| { | |
| // We read input from the parts we have by simply | |
| // supplying the part IDs that the input system has set up | |
| // for us to ReadValue. | |
| // | |
| // NOTE: Vector2 is a less straightforward than primitive value types | |
| // like int and float. If there are multiple controls bound to the | |
| // "stick" part, we need to tell the input system which one to pick. | |
| // We do so by giving it an IComparer. In this case, we choose | |
| // Vector2MagnitudeComparer to return the Vector2 with the greatest | |
| // length. | |
| var stickValue = context.ReadValue<Vector2, Vector2MagnitudeComparer>(stick); | |
| var multiplierValue = context.ReadValue<float>(multiplier); | |
| // The rest is simple. We just scale the vector we read by the | |
| // multiple from the axis and apply our scale factor. | |
| return stickValue * (multiplierValue * scaleFactor); | |
| } | |
| } | |
| // Our custom composite is complete and fully functional. We could stop here and | |
| // call it a day. However, for the sake of demonstration, let's say we also want | |
| // to customize how the parameters for our composite are edited. We have "scaleFactor" | |
| // so let's say we want to replace the default float inspector with a slider. | |
| // | |
| // We can replace the default UI by simply deriving a custom InputParameterEditor | |
| // for our composite. | |
| public class CustomCompositeEditor : InputParameterEditor<CustomComposite> | |
| { | |
| public override void OnGUI() | |
| { | |
| // Using the 'target' property, we can access an instance of our composite. | |
| var currentValue = target.scaleFactor; | |
| // The easiest way to lay out our UI is to simply use EditorGUILayout. | |
| // We simply assign the changed value back to the 'target' object. The input | |
| // system will automatically detect a change in value. | |
| target.scaleFactor = EditorGUILayout.Slider(m_ScaleFactorLabel, currentValue, 0, 2); | |
| } | |
| public override void OnDrawVisualElements(VisualElement root, Action onChangedCallback) | |
| { | |
| var slider = new Slider(m_ScaleFactorLabel.text, 0, 2) | |
| { | |
| value = target.scaleFactor, | |
| showInputField = true | |
| }; | |
| // Note: For UIToolkit sliders, as of Feb 2022, we can't register for the mouse up event directly | |
| // on the slider because an element inside the slider captures the event. The workaround is to | |
| // register for the event on the slider container. This will be fixed in a future version of | |
| // UIToolkit. | |
| slider.Q("unity-drag-container").RegisterCallback<MouseUpEvent>(evt => | |
| { | |
| target.scaleFactor = slider.value; | |
| onChangedCallback?.Invoke(); | |
| }); | |
| root.Add(slider); | |
| } | |
| private GUIContent m_ScaleFactorLabel = new GUIContent("Scale Factor"); | |
| } | |