27
|
1 #############################################################################
|
|
2 #
|
|
3 # Copyright (c) 2010 by Casey Duncan
|
|
4 # All Rights Reserved.
|
|
5 #
|
|
6 # This software is subject to the provisions of the MIT License
|
|
7 # A copy of the license should accompany this distribution.
|
|
8 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
9 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
10 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
11 #
|
|
12 #############################################################################
|
|
13 """Control systems for binding controls to game logic"""
|
|
14
|
|
15 import grease
|
|
16 from pyglet.window import key
|
|
17
|
|
18 class KeyControls(grease.System):
|
|
19 """System that maps subclass-defined action methods to keys.
|
|
20
|
|
21 Keys may be mapped in the subclass definition using decorators
|
|
22 defined here as class methods or at runtime using the ``bind_key_*``
|
|
23 instance methods.
|
|
24
|
|
25 See :ref:`an example implementation in the tutorial <tut-controls-example>`.
|
|
26 """
|
|
27 MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CAPSLOCK)
|
|
28 """The MODIFIER_MASK allows you to filter out modifier keys that should be
|
|
29 ignored by the application. By default, capslock, numlock, and scrolllock
|
|
30 are ignored.
|
|
31 """
|
|
32
|
|
33 world = None
|
|
34 """:class:`grease.World` object this system is bound to"""
|
|
35
|
|
36 def __init__(self):
|
|
37 self._key_press_map = {}
|
|
38 self._key_release_map = {}
|
|
39 self._key_hold_map = {}
|
|
40 for name in self.__class__.__dict__:
|
|
41 member = getattr(self, name)
|
|
42 if hasattr(member, '_grease_hold_key_binding'):
|
|
43 for binding in member._grease_hold_key_binding:
|
|
44 self.bind_key_hold(member, *binding)
|
|
45 if hasattr(member, '_grease_press_key_binding'):
|
|
46 for binding in member._grease_press_key_binding:
|
|
47 self.bind_key_press(member, *binding)
|
|
48 if hasattr(member, '_grease_release_key_binding'):
|
|
49 for binding in member._grease_release_key_binding:
|
|
50 self.bind_key_release(member, *binding)
|
|
51 self.held_keys = set()
|
|
52
|
|
53 ## decorator methods for binding methods to key input events ##
|
|
54
|
|
55 @classmethod
|
|
56 def key_hold(cls, symbol, modifiers=0):
|
|
57 """Decorator to bind a method to be executed where a key is held down"""
|
|
58 def bind(f):
|
|
59 if not hasattr(f, '_grease_hold_key_binding'):
|
|
60 f._grease_hold_key_binding = []
|
|
61 f._grease_hold_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK))
|
|
62 return f
|
|
63 return bind
|
|
64
|
|
65 @classmethod
|
|
66 def key_press(cls, symbol, modifiers=0):
|
|
67 """Decorator to bind a method to be executed where a key is initially depressed"""
|
|
68 def bind(f):
|
|
69 if not hasattr(f, '_grease_press_key_binding'):
|
|
70 f._grease_press_key_binding = []
|
|
71 f._grease_press_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK))
|
|
72 return f
|
|
73 return bind
|
|
74
|
|
75 @classmethod
|
|
76 def key_release(cls, symbol, modifiers=0):
|
|
77 """Decorator to bind a method to be executed where a key is released"""
|
|
78 def bind(f):
|
|
79 if not hasattr(f, '_grease_release_key_binding'):
|
|
80 f._grease_release_key_binding = []
|
|
81 f._grease_release_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK))
|
|
82 return f
|
|
83 return bind
|
|
84
|
|
85 ## runtime binding methods ##
|
|
86
|
|
87 def bind_key_hold(self, method, key, modifiers=0):
|
|
88 """Bind a method to a key at runtime to be invoked when the key is
|
|
89 held down, this replaces any existing key hold binding for this key.
|
|
90 To unbind the key entirely, pass ``None`` for method.
|
|
91 """
|
|
92 if method is not None:
|
|
93 self._key_hold_map[key, modifiers & self.MODIFIER_MASK] = method
|
|
94 else:
|
|
95 try:
|
|
96 del self._key_hold_map[key, modifiers & self.MODIFIER_MASK]
|
|
97 except KeyError:
|
|
98 pass
|
|
99
|
|
100 def bind_key_press(self, method, key, modifiers=0):
|
|
101 """Bind a method to a key at runtime to be invoked when the key is initially
|
|
102 pressed, this replaces any existing key hold binding for this key. To unbind
|
|
103 the key entirely, pass ``None`` for method.
|
|
104 """
|
|
105 if method is not None:
|
|
106 self._key_press_map[key, modifiers & self.MODIFIER_MASK] = method
|
|
107 else:
|
|
108 try:
|
|
109 del self._key_press_map[key, modifiers & self.MODIFIER_MASK]
|
|
110 except KeyError:
|
|
111 pass
|
|
112
|
|
113 def bind_key_release(self, method, key, modifiers=0):
|
|
114 """Bind a method to a key at runtime to be invoked when the key is releaseed,
|
|
115 this replaces any existing key hold binding for this key. To unbind
|
|
116 the key entirely, pass ``None`` for method.
|
|
117 """
|
|
118 if method is not None:
|
|
119 self._key_release_map[key, modifiers & self.MODIFIER_MASK] = method
|
|
120 else:
|
|
121 try:
|
|
122 del self._key_release_map[key, modifiers & self.MODIFIER_MASK]
|
|
123 except KeyError:
|
|
124 pass
|
|
125
|
|
126 def step(self, dt):
|
|
127 """invoke held key functions"""
|
|
128 already_run = set()
|
|
129 for key in self.held_keys:
|
|
130 func = self._key_hold_map.get(key)
|
|
131 if func is not None and func not in already_run:
|
|
132 already_run.add(func)
|
|
133 func(dt)
|
|
134
|
|
135 def on_key_press(self, key, modifiers):
|
|
136 """Handle pyglet key press. Invoke key press methods and
|
|
137 activate key hold functions
|
|
138 """
|
|
139 key_mod = (key, modifiers & self.MODIFIER_MASK)
|
|
140 if key_mod in self._key_press_map:
|
|
141 self._key_press_map[key_mod]()
|
|
142 self.held_keys.add(key_mod)
|
|
143
|
|
144 def on_key_release(self, key, modifiers):
|
|
145 """Handle pyglet key release. Invoke key release methods and
|
|
146 deactivate key hold functions
|
|
147 """
|
|
148 key_mod = (key, modifiers & self.MODIFIER_MASK)
|
|
149 if key_mod in self._key_release_map:
|
|
150 self._key_release_map[key_mod]()
|
|
151 self.held_keys.discard(key_mod)
|
|
152
|
|
153
|
|
154 if __name__ == '__main__':
|
|
155 import pyglet
|
|
156
|
|
157 class TestKeyControls(KeyControls):
|
|
158
|
|
159 MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CTRL)
|
|
160
|
|
161 remapped = False
|
|
162
|
|
163 @KeyControls.key_hold(key.UP)
|
|
164 @KeyControls.key_hold(key.W)
|
|
165 def up(self, dt):
|
|
166 print 'UP!'
|
|
167
|
|
168 @KeyControls.key_hold(key.LEFT)
|
|
169 @KeyControls.key_hold(key.A)
|
|
170 def left(self, dt):
|
|
171 print 'LEFT!'
|
|
172
|
|
173 @KeyControls.key_hold(key.RIGHT)
|
|
174 @KeyControls.key_hold(key.D)
|
|
175 def right(self, dt):
|
|
176 print 'RIGHT!'
|
|
177
|
|
178 @KeyControls.key_hold(key.DOWN)
|
|
179 @KeyControls.key_hold(key.S)
|
|
180 def down(self, dt):
|
|
181 print 'DOWN!'
|
|
182
|
|
183 @KeyControls.key_press(key.SPACE)
|
|
184 def fire(self):
|
|
185 print 'FIRE!'
|
|
186
|
|
187 @KeyControls.key_press(key.R)
|
|
188 def remap_keys(self):
|
|
189 if not self.remapped:
|
|
190 self.bind_key_hold(None, key.W)
|
|
191 self.bind_key_hold(None, key.A)
|
|
192 self.bind_key_hold(None, key.S)
|
|
193 self.bind_key_hold(None, key.D)
|
|
194 self.bind_key_hold(self.up, key.I)
|
|
195 self.bind_key_hold(self.left, key.J)
|
|
196 self.bind_key_hold(self.right, key.L)
|
|
197 self.bind_key_hold(self.down, key.K)
|
|
198 else:
|
|
199 self.bind_key_hold(None, key.I)
|
|
200 self.bind_key_hold(None, key.J)
|
|
201 self.bind_key_hold(None, key.K)
|
|
202 self.bind_key_hold(None, key.L)
|
|
203 self.bind_key_hold(self.up, key.W)
|
|
204 self.bind_key_hold(self.left, key.A)
|
|
205 self.bind_key_hold(self.right, key.D)
|
|
206 self.bind_key_hold(self.down, key.S)
|
|
207 self.remapped = not self.remapped
|
|
208
|
|
209
|
|
210 window = pyglet.window.Window()
|
|
211 window.clear()
|
|
212 controls = TestKeyControls()
|
|
213 window.push_handlers(controls)
|
|
214 pyglet.clock.schedule_interval(controls.step, 0.5)
|
|
215 pyglet.app.run()
|
|
216
|