|
3 | 3 | Advanced Input |
4 | 4 | ============== |
5 | 5 |
|
6 | | -hello |
| 6 | +Advanced Input in Arcade is handled through the use of an :class:`arcade.InputManager` |
| 7 | + |
| 8 | +Key Concepts |
| 9 | +------------ |
| 10 | + |
| 11 | +Actions |
| 12 | +^^^^^^^ |
| 13 | + |
| 14 | +Actions are essentially named actions which can have inputs mapped to them. For example, you might have a ``Jump`` action |
| 15 | +with the Spacebar and the bottom controller face button mapped to it. You can then subscribe a callback to this action, which |
| 16 | +will be hit whenever the action is triggered, regardless of the underlying input source. |
| 17 | + |
| 18 | +Axis Inputs |
| 19 | +^^^^^^^^^^^ |
| 20 | + |
| 21 | +Axis Inputs are named inputs similar to actions, but are used for generally analog inputs or more "constant" inputs. These are |
| 22 | +intended to be polled for their state, rather than being notified via a callback. Generally these inputs would be used to map onto |
| 23 | +analog devices such as thumbsticks, or triggers on controllers, however as we will demonstrate later you can also use buttons or keyboard |
| 24 | +input to control these. These inputs generally make it simple to handle something like movement with either keyboard input or a controller. |
| 25 | + |
| 26 | +A Small Example |
| 27 | +--------------- |
| 28 | + |
| 29 | +Create an InputManager |
| 30 | +^^^^^^^^^^^^^^^^^^^^^^ |
| 31 | + |
| 32 | +.. code-block:: python |
| 33 | +
|
| 34 | + input_manager = arcade.InputManager() |
| 35 | +
|
| 36 | + input_manager.new_action("Jump") |
| 37 | + input_manager.add_action_input("Jump", arcade.Keys.SPACE) |
| 38 | + input_manager.add_action_input("Jump", arcade.ControllerButtons.BOTTOM_FACE) |
| 39 | +
|
| 40 | + input_manager.new_axis("Move") |
| 41 | + input_manager.add_axis_input("Move", arcade.Keys.LEFT, scale=-1.0) |
| 42 | + input_manager.add_axis_input("Move", arcade.Keys.RIGHT, scale=1.0) |
| 43 | + input_manager.add_axis_input("Move", arcade.ControllerAxes.LEFT_STICK_X, scale=1.0) |
| 44 | +
|
| 45 | +The above block of code demonstrates how you would create an :class:`arcade.InputManager` and create an action for jumping, and |
| 46 | +an axis for moving. You'll notice for the movement axis, we assign the left and right keyboard keys with a different scale, but for the |
| 47 | +controller input, we only define a positive scale value. This is because a controller feeds us analog input that might range anywhere from |
| 48 | +-1.0 to 1.0. When the input is a controller axis, Arcade will multiply the input against the specified scale value, so in this example |
| 49 | +using a scale of 1.0 means we get the exact value from the controller. |
| 50 | + |
| 51 | +However when we assign keyboard keys, or buttons of any kind to an axis, all we know from the underlying input is wether that key/button is |
| 52 | +pressed or not, but there is no value to multiply against a scale. In the case of a key/button being added to an axis input, Arcade will |
| 53 | +use the scale specified as the value for the axis. |
| 54 | + |
| 55 | +In order to actually make use of that InputManager, |
| 56 | + |
| 57 | +Handling the Jump Action |
| 58 | +^^^^^^^^^^^^^^^^^^^^^^^^ |
| 59 | + |
| 60 | +For handling actions from our InputManager, we have two options: |
| 61 | + |
| 62 | +- The global :meth:`arcade.Window.on_action` method which can be added to any :class:`arcade.Window`, :class:`arcade.View`, or :class:`arcade.Section` and will receive notification of all actions. |
| 63 | +- A callback function registered to our ``Jump`` action. |
| 64 | + |
| 65 | +The global :meth:`arcade.Window.on_action` approach: |
| 66 | + |
| 67 | +.. code-block:: python |
| 68 | +
|
| 69 | + def on_action(self, action: str, state: arcade.ActionState): |
| 70 | + if (action == "Jump"): |
| 71 | + do_player_jump() |
| 72 | +
|
| 73 | +.. note:: |
| 74 | + |
| 75 | + If you want to have the ``on_action`` function be on a class other than the Window, View, or Section. You can use :meth:`arcade.InputManager.register_action_handler` to |
| 76 | + explicitly register the function to the InputManager. However if the function is on the Window, View, or Section it will receive the actions automatically. |
| 77 | + |
| 78 | +The callback function approach: |
| 79 | + |
| 80 | +.. code-block:: python |
| 81 | +
|
| 82 | + def handle_jump(state: arcade.ActionState): |
| 83 | + do_player_jump() |
| 84 | +
|
| 85 | + input_manager.subscribe_to_action("Jump", handle_jump) |
| 86 | +
|
| 87 | +Handling the Move Axis |
| 88 | +^^^^^^^^^^^^^^^^^^^^^^ |
| 89 | + |
| 90 | +For handling axis inputs, it is important that we make sure the input manager is being updated. You will need to manually do this as part of your :meth:`arcade.Window.on_update` |
| 91 | +function, or via somewhere else that is called every update. |
| 92 | + |
| 93 | +.. code-block:: python |
| 94 | +
|
| 95 | + input_manager.update() |
| 96 | +
|
| 97 | +When the InputManager is updated, it will update the values of every Axis input within it. You can then poll it simply by using the :meth:`arcade.InputManager.axis` function. |
| 98 | +Below is an example of getting the axis value and one way you might use it to move a player. |
| 99 | + |
| 100 | +.. code-block:: |
| 101 | +
|
| 102 | + player.change_x = input_manager.axis("Move") * PLAYER_MOVEMENT_SPEED |
| 103 | +
|
| 104 | +Active Device Switching |
| 105 | +----------------------- |
| 106 | + |
| 107 | +One question you might have had, is that if we are handling inputs on the "Move" axis for both the keyboard and a controller, which devices input will be used? |
| 108 | +The answer depends on a couple different factors. |
| 109 | + |
| 110 | +It is possible to have never bound a controller to the InputManager, in which case the controller inputs will be ignored. If there is no controller bound, and the ``allow_keyboard`` option |
| 111 | +of the InputManager has been set to false, then all Axis values will just return 0, and no actions will ever be triggered. |
| 112 | + |
| 113 | +However in the scenario that ``allow_keyboard`` is true, and we have a controller bound, the InputManager has somewhat intelligent active device switching which will prioritize the last device that was used. |
| 114 | +For example if the controller is currently active, and the user pressed a key on their keyboard, Arcade will switch the active device to the keyboard, so the controller input will be ignored for axis inputs. |
| 115 | +If the player then presses a button on their controller, or moves a stick out of the deadzone, then it will switch back to the controller as the active device and ignore keyboard inputs. |
| 116 | + |
| 117 | +Controller Binding and Multiple Players |
| 118 | +--------------------------------------- |
| 119 | + |
| 120 | +One thing we haven't covered yet, is how the InputManager actually gets a controller bound to it. To keep the InputManager flexible, it does not do this automatically on it's own, and it is up to you to provide |
| 121 | +a :class:`pyglet.input.Controller` object to it. See the full examples below for more code on how to create a new Controller. |
| 122 | + |
| 123 | +Once you have a controller object, you can either bind it to an InputManager during creation by passing it to the ``controller`` argument. Or you can use the :meth:`arcade.InputManager.bind_controller` function after |
| 124 | +the InputManager has been created. If you want to unbind the controller, you can use :meth:`arcade.InputManager.unbind_controller`. |
| 125 | + |
| 126 | +If your game is intended to support multiple players via multiple controllers. The general idea is that you would have one InputManager per controller/player. A common approach to this would be to construct one InputManager |
| 127 | +with all of your desired actions/axis inputs, and then create a new one using the :meth:`arcade.InputManager.from_existing` function, as shown below. This function will copy all of the actions/axis from the specified |
| 128 | +InputManager into the new one, but ignore the controller binding, allowing you to bind a different controller to the newly created manager. |
| 129 | + |
| 130 | +.. code-block:: python |
| 131 | +
|
| 132 | + # Not real code, see Pyglet input docs for more on Controller management |
| 133 | + controller_one = Controller() |
| 134 | + controller_two = Controller() |
| 135 | +
|
| 136 | + input_manager_one = arcade.InputManager(controller_one) |
| 137 | + input_manager_one.new_action("Jump") |
| 138 | + input_manager_one.add_action_input("Jump", arcade.Keys.SPACE) |
| 139 | +
|
| 140 | + input_manager_two = arcade.InputManager.from_existing(input_manager_one, controller_two) |
0 commit comments