123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- using UnityEngine;
- using UnityEngine.InputSystem;
- using UnityEngine.InputSystem.Layouts;
- using UnityEngine.InputSystem.Utilities;
- #if UNITY_EDITOR
- using UnityEditor;
- using UnityEngine.InputSystem.Editor;
- #endif
- // 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.
- #if UNITY_EDITOR
- [InitializeOnLoad]
- #endif
- // 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.
- [DisplayStringFormat("{multiplier}*{stick}")]
- public class CustomComposite : InputBindingComposite<Vector2>
- {
- // In the editor, the static class constructor will be called on startup
- // because of [InitializeOnLoad].
- #if UNITY_EDITOR
- static CustomComposite()
- {
- // Trigger our RegisterBindingComposite code in the editor.
- Initialize();
- }
- #endif
- // In the player, [RuntimeInitializeOnLoadMethod] will make sure our
- // initialization code gets called during startup.
- [RuntimeInitializeOnLoadMethod]
- 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.
- [InputControl(layout = "Axis")]
- 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.
- [InputControl(layout = "Vector2")]
- 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.
- #if UNITY_EDITOR
- 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);
- }
- private GUIContent m_ScaleFactorLabel = new GUIContent("Scale Factor");
- }
- #endif
|