3.5 GLOBALE VARIABLEN, ANIMATIONEN

 

 

EINFÜHRUNG

 

Die Computergrafik wird vielfach zur Darstellung von zeitlich veränderlichen Inhalten verwendet. Damit kannst du einen Ablauf in der Physik oder Biologie simulieren oder ein Computergame erstellen. Man nennt ein solches Programm allgemein eine Animation. Um den zeitlichen Ablauf sichtbar zu machen, wird immer nach einem gleich grossen Zeitschritt, den man Animationsschritt nennt,  ein neues Bild gezeichnet.

PROGRAMMIERKONZEPTE: Globale Variablen, Nebenwirkungen (Seiteneffekte), Doppelpufferung

 

 

VERÄNDERUNG GLOBALER VARIABLEN

 

Du willst einen Ball darstellen, der sich auf einem Kreis bewegt. Eine Kreisbewegung mit dem Radius 1 kriegst du, indem du die x-Koordinate mit einem ansteigenden Parameter t, welcher der fortlaufenden Zeit entspricht, mit der Cosinusfunktion und die y-Koordinate mit der Sinusfunktion berechnest, also x = cos(t) und y = sin(t) . Willst du einen anderen Radius, so musst du die beiden Werte noch mit dem Radius multiplizieren.

In der Funktion step() wird die Situation zu jedem Animationsschritt gezeichnet. Wenn der Ball den Kreis einmal umrundet hat, soll sich seine Farbe verändern.

 

Es ist üblich, im Hauptprogramm eine  endlose Animationsschleife einzuführen, die step() immer wieder aufruft. Durch Einbau einer Verzögerung, kann man die Geschwindigkeit der Animation verändern. In step() soll die globale Variable t in jedem Schritt erhöht und beim Erreichen von 2P wieder auf Null gesetzt und die Farbe verändert werden.

import math
from gpanel import *

def step():
    global t
    x = r * math.cos(t)
    y = r * math.sin(t)
    t = t + 0.1
    if t > 6.28:
        t = 0
        setColor(getRandomX11Color())
    move(x, y)
    fillCircle(10)

makeGPanel(-500, 500, -500, 500)
bgColor("darkgreen")

t = 0
r = 200 

while True:
    step()
    delay(10)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Python verbietet, in Funktionen den Wert von globalen Variablen zu verändern. Wir können dieses Verbot aber umgehen, indem wir in der Funktion die Variable  mit dem Schlüsselwort global versehen.

Der Bezeichner global birgt Gefahren: Jede beliebige Funktion kann nicht nur eine als global bezeichnete Variable verändern, sondern sie sogar erzeugen, wie folgendes Beispiel zeigt:

def set():
    global a
    a = 2

def get():
    print "a =", a
set()
get()

Da set() eine Variable a erzeugt, die im ganzen Programm sichtbar ist, sagt man, dass die Funktion set() Nebenwirkungen (auch Seiteneffekte gennant) habe. Beachte auch, wie man elegant mit der print-Anweisung durch Kommatrennung mehrere Grössen hintereinander in die Console schreiben kann. Beim Ausschreiben wird das Komma durch einen Leerschlag ersetzt.

 

 

DER TRICK MIT DEM EXAKTEN TICK

 

Die Animationsschleife sollte in möglichst gleichen Zeitticks, d.h. mit der gewünschten Animationsperiode, durchlaufen werden, da sonst die Bewegung ruckelt. In step() wird jeweils der neue Animationszustand aufgebaut und dies kann unterschiedlich viel Zeit in Anspruch nehmen, da eventuell nicht immer gleiche Codeteile durchlaufen werden, aber auch deswegen, weil der Computer im Hintergrund noch mit anderen Aufgaben beschäftigt ist, was die Ausführung des Python-Codes verzögert. Um das verschieden lange dauernde step() auszugleichen, wird daher folgender Trick angewendet, auf den du auch selbst gekommen wärst: Man merkt sich in der Variable startTime vor dem Aufruf von step() die aktuelle Uhrzeit. Nach der Rückkehr von step() wartet man in einer Warteschleife so lange, bis die Differenz der neuen Uhrzeit und der Startzeit die Animationsperiode erreicht.

