Im dritten Teil meines kleinen Spiele-Tutorials mit Processing.py, dem Python-Mode von Processing (hier die Links zu Teil 1 und Teil 2) wollte ich dem kleinen Avoider-Spiel ein wenig optische Tiefe verpassen. Daher habe ich einen Sternenhimmel inszeniert, bei dem die kleinen Sternen im fernen Hintergrund sich sehr langsam bewegen und die größeren Sterne etwas schneller. So, wie wenn man bei einer Zugfahrt aus dem Fenster schaut, da scheinen die nahen Bäume auch schnell vorbeizufliegen, während der Wald im Hintergrund sich nur langsam bewegt. Diese Wahrnehmung nennt man Bewegungsparallaxe und sie wird besonders gerne in Plattformspielen angewandt.
Um dies zu inszenieren, habe ich erst einmal im Reiter sprite.py
eine Klasse Star
angelegt:
class Star(object):
def __init__(self, posX, posY, dia, speed):
self.x = posX
self.y = posY
self.r = dia
self.dy = speed
self.a = 255 # Transparency
def move(self):
self.outside = False
self.y += self.dy
if self.y >= height:
self.outside = True
self.y = -2*self.r
self.x = randint(0, width - 2*self.r)
def display(self):
fill(255, 255, 255, self.a)
noStroke()
ellipse(self.x, self.y, self.r, self.r)
Ich hätte die Sterne natürlich auch von der Klasse Sprite
ableiten können, aber da für sie ja keine Kollisionserkennung benötigt wird, hielt ich dies für Overkill. Da zumindest die größeren Sterne blinken sollen, bekommmen sie eine Alpha-Kanal für Transparenz zugewiesen (self.a
). Ansonsten bewegen sie sich genauso wie die Smileys von oben nach unten, nur viel, viel langsamer.
Jeder Stern wird mit seiner Position, seiner Größe und seiner Geschwindigkeit initialisiert. Per Default erhält er die größtmögliche Transparenz, das heißt, er ist strahlend weiß.
Im Hauptprogramm werden für die Sterne zwei Listen angelegt, eine (bstar[]
) für die weit entfernten, kleinen Sterne und eine nStar
für die größeren, näher erscheinenden Sterne. Das Auffüllen aller Listen habe ich in die setup
-Funktion verschoben, dort wird nun die Funktion loadData()
aufgerufen:
def loadData():
for i in range(noSmileys):
smiley.append(Smiley(randint(0, w-tw), -randint(50, 250)))
for i in range(nobStars):
bStar.append(Star(randint(0, w-2), randint(2, h-2), 1, 0.1))
for i in range(nonStars):
nStar.append(Star(randint(0, w-4), randint(2, h-2), randint(2, 3), 0.2))
Die kleinen Sterne werden mit einem Durchmesser von 1 initialisert, die größeren Sterne bekommen per Zufallszahl entweder einen Durchmesser von 2 oder 3 zugewiesen. Interessant ist die Geschwindigkeit, mit der die Sterne sich bewegen: 0.1 per Frame für die kleinen, 0.2 per Frame für die großen. Processing kommt intern erstaunlich gut mit diesen dezimalen Werten bei der Positionierung zurecht, obwohl ja eigentlich nur ganzzahlige Pixel möglich sind.
Es gibt jeweils eine feste Anzahl von Sternen, wie bei den Smileys auch werden sie, wenn sie den unteren Bildrand passiert haben, wieder auf eine zufällige Position oberhalb des Fensters zurückversetzt.
Die Bewegung der Sterne findet natürlich in der Funktion playGame()
statt, und zwar als erstes, bevor alle anderen Akteure gezeichnet werden (schließlich bilden sie den Hintergrund des Spiels):
for i in range(len(bStar)):
bStar[i].move()
bStar[i].display()
for i in range(len(nStar)):
nStar[i].move()
if (frameCount % randint(15, 30)) < randint(1, 15):
nStar[i].a = 120
else:
nStar[i].a = 255
nStar[i].display()
Die größeren Sterne sollen zusätzlich zur Bewegung auch noch Blinken, daher habe ich ihnen zufällige Intervalle zugewiesen, in denen der Alpha-Kanal auf 120 gesetzt wird (nStar[i].a = 120
). Die Werte für die Zufallszahlen habe ich experimentell herausgefunden, Ihr könnt ruhig auch einmal andere Intervalle ausprobieren.
Und nun zum Nachvollziehen der vollständige Quellcode. Zuerst der Code aus dem Reiter sprite.py
:
from random import randint
tw = th = 36
class Sprite(object):
def __init__(self, posX, posY):
self.x = posX
self.y = posY
def checkCollision(self, otherSprite):
if (self.x < otherSprite.x + tw and otherSprite.x < self.x + tw
and self.y < otherSprite.y + th and otherSprite.y < self.y + th):
return True
else:
return False
class Skull(Sprite):
def __init__(self, posX, posY):
super(Skull, self).__init__(posX, posY)
self.score = 0
self.health = 0
def loadPics(self):
self.im1 = loadImage("skull.png")
def move(self):
self.x = mouseX
if self.x <= 0:
self.x = 0
elif self.x >= width-tw:
self.x = width - tw
def display(self):
image(self.im1, self.x, self.y)
class Smiley(Sprite):
def __init__(self, posX, posY):
super(Smiley, self).__init__(posX, posY)
self.outside = False
def loadPics(self):
self.im0 = loadImage("smiley0.png")
self.im1 = loadImage("smiley1.png")
self.im2 = loadImage("smiley4.png")
def move(self):
self.outside = False
self.y += self.dy
if self.y >= height:
self.outside = True
self.y = -randint(50, 250)
self.x = randint(0, width-tw)
self.dy = randint(4, 10)
def display(self):
if (self.y > -30) and (self.y <= 250):
image(self.im0, self.x, self.y)
elif (self.y > 250) and (self.y <= 320):
image(self.im1, self.x, self.y)
elif (self.y > 320):
image(self.im2, self.x, self.y)
def reset(self, posX, posY):
self.x = posX
self.y = posY
class Star(object):
def __init__(self, posX, posY, dia, speed):
self.x = posX
self.y = posY
self.r = dia
self.dy = speed
self.a = 255 # Transparency
def move(self):
self.outside = False
self.y += self.dy
if self.y >= height:
self.outside = True
self.y = -2*self.r
self.x = randint(0, width - 2*self.r)
def display(self):
fill(255, 255, 255, self.a)
noStroke()
ellipse(self.x, self.y, self.r, self.r)
Außer dem schon oben besprochen Objekt Star
gibt es hier nichts Neues. Aber auch im Hauptprogramm sind nur die erwähnten Änderungen neu:
from random import randint
from sprite import Skull, Smiley, Star
w = 640
h = 480
tw = th = 36
noSmileys = 10
nobStars = 30
nonStars = 15
startgame = True
playgame = False
gameover = False
skull = Skull(w/2, 320)
smiley = []
bStar = []
nStar = []
def setup():
global heart
skull.score = 0
skull.health = 5
size(640, 480)
frameRate(30)
loadData()
skull.loadPics()
for i in range(len(smiley)):
smiley[i].loadPics()
smiley[i].dy = randint(4, 10)
font = loadFont("ComicSansMS-32.vlw")
textFont(font, 32)
heart = loadImage("heart.png")
# noCursor()
# cursor(HAND)
def draw():
global heart
background(0, 0, 0)
fill(255, 255, 255, 255)
text("Score: " + str(skull.score), 10, 32)
for i in range(skull.health):
image(heart, width - i*tw - tw - 2, 2)
if startgame:
startGame()
elif playgame:
playGame()
elif gameover:
gameOver()
def loadData():
for i in range(noSmileys):
smiley.append(Smiley(randint(0, w-tw), -randint(50, 250)))
for i in range(nobStars):
bStar.append(Star(randint(0, w-2), randint(2, h-2), 1, 0.1))
for i in range(nonStars):
nStar.append(Star(randint(0, w-4), randint(2, h-2), randint(2, 3), 0.2))
def startGame():
global startgame, playgame
text("Klick to Play", 200, height/2)
if mousePressed:
startgame = False
playgame = True
def playGame():
global playgame, gameover
for i in range(len(bStar)):
bStar[i].move()
bStar[i].display()
for i in range(len(nStar)):
nStar[i].move()
if (frameCount % randint(15, 30)) < randint(1, 15):
nStar[i].a = 120
else:
nStar[i].a = 255
nStar[i].display()
skull.move()
for i in range(len(smiley)):
if skull.checkCollision(smiley[i]):
if skull.health > 0:
skull.health -= 1
smiley[i].reset(randint(0, w-tw), -randint(50, 250))
else:
playgame = False
gameover = True
skull.display()
for i in range(len(smiley)):
smiley[i].move()
if smiley[i].outside:
skull.score += 1
smiley[i].display()
def gameOver():
global playgame, gameover
text("Game Over!", 200, height/2)
text("Klick to play again.", 200, 300)
if mousePressed:
gameover = False
for i in range(len(smiley)):
smiley[i].reset(randint(0, w-tw), -randint(50, 250))
playgame = True
skull.health = 5
Das Spiel ist schon recht spielbar geworden, durch die Sterne entsteht tatsächlich die Illusion von Tiefe und es ist auch nicht einfach, den Schädel für längere Zeit an den herunterfallenden Smileys vorbei zu manövrieren. Irgendwann erwischt es einen immer.
Wie alle meine Tutorien zu Processing.py habe ich auch dieses auf meine Site »Processing.py lernen« hochgeladen. Korrekturen, Änderungen und Ergänzungen wird es daher nur dort geben.
Über …
Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!
Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.
Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.
Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes
von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.
Werbung