summaryrefslogtreecommitdiff
path: root/src/samples/firebirds/firebirds.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/samples/firebirds/firebirds.py')
-rwxr-xr-xsrc/samples/firebirds/firebirds.py469
1 files changed, 469 insertions, 0 deletions
diff --git a/src/samples/firebirds/firebirds.py b/src/samples/firebirds/firebirds.py
new file mode 100755
index 0000000..3656255
--- /dev/null
+++ b/src/samples/firebirds/firebirds.py
@@ -0,0 +1,469 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# libavg - Media Playback Engine.
+# Copyright (C) 2012-2013 Ulrich von Zadow
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+# Current versions can be found at www.libavg.de
+#
+# Original author of this file: Thomas Schott <scotty@c-base.org>
+
+# See the LICENSE file for license information regarding the media
+# used by this sample.
+
+from random import randint
+
+from libavg import avg, player
+from libavg import app
+from libavg.utils import getMediaDir
+
+player.loadPlugin('collisiondetector')
+
+def playVideo(video):
+ if not player.isUsingGLES():
+ video.play()
+
+
+### game elements ###
+
+class Bullet(avg.VideoNode):
+ __SPEED = 360 # px/s
+
+ def __init__(self, parent=None, **kwargs):
+ super(Bullet, self).__init__(href='bullet.mov', loop=True, active=False, **kwargs)
+ self.registerInstance(self, parent)
+ self.pause()
+
+ def reset(self, pos):
+ self.pos = pos
+ self.active = True
+ playVideo(self)
+
+ def update(self, dt):
+ y = self.y - Bullet.__SPEED * dt
+ if y > -self.height:
+ self.y = y
+ else:
+ self.destroy()
+
+ def destroy(self):
+ self.active = False
+ self.pause()
+
+
+class _Aircraft(avg.DivNode):
+ _SPEED = 300 # px/s
+ __SHADOW_SCALE = 0.5
+ __SHADOW_OFFSET = avg.Point2D(170, 170)
+
+ def __init__(self, mediabase, shadowdiv, parent=None, **kwargs):
+ super(_Aircraft, self).__init__(**kwargs)
+ self.registerInstance(self, parent)
+ self.__alive = False
+ self.__aircraftVid = avg.VideoNode(href=mediabase+'.mov', loop=True, parent=self)
+ self.__aircraftVid.pause()
+ self.__destroyVid = avg.VideoNode(href='explosion.mov', active=False,
+ threaded=False, parent=self)
+ self.__destroyVid.pause()
+ self.__destroyVid.subscribe(avg.VideoNode.END_OF_FILE, self._hide)
+ self.__shadowImg = avg.ImageNode(href=mediabase+'.gif', opacity=0.5,
+ pos=self.pos + _Aircraft.__SHADOW_OFFSET, parent=shadowdiv)
+ self.__shadowImg.size *= _Aircraft.__SHADOW_SCALE
+ if not player.isUsingGLES():
+ self.__shadowImg.setEffect(avg.BlurFXNode(6.0))
+ self.size = self.__aircraftVid.size
+ #self._debug('created')
+
+ @property
+ def alive(self):
+ return self.__alive
+
+ def reset(self):
+ #self._debug('reset')
+ self.active = True
+ self.__alive = True
+ self.__aircraftVid.active = True
+ playVideo(self.__aircraftVid)
+ self.__destroyVid.active = False
+ self.__destroyVid.pause()
+ self.__shadowImg.active = True
+
+ def destroy(self):
+ #self._debug('destroy')
+ self.__alive = False
+ self.__aircraftVid.active = False
+ self.__aircraftVid.pause()
+ self.__destroyVid.active = True
+ playVideo(self.__destroyVid)
+ self.__destroyVid.seekToFrame(0)
+ self.__shadowImg.active = False
+ if player.isUsingGLES():
+ self._hide()
+
+ def _move(self, pos):
+ self.pos = pos
+ self.__shadowImg.pos = self.pos + _Aircraft.__SHADOW_OFFSET
+
+ def _hide(self):
+ #self._debug('hide')
+ self.active = False
+ self.__alive = False
+ self.__aircraftVid.pause()
+ self.__destroyVid.pause()
+ self.__shadowImg.active = False
+
+ @classmethod
+ def _debug(cls, msg):
+ print '%6d [%s] %s' %(player.getFrameTime(), cls.__name__, msg)
+
+
+class PlayerAircraft(_Aircraft):
+ ACTION_KEYS = ('left', 'right', 'up', 'down', 'space')
+ __BULLET_OFFSET_L = avg.Point2D( 52, 16)
+ __BULLET_OFFSET_R = avg.Point2D(140, 16)
+
+ def __init__(self, shadowdiv, gunCtrl, parent=None, **kwargs):
+ super(PlayerAircraft, self).__init__('spitfire', shadowdiv, parent, **kwargs)
+ self.__gunCtrl = gunCtrl
+ self.__bullets = [Bullet(parent=self.parent) for i in xrange(10)]
+ self.__engineSnd = avg.SoundNode(href='flySound.mp3', loop=True, parent=self)
+ self.__bulletSnd = avg.SoundNode(href='bulletSound.mp3', volume=0.75, parent=self)
+ self.__maxX, self.__maxY = self.parent.size - self.size
+
+ def reset(self):
+ super(PlayerAircraft, self).reset()
+ self.__gunCtrl.reset()
+ self._move((self.__maxX / 2, self.__maxY))
+ self.__engineSnd.play()
+
+ def destroy(self):
+ super(PlayerAircraft, self).destroy()
+ self.__engineSnd.stop()
+
+ def update(self, dt, keyStates):
+ d = _Aircraft._SPEED * dt
+ dx = 0
+ if keyStates['left']:
+ dx = -d
+ if keyStates['right']:
+ dx += d
+ dy = 0
+ if keyStates['up']:
+ dy = -d
+ if keyStates['down']:
+ dy += d
+ pos = (max(min(self.x + dx, self.__maxX), 0),
+ max(min(self.y + dy, self.__maxY), 0))
+ if pos != self.pos:
+ self._move(pos)
+
+ if keyStates['space'] and self.__gunCtrl.shoot():
+ # fire bullets
+ bulletLeft = None
+ bulletRight = None
+ for b in self.__bullets:
+ if b.active:
+ b.update(dt)
+ elif not bulletLeft:
+ bulletLeft = b
+ elif not bulletRight:
+ bulletRight = b
+ if not bulletLeft:
+ bulletLeft = Bullet(parent=self.parent)
+ self.__bullets.append(bulletLeft)
+ if not bulletRight:
+ bulletRight = Bullet(parent=self.parent)
+ self.__bullets.append(bulletRight)
+ bulletLeft.reset(self.pos + PlayerAircraft.__BULLET_OFFSET_L)
+ bulletRight.reset(self.pos + PlayerAircraft.__BULLET_OFFSET_R)
+ self.__bulletSnd.play()
+ self.__bulletSnd.seekToTime(0)
+ else:
+ self.__gunCtrl.update(dt)
+ self.updateBullets(dt)
+
+ def updateBullets(self, dt):
+ bulletsAlive = False
+ for b in self.__bullets:
+ if b.active:
+ bulletsAlive = True
+ b.update(dt)
+ return bulletsAlive
+
+ def getActiveBullets(self):
+ return [b for b in self.__bullets if b.active]
+
+
+class EnemyAircraft(_Aircraft):
+ ESCAPED = avg.Publisher.genMessageID()
+
+ def __init__(self, shadowdiv, parent=None, **kwargs):
+ super(EnemyAircraft, self).__init__('enemy', shadowdiv, parent, **kwargs)
+ self.publish(EnemyAircraft.ESCAPED)
+ self.__destroySnd = avg.SoundNode(href='enemyDeath.mp3', volume=2.0, parent=self)
+ self._hide()
+
+ def reset(self):
+ super(EnemyAircraft, self).reset()
+ self._move((randint(0, self.parent.width - self.width), -self.height))
+
+ def destroy(self):
+ super(EnemyAircraft, self).destroy()
+ self.__destroySnd.play()
+ self.__destroySnd.seekToTime(0)
+
+ def update(self, dt):
+ y = self.y + _Aircraft._SPEED * dt
+ if y < self.parent.height:
+ self._move((self.x, y))
+ else:
+ self._hide()
+ self.notifySubscribers(EnemyAircraft.ESCAPED, [])
+
+
+### gui elements ###
+
+class ScrollingBackground(object):
+ __SCROLL_SPEED = 120.0 # px/s
+
+ def __init__(self, parent):
+ self.__imgA = avg.ImageNode(href='ground.jpg', parent=parent)
+ self.__imgB = avg.ImageNode(href='ground.jpg', pos=(0, -self.__imgA.height),
+ parent=parent)
+
+ def update(self, dt):
+ dy = ScrollingBackground.__SCROLL_SPEED * dt
+ self.__imgA.y += dy
+ self.__imgB.y += dy
+ if self.__imgA.y >= self.__imgA.height:
+ self.__imgA.y = self.__imgB.y - self.__imgA.height
+ elif self.__imgB.y >= self.__imgA.height:
+ self.__imgB.y = self.__imgA.y - self.__imgA.height
+
+
+class LiveCounter(avg.DivNode):
+ __NUM_LIVES = 3
+
+ def __init__(self, parent=None, **kwargs):
+ super(LiveCounter, self).__init__(**kwargs)
+ self.registerInstance(self, parent)
+ self.__numLives = 0
+ self.__images = []
+ x = 0
+ for i in xrange(LiveCounter.__NUM_LIVES):
+ avg.ImageNode(href='gui_lives_bg.png', pos=(x, 0), parent=self)
+ img = avg.ImageNode(href='gui_lives_fg.png', pos=(x, 0), parent=self)
+ self.__images.append(img)
+ x += img.width
+
+ def reset(self):
+ self.__numLives = 3
+ for img in self.__images:
+ avg.fadeIn(img, 250)
+
+ def dec(self):
+ assert(self.__numLives)
+ self.__numLives -= 1
+ avg.fadeOut(self.__images[self.__numLives], 250)
+ return not self.__numLives
+
+
+class ScoreCounter(avg.DivNode):
+ def __init__(self, parent=None, **kwargs):
+ super(ScoreCounter, self).__init__(size=(3 * 34, 34), crop=True, **kwargs)
+ self.registerInstance(self, parent)
+ self.__score = 0
+ self.__images = [avg.ImageNode(href='gui_numbers.png', pos=((2 - i) * 34, 0),
+ parent=self) for i in xrange(3)]
+
+ def reset(self):
+ self.__score = 0
+ for img in self.__images:
+ if img.y != 0:
+ avg.LinearAnim(img, 'y', 250, img.y, 0).start()
+
+ def inc(self):
+ self.__score += 1
+ self.__updateImages()
+
+ def dec(self):
+ if self.__score > 0:
+ self.__score -= 1
+ self.__updateImages()
+
+ def __updateImages(self):
+ s = self.__score
+ for img in self.__images:
+ y = s % 10 * -34
+ if img.y != y:
+ avg.LinearAnim(img, 'y', 250, img.y, y).start()
+ s /= 10
+
+
+class GunControl(avg.DivNode):
+ __SHOOT_INTERVAL = 1.0 / 7 # ms
+ __TEMPERATURE_INC = 30 # px/shot
+ __TEMPERATURE_DEC = 60 # px/s
+
+ def __init__(self, parent=None, **kwargs):
+ super(GunControl, self).__init__(**kwargs)
+ self.registerInstance(self, parent)
+ bg = avg.ImageNode(href='gui_heatbar_bg.png', parent=self)
+ self.__heatbar = avg.DivNode(size=(1, bg.height), crop=True, parent=self)
+ avg.ImageNode(href='gui_heatbar_fg.png', parent=self.__heatbar)
+ self.__maxTemp = bg.width - GunControl.__TEMPERATURE_INC
+ self.__shootTimeout = 0.0
+
+ def reset(self):
+ self.__heatbar.width = 1
+
+ def update(self, dt):
+ if self.__shootTimeout > 0.0:
+ self.__shootTimeout = max(0.0, self.__shootTimeout - dt)
+ dw = GunControl.__TEMPERATURE_DEC * dt
+ if self.__heatbar.width > dw:
+ self.__heatbar.width -= dw
+
+ def shoot(self):
+ if self.__shootTimeout == 0.0 and self.__heatbar.width < self.__maxTemp:
+ self.__shootTimeout = GunControl.__SHOOT_INTERVAL
+ self.__heatbar.width += GunControl.__TEMPERATURE_INC
+ return True
+ return False
+
+
+### application ###
+
+class FireBirds(app.MainDiv):
+ ENEMY_SPAWN_TIMEOUT = 1000 # ms
+
+ def onInit(self):
+ self.mediadir = getMediaDir(__file__)
+
+ self.__gameMusic = avg.SoundNode(href='Fire_Birds.mp3', loop=True,
+ volume=0.75, parent=self)
+ self.__scrollingBg = ScrollingBackground(self)
+ self.__shadowDiv = avg.DivNode(parent=self)
+ self.__gameDiv = avg.DivNode(size=self.size, parent=self)
+ self.__guiDiv = avg.DivNode(parent=self)
+
+ bg = avg.ImageNode(href='gui_frame.png', parent=self.__guiDiv)
+ self.__guiDiv.pos = (0, self.height - bg.height)
+ self.__liveCounter = LiveCounter(pos=(8, 12), parent=self.__guiDiv)
+ gunCtrl = GunControl(pos=(300, 54), parent=self.__guiDiv)
+ self.__scoreCounter = ScoreCounter(pos=(1142, 54), parent=self.__guiDiv)
+
+ self.__enemies = []
+ for i in xrange(2):
+ self.__createEnemy()
+ self.__player = PlayerAircraft(self.__shadowDiv, gunCtrl, parent=self.__gameDiv)
+
+ enemyMask = avg.Bitmap(self.mediadir + '/enemy.gif')
+ self.__playerCollisionDetector = collisiondetector.CollisionDetector(
+ enemyMask, avg.Bitmap(self.mediadir + '/spitfire.gif'))
+ self.__bulletCollisionDetector = collisiondetector.CollisionDetector(
+ enemyMask, avg.Bitmap(self.mediadir + '/bullet.gif'))
+
+ self.__keyStates = dict.fromkeys(PlayerAircraft.ACTION_KEYS, False)
+ self.__frameHandlerId = None
+ self.__spawnTimeoutId = None
+ self.__gameMusic.play()
+ self.__start()
+ player.subscribe(player.KEY_DOWN, self.__onKeyDown)
+ player.subscribe(player.KEY_UP, self.__onKeyUp)
+
+ def __onKeyDown(self, event):
+ if self.__player.alive:
+ if event.keystring in PlayerAircraft.ACTION_KEYS:
+ self.__keyStates[event.keystring] = True
+ elif not self.__frameHandlerId: # game stopped
+ if event.keystring == 'space':
+ self.__start()
+ # else: wait for bullets and enemies to leave the screen
+
+ def __onKeyUp(self, event):
+ if event.keystring in PlayerAircraft.ACTION_KEYS:
+ self.__keyStates[event.keystring] = False
+
+ def __start(self):
+ assert(not self.__frameHandlerId and not self.__spawnTimeoutId)
+ self.__liveCounter.reset()
+ self.__scoreCounter.reset()
+ self.__player.reset()
+ self.__frameHandlerId = player.subscribe(player.ON_FRAME, self.__onFrame)
+ self.__spawnTimeoutId = player.setInterval(self.ENEMY_SPAWN_TIMEOUT,
+ self.__spawnEnemy)
+
+ def __stop(self):
+ assert(self.__frameHandlerId and self.__spawnTimeoutId)
+ player.clearInterval(self.__spawnTimeoutId)
+ self.__spawnTimeoutId = None
+
+ def __createEnemy(self):
+ enemy = EnemyAircraft(self.__shadowDiv, parent=self.__gameDiv)
+ enemy.subscribe(EnemyAircraft.ESCAPED, self.__scoreCounter.dec)
+ self.__enemies.append(enemy)
+ return enemy
+
+ def __spawnEnemy(self):
+ assert(self.__frameHandlerId)
+ enemy = None
+ for e in self.__enemies:
+ if not e.active:
+ enemy = e
+ break
+ if not enemy:
+ enemy = self.__createEnemy()
+ enemy.reset()
+
+ def __onFrame(self):
+ dt = player.getFrameDuration() * 0.001
+ self.__scrollingBg.update(dt)
+
+ bullets = self.__player.getActiveBullets()
+ enemiesActive = False
+ for e in self.__enemies:
+ if e.active:
+ enemiesActive = True
+ if e.alive:
+ for b in bullets:
+ if self.__bulletCollisionDetector.detect(e.pos, b.pos):
+ self.__scoreCounter.inc()
+ e.destroy()
+ b.destroy()
+ break
+ if e.alive: # no bullet hit
+ if self.__player.alive and \
+ self.__playerCollisionDetector.detect(e.pos, self.__player.pos):
+ e.destroy()
+ if self.__liveCounter.dec():
+ self.__stop()
+ self.__player.destroy()
+ if e.alive: # no player collision
+ e.update(dt)
+
+ if self.__player.alive:
+ self.__player.update(dt, self.__keyStates)
+ elif not self.__player.updateBullets(dt) and not enemiesActive:
+ # player dead, all bullets and enemies left the screen, all destroy videos played
+ player.unsubscribe(player.ON_FRAME, self.__frameHandlerId)
+ self.__frameHandlerId = None
+
+
+if __name__ == '__main__':
+ app.App().run(FireBirds(), app_resolution='1280x720')
+