Das Programm bewegt einen Fussball von Tor zu Tor. Dabei verwendest du ein Bild football.gif, das sich im Verzeichnis sprites der TigerJython-Distribution befindet. Du kannst aber auch ein eigenes Bild nehmen, indem du die Datei in ein entsprechendes Verzeichnis auf deinem Computer kopierst und den Dateipfad als Parameter in image() angibst (absolut oder relativ zum Verzeichnis, in dem sich tigerjython2.jar befindet).

 
from gpanel import *
import time

def step():
    global x
    global v
    clear()
    lineWidth(5)
    move(25, 300)
    rectangle(50, 100)
    move(575, 300)
    rectangle(50, 100)
    x = x + v
    image("_sprites/football.gif", x, 275)
    if x > 500 or x < 50:
        v = -v

makeGPanel(0, 600, 0, 600)
bgColor("forestgreen")
enableRepaint(False)

x = 300
v = 10

while True:
    startTime = time.clock()
    step()
    repaint()
    while (time.clock() - startTime)  < 0.020:
        delay(1)
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Du kannst mit time.clock() die aktuelle Zeit als Dezimalzahl erhalten. Der erhaltene Wert ist zwar computerabhängig (Prozessorzeit oder Zeit seit dem ersten Aufruf von clock()). Da du aber nur die Zeitdifferenz benötigst, spielt dies keine Rolle. Du speicherst die Zeit vor dem Aufruf von step() und wartest am Ende der Animationsschleife mit einem delay(1) so lange, bis die Zeitdifferenz zwischen aktueller Zeit und Startzeit die Animationsperiode (in Sekunden) erreicht. Merke dir diesen Trick, denn du wirst ihn für viele Prozesse, die möglichst periodisch ablaufen sollen, anwenden.

Im GPanel ist jeder Grafikbefehl im Bildschirmfenster sofort sichtbar. Das Löschen mit clear() bei Animationen zeigt daher auch kurzzeitig ein leeres Grafikfenster, was zu einem Flackereffekt führen kann. Um dies zu vermeiden, sollte bei Animationen die Doppelpufferung angewendet werden.

Man erreicht dies durch den Befehl enableRepaint(False), der bewirkt, dass die Grafikbefehle  nur noch in einem Hintergrund-Puffer (offscreen buffer) ausgeführt werden und nicht mehr automatisch im Grafikfenster sichtbar sind. Auch clear() löscht dann nur noch den Hintergrund-Puffer. Das Anzeigen des Grafikpuffers auf dem Bildschirm (Rendern genannt) musst du dann selbst im richtigen Moment durch Aufruf von  repaint() auslösen.

Auch in diesem Programm musst du in der Funktion step() die Variablen x und v mit global kennzeichnen, da sie in der Funktion verändert werden.

 

 

AUFGABEN

 
1.

Wenn man die x- und y-Koordinate nicht wie in deinem ersten Programm mit einer gewöhnlichen Cosinus- bzw. Sinusfunktion bewegt, sondern unterschiedlich schnell, so entstehen interessante Kurvenmuster, die man Lissajoux-Figuren nennt. Zeichne solche Figuren mit einer Auflösung von 1/1000 im Bereich t = 0 bis 2π  mit der Wahl

x = cos(4.5 * t) und y = sin(6.3 * t)


2.


Verwende statt fixe Zahlen die Variablen omega_x und omega_y und zeichne die Figur für folgende Werte:

omega_x
omega_y
3
5
3
7
5
7

Erkennst du einen Zusammenhang zwischen der Figur und den Werten von omega_x und omega_y?


3.


Zeichne die Lissajoux-Figur mit omega_x =  2 und omega_y = 7 im Bereich t = 0 bis 2π mit einer Auflösung von 1/100 in einem GPanel mit den Koordinaten -2 bis 2 (beide Achsen). Statt die Punkte mit Linien zu verbinden, zeichnest du nun im jedem Punkt einen Kreis mit dem Radius 0.2. So entstehen "federartige" Figuren. Wie du im Bild siehst, kannst du die Kreise einfarbig oder mit getRandomX11Color() füllen. Spiele ein wenig damit.