Permalink
Fetching contributors…
Cannot retrieve contributors at this time
executable file 470 lines (389 sloc) 15.8 KB
#!/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, [])
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
### gui elements ###
class LifeCounter(avg.DivNode):
__NUM_LIVES = 3
def __init__(self, parent=None, **kwargs):
super(LifeCounter, self).__init__(**kwargs)
self.registerInstance(self, parent)
self.__numLives = 0
self.__images = []
x = 0
for i in xrange(LifeCounter.__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.Anim.fadeIn(img, 250)
def dec(self):
assert(self.__numLives)
self.__numLives -= 1
avg.Anim.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.__lifeCounter = LifeCounter(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.keyname in PlayerAircraft.ACTION_KEYS:
self.__keyStates[event.keyname] = True
elif not self.__frameHandlerId: # game stopped
if event.keyname == 'Space':
self.__start()
# else: wait for bullets and enemies to leave the screen
def __onKeyUp(self, event):
if event.keyname in PlayerAircraft.ACTION_KEYS:
self.__keyStates[event.keyname] = False
def __start(self):
assert(not self.__frameHandlerId and not self.__spawnTimeoutId)
self.__lifeCounter.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):
player.unsubscribe(player.ON_FRAME, self.__frameHandlerId)
self.__frameHandlerId = 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.__lifeCounter.dec():
player.clearInterval(self.__spawnTimeoutId)
self.__spawnTimeoutId = None
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
self.__stop()
if __name__ == '__main__':
app.App().run(FireBirds(), app_resolution='1280x720')