Module gamelogic01
[frames] | no frames]

Source Code for Module gamelogic01

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3  Base class for simple gamelogic. 
  4  Subclass gameLogic01 to set custom menus, splash screen etc... passing as a parameter a gameplay class, subclassed as well to have a custom gameplay. 
  5   
  6  @author: fabius 
  7  @copyright: fabius@2010 
  8  @license: GCTA give credit to (the) authors 
  9  @version: 0.1 
 10  @date: 2010-09 
 11  @contact: astelix (U{panda3d forums<http://www.panda3d.org/forums/profile.php?mode=viewprofile&u=752>}) 
 12  @status: eternal WIP 
 13   
 14  @todo: 
 15  aggiungere un terzo slide oltre titologame e scores di spiega del game (o meglio metterlo in gameplay? magari per ora solo immagini) e/o fare una voce di menu' HELP 
 16   
 17  @note: 
 18    - FSM mechanic: 
 19      - a) call a request 'X' 
 20      - b) FSM status goes to 'X' either if we specified enterX and filterX (respectively) methods or not, otherwise it will fall in the default methods 
 21      - c) stay cool in the last filterX method, waiting another different request and when it comes: 
 22        - will be summoned the eventual exitX method or the default one and then again b) 
 23   
 24  @warning: in this stuff will interact different input layers with FSM. That could be problematic so we gotta enable/disable the inputs passing fom FSM to other not FSM-driven pieces of code 
 25  """ 
 26  import os, random, sys 
 27  from direct.showbase.ShowBase import ShowBase 
 28  from pandac.PandaModules import * 
 29  from direct.showbase.DirectObject import DirectObject 
 30  from direct.interval.IntervalGlobal import * 
 31  from direct.fsm import FSM 
 32  from direct.gui.OnscreenText import OnscreenText 
 33  from dgstuff import * 
 34  import scorer 
 35   
 36  #========================================================================= 
 37  # 
38 -class gameLogic01(ShowBase, FSM.FSM):
39 _DEBUG=False
40 - def __init__(self, gameplay, settings={}):
41 """ 42 """ 43 ShowBase.__init__(self) 44 FSM.FSM.__init__(self, 'fsm_%r'%id(self)) 45 46 self.gameplay=gameplay 47 self._backto=[] 48 self._menuqueue=[] 49 self.playername="Player1" 50 self.titledata=None 51 self.splashfile='data/textures/splash.png' 52 #** used to carry and pass around the gameplay settings 53 self.gamesettings={ 54 'playernames': ['cpu', self.playername], 55 'opponent': 'cpu', 56 } 57 self.gamesettings.update(settings) 58 # 59 self.setinputs() 60 # 61 self._loadguidata() 62 # 63 self.request('Title')
64 # 65 #
66 - def setinputs(self, on=True):
67 """ FSM keyboard inputs 68 @note: with this we'll define all the FSM states inputs once for all - if an input is not used will fall in the default, safely unmanaged 69 """ 70 allinputs={ 71 'tab': 'advance', 'shift-tab': 'back', 72 'enter': 'select', 'escape': 'quit', 73 'arrow_down': 'down', 'arrow_left': 'left', 'arrow_up': 'up', 74 'arrow_right': 'right', 75 } 76 for k,v in allinputs.items(): 77 if on: self.accept(k, self.request, [v]) 78 else: self.ignore(k)
79 80 #----------------------------------------------------- 81 # 82 _currenttune=-1 83 _tunes=[] 84 _tunesfolder="./data/sounds/music/" 85 playingtune=None
86 - def playtune(self, tunetoplay=""):
87 """ Helper to play sounds 88 """ 89 self._tunes=self.walktree(self._tunesfolder) 90 if len(self._tunes): 91 # if no tune specified will be played the next one in the list 92 if not tunetoplay: 93 self._currenttune=(self._currenttune+1 if 94 (self._currenttune+1) < len(self._tunes) else 0 95 ) 96 tunetoplay=self._tunes[self._currenttune] 97 if (self.istuneplaying()): self.playingtune.stop() 98 self.playingtune = self.loader.loadSfx(tunetoplay) 99 self.playingtune.setLoop(False) 100 self.playingtune.play()
101
102 - def istuneplaying(self):
103 return (self.playingtune and 104 (self.playingtune.status() == self.playingtune.PLAYING) 105 )
106 107 #----------------------------------------------------- 108 #
109 - def walktree(self, top, ext='.ogg'):
110 l=[] 111 for f in os.listdir(top): 112 pathname = os.path.join(top, f) 113 if os.path.isfile(pathname) and os.path.splitext(pathname)[1] == ext: 114 l.append(pathname) 115 l.sort() 116 return l
117 118 #----------------------------------------------------- 119 # 120 #** default FSM REQUEST - here will fall unintercepted requests 121 #def defaultEnter(self, *args): 122 #print "[DEF]entering '%s' from '%s'..." % (self.newState, self.oldState)
123 - def defaultFilter(self, request, args):
124 if request == 'quit': 125 ###print "[DEF] routing request '%s' to Menu" % request 126 return ('Menu', self.menu_main) 127 else: 128 ###print "[DEF] unhandled filter request '%s'" % request 129 return FSM.FSM.defaultFilter(self, request, args)
130 #def defaultExit(self): 131 #print "[DEF]exiting from '%s' to '%s'..." % (self.oldState, self.newState) 132 133 #----------------------------------------------------- 134 # 135 #** TITLE FSM REQUEST
136 - def enterTitle(self, *args):
137 self.dbgprint("[Title] entering") 138 self._title=gametitle( 139 self, top10data=self.titledata, splashfile=self.splashfile 140 ) 141 if (not self.istuneplaying()): self.playtune()
142
143 - def filterTitle(self, request, args):
144 self.dbgprint("[Title] filtering (%s)"%request) 145 if request in ['advance', 'select']: return 'Gameplay' 146 else: return self.defaultFilter(request, args)
147
148 - def exitTitle(self):
149 self.dbgprint("[Title] exiting") 150 self._title.finish() 151 del self._title
152 153 #----------------------------------------------------- 154 """ methods for the menu ovelapping mechanic to manage the add/remove of gui controls like menus etc. 155 """ 156 #** add a menu over
157 - def push_menu(self, menudata):
158 if len(self._menuqueue): self._menuqueue[-1].pause() 159 self._menuqueue.append(dgmenu(menudata))
160 #** add an entry control
161 - def push_entry(self, menudata):
162 if len(self._menuqueue): self._menuqueue[-1].pause() 163 self._menuqueue.append(dginput(menudata))
164 #** to remove topmost control
165 - def pop_menu(self, foo=None): self.request('_menuend')
166 167 #----------------------------------------------------- 168 # initialise menus and such (override it)
169 - def _loadguidata(self): pass
170 171 #----------------------------------------------------- 172 # 173 #** MENU FSM REQUEST
174 - def enterMenu(self, *args):
175 self.dbgprint("[Menu] entering") 176 # turning off all inputs here - we listen just the menu 177 self.setinputs(False) 178 self._backto.append(self.oldState) 179 #args[0]=menu to load 180 self.push_menu(args[0])
181 182 #**
183 - def filterMenu(self, request, *args):
184 self.dbgprint("[Menu] filtering (%s)"%request) 185 if request == '_menuend': 186 menu=self._menuqueue.pop() 187 menu.finish() 188 if len(self._menuqueue): self._menuqueue[-1].play() 189 else: 190 # when the root menu close we drive back to the caller state 191 bt=self._backto.pop() 192 self.dbgprint("Menu: back to old:%s" % bt) 193 return bt 194 else: return self.defaultFilter(request, args)
195 196 #**
197 - def exitMenu(self):
198 # cleaning if exiting menu state abruptedly 199 self.dbgprint("[Menu] exiting") 200 self.setinputs() 201 self._backto=[] 202 for menu in self._menuqueue: 203 menu.finish() 204 self._menuqueue=[]
205 206 #----------------------------------------------------- 207 # 208 #** GAMEPLAY FSM REQUEST 209 gameplayshell=None 210 # override eventually
211 - def postgameover(self, score=None): return None
212
213 - def abortgame(self, foo=None):
214 #self.gameplayshell.pause(0) 215 if self.gameplayshell: 216 self.dbgprint("abort game") 217 self.gameplayshell.finish() 218 self.gameplayshell=None 219 self.request('Title')
220
221 - def enterGameplay(self, *args):
222 """ will enter here after 'Gameplay' FSM request. 223 Will be evaluated here the gameplay status and eventually the game will be paused/restarted. 224 """ 225 self.dbgprint("[Gameplay] entering") 226 if self.gameplayshell: 227 self.dbgprint("returning off menu") 228 self.gameplayshell.pause(0) 229 else: 230 self.dbgprint("Starting new game") 231 if (self.istuneplaying()): self.playingtune.stop() 232 233 self.gameplayshell=self.gameplay( 234 self, settings=self.gamesettings, 235 callback=lambda arg: taskMgr.doMethodLater( 236 5, self.postgameover, '_pogaov',[arg] 237 ) 238 )
239
240 - def filterGameplay(self, request, args):
241 """ 242 @note: 'quit' request here will just pause the game, therefore the 'Menu' request must be called explicitly 243 """ 244 self.dbgprint("[Gameplay] filtering (%s)"%request) 245 if request == 'quit': 246 self.gameplayshell.pause(1) 247 return 'Menu', self.menu_stopgame 248 else: 249 return self.defaultFilter(request, args)
250
251 - def exitGameplay(self):
252 self.dbgprint("[Gameplay] exiting")
253 254 #----------------------------------------------------- 255 #
256 - def dbgprint(self, msg):
257 if self._DEBUG: print msg
258 259 #========================================================================= 260 #
261 -class gametitle(object):
262 """ 263 The game presentation class. 264 Usually shows a splash screen, the scoreboard and help pages (in the future maybe). 265 """
266 - def __init__( 267 self, parent, top10data=None, splashfile='data/textures/splash.png' 268 ):
269 self.parent=parent 270 if top10data: 271 self._top10sb=scorer.scorer(filename=top10data['scorefile'], clamp=10) 272 fs="%%0%dd"%top10data.get('scoredigits',3) 273 items=[ 274 [ 275 {'label': line[1].strip()}, {'label': fs%line[0]} 276 ] for line in self._top10sb.getscores() 277 ] 278 top10data['items']=items 279 top10data['visible']=False 280 self.top10table=dgtable(top10data) 281 else: self.top10table=None 282 self.splash=OnscreenImage( 283 parent= self.parent.render2d, image = splashfile 284 ) 285 self._setsplash()
286 287 #----------------------------------------------------- 288 #
289 - def finish(self):
290 self.splash.destroy() 291 if self.top10table: 292 taskMgr.remove('gametitletsk') 293 self.top10table.finish()
294 295 #----------------------------------------------------- 296 #
297 - def _setsplash(self, *args):
298 if self.top10table: 299 taskMgr.remove('gametitletsk') 300 self.top10table.canvas.hide() 301 self.splash.show() 302 taskMgr.doMethodLater(5, self._settop10, 'gametitletsk')
303 304 #----------------------------------------------------- 305 #
306 - def _settop10(self, *args):
307 taskMgr.remove('gametitletsk') 308 self.splash.hide() 309 taskMgr.doMethodLater(8, self._setsplash, 'gametitletsk') 310 self.top10table.canvas.show()
311 312 #========================================================================= 313 #
314 -class gameplaybase(DirectObject):
315 """ To make a custom game, fist start subclassing this. 316 """ 317 _DEBUG=True 318 _SETTINGS={ 319 'opponent': 'human', 320 'playernames': ['CPU0', 'Player1'] 321 } 322 #----------------------------------------------------- 323 #
324 - def __init__(self, parent, settings=_SETTINGS, callback=None):
325 self._SETTINGS.update(settings) 326 self.parent=parent 327 self.gameovercallback=callback 328 329 self.setscene() 330 self.setcamera() 331 self.setinputs() 332 self.setgame() 333 self.loadeffects()
334 335 #----------------------------------------------------- 336 # to be overriden
337 - def setcamera(self): pass
338 - def setinputs(self): pass
339 - def setscene(self): pass
340 - def setgame(self): pass
341 - def onfinish(self): pass
342 343 #----------------------------------------------------- 344 # 345 _effects={} 346 _effectsfolder="./data/sounds/effects/"
347 - def loadeffects(self):
348 self._effects=self.walktree(self._effectsfolder)
349
350 - def playeffect(self, name):
351 if name in self._effects: 352 self._effects[name].play()
353 354 #----------------------------------------------------- 355 #
356 - def walktree(self, top, ext='.ogg'):
357 d={} 358 for f in os.listdir(top): 359 pathname = os.path.join(top, f) 360 if os.path.isfile(pathname) and os.path.splitext(pathname)[1] == ext: 361 d[os.path.splitext(f)[0]]=self.parent.loadSfx(pathname) 362 return d
363 364 #----------------------------------------------------- 365 #
366 - def finish(self):
367 # @attention: self.startclock() call is very important cos if we paused the game before, all the application hangs therefore everything outside the game will be freezed as well. 368 self.startclock() 369 self.ignoreAll() 370 for tsk in taskMgr.getTasksMatching('gpl01_*'): taskMgr.remove(tsk) 371 self.onfinish()
372 #----------------------------------------------------- 373 # 374 _GCLK=None 375 _FT=None
376 - def startclock(self):
377 if self._GCLK and self._FT: 378 self._GCLK.setRealTime(self._FT) 379 self._GCLK.setMode(ClockObject.MNormal) 380 self.parent.enableParticles() 381 self._GCLK=None 382 self.dbgprint("[gpl01] restarting...")
383
384 - def stopclock(self):
385 if not self._GCLK: 386 self.dbgprint("[gpl01] pausing...") 387 self.parent.disableParticles() 388 self._GCLK=ClockObject.getGlobalClock() 389 self._FT=self._GCLK.getFrameTime() 390 self._GCLK.setMode(ClockObject.MSlave)
391
392 - def pause(self, force=None):
393 if (self._GCLK == None) or (force == 1): self.stopclock() 394 elif self._GCLK or (force == 0): self.startclock()
395 396 #----------------------------------------------------- 397 #
398 - def dbgprint(self, msg):
399 if self._DEBUG: print msg
400 401 #========================================================================= 402 # 403 if __name__ == "__main__": 404 loadPrcFileData("", """win-size 800 600 405 win-origin 0 0 406 model-path $MAIN_DIR/data/models/ 407 sync-video 0 408 #show-frame-rate-meter #t 409 """ 410 ) 411 412 """ Sample to show how to subclass the parent gamelogic class to have a functional game mechanic 413 """
414 - class myGame(gameLogic01):
415 - def __init__(self, gameplay):
416 gameLogic01.__init__(self, gameplay) 417 self.gamesettings['mode']='2d'
418 419 #** manages what happens as the game ends
420 - def postgameover(self, score=None):
421 self.dbgprint("Score report after gameover:\n%r"%score) 422 if score['opponent'] == 'cpu': 423 sc=scorer.scorer(filename='pong.sco') 424 sc.putscore(self.playername, score['value']) 425 self.abortgame() 426 return None
427
428 - def _setplayername(self, v):
429 if v.strip(): 430 self.playername=v 431 self.gamesettings['playernames'][1]=self.playername
432
433 - def _loadguidata(self):
434 #** splash screen plus top-10 table 435 self.titledata={'scorefile': 'gameplay01.sco', 'scoredigits': 6, 436 'title': '** TOP 10 SCORES **', 'titlescale': .09, 437 'scale':(1.5, 1.1), 'itemsvisible':10, 'margin': (.1,.15), 438 'texture':'data/models/textures/menu_pong.png', 439 'textfont': loader.loadFont("data/fonts/slkscre.ttf"), 440 'itemswidth': (.9, .4), 'itemsscale':.07, 441 'head': [ 442 { 'label': 'Player', 'color':(1,1,1,1), 'textscale':.09}, 443 { 'label': 'Score', 'color':(1,1,1,1), 'textscale':.09}, 444 ], 445 } 446 447 def menumerge(a, intob): 448 r=intob.copy() 449 r.update(a) 450 return r
451 #** common parameters for all menus - will be merged with each specific menu 452 menucommon={'scale':(1.1, .6), 'titlescale':.1, 'pos':(0,0), 453 'texture':'data/models/textures/menu_pong.png', 454 'titlecolor': (1,1,1,1), 'itemscolor': (1,1,1,1), 'itemsscale': .08, 455 'highlightcolor':(0,0,0,1), 456 'textfont': loader.loadFont("data/fonts/slkscre.ttf"), 457 } 458 #** quitgame 459 menu={'title': 'Sure to quit?', 'selected':1, 460 'items': [ 461 {'label': 'Yes', 'callback': sys.exit}, 462 {'label': 'No', 'callback': self.pop_menu}, 463 ], 464 'exit': self.pop_menu, 465 } 466 self.menu_quitgame=menumerge(menu, menucommon) 467 #** player name 468 def menu_name(): 469 # e' in forma di funza perche' playername puo' cambiare 470 menu={ 471 'align': 'center', 472 'title': 'Put Your Name', 'scale': (1.2,.6), 'margin':(.07, .05), 473 'titlecolor': (1,1,1,1), 474 'initialtext': self.playername, 'inputscale': .07, 'inputwidth': 25, 475 'inputcolor':(0,0,0,1), 'callback': lambda v: self._setplayername(v), 476 'exit': self.pop_menu, 'autoexit': True, 477 } 478 return menumerge(menu, menucommon)
479 #** main menu 480 menu={'title': 'MAIN MENU', 481 'items': [ 482 {'label': 'Player Name', 483 'callback': lambda foo=None: self.push_entry(menu_name()), 484 }, 485 {'label': 'Exit Game', 486 'callback': lambda foo=None: self.push_menu(self.menu_quitgame), 487 }, 488 ], 489 'exit': self.pop_menu, 490 } 491 self.menu_main=menumerge(menu, menucommon) 492 #** used in gameplay request 493 menu={'title': 'Stop playing?', 'selected': 1, 'scale':(1.2, .5), 494 'items': [ 495 {'label': 'Yess', 'callback': self.abortgame, }, 496 {'label': 'Nope', 'callback': self.pop_menu, }, 497 ], 498 'exit': self.pop_menu, 499 } 500 self.menu_stopgame=menumerge(menu, menucommon) 501 502 from pong3dgpl import gameplay 503 game=myGame(gameplay) 504 run() 505