| Home | Trees | Indices | Help |
|
|---|
|
|
1 # -*- coding: utf-8 -*-
2 """
3 Input helper to ease the input setup
4
5 @author: fabius
6 @copyright: fabius@2010
7 @license: GCTA give credit to (the) authors
8 @version: 0.1
9 @date: 2010-06
10 @contact: astelix (U{panda3d forums<http://www.panda3d.org/forums/profile.php?mode=viewprofile&u=752>})
11 @status: eternal WIP
12
13 @todo:
14 - introdurre bind/unbind dinamico dei controlli o di singoli input
15 """
16 __all__ = ['easyinput']
17
18 from direct.showbase.DirectObject import DirectObject
19 from direct.gui.OnscreenText import OnscreenText
20 from direct.task.Task import Task
21 import sys
22 from pandac.PandaModules import WindowProperties
23
24 #================================================================
25 #
26 try:
27 import pygame as PYG
28 PYG.init()
29 except ImportError:
30 print "[WARNING] Pygame module not present"
31 PYG=None
32
34 """
35 A class to ease the input bind of actions.
36 Setup:
37 - define a config string similar to what we find in a quake config file, where a bind command assign a keyword to an input device event to act as a bridge between the device input and the game action handler
38 - define a dictionary with the config keywords as keys and the handlers as values
39 """
40 _DEBUG=False
41 #------------------------------------------------------
42 #
43 # predefined bind names/named-handlers; you may change'em passing an overrider config text overrider
44 DEFAULT_CONFIG="""
45 //JOY ------
46 bind joy0-button1 "action"
47 //+- y axis movement : by default will fire forward/back events
48 bind joy0-axis1 "thrust"
49 bind joy0-button2 "jump"
50 //+- x axis movement : by default will fire moveleft/moveright events
51 bind joy0-axis0 "heading"
52 bind joy0-button11 "quit"
53 bind joy0-button6 "run"
54 //KB ------
55 bind enter "action"
56 bind w "forward"
57 bind s "back"
58 bind a "left"
59 bind d "right"
60 bind arrow_down "down"
61 bind arrow_up "up"
62 bind arrow_left "left"
63 bind arrow_right "right"
64 bind rcontrol "jump"
65 bind page_up "headup"
66 bind page_down "headdown"
67 bind escape "quit"
68 bind rshift "run"
69 bind \ "alwaysrun"
70 // MOUSE ------
71 //to unbind some event specify an empty string as handler
72 bind mouse1 "mouse1"
73 bind mouse2 "mouse2"
74 bind mouse3 "mouse3"
75 bind mouse-x "heading"
76 //+- z axis movement : by default will fire headup/headdown events
77 bind mouse-y "pitch"
78 bind wheel_up "zoomin"
79 bind wheel_down "zoomout"
80 """
82 """
83 @param config:
84 E{\}n sepatated string of 3 element lines:
85 [0]=bind [1]=event name [2]=bridge keyword.
86 @param bridge: dictionary of event keywordE{:}handler elements.
87 """
88 DirectObject.__init__(self)
89 #
90 self._DEBUG=debug
91 #
92 self.JOY=0
93
94 # self.config={'<command>':{'<event>': <handler>}}
95 self.config={}
96 self.add_config(self.DEFAULT_CONFIG)
97 self.add_config(config)
98
99 # bridge with predefined events connected to handlers to be overrided
100 _bridge={
101 'action': self.action,
102 'back': self.back,
103 'forward': self.forward,
104 'jump': self.jump,
105 'left': self.left,
106 'right': self.right,
107 'thrust': self.thrust,
108 'pitch': self.pitch,
109 'heading': self.heading,
110 'up': self.headup,
111 'down': self.headdown,
112 'quit': self.quitme,
113 'run': self.run,
114 'alwaysrun': self.alwaysrunToggle,
115 'zoomin': lambda foo: self.zoom(1),
116 'zoomout': lambda foo: self.zoom(-1),
117 }
118 # melt default bridge with user defined
119 for k,v in bridge.iteritems(): _bridge[k]=v
120
121 # returns the number of joysticks found
122 self.JOY=self.pyga_joysetup()
123
124 # bind operations happens here
125 self.readmouse_binds={}
126 readmouse={}
127 for evt, hndrepeat in self.config['bind'].iteritems():
128 hnd,repeat=hndrepeat
129 if hnd in _bridge:
130 hname=_bridge[hnd]
131 #bind joystick
132 if evt.startswith('joy'):
133 if self.JOY:
134 ###print ">>>JOaccept:%s->%s" % (evt, hname)
135 self.accept(evt, _bridge[hnd])
136 #bind mouse
137 elif evt.startswith('mouse'):
138 # if there is a bind for mouse movement will be created relative listeners to hear mouse-x or mouse-y events fired by a mouse polling task eventually created below
139 if evt in ["mouse-x", "mouse-y"]:
140 readmouse[evt]=_bridge[hnd]
141 else:
142 #here should bind button listeners, but not necessarily
143 ###print ">>>MOaccept:%s->%s" % (evt, hname)
144 self.accept(evt, _bridge[hnd], [1])
145 self.accept(evt+"-up", _bridge[hnd], [0])
146 # qui dovrebbe arrivare solo kbkey
147 else:
148 ###print ">>>KBaccept:%s->%s" % (evt, hname)
149 repeat='-repeat' if repeat else ''
150 self.accept(evt+'-up', _bridge[hnd], [0])
151 self.accept(evt+repeat, _bridge[hnd], [1])
152
153 #** spawn the mouse movement tracking task (see above the mouse bind part)
154 self.mouseTask=None
155 if readmouse: self.set_mouse_read(readmouse, True)
156 #taskMgr.popupControls()
157 ###print messenger
158 self.pyga_joytask=None
159
160 #------------------------------------------------------
161 #
163 """ Invoke this before this class object will be off duty """
164 self.ignoreAll()
165 if self.mouseTask:
166 taskMgr.remove(self.mouseTask)
167 self.mouseTask=None
168 if self.pyga_joytask:
169 taskMgr.remove(self.pyga_joytask)
170 self.pyga_joytask=None
171 props = WindowProperties()
172 props.setCursorHidden(False)
173 props.setMouseMode(WindowProperties.MAbsolute)
174 base.win.requestProperties(props)
175
176 #------------------------------------------------------
177 #
179 if self.mouseTask:
180 taskMgr.remove(self.mouseTask)
181 self.mouseTask=None
182
183 if activate:
184 if not self.readmouse_binds:
185 for xy in ['mouse-x', 'mouse-y']:
186 self.readmouse_binds[xy]=readmouse.get(xy, self._foo)
187 taskName = "_esnptmo-%s" % id(self)
188 self.mouseTask = taskMgr.add(self._read_mouse, taskName, sort=40)
189
190 props = WindowProperties()
191 props.setCursorHidden(activate)
192 props.setMouseMode(
193 [WindowProperties.MAbsolute, WindowProperties.MRelative][activate])
194 base.win.requestProperties(props)
195
196 #------------------------------------------------------
197 #
198 # to avoid to flood with useless still mouse events
199 _pre_mox=0
200 _pre_moy=0
201 mouse_speed_factor=5.
203 """Read the mouse position and then route mouse position to the relative binded handler
204 """
205 if base.mouseWatcherNode.hasMouse():
206 x = base.win.getPointer(0).getX()
207 y = base.win.getPointer(0).getY()
208 deltax=(x-self._pre_mox)
209 deltay=(y-self._pre_moy)
210 if deltax or deltay:
211 self.readmouse_binds["mouse-x"](deltax/self.mouse_speed_factor)
212 self.readmouse_binds["mouse-y"](deltay/self.mouse_speed_factor)
213 self._predelta=True
214 elif self._predelta:
215 self._predelta=False
216 self.readmouse_binds["mouse-x"](0)
217 self.readmouse_binds["mouse-y"](0)
218 self._pre_mox,self._pre_moy=(x, y)
219 else:
220 self.readmouse_binds["mouse-x"](0)
221 self.readmouse_binds["mouse-y"](0)
222
223 return Task.cont
224
225 #------------------------------------------------------
226 #
228 #------------------------------------------------------
229 #
231 """Merge a text file config in the main config data collector where the latter override the former
232 """
233 clean=lambda n: n.strip().strip('"').lower()
234 for line in config.split('\n'):
235 items=line.strip().split()
236 if items and len(items) >= 3:
237 cmd, evt, hnd=items[:3]
238 """ NOTE
239 - just 'bind' command expected right now
240 - '+' prepended ti the handler means REPEAT (make sense just for keyboard keys actually)
241 """
242 cmd=clean(cmd)
243 if cmd in ['bind']:
244 evt,hnd=(clean(evt), clean(hnd))
245 if not cmd in self.config: self.config[cmd]={}
246 repeat=hnd.startswith('+')
247 if repeat: hnd=hnd[1:]
248 self.config[cmd].update([[evt, [hnd, repeat]]])
249
250 #------------------------------------------------------
251 #
253 """Pygame joystick(s) startup - returns the number of joysticks found
254 """
255 jcount=0
256 if PYG:
257 self.dbgprint("pygame starts")
258 jcount=PYG.joystick.get_count()
259 if jcount > 0:
260 for x in range(jcount):
261 j = PYG.joystick.Joystick(x)
262 j.init()
263 self.dbgprint(">>>Enabled joystick: %s" % j.get_name())
264 taskMgr.add(self.pyga_joytask, 'tsk_pygajoy')
265 else:
266 self.dbgprint("No Joysticks to Initialize!")
267
268 return jcount
269 #------------------------------------------------------
270 #
272 """If there is a joy event it will be sent a proper string message and event value to the messenger queue
273 """
274 for e in PYG.event.get():
275 # joystick buttons up and down events routing
276 # will send a string like, i.e.: "joy0-button1" for the button 1 of the joy 0 and 0 or 1 as a parameter for button up or down status respectively
277 if e.type in [PYG.JOYBUTTONDOWN, PYG.JOYBUTTONUP]:
278 s="joy%d-button%d" % (e.joy, e.button)
279 messenger.send(s, [1 if e.type == PYG.JOYBUTTONDOWN else 0])
280 # joistick axis (analog and digital)
281 # will send a string like, i.e.: "joy0-axis1" for the axis 1 of the joy 0 and a number between 0 and 1 or 0 and -1 as the stick or hat status (the digital stick returns 0 OR +-1 but analog sticks floating values from 0.0 and +-1.0)
282 elif e.type == PYG.JOYAXISMOTION:
283 s="joy%d-axis%d" % (e.joy, e.axis)
284 ###print "Jax-%r(%r)" % (e.axis, e.value)
285 if e.axis in [1,2]:
286 messenger.send(s, [-e.value])
287 else:
288 messenger.send(s, [e.value])
289 return Task.cont
290 #-----------------------------------------------------
291 #
293 if self._DEBUG: print msg
294 #------------------------------------------------------
295 # PREDEFINED INPUT EVENT HANDLERS (to override)
296 """
297 NOTE some events are grouped in one such us thrust collect forward and back events so that you may subclass just thrust and ease your game logic
298 """
299 #------------------------------------------------------
300 #
304 #------------------------------------------------------
305 #
309 #------------------------------------------------------
310 #
314 #------------------------------------------------------
315 #
319 #------------------------------------------------------
320 #
324 #------------------------------------------------------
325 #
327 """evt=-1. to 1. - for x movements
328 to use for steering
329 """
330 self.dbgprint("heading(%r)"%evt)
331 #------------------------------------------------------
332 #
336 #------------------------------------------------------
337 #
341 #------------------------------------------------------
342 #
346 #------------------------------------------------------
347 #
351 #------------------------------------------------------
352 #
354 """evt=-1. to 1. - for z movements
355 by default the evt collect headup and headdown events
356 override or bind this in your subclass
357 """
358 self.dbgprint("pitch(%r)"%evt)
359 #------------------------------------------------------
360 #
364 #------------------------------------------------------
365 #
369 #------------------------------------------------------
370 #
372 """evt=-1. to 1. - for y movements
373 by default the evt collect forward and back events
374 override or bind this in your subclass
375 """
376 self.dbgprint("thrust(%r)"%evt)
377 #------------------------------------------------------
378 #
380 self.dbgprint("z00ming(%r)"%evt)
381
382 #================================================================
383 #
384 if __name__ == '__main__':
385 import direct.directbase.DirectStart
386 from direct.gui.DirectGui import *
387 from direct.gui.OnscreenText import OnscreenText
388
389 #============================================================
390 #
391 text2d=lambda line=0, text='': OnscreenText(
392 text = text, pos = (0, line*.1), scale = 0.05, mayChange=True, fg=(1,1,0,1), bg=(0,0,0,.5)
393 )
395 """Sample use of easyinput class
396 in brief you define a text very similar to quake .cfg files strings where to issue a bind command followed by a input event name and a keyword that define a bridge between the input event and a handler that will be called when that event will be fired
397 """
398 #------------------------------------------------------
399 #
401 DirectObject.__init__(self)
402 #
403 #self.accept('escape-up', self.quitme)
404 # crea ost di debug
405 self.display={}
406 l=0
407 for x in ['joy', 'mouse', 'kb', 'unspecified']:
408 l+=1
409 self.display[x]=text2d(line=l, text=x.upper()+":")
410 ###
411 # bind eventi device di input
412 #file.cfg tipo quake3
413 cfg="""//JOY ------
414 bind joy0-button9 "quit" // a comment
415 bind joy0-button1 "action"
416 bind joy0-axis1 "axis1"
417 //KB ------
418 bind arrow_up "forward"
419 bind escape "quit"
420 bind enter "action"
421 // MOUSE ------
422 bind mouse1 "action"
423 """
424 #these are the allowed handlers we need
425 hndbridge={
426 'axis1': self.axis1test,
427 'action': self.allaction,
428 'quit': self.quitme,
429 }
430 #
431 self.xinput=easyinput(cfg, hndbridge)
432 #------------------------------------------------------
433 #
435 """qui ci si aspetta un evt click (evt=true)"""
436 if evt:
437 self.dbgprint("too much for testing: so-long")
438 sys.exit()
439 #------------------------------------------------------
440 #
443 #------------------------------------------------------
444 #
447
448 #============================================================
449 #
460 #
461 # the test menu
462 #
474
475 DO=DirectObject()
476 DO.accept('1', setchoice, ['1'])
477 DO.accept('2', setchoice, ['2'])
478 DO.accept('escape', sys.exit)
479
480 text = """
481 Sample choice
482 =============
483 1) Instanced easyInput class
484 2) Derived class from easyInput
485 ESC) exit
486 """
487 menu = OnscreenText(parent=base.a2dTopLeft,
488 text = text, pos = (.5, -.1), scale = 0.05, mayChange=False,
489 fg=(1,1,0,1), bg=(0,0,0,.5)
490 )
491
492 run()
493
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Tue Sep 21 14:49:51 2010 | http://epydoc.sourceforge.net |