1
2 """
3 3dpong gameplay module - see __main__ section for demo usage
4
5 Gameplay: press SPACEBAR to launch the ball, move the mouse or use the WASD or the arrow keys or even the joystick to move the pad. A game is won by the player who wins 2 sets more than his foe. Each set is won by the player who wons 3 rallies but gotta win 2 rallies more than his foe in case of tie score.
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 """
15 import random, sys, time
16 from direct.showbase.ShowBase import ShowBase
17 from pandac.PandaModules import *
18 from direct.gui.OnscreenText import OnscreenText
19 from direct.showbase.DirectObject import DirectObject
20 from direct.interval.IntervalGlobal import *
21 from gamelogic01 import gameplaybase
22 try:
23 from easyinput import *
24 _EASYINPUT=True
25 except ImportError: _EASYINPUT=False
26
27
28
30 """ Pong3D gameplay class. Derived from a generic base gameply class, this is custom-specialized to play the pong3D game.
31 """
32 _DEBUG=False
33 PADVEL=1.
34 CPUSPD=.15
35 NPCSPD=PADVEL
36 BALLSPEED=1.0
37 BALLSPEEDMAX=2.0
38 _CPUPAD=0
39 _HUMANPAD=1
40 _SETTINGS={
41
42 'mode': '3dg',
43 'opponent': 'human',
44 'games': 3,
45 'sets': 3,
46 'maxfoes': 4,
47 'playernames': ['CPU0', 'Player1']
48 }
49 _SCOREPOINTS={
50 'rallyunit': 4,
51 'bonusunit': 100,
52 'penaltyunit': 10,
53 }
54
55
57 """ WARNING you actually cannot dynamically switch from 2d to 3d and vice-versa but just stay stick to one dimension or the other one
58 """
59 if self._SETTINGS['mode'].lower().startswith('3d'):
60
61 leftColor = ColorWriteAttrib.CRed
62 rightColor = ColorWriteAttrib.CBlue | ColorWriteAttrib.CGreen
63
64 base.win.setRedBlueStereo(True, leftColor, rightColor)
65
66 oldDr = base.cam.node().getDisplayRegion(0)
67 oldDr.setCamera(NodePath())
68
69 dr = base.win.makeStereoDisplayRegion()
70 dr.setCamera(base.cam)
71
72
73
74 dr.getRightEye().setClearDepthActive(True)
75 base.camLens.setInterocularDistance(0.339)
76
77 if self._SETTINGS['mode'].lower() == '3dg':
78 dr.setStereoChannel(Lens.SCStereo)
79 else: dr.setStereoChannel(Lens.SCMono)
80
81 self.parent.cam.setPos(-25,-15,0)
82 self.parent.cam.lookAt(self.field)
83 self.parent.cam.setR(90)
84 else:
85 lens = OrthographicLens()
86 lens.setFilmSize(20, 15)
87 self.parent.cam.node().setLens(lens)
88 self.parent.cam.setY(-2)
89
90
91
104
112
113 if self._SETTINGS['mode'].lower().startswith('3d'):
114 keys=[['a', 'd'],['arrow_left', 'arrow_right']]
115 else: keys=[['w', 's'],['arrow_up', 'arrow_down']]
116 if self._SETTINGS['opponent'] <> "cpu":
117 self._SETTINGS['playernames']=['Player1','Player2']
118 accepad(keys[0], 0, self.PADVEL)
119 accepad(keys[1], 1, self.PADVEL)
120
154
157
159 d=.05
160 v=base.camLens.getInterocularDistance()+(v*d)
161 base.camLens.setInterocularDistance(v)
162 self.dbgprint("IOD:%r"%base.camLens.getInterocularDistance())
164 d=.1
165 v=base.camLens.getConvergenceDistance()+(v*d)
166 base.camLens.setConvergenceDistance(v)
167 self.dbgprint("CVD:%r"%base.camLens.getConvergenceDistance())
168
169
170
172 if self._SETTINGS['opponent'] <> "cpu":
173 self._SETTINGS['playernames']=['Player1','Player2']
174
175 self.field=loader.loadModel('pong3d')
176 self.field.setScale(10)
177 self.field.reparentTo(self.parent.render)
178 self.parent.cam.lookAt(self.field)
179
180 self.lights=[]
181
182 lv=.3
183 ambientLight = AmbientLight('ambientLight')
184 ambientLight.setColor(Vec4(lv, lv, lv, 1))
185 ambientLightNP = self.parent.render.attachNewNode(ambientLight)
186 self.lights.append(ambientLightNP)
187 self.parent.render.setLight(ambientLightNP)
188
189 lpos=(-4, -6, 2)
190 lv=.7
191 directionalLight = DirectionalLight('directionalLight')
192 directionalLight.setColor(Vec4(lv, lv, lv, 1))
193 directionalLightNP = self.parent.render.attachNewNode(directionalLight)
194 directionalLightNP.setPos(lpos)
195 directionalLightNP.lookAt(self.field)
196 self.lights.append(directionalLightNP)
197 self.parent.render.setLight(directionalLightNP)
198
199 font=loader.loadFont("data/fonts/slkscre.ttf")
200 font.setMagfilter(Texture.FTNearest)
201 self.text={
202 'score': OnscreenText(fg=(1,1,1,1), font = font,
203 text = '', pos = (0, .75), scale = 0.3, mayChange=True,
204 ),
205 'sets': OnscreenText(fg=(1,1,1,1), font = font,
206 text = '', pos = (0, .65), scale = 0.08, mayChange=True,
207 ),
208 'cmsg': OnscreenText(fg=(1,1,1,1), font = font,
209 text = '', pos = (.0, .0), scale = 0.3, mayChange=True,
210 ),
211 }
212
213
214
216 self.pad=[]
217 for i in range(2):
218 self.pad.append({})
219 self.pad[i]['name']=self._SETTINGS['playernames'][i]
220 self.pad[i]['totrly']=0
221 self.pad[i]['totgam']=0
222 self.pad[i]['gam']=0
223 self.pad[i]['set']=0
224 self.pad[i]['vel']=0
225 self.pad[i]['mdl']=self.field.find("**/pad%d"%i)
226 self.pad[i]['mdl'].setBin('fixed', 40)
227
228 self._collphysetup()
229
230 self._BOTS=[self.bot0, self.bot0, self.bot1, self.bot1, self.bot1]
231 self.actualbot=self._BOTS[0]
232
233 self.maintask = None
234 self.npctask=None
235 self.setmaintask(1)
236
237 self.playerturn=random.choice(range(2))
238 self.updatescore()
239 self.ballreset()
240
241
242
243 - def setmaintask(self, on):
244 if on:
245 if not self.maintask:
246 self.maintask = taskMgr.add(self.mainloop, "gpl01_main", priority = 35)
247 self.maintask.last = 0
248 if self._SETTINGS['opponent'] == "cpu":
249 self.npctask = taskMgr.doMethodLater(self.CPUSPD, self.npcloop, 'gpl01_npctsk')
250 else: self.npctask=None
251 else:
252 if self.maintask:
253 taskMgr.remove(self.maintask)
254 self.maintask=None
255 if self._SETTINGS['opponent'] == "cpu": taskMgr.remove(self.npctask)
256
257
258
260 self.xinput.finish()
261 self.parent.physicsMgr.clearPhysicals()
262 self.parent.cTrav.clearColliders()
263 self.field.removeNode()
264 for t in self.text: self.text[t].destroy()
265 for l in self.lights: self.parent.render.clearLight(l)
266
267
268
269 - def mainloop(self, task):
270 dt = task.time - task.last
271 task.last = task.time
272 for i in range(2):
273 padvel=self.pad[i]['vel']
274 if padvel:
275 pad=self.pad[i]['mdl']
276 z=pad.getZ()+(padvel*dt)
277 z=max(min(z, self.PADLIMZ), -self.PADLIMZ)
278 pad.setZ(z)
279
280 if (
281 (abs(self.ball.getPosition()[2]) > .75) or
282 (abs(self.ball.getPosition()[0]) > 1.08)
283 ):
284 self.playerscore(self.ball.getPosition()[0] > 0)
285 return task.cont
286
287
288
290 v=0.
291 if abs(padz) > random.uniform(.0, .06): v=-1. if padz > 0. else 1.
292 return v
293
294
295
296 - def bot0(self, padz, ballpos, balldir):
297 """ npc AI (actually very dumb) """
298 bpx,bpy,bpz=ballpos
299 v=0.
300
301
302 if bpx > .0:
303 if (abs(bpz-padz) > .08) and (abs(bpz) < .65):
304 v=1. if bpz > padz else -1.
305 else: v=self._centerpad(padz)
306 return v
307
308
309
310 - def bot1(self, padz, ballpos, balldir):
311 """ npc AI (less dumb) """
312 bpx,bpy,bpz=ballpos
313 balldirz=balldir[2]
314 v=0.
315 if bpx > 0.:
316 d=.5/self.BALLSPEED
317 self.dbgprint("[CPU1]rot:%0.3f lim: %0.3f bpx:%0.3f"%(balldirz, d, bpx))
318 if bpx < d:
319 self.dbgprint("...centering")
320 if abs(balldirz) > .003: return self._centerpad(padz)
321 if (abs(bpz-padz) > .08) and (abs(bpz) < .65):
322 v=1. if bpz > padz else -1.
323 self.dbgprint("...following")
324 else: v=self._centerpad(padz)
325 return v
326
327
328
330 pv=self.actualbot(
331 self.pad[0]['mdl'].getZ(),
332 self.ball.getPosition(),
333 self.ball.getImplicitVelocity()
334 )
335 self.setpadvel(0, pv*self.PADVEL*self.CPUSPD*6.)
336 return task.again
337
338
339
341 self.parent.cTrav=CollisionTraverser()
342 self.parent.cTrav.setRespectPrevTransform(True)
343 self.parent.enableParticles()
344 self.collisionHandler = PhysicsCollisionHandler()
345
346 ballmodel=self.field.find("**/ball")
347 self.ballNP=self.field.attachNewNode(PandaNode("phball"))
348 ballAN=ActorNode("ballactnode")
349 ballANP=self.ballNP.attachNewNode(ballAN)
350 ballmodel.reparentTo(ballANP)
351 ballCollider = ballmodel.find("**/ball_collide")
352 self.collisionHandler.addCollider(ballCollider, ballANP)
353 self.parent.cTrav.addCollider(ballCollider, self.collisionHandler)
354 self.collisionHandler.addInPattern('ball-into-all')
355 self.accept('ball-into-all', self.collideEventIn)
356 self.parent.physicsMgr.attachPhysicalNode(ballAN)
357
358 self.ball=self.ballNP.getChild(0).node().getPhysicsObject()
359
360
361
362
363
365 intoNP=entry.getIntoNodePath()
366 vec=entry.getSurfaceNormal(intoNP)
367
368 if intoNP.getName().startswith("pad"):
369
370 vec=vec*self.BALLSPEED*1.5
371 self.wallbouncheck(reset=True)
372 padi=int(intoNP.getName()[3])
373 self.pad[padi]['totrly']+=1
374 self.playeffect('pad%d'%padi)
375
376 else:
377 wbc=self.wallbouncheck()
378 if wbc <> None: return self.playerscore(wbc)
379 else: vec=vec+self.ball.getVelocity()
380 self.playeffect('borderhit')
381 vec*=self.BALLSPEED
382 self.ballinpulse(vec=Vec3(vec.getX(), 0, vec.getZ()))
383
384
385
387 self.text['score'].setText('%0d %0d'%(
388 self.pad[1]['gam'], self.pad[0]['gam']
389 )
390 )
391 self.text['sets'].setText('%s: %0d %s: %0d'%(
392 self.pad[1]['name'], self.pad[1]['set'],
393 self.pad[0]['name'], self.pad[0]['set'],
394 )
395 )
396
397
398
399 foesdefeated=0
400 totsets=0
402 """ a set is won by one player """
403 self.playeffect('applause_1')
404
405 won=self.pad[1]['gam'] > self.pad[0]['gam']
406 self.dbgprint(">> END OF GAME - won by player %d"%won)
407 self.pad[0]['gam']=0
408 self.pad[1]['gam']=0
409 self.pad[won]['set']+=1
410 self.totsets+=1
411
412
413 if self.BALLSPEED < self.BALLSPEEDMAX: self.BALLSPEED*=1.05
414 self.BALLSPEED = min(self.BALLSPEED, self.BALLSPEEDMAX)
415 self.dbgprint(">> BALLSPEED increased to %0.2f"%self.BALLSPEED)
416
417
418 if (
419 self.pad[won]['set'] >= ((self._SETTINGS['sets']/2)+1)
420 and (abs(self.pad[0]['set'] - self.pad[1]['set'])) >= 2
421 ):
422
423 if (
424 (self.pad[1]['set'] > self.pad[0]['set'])
425 and (self._SETTINGS['opponent'] == 'cpu')
426 ):
427 self.intermessage(
428 "%s defeated!\nHere come another"%self.pad[0]['name'], delay=3
429 )
430 self.dbgprint(">>> %s defeated" % self.pad[0]['name'])
431 self.foesdefeated+=1
432 self.pad[0]['name']="CPU%d"%self.foesdefeated
433 self.pad[0]['set']=0
434 self.pad[1]['set']=0
435 if self.foesdefeated <= self._SETTINGS['maxfoes']:
436 self.PADVEL+=.5
437 self.actualbot=self._BOTS[self.foesdefeated]
438 for i in range(2):
439
440 psz=self.pad[i]['mdl'].getSz()
441 dsz=60./self._SETTINGS['maxfoes']
442 self.pad[i]['mdl'].setSz(psz-(psz*dsz/100))
443
444 b=self.pad[1]['mdl'].getTightBounds()
445 w,n,h=b[1]-b[0]
446 self.PADLIMZ=.7-(h/2.)
447 self.ballreset()
448 else: self.gameover()
449 else:
450 self.ballreset()
451 self.intermessage("Set won by\n%s"%self.pad[won]['name'], delay=3)
452 self.updatescore()
453
454
455
457 self.text['cmsg'].hide()
458 if msg and type(msg) == str:
459 self.text['cmsg'].setScale(scale)
460 self.text['cmsg'].setText(msg)
461 self.text['cmsg'].setPos(0,0)
462 self.text['cmsg'].show()
463 if delay > 0:
464 taskMgr.doMethodLater(delay, self.intermessage, 'gpl01_intmsg')
465 return None
466
467
468
470 self.setmaintask(0)
471 self.ballNP.hide()
472 self.dbgprint("GAME OVER!")
473 seq=Sequence()
474 won=self.pad[1]['set'] > self.pad[0]['set']
475
476 if self._SETTINGS['opponent'] == 'human':
477 wonmsg="Game won by\n'%s'"%self.pad[won]['name']
478 seq.append(Func(self.intermessage, wonmsg))
479 seq.append(Wait(3.0))
480 value=self.pad[won]['set']
481 else:
482 score=self.humanvscpuscore()
483 msg=[
484 "Rallies %d x %d = %d"%(
485 self.pad[self._HUMANPAD]['totrly'], self._SCOREPOINTS['rallyunit'],
486 score['rallies']
487 ),
488 "Bonus foes = %d"%score['bonus'],
489 "Penalty = %d"%score['penalty'],
490 "TOTAL SCORE = %d"%sum(score.values())
491 ]
492
493 for i in range(1,len(msg)+1):
494 seq.append(Func(self.intermessage, "\n".join(msg[:i])))
495 seq.append(Func(self.playeffect, 'ball_in'))
496 seq.append(Wait(1.5))
497
498 value=sum(score.values())
499
500 if callable(self.gameovercallback):
501 fu=Func(self.gameovercallback,
502 {
503 'opponent': self._SETTINGS['opponent'],
504 'winner': self.pad[won]['name'],
505 'value': str(value)
506 }
507 )
508 else: fu=Wait(1)
509
510 seq.append(Wait(3.0))
511 seq.append(Func(self.intermessage, 'GAME OVER', 0.3))
512 seq.append(Wait(1.0))
513 seq.append(fu)
514
515 seq.start()
516
517
518
539
540
542
543 self.playeffect('ball_out')
544 self.setmaintask(0)
545 self.ball.setActive(False)
546 self.wallbouncheck(reset=True)
547 self.pad[player]['gam']+=1
548 self.playerturn=player
549 self.updatescore()
550
551 if (
552 (
553 self.pad[0]['gam'] >= self._SETTINGS['games']
554 or self.pad[1]['gam'] >= self._SETTINGS['games']
555 ) and abs(self.pad[0]['gam'] - self.pad[1]['gam']) >= 2
556 ): self.endset()
557 else:
558 self.checkmatchball()
559 self.ballreset()
560
561
562
564
565 p0=0 if self.pad[0]['set'] >= self.pad[1]['set'] else 1
566
567 p1=not p0
568 if (
569 self.pad[p0]['set'] >= (self._SETTINGS['sets']/2)
570 ) and (
571 (self.pad[p0]['set'] - self.pad[p1]['set']) >= 1
572 ) and (
573 self.pad[p0]['gam'] >= (self._SETTINGS['games']-1)
574 ) and (
575 (self.pad[p0]['gam'] - self.pad[p1]['gam']) >= 1
576 ):
577 self.dbgprint("@@ MATCH BALL FOR PLAYER\n%s!"%self.pad[p0]['name'])
578 self.intermessage(
579 "MATCH BALL FOR PLAYER\n%s!"%self.pad[p0]['name'],
580 delay=3
581 )
582
583
584
585 _bcc=[0,0]
587 """ check for triple bounces in a player's middlefield
588 """
589 if reset: self._bcc=[0,0]
590 else:
591
592 bi=self.ball.getPosition()[0] <= 0
593
594 self._bcc[bi]+=1
595 self._bcc[not bi]=0
596 if sum(self._bcc) > 2: return 1 if self._bcc[0] else 0
597 return None
598
599
600
602 self.ball.setVelocity(0,0,0)
603 self.ball.addImpulse(vec)
604
605
606
608 """ """
609 b=self.pad[1]['mdl'].getTightBounds()
610 w,n,h=b[1]-b[0]
611 z=.67
612 self.PADLIMZ=z-(h/2.)
613
614 self.ballNP.hide()
615 self.ball.setActive(False)
616 self.ball.setVelocity(Vec3(0,0,0))
617 self.ball.resetPosition(Point3(0,0,0))
618 self._ballprepare()
619 self.ball.setActive(True)
620 self.ballNP.show()
621 self.setmaintask(1)
622 self.playeffect('ball_in')
623
624
625
627 """ to start the ball off the pad """
628 self.ignore('space')
629 self._ballpreparego()
630 p=self.playerturn
631
632 bs=random.uniform(self.BALLSPEED*1.0, self.BALLSPEED*2.0)
633 self.ballinpulse(Vec3([-bs, bs][p], 0, self.pad[p]['vel']))
634 return None
635
636
637
639 p=self.playerturn
640 self.ballNP.wrtReparentTo(self.pad[p]['mdl'])
641 self.ballNP.setPos([-.08, .08][p], 0, 0)
642 if p == 0 and self._SETTINGS['opponent'] == "cpu":
643 taskMgr.doMethodLater(3, self.ballrestart, 'gpl01_npcbrstrt')
644 else: self.accept('space', self.ballrestart)
645
647 x,y,z=self.ballNP.getPos(self.field)
648 self.ballNP.wrtReparentTo(self.field)
649 self.ballNP.setPos(Point3(0,0,0))
650 self.ball.resetPosition(Point3(x,0,z))
651
652
653
654 - def setpadvel(self, p, v): self.pad[p]['vel']=v
655
656
657
658 _collshow=0
660 self._collshow=not self._collshow
661 try:
662 l=self.parent.render.findAllMatches("**/+CollisionNode")
663 if self._collshow:
664 self.parent.cTrav.showCollisions(self.parent.render)
665 for cn in l: cn.show()
666 else:
667 self.parent.cTrav.hideCollisions()
668 for cn in l: cn.hide()
669 except: self.dbgprint("[cbsnipBase:toggle_collisions] no traverser")
670
671
672
673 if __name__ == "__main__":
674 """ Demo to show off how to use this class to run the pong game
675 press '1' for Human vs computer, '2' for Human vs human
676 press 'p' to pause the game
677 """
678 loadPrcFileData("", """win-size 800 600
679 win-origin 0 0
680 model-path $MAIN_DIR/data/models/
681 sync-video 0
682 #show-frame-rate-meter #t
683 """
684 )
687 ShowBase.__init__(self)
688 self.game=None
689 self.accept('escape', sys.exit)
690 self.accept('p', self.pausegame)
691 self.accept('1', self.startgame)
692 self.accept('2', self.startgame2)
693
695 if self.game: self.game.pause()
696
698 if self.game:
699 self.game.finish()
700 del self.game
701 self.game=None
702
704 self.dbgprint(">> the scoreboard:\n'%r'"%score)
705
707 self.stopgame()
708 if self.game == None:
709 self.game=gameplay(
710 self, settings={'opponent': 'cpu'}, callback=self.gocallback
711 )
712
714 self.stopgame()
715 if self.game == None:
716 self.game=gameplay(
717 self, settings={'opponent': 'human'}, callback=self.gocallback
718 )
719
720 W=world()
721 run()
722