Skip to content

Commit 4ee745a

Browse files
committed
Initial write-up of InputManager programming guide
1 parent 71cd4f0 commit 4ee745a

1 file changed

Lines changed: 135 additions & 1 deletion

File tree

‎doc/programming_guide/input/advanced_input.rst‎

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,138 @@
33
Advanced Input
44
==============
55

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

Comments
 (0)