CustomComposite.cs (8529B)
1 using UnityEngine; 2 using UnityEngine.InputSystem; 3 using UnityEngine.InputSystem.Layouts; 4 using UnityEngine.InputSystem.Utilities; 5 6 #if UNITY_EDITOR 7 using UnityEditor; 8 using UnityEngine.InputSystem.Editor; 9 #endif 10 11 // Let's say we want to have a composite that takes an axis and uses 12 // it's value to multiply the length of a vector from a stick. This could 13 // be used, for example, to have the right trigger on the gamepad act as 14 // a strength multiplier on the value of the left stick. 15 // 16 // We start by creating a class that is based on InputBindingComposite<>. 17 // The type we give it is the type of value that we will compute. In this 18 // case, we will consume a Vector2 from the stick so that is the type 19 // of value we return. 20 // 21 // NOTE: By advertising the type of value we return, we also allow the 22 // input system to filter out our composite if it is not applicable 23 // to a specific type of action. For example, if an action is set 24 // to "Value" as its type and its "Control Type" is set to "Axis", 25 // our composite will not be shown as our value type (Vector2) is 26 // incompatible with the value type of Axis (float). 27 // 28 // Also, we need to register our composite with the input system. And we 29 // want to do it in a way that makes the composite visible in the action 30 // editor of the input system. 31 // 32 // For that to happen, we need to call InputSystem.RegisterBindingComposite 33 // sometime during startup. We make that happen by using [InitializeOnLoad] 34 // in the editor and [RuntimeInitializeOnLoadMethod] in the player. 35 #if UNITY_EDITOR 36 [InitializeOnLoad] 37 #endif 38 // We can customize the way display strings are formed for our composite by 39 // annotating it with DisplayStringFormatAttribute. The string is simply a 40 // list with elements to be replaced enclosed in curly braces. Everything 41 // outside those will taken verbatim. The fragments inside the curly braces 42 // in this case refer to the binding composite parts by name. Each such 43 // instance is replaced with the display text for the corresponding 44 // part binding. 45 [DisplayStringFormat("{multiplier}*{stick}")] 46 public class CustomComposite : InputBindingComposite<Vector2> 47 { 48 // In the editor, the static class constructor will be called on startup 49 // because of [InitializeOnLoad]. 50 #if UNITY_EDITOR 51 static CustomComposite() 52 { 53 // Trigger our RegisterBindingComposite code in the editor. 54 Initialize(); 55 } 56 57 #endif 58 59 // In the player, [RuntimeInitializeOnLoadMethod] will make sure our 60 // initialization code gets called during startup. 61 [RuntimeInitializeOnLoadMethod] 62 private static void Initialize() 63 { 64 // This registers the composite with the input system. After calling this 65 // method, we can have bindings reference the composite. Also, the 66 // composite will show up in the action editor. 67 // 68 // NOTE: We don't supply a name for the composite here. The default logic 69 // will take the name of the type ("CustomComposite" in our case) 70 // and snip off "Composite" if used as a suffix (which is the case 71 // for us) and then use that as the name. So in our case, we are 72 // registering a composite called "Custom" here. 73 // 74 // If we were to use our composite with the AddCompositeBinding API, 75 // for example, it would look like this: 76 // 77 // myAction.AddCompositeBinding("Custom") 78 // .With("Stick", "<Gamepad>/leftStick") 79 // .With("Multiplier", "<Gamepad>/rightTrigger"); 80 InputSystem.RegisterBindingComposite<CustomComposite>(); 81 } 82 83 // So, we need two parts for our composite. The part that delivers the stick 84 // value and the part that delivers the axis multiplier. Note that each part 85 // may be bound to multiple controls. The input system handles that for us 86 // by giving us an integer identifier for each part that reads a single value 87 // from however many controls are bound to the part. 88 // 89 // In our case, this could be used, for example, to bind the "multiplier" part 90 // to both the left and the right trigger on the gamepad. 91 92 // To tell the input system of a "part" binding that we need for a composite, 93 // we add a public field with an "int" type and annotated with an [InputControl] 94 // attribute. We set the "layout" property on the attribute to tell the system 95 // what kind of control we expect to be bound to the part. 96 // 97 // NOTE: These part binding need to be *public fields* for the input system 98 // to find them. 99 // 100 // So this is introduces a part to the composite called "multiplier" and 101 // expecting an "Axis" control. The value of the field will be set by the 102 // input system. It will be some internal, unique numeric ID for the part 103 // which we can then use with InputBindingCompositeContext.ReadValue to 104 // read out the value of just that part. 105 [InputControl(layout = "Axis")] 106 public int multiplier; 107 108 // The other part we need is for the stick. 109 // 110 // NOTE: We could use "Stick" here but "Vector2" is a little less restrictive. 111 [InputControl(layout = "Vector2")] 112 public int stick; 113 114 // We may also expose "parameters" on our composite. These can be configured 115 // graphically in the action editor and also through AddCompositeBinding. 116 // 117 // Let's say we want to allow the user to specify an additional scale factor 118 // to apply to the value of "multiplier". We can do so by simply adding a 119 // public field of type float. Any public field that is not annotated with 120 // [InputControl] will be treated as a possible parameter. 121 // 122 // If we added a composite with AddCompositeBinding, we could configure the 123 // parameter like so: 124 // 125 // myAction.AddCompositeBinding("Custom(scaleFactor=0.5)" 126 // .With("Multiplier", "<Gamepad>/rightTrigger") 127 // .With("Stick", "<Gamepad>/leftStick"); 128 public float scaleFactor = 1; 129 130 // Ok, so now we have all the configuration in place. The final piece we 131 // need is the actual logic that reads input from "multiplier" and "stick" 132 // and computes a final input value. 133 // 134 // We can do that by defining a ReadValue method which is the actual workhorse 135 // for our composite. 136 public override Vector2 ReadValue(ref InputBindingCompositeContext context) 137 { 138 // We read input from the parts we have by simply 139 // supplying the part IDs that the input system has set up 140 // for us to ReadValue. 141 // 142 // NOTE: Vector2 is a less straightforward than primitive value types 143 // like int and float. If there are multiple controls bound to the 144 // "stick" part, we need to tell the input system which one to pick. 145 // We do so by giving it an IComparer. In this case, we choose 146 // Vector2MagnitudeComparer to return the Vector2 with the greatest 147 // length. 148 var stickValue = context.ReadValue<Vector2, Vector2MagnitudeComparer>(stick); 149 var multiplierValue = context.ReadValue<float>(multiplier); 150 151 // The rest is simple. We just scale the vector we read by the 152 // multiple from the axis and apply our scale factor. 153 return stickValue * (multiplierValue * scaleFactor); 154 } 155 } 156 157 // Our custom composite is complete and fully functional. We could stop here and 158 // call it a day. However, for the sake of demonstration, let's say we also want 159 // to customize how the parameters for our composite are edited. We have "scaleFactor" 160 // so let's say we want to replace the default float inspector with a slider. 161 // 162 // We can replace the default UI by simply deriving a custom InputParameterEditor 163 // for our composite. 164 #if UNITY_EDITOR 165 public class CustomCompositeEditor : InputParameterEditor<CustomComposite> 166 { 167 public override void OnGUI() 168 { 169 // Using the 'target' property, we can access an instance of our composite. 170 var currentValue = target.scaleFactor; 171 172 // The easiest way to lay out our UI is to simply use EditorGUILayout. 173 // We simply assign the changed value back to the 'target' object. The input 174 // system will automatically detect a change in value. 175 target.scaleFactor = EditorGUILayout.Slider(m_ScaleFactorLabel, currentValue, 0, 2); 176 } 177 178 private GUIContent m_ScaleFactorLabel = new GUIContent("Scale Factor"); 179 } 180 #endif