====== PyGame 2021 ======
===== Setup =====
==== Auftrag 0A: PyGame einrichten ====
Installiere PyGame. Achtung: Typischerweise funktioniert dieses nicht mit der aktuellsten Python Version. Stand Januar 2021 läuft es sicher mit Python 3.78. Benötigt man für verschiedene Projekte verschiedene Installationen und verschiedene Versionen von Python, so lohnt sich die Verwendung von Virtual Environments. Verwende pip um Module wie PyGame zu installieren.
Recherchiere, was Virtual Environments sind, und wie man mit diesen arbeitet. Erstelle dann ein neues für Pygame Projekte und installiere darin (mit pip) PyGame.
Quellen:
* Seite "Python Setup" in diesem Wiki
* https://www.youtube.com/watch?v=APOPm01BVrk
* Selber googlen macht den Meister!
==== Auftrag 0B: GitHub einrichten ====
1. Erstelle einen GitHub Account, falls du noch keinen hast.
1. Erstelle ein neues Repo `pygame_exercises`
1. Gib dieses deinem Lehrer frei (Name: anschae)
1. ALLE Übungen, die du hier erledigst, müssen auf GitHub gestellt werden. Commit und pushe immer, nachdem du an einer Übung gearbeitet hast. Mache dies //unaufgefordert//!
==== Konventionen ====
1. Für unterschiedliche Programmiersprachen gibt es unterschiedliche Konventionen dazu, wie man Variablen, Klassen usw. benennt. Zum Beispiel sollten Klassen im `UpperCamelCase` Stil und Variablen im `snake_case` Stil notiert werden. Studiere für weitere Infos den folgenden Convention Guide: https://www.fcodelabs.com/2018/12/03/Python-Coding-Standard/
1. Pfade von Ordner und Dokumente werden in Windows (`myfolder\\myfile.txt`) anders angegeben als in MacOS (`myfolder/myfile.txt`). Damit dein Code auf sämtlichen Plattformen funktioniert, musst du also solche Pfadangaben vermeiden. Stattdessen sollst du das os Modul verwenden:
import os.path
path = os.path.join("myfolder", "myfile.txt")
===== Auftrag 1: Billard =====
Programmiere ein Spiel, welches Ähnlichkeiten mit dem Spiel Billard hat. Es soll schlussendlich beinhalten:
* Eine Kugel //Player//, welche man in eine beliebige Richtung mit einer bestimmten Geschwindigkeit abschiessen kann.
* Dazu musst du die ein System überlegen, wie du dies umsetzen möchtest: Pfeiltasten, Maus, ...
* Mehrere **weitere Kugeln**, welche miteinander und mit dem Player **kollidieren** können. Die Kollisionen sollen physikalisch korrekt ablaufen.
* Auch an den **Wänden** sollen die Kollisionen korrekt ablaufen.
* Die Kugeln sollen wieder zum Stillstand kommen. Es muss also **Reibung** (quasi Luftwiderstand) eingebaut werden.
* Überlege dir ein **Spielsystem** und ein **Ziel**, z.B. durch das Versenken von Kugeln.
* Es kann ein One Player Game oder ein Two Player Game (abwechselnd) sein.
* Sei kreativ!
Alles was du an Mathematik und Physik wissen musst, findest du hier: [[talit:python_kinematik_vektoren|Vektoren, Kinematik & Python]]
==== Version 0: Template ====
Dir wird ein Template (siehe ZIP-File unten) zur Verfügung gestellt, welches dir als Grundlage dienen soll. Erstelle wie weiter oben erklärt ein GitHub Repo für diesen Auftrag und füge den Inhalt des ZIPs hinzu.
Verschaffe dir nun einen **Überblick** über den Code. Setze dazu einen **Breakpoint** ganz zu Beginn der `play()`-Methode und gehe Schritt-für-Schritt durch den Code. Es ist wichtig, dass du verstehst, was in jedem Schritt passiert! Konsultiere Regelmässig das **Manual** https://www.pygame.org/docs/, um mehr über die verschiednen Features von PyGame zu lernen!
{{ :talit:pygame_billard_template.zip |}}
==== Version 1: Kollision an Wänden ====
Gib dem Player eine Anfangsgeschwindigkeit. Implementiere die Kollisionen an den Wänden. Es soll noch keine Reibung vorkommen, d.h., die Geschwindigkeit muss konstant bleiben! Die Bewegung des Players ist also eine uniforme (gleichförmige) Bewegung in 2D.
Studiere zuerst die **Theorie** zur [[talit:python_kinematik_vektoren#uniforme_bewegung|uniformen Bewegung]] und zu [[talit:python_kinematik_vektoren#vektoren_mit_numpy|Vektoren mit Numpy]]
==== Version 2: Reibung ====
Mache eine Kopie der ersten Version des Spiels und benenne diese `billard_v02.py`.
Implementiere Reibung. Ziehe dazu in jedem Schritt einen bestimmten, immer gleich bleibenden Wert vom //Betrag der Geschwindigkeit// ab.
==== Version 3: Ball schiessen ====
File name: `billard_03.py` (Mache dies für jede Version, wird dann nicht mehr explizit erwähnt.
Der Player soll nun in eine beliebige Richtung mit einer bestimmten Geschwindigkeit abgeschossen werden können. Überlege dir zuerst, wie du das machen möchtest und setze es dann um.
==== Version 4: Weitere Bälle ====
Füge weitere Bälle hinzu. Sämtliche Kollisionen sollen physikalisch korrekt sein.
Studiere dazu die Seite [[talit:python_kinematik_vektoren#kollisionen|Kollisionen]] und insbesondere das PDF dort.
==== Version 5: Spielsystem ====
Überlege die ein Spielsystem und setze es um.
==== Version 6: Feinschliff ====
Der **Spielstand** soll angezeigt angezeigt werden. Was das ist, hängt vom Spiel ab: Gesammelte Punkte, verstrichene Zeit, ...
**Verschönere** nun dein Spiel. Es muss enthalten:
* schöne Grafiken (Background, ...)
* Animationen, z.B. wenn man einen Ball versenkt
* Sound (Hintergrundmusik, Soundeffekte)
===== Auftrag 2: Autoscooter =====
Programmiere das Spiel **Autoscooter**, welches wie folgt funktionieren soll:
- **Steuerung:** Der Spieler steuert einen Sprite (Player) in Form eines Balles. Für die Steuerung gibt es mehrere Optionen:
- direkt mit Tastatur steuern
- Der Player bewegt sich immer vorwärts. Mit der Tastatur kann man dann Impulse geben, um die Richtung zu ändern.
- irgendwie mit der Maus ...
- ...
- Im Spielfeld gibt es eine Anzahl **Bälle**, die per Zufall platziert werden.
- **Ziel** des Spiels: in möglichst kurzer Zeit **alle Bälle eliminieren**!
- Zu Beginn sind alle Bälle grün. Nach einer ersten Kollision mit dem Player oder einem anderen Ball wird ein Ball orange, nach einer weiteren Kollision rot und nach einer weiteren Kollision explodiert der Ball und verschwindet.
- An den Wänden soll der Ball abprallen, dies wird nicht als Kollision gewertet.
- Kollisionen und das abprallen an den Wänden soll nach den Gesetzen der Physik ablaufen.
- Die Bälle können etwas unterschiedliche Massen haben.
- Die aktuelle **Spielzeit** soll angezeigt werden.
- Der **Highscore** soll in einem File gespeichert und angezeigt werden.
==== Teil 1: Template ====
Dir wird ein Template (siehe ZIP-File unten) zur Verfügung gestellt, welches dir als Grundlage dienen soll. Erstelle wie weiter oben erklärt ein GitHub Repo für diesen Auftrag und füge das Template hinzu.
Verschaffe dir nun einen **Überblick** über den Code. Setze dazu einen **Breakpoint** ganz zu Beginn der `play()`-Methode und gehe Schritt-für-Schritt durch den Code. Es ist wichtig, dass du verstehst, was in jedem Schritt passiert! Konsultiere Regelmässig das **Manual** https://www.pygame.org/docs/, um mehr über die verschiednen Features von PyGame zu lernen!
{{ :talit:autoscooter_template.zip |}}
import os
import sys
import pygame
from PIL import Image, ImageDraw
from pathlib import Path
print(f"python executable: {sys.executable}")
### CONSTANTS ###
# Convention: use capital letters for constants (= variables you leave unchanged)
# Windows
WIN_HEIGHT = 800
WIN_WIDTH = 500
# Define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
BG_COLOR = WHITE
# Framerate
FPS = 30
TIME_DELAY = int(1000 / FPS)
# Game Constants
SPEED = 12
### FUNCTIONS ###
### CLASSES ###
class Player(pygame.sprite.Sprite):
def __init__(self, img_path, xy_center, v, mass):
super().__init__() # call __init__ of parent class (i.e. of pygame.sprite.Sprite)
# ASSIGN CLASS ATTRIBUTES
if not os.path.exists(img_path):
raise Exception("THE FOLLOWING FILE DOES NOT EXIST: {0}".format(img_path))
self.image = pygame.image.load(str(img_path)) # load image
self.rect = self.image.get_rect() # create rectangle containing ball image
self.rect.center = (int(xy_center[0]),int(xy_center[1])) # set center coords of ball
self.mask = pygame.mask.from_surface(self.image) # creates a mask, used for collision detection (see manual about pygame.sprite.collide_mask())
self.mass = mass # give sprite a mass -> realistic collisions
def update(self):
# in each step, this method is called
if self.rect.left <= 0: self.rect.left = 0
elif self.rect.right >= WIN_WIDTH: self.rect.right = WIN_WIDTH
if self.rect.top <= 0: self.rect.top = 0
elif self.rect.bottom >= WIN_HEIGHT: self.rect.bottom = WIN_HEIGHT
def collide(self,ball):
pass
class Ball(pygame.sprite.Sprite):
"""
Class for balls, derive from pygame's sprite class
-> makes your life easier since you can use e.g. the collision detection of the sprite class
"""
def __init__(self, img_path, xy_center, v, mass):
super().__init__() # call __init__ of parent class (i.e. of pygame.sprite.Sprite)
# ASSIGN CLASS ATTRIBUTES
if not os.path.exists(img_path): # check if folder of image exists
raise Exception("THE FOLLOWING FILE DOES NOT EXIST: {0}".format(img_path))
self.image = pygame.image.load(str(img_path)) # load image
self.rect = self.image.get_rect() # create rectangle containing ball image
self.rect.center = (int(xy_center[0]),int(xy_center[1])) # set center coords of ball
self.mask = pygame.mask.from_surface(self.image) # creates a mask, used for collision detection (see manual about pygame.sprite.collide_mask())
self.mass = mass # is relevant for realistic collisions
def update(self):
"""
- update function gets executed in every step
- determines motion of ball
"""
pass
def collide(self,ball):
"""
ball self collides with other ball, given as argument
this method updates velocities of BOTH balls
"""
pass
class Game:
"""
Main GAME class
"""
def __init__(self):
pygame.init()
pygame.font.init()
self.time_delay = TIME_DELAY
self.screen = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) # create screen which will display everything
self.win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pygame.display.set_caption("Bouncing Balls") # Game title
def quit(self):
pygame.quit()
sys.exit(0)
def play(self):
# CREATE PLAYER
player = Player(os.path.join("data","ball_r=100_col=0_255_0.png"),[150,550],[0,0],1)
# CREATE GAME GROUPS
Balls = pygame.sprite.Group()
# CREATE OBJECTS AND ASSIGN TO GROUPS
balls_list = [
Ball(os.path.join("data","ball_r=100_col=0_0_255.png"),[100,200],[0,0],1),
Ball(os.path.join("data","ball_r=100_col=0_0_255.png"),[200,400],[0,0],1)
]
Balls.add(balls_list)
# GAME PERMANENT LOOP
while True:
pygame.time.delay(TIME_DELAY)
# KEY EVENTS
for event in pygame.event.get():
# Exit app if click quit button
if event.type == pygame.QUIT:
self.quit()
# Naviation of player
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
player.rect.top -= SPEED
# and so on ...
# COLLISION DETECTION
# see manual for all types of collisions: https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollide
# TODO: check for collisions between any two balls. If there is any, call the collision() method of the Ball class.
for ball in balls_list:
pass
# UPDATE
Balls.update()
player.update()
# DRAW
self.screen.fill(BG_COLOR) # draw empty screen
Balls.draw(self.screen)
self.screen.blit(player.image, player.rect)
pygame.display.update()
pygame.quit()
if __name__ == "__main__":
Game().play()
==== Teil 2: Steuereung ====
Implementiere nun eine Steuerung für den Player.
===== Auftrag 3: Eigenes Projekt =====
==== Auftrag ====
Programmiere mit PyGame ein eigenes Game, welches folgende Eigenschaften haben muss:
1. **Originale (und originelle) Idee**, also kein billiger Abklatsch von existierende Game. Natürlich darf man sich inspirieren lassen.
1. **Sauber programmiert**:
1. strikt objektorientiert (was zu Objekt gehört, kommt in entsprechende Klasse)
1. Klar und sauber strukturiert. Z.B. Permanent-Loop sollte wie folgt strukturiert sein: Time delay / Key Events / Collision handling / Update / Draw / pygame.display.update()
1. sauber und relativ ausführlich kommentiert
1. **Game-Balancing**:
1. Spiel ist so designed, dass der Spieler dieses gerne und längerfristig spielt.
1. Dazu müssen dem Spieler Anreize gegeben werden: Schwierigkeitsgrad erhöht sich, neue Levels, besseres Gear, ...
1. Schwierigkeitsgrad ist so gewählt, dass Spiele nicht überfordert, aber auch nicht gelangweilt ist.
1. **Assets**:
1. enthält Animationen, Sounds und ansprechende Grafiken
1. sämtliche Animationen werden komplett selbst gefertigt
1. Soundeffekte werden nach Möglichkeit selbst aufgenommen
1. Sound und Soundeffekte dürfen aus dem Internet genommen werden, solange sie royalty-free sind.
1. **Game-Testing**:
1. Game wird fleissig getestet. Nicht nur von sich selbst, aber auch von Kolleg*Innen. Am besten von erfahrenen und unerfahrenen Gamer*Innen
1. keine Bugs mehr
1. Keine Errors, auch wenn Spieler komische, unvorhergesehene Dinge tut. Fehler werden abgefangen.
==== Ablauf ====
1. Spiel ausdenken, grobe Skizze
1. Detaillierter Projektbeschrieb (siehe unten)
1. ... und mit LP besprechen
1. Programmier-Phase
1. Test-Phase
1. Feinschliff
==== Projektbeschrieb ====
- Kurzbeschrieb Game: Um was geht es?
- Klassen:
- Welche Klassen werden benötigt?
- Welche wichtigen Attribute und Methoden sollen diese haben?
- Assets:
- welche Assets (Bilder, Animationen, Sounds) werden benötigt
- zeichne von Hand grobe Skizzen der wichtigsten graphischen Assets
- Erwartet Hauptschwierigkeiten: Wo erwartest du die grössten Schwierigkeiten?
- Zeitplan: Erstelle einen relativ detaillierten Zeitplan, wann du was erledigen möchtest.