Om någon där ute har väntat på den sista delen i min tutorial om Pygame Zero så kan jag härmed meddela att väntan nu är över. Några kodexempel blir det dock inte den här gången. Eftersom jag inte ville att inlägget skulle bli ännu längre och rörigare än föregående mastodontinlägg så ger jag i texten nedan bara sammanfattande beskrivningar av de ändringar och tillägg som jag har gjort i vårt lilla spel, som ju är en version av klassiska Space Invaders. Och så får du som vill göra de här ändringarna i ditt eget program kika i källkoden som jag har lagt upp på Github. Är du med? Då kör vi.

(Är du inte med? I så fall hittar du samtliga tidigare inlägg om Pygame Zero här.)

Scener

I den tidigare versionen så startade själva spelet omedelbart när man körde programmet. Så brukar det ju inte vara i riktiga spel, utan först brukar man komma till någon form av startskärm eller meny där man kan välja att starta spelet när man själv är redo. Därför så har jag infört olika ”lägen”, eller scener som man brukar kalla det, i spelet. Den första scenen är menyscenen och den representeras av en klass som heter just MenuScene. Precis som spelkoden i den gamla versionen så har MenuScene två metoder som heter update och draw, och det är dessa metoder som nu anropas när programmet körs. Så här ser menyskärmen ut:

menu

Så mycket till meny är det ju egentligen inte – det enda användaren kan göra är att starta spelet genom att trycka på tangenten ”s” (eller stänga fönstret igen genom att klicka på krysset). Men någonting ska scenen heta och jag valde MenuScene. Vill du hellre döpa din klass till t ex StartScene så gör det.

När användaren väl trycker på ”s” så växlar programmet till spelscenen, som är ett objekt av klassen PlayScene. Till denna klass har all den kod som tidigare låg sist i programmet, själva spelkoden, flyttats. Den tredje och sista scenen är GameOverScene och den kommer man till när man antingen har dödat alla rymdvarelser eller…

Ond bråd död

… när man själv har dött. Som du kanske minns så var kanonen, som är det objekt som spelaren styr, ”odödlig” i den tidigare versionen av spelet. Så är det inte längre, utan nu ”dör” man om man antingen krockar med en rymdvarelse eller om en rymdvarelse når botten på spelfönstret. Hur växlar man då mellan liv och död? Jo, om du tittar i klassen PlayScene så ser du att det finns en variabel som heter running. Det är en så kallad boolesk variabel som bara kan ha två värden: True (sant) eller False (falskt). När spelet startas så sätts running till True. I metoden update i PlayScene så kontrolleras värdet och det är bara om running har värdet True som den kod som uppdaterar spelet körs. Om spelaren på det ena eller andra sättet dör så sätts running till False och spelscenen ”avvecklas” innan spelet växlar till slutskärmen.

Klassen Game

En annan ny klass är Game och den representerar just själva spelet, i sin helhet. Den innehåller inte särskilt mycket, det mesta av den kod som så att säga uträttar något i spelet ligger ju i scen-klasserna och i synnerhet då i PlayScene. Men den har en liten lista, scenes, med de olika scenerna och en metod, change_scene, för att växla mellan scenerna. Uppmärksamma läsare kanske noterar att när scenes skapas i Games metod __init__  så används inte hakparenteser utan vanliga parenteser. När man gör på detta vis så skapas en tuple, en speciell sorts lista som inte går att ändra på. Eftersom vi inte planerar att ta bort eller lägga till scener utan bara ska växla mellan samma tre scener så passar det bra att göra scenes till en sådan lista. I Game finns även en metod som heter set_game_over_message och som används för att sätta det budskap som ska visas på slutskärmen.

Explosioner

Den sista nya klassen är Explosion och vad den representerar kan du nog lista ut. Varje gång en rymdvarelse dör och tas bort ur listan med rymdvarelse-objekt så skapas i stället en explosion som ritas på samma position och lagras i spelscenens lista explosions. Explosionen visas bara under ett visst antal ”ticks”, det vill säga varv i spelloopen. Sedan får spelscenen via metoden is_finished besked att explosionen är klar och även den tas bort ur spelet. Samma sak händer när kanonen/spelaren dör, men då visas en annan bild.

”Konstanter”

Till sist så har jag, längst upp i källkoden, lagt till ett antal ”konstanter”. Med konstanter menas i kodsammanhang variabler vars värden man inte kan ändra på. Namn på sådana variabler brukar i Python och många andra språk skrivas med versaler. I Python kan man faktiskt ändra värdet på en variabel av detta slag, det är därför jag har satt citattecken kring konstanter ovan. Men versalerna är åtminstone en signal om att man inte ska ändra på dem. Hur som helst: I dessa variabler har jag lagrat värden som senare används i olika beräkningar, t ex spelfönstrets bredd och höjd, värden som bestämmer rymdvarelsernas positioner och med vilken hastighet olika objekt rör sig. Det finns flera fördelar med att göra på det här sättet i stället för att ”hårdkoda” värden, skriva dem uttryckligen i koden, som vi gjorde i den tidigare versionen. Dels så blir koden mer läsbar. Dels så behöver man, om man senare vill ändra på något som exempelvis spelfönstrets bredd och antalet rymdvarelser per rad, bara ändra på ett enda ställe.

Nu har jag redogjort för alla ändringar – tror jag, om du hittar något i koden som jag inte har tagit upp får du gärna ställa en fråga i kommentarsfältet – så det här inlägget och därmed hela denna tutorial börjar lida mot sitt slut.

gameover

Men om du själv är sugen på att bygga vidare på spelet så har jag ett par förslag. Till att börja med skulle du kunna lägga till andra typer av aliens. De bilder som jag har använt är hämtade från denna fina spritesheet där de ligger under rubriken ”Original Arcade”. Ett annat förslag, som nog kräver lite mer klurande, är att låta rymdvarelserna skjuta tillbaka mot kanonen. Ett sätt att implementera detta kan vara att ha en separat lista i PlayScene med kulor från rymdvarelserna, som t ex kan heta alien_bullets. Sedan kan man vid varje varv i spelloopen låta slumpen avgöra dels om rymdvarelserna ska skjuta och dels vilken rymdvarelse som i så fall ska avfyra kulan. Pythons modul random kan vara användbar i sammanhanget.

Nu har du förhoppningsvis lite att bita i. Lycka till!

/Mats

Det har blivit dags för andra delen i min tutorial om hur man kan skapa ett litet 2D-spel i programspråket Python med hjälp av ramverket Pygame Zero. Den första delen hittar du här och ett introducerande blogginlägg om Pygame Zero ligger här. Handledningen vänder sig till nybörjare och du behöver inte mycket förkunskaper alls för att kunna hänga med (hoppas jag, om något är oklart får du gärna lämna en kommentar).

Sedan tidigare så har vi ett spelfönster och en kanon som vi kan styra och skjuta med. I den följande ”lektionen” så ska vi skapa de rymdvarelser som ska stå för motståndet i spelet. Vi ska även lägga till en poängräknare och lite bling-bling i form av ljud av kanonskott och explosioner. Slutresultatet kommer att se ut på det här viset:

space_invaders

Om du har gått igenom den första delen av denna tutorial så vet du säkert vad vi behöver göra när vi nu ska lägga till en ny typ av objekt: Vi behöver skapa ytterligare en klass. Lägg till följande kod i ”klassdelen” av programmet, under de befintliga klasserna Cannon och Bullet:

class Alien(Actor):
    def __init__(self, sprite, position):
        super(Alien, self).__init__(sprite, position)

För att kunna rita ut rymdvarelserna i spelfönstret så behöver vi också en bild. Högerklicka på bilden nedan och spara den som ”alien.png” i samma katalog som de övriga bilderna, ”images”.

alien
Vidare så behöver vi en ny lista, aliens, att förvara våra rymdvarelser i. Och så måste vi ju skapa var och en av rymdvarelserna. De är, som du ser här ovan, rätt många från början (35 stycken, närmare bestämt) så att tillverka dem ”för hand” skulle vara rätt trist. Det skulle resultera i 35 rader kod i stil med dessa:

aliens.append(Alien('alien', (60, 40)))
aliens.append(Alien('alien', (120, 40)))
aliens.append(Alien('alien', (180, 40)))
...

Lyckligtvis så finns det en konstruktion i Python (och typ alla andra programspråk) som passar perfekt i situationer som denna, och vi har redan använt den några gånger: For-loopen! Uppdatera koden i början av ”speldelen” på det här viset:

# Här startar själva spelet

cannon = Cannon('cannon', (WIDTH / 2, 560))
bullets = []
aliens = []

alien_x = 60
alien_y = 40
for i in range(7):
    aliens.append(Alien('alien', (alien_x, alien_y)))
    alien_x += 60

Ta sedan och lägg till ytterligare en for-loop, som går igenom vår lista med rymdvarelser och ritar var och en på skärmen, i metoden draw:

def draw():
    screen.clear()
    cannon.draw()
  
    for bullet in bullets:
        bullet.draw()
    
    for alien in aliens:
        alien.draw()

I den övre loopen ser vi, genom att skriva for i in range(7), till så att koden i det följande blocket körs just sju gånger. För varje varv skapas en ny rymdvarelse som läggs till i listan aliens. Dessutom så ökas x-positionen, så att nästa rymdvarelse hamnar 60 pixlar längre till höger. Om du nu sparar och provkör programmet så ser du förhoppningsvis en prydlig rad med rymdvarelser längst upp i spelfönstret.

Men det finns flera problem. För det första så vill vi ju ha betydligt fler rymdvarelser. För det andra så rör de sig inte ur fläcken. Och för det tredje så händer ingenting alls när vi skjuter på dem.

Det första problemet är faktiskt väldigt enkelt att lösa. Vi vill alltså ha fem rader med rymdvarelser i stället för en. Vad gör vi då? Jo, vi lägger den for-loop som skapar den första raden inuti en annan loop! I den yttre, omslutande loopen så plussar vi även på y-positionen för rymdvarelserna, så att varje ny rad hamnar 40 pixlar under den föregående. Och så sätter vi tillbaka x-positionen till 60 efter varje varv, så att raderna hela tiden börjar ritas på samma ställe i sidled. Bygg ut koden på det här viset:

alien_x = 60
alien_y = 40
for i in range(5):
    for i in range(7):
        aliens.append(Alien('alien', (alien_x, alien_y)))
        alien_x += 60
    alien_x = 60
    alien_y += 40

Så till det här med att få rymdvarelserna att röra sig. Precis som i den klassiska förlagan så vill vi både att de gradvis ska närma sig kanonen, det vill säga röra sig nedåt, och att de hela tiden ska förflytta sig fram och tillbaka i sidled så att de blir svårare att träffa. För att det här ska funka så behöver vi flera nya variabler. Dels behöver vi separata variabler för hastigheten i sidled och hastigheten i höjdled. Dels så behöver vi variabler för att hålla koll på hur mycket rymdvarelserna har rört sig i sidled och när det är dags för dem att vända och röra sig åt andra hållet.

Först gör vi följande tillägg i klassen Alien:

class Alien(Actor):
    def __init__(self, sprite, position):
        super(Alien, self).__init__(sprite, position)
        self.movement = 20
        self.max_movement = 40
        self.x_speed = 1
        self.y_speed = 7
    
    def update(self):
        self.x += self.x_speed
        self.movement += self.x_speed
        if abs(self.movement) >= self.max_movement:
            self.x_speed *= -1
            self.y += self.y_speed
            self.movement = 0

I __init__-metoden, eller konstruktorn som man också kan kalla den, sätter vi nu x-hastigheten till en pixel och y-hastigheten till sju pixlar. Vi sätter även gränsen för rörelser i sidled till 40 pixlar. Eftersom vi vill att rymdvarelserna ska röra sig både åt höger och åt vänster jämfört med utgångsläget så ”låtsas” vi att de, när spelet börjar, redan har rört sig 20 pixlar åt höger, dvs att de står i mitten.

I Aliens metod update så ökar vi både x-positionen och den variabel som mäter den sammanlagda förflyttningen, self.movement, med x-hastigheten (1). I if-satsen så kontrollerar vi sedan om rymdvarelsen har nått gränsen för förflyttningar, 40 pixlar. När rymdvarelsen rör sig åt vänster så kommer self.movement att innehålla ett negativt värde. Därför jämför vi i stället det så kallade absolutvärdet med maxvärdet genom att skriva if abs(self.movement) >= self.max_movement. Om absolutvärdet är 40 eller mer så inverterar vi x-hastigheten, om den är 1 så blir den -1 och är den -1 så blir den 1. På så vis får vi rymdvarelsen att byta riktning i sidled. Och så låter vi den hoppa ner ett snäpp i höjdled genom att öka y-positionen.

Vi behöver också lägga till ytterligare en liten for-loop längst ner i speldelens update-metod, under den loop som uppdaterar skotten från kanonen och tar bort dem om de hamnat utanför fönstret:

def update():
    if keyboard.right:
        cannon.move_right()
    elif keyboard.left:
        cannon.move_left()
  
    if keyboard.space:
        if get_ticks() - cannon.last_fire > cannon.firing_interval:
            bullets.append(Bullet('bullet', cannon.pos))
            cannon.last_fire = get_ticks()
      
    for bullet in bullets[:]:
        bullet.update()
        if bullet.is_dead():
            bullets.remove(bullet)
    
    for alien in aliens:
        alien.update()

Du kanske har noterat att den for-loop som går igenom listan bullets ser lite annorlunda ut jämfört med den som går igenom aliens. Jag ska strax berätta varför.

Som jag var inne på ovan så vill vi förstås också att något ska hända när vi skjuter på rymdvarelserna – de ska ”dö”, eller åtminstone skadas. För att åstadkomma detta så behöver vi hela tiden kontrollera om någon av rymdvarelserna har kolliderat med något av de skott som har avfyrats från kanonen. Det här är enklare än vad man kan tro. Du kanske minns från förra lektionen att våra spelobjekt ärver från klassen Actor, som är en färdig klass som ingår i Pygame Zero, och att de därmed får med sig en hel del egenskaper därifrån.

Bland annat så omges varje objekt i spelet av en rektangel som är osynlig men som gör det lätt att ta reda på var i fönstret som objektet befinner sig och var dess gränser går. Dessa rektanglar har en x- och en y-koordinat, en bredd och en höjd som motsvarar de sprites som används. Dessutom så har våra objekt en nedärvd metod, colliderect, som kan användas för att kontrollera om dess rektangel överlappar ett annat objekts rektangel. Den ska vi dra nytta av nu.

I speldelens update-metod, ta och uppdatera den avslutande for-loopen som går igenom listan med rymdvarelser på följande vis:

    for alien in aliens[:]:
        alien.update()
        for bullet in bullets[:]:
            if alien.colliderect(bullet):
                aliens.remove(alien)
                bullets.remove(bullet)

Som du ser så har jag lagt till tecknen [:] i for-satserna, både i den yttre och den inre loopen. Samma tecken, Pythons så kallade slice-operator, finns med i den for-loop som går igenom listan med skott från kanonen och uppdaterar dem, och det är av följande orsak: Att ändra på en lista, t ex genom att ta bort ett element, samtidigt som man loopar igenom den kan vara vanskligt – det kan ge oväntade resultat. Genom att lägga till slice-operatorn efter namnet på en lista så ser vi till så att det i stället är en kopia av listan som vi går igenom. Däremot så anropar vi, när vi vill ta bort ”döda” objekt, metoden remove på originallistorna.

Nu går det att skjuta rymdvarelserna. Men det är, åtminstone enligt min mening, alldeles för lätt att ha ihjäl dem – man behöver bara träffa med ett enda skott. Låt oss ge varje rymdvarelse tre liv, minska antalet liv när de träffas och ta bort dem först när antalet liv är noll. Först gör vi två tillägg i klassen Alien, variabeln self.lives och metoden is_dead:

class Alien(Actor):
    def __init__(self, sprite, position):
        super(Alien, self).__init__(sprite, position)
        self.movement = 20
        self.max_movement = 40
        self.x_speed = 1
        self.y_speed = 7
        self.lives = 3
    
    def update(self):
        self.x += self.x_speed
        self.movement += self.x_speed
        if abs(self.movement) >= self.max_movement:
            self.x_speed *= -1
            self.y += self.y_speed
            self.movement = 0
            
    def is_dead(self):
        return self.lives == 0

Och så ändrar vi koden lite grann i den for-loop i speldelens update-metod där vi kontrollerar om någon rymdvarelse har kolliderat med ett skott:

    for alien in aliens[:]:
        alien.update()
        for bullet in bullets[:]:
            if alien.colliderect(bullet):
                alien.lives -= 1
                if alien.is_dead():
                    aliens.remove(alien)
                bullets.remove(bullet)

Mer än så krävs inte för att rymdvarelserna ska bli mer seglivade. Prova, så får du se.

Om du nu börjar krokna så har jag all förståelse. Men härda ut – nu återstår bara det där bling-blinget och det är inte särskilt kodkrävande. Börja med att ladda ner ljudfilerna ”explosion.wav” och ”shot.wav” som ligger här respektive här. Självklart kan du, om du föredrar det, skapa ljudeffekterna själv med ett verktyg i stil med Bfxr men ge dem i så fall samma namn. Skapa en katalog som heter ”sounds” i den katalog där ditt program ligger och lägg ljudfilerna i den. Frivillig överkurs är att även ladda ner typsnittet Space Invaders, exempelvis från denna sajt, och lägga filen (”space_invaders.ttf”) i en katalog som heter ”fonts”.

Sedan ska vi se till så att kanonen, det vill säga spelaren, kan få poäng och att dessa skrivs ut på skärmen. Lägg till variabeln self.score i klassen init-metoden i klassen Cannon och sätt den till noll.

class Cannon(Actor):
    def __init__(self, sprite, position):
        super(Cannon, self).__init__(sprite, position)
        self.speed = 5
        self.last_fire = 0
        self.firing_interval = 300
        self.score = 0

Och så behöver vi göra ett par tillägg i speldelens update-metod. Först i den if-sats som kontrollerar om space-tangenten är nedtryckt, där vi ska se till att skott-ljudet spelas upp…

    if keyboard.space:
        if get_ticks() - cannon.last_fire > cannon.firing_interval:
            bullets.append(Bullet('bullet', cannon.pos))
            sounds.shot.play()
            cannon.last_fire = get_ticks()

… och sedan i for-loopen där vi kontrollerar om några kollisioner har inträffat mellan rymdvarelser och skott. Där ska vi både spela upp explosions-ljudet och öka antalet poäng med 100 när en rymdvarelse dör.

    for alien in aliens[:]:
        alien.update()
        for bullet in bullets[:]:
            if alien.colliderect(bullet):
                alien.lives -= 1
                if alien.is_dead():
                    aliens.remove(alien)
                    sounds.explosion.play()
                    cannon.score += 100
                bullets.remove(bullet)

Vi behöver också skriva ut poängen i spelfönstret. Lägg till en rad sist i speldelens draw-metod enligt nedan. Om du inte har laddat ned typsnittet Space Invaders, strunta i koden fontname="space_invaders".

def draw():
    screen.clear()
    cannon.draw()
  
    for bullet in bullets:
        bullet.draw()
    
    for alien in aliens:
        alien.draw()

    screen.draw.text("SCORE: %d" % cannon.score, (20, 20), fontname="space_invaders", fontsize=20)

Puh! Det var allt för den här gången. En hel del arbete återstår visserligen innan spelet är komplett. Till exempel är ju kanonen än så länge odödlig, så det går inte att förlora. Och så kan vi ju inte ha det. Men det och mycket annat ordnar vi i nästa, avslutande inlägg.

/Mats

PS. Om du har haft svårt att hänga med, eller får felmeddelanden och behöver kontrollera din egen kod mot ett ”facit”, så ligger hela källkoden här.

Jag tänkte följa upp mitt förra inlägg med en liten tutorial om hur man kan skapa ett simpelt 2D-spel, en förenklad version av det klassiska arkadspelet Space Invaders, med Python och det nybörjarvänliga ramverket Pygame Zero. I den här första delen ska vi skapa ett spelfönster och förbereda oss för rymdinvasionen genom att se till så att vi har en kanon som vi kan styra och skjuta med. Tanken är att även den som har ytterst begränsade kunskaper om programmering ska kunna hänga med. Jag förutsätter dock att du har installerat Python 3, Pygame och Pygame Zero samt att du vet hur man skriver ett program i en texteditor och kör det via terminalen/kommandofönstret.

Det här med spelfönstret är snabbt avklarat. Du behöver bara skapa en tom fil och spara den som exempelvis ”game.py” och sedan köra den med kommandot pgzrun game.py. Ett ögonblick senare öppnas ett färdigt spelfönster! Men dimensionerna lämpar sig inte riktigt för det spel vi ska skapa. Och så skulle det vara trevligt om det stod något annat än ”Pygame Zero Game” längst upp i fönstret. Gå tillbaka till din texteditor och lägg till följande rader i ”game.py”:

WIDTH = 480
HEIGHT = 600
TITLE = '---=== SPACE INVADERS ===---'

Om du nu sparar och kör programmet igen så öppnas ett mer långsmalt fönster med vår egen text i titelraden. Men för att vi ska kunna uträtta något i vårt spel så måste vi också lägga till två metoder som uppdaterar och ritar de olika objekten i spelet. De här metoderna ska heta just update respektive draw och Pygame Zero kommer att se till så att de anropas, det vill säga att koden i dem körs, ungefär 60 gånger i sekunden. Vi gör följande lilla tillägg efter de tre första raderna:

# Här börjar själva spelet

def update():
    pass

def draw():
    pass

Den första raden, den som börjar med ”#”, är en kommentar. Den har ingen som helst betydelse för körningen av programmet men gör det lite lättare att tolka och hitta i koden. Sedan följer definitionerna av våra metoder. Nyckelordet pass betyder att metoderna än så länge inte utför något alls. Vi ska snart ersätta pass med betydligt intressantare kod. Först är det dock viktigt att du noterar att ordet pass i både update och draw är indraget med exakt fyra mellanslag. Det här med indrag, eller indentering, är oerhört viktigt i Python eftersom indraget avgör vilken kod som hör till vilket block. Så se till att även indragen finns med i ditt eget program.

För att kunna lägga till det första objektet i spelet, en kanon, så behöver vi skapa en klass. Om du inte är bekant med terminologin inom objektorienterad programmering så är en klass en beskrivning som talar om för datorn vilka egenskaper och förmågor ett objekt ska ha. När objektet ska skapas så följer datorn beskrivningen i klassen, ungefär som när en snickare följer en ritning. Vår första klass ska heta Cannon. Lägg till dessa rader ovanför raderna med metoderna update och draw, precis under raden där vi ger spelet en titel:

# Här nedan är de klasser som används i spelet

class Cannon(Actor):
    def __init__(self, sprite, position):
        super(Cannon, self).__init__(sprite, position)

Tycker du att koden ser konstig ut? Oroa dig inte, det tycker jag också. Python är känt för sin läsbara kod, men när man använder objektorientering så blir det lätt ganska rörigt. På första raden efter den inledande kommentaren talar vi om att klassen ska heta Cannon och att den ska ärva, som det heter, från klassen Actor, en fördefinierad klass som ingår i Pygame Zero. När en klass ärver från en annan klass så får den samma egenskaper och förmågor, utöver de egna, som ”föräldern” eller superklassen som man oftast kallar den. På de två följande raderna definieras metoden __init__  (ja, det ska vara två understreck både före och efter ordet ”init”) som är en speciell metod som körs automatiskt när ett objekt av klassen konstrueras. Där kan man initiera objektet, t ex tala om vilken startposition det ska ha i spelfönstret. Det enda som i nuläget sker i vår init-metod är att vi anropar superklassens, det vill säga Actors, init-metod.

Okej. För att vår kanon ska visas på skärmen behöver vi lägga till ytterligare lite kod. Men först behöver du ladda ner den lilla bilden nedan. Skapa en ny katalog kallad ”images” i den katalog där ditt program ligger och spara bildfilen som ”cannon.png”.  Obs att det är viktigt att filen heter just så, och att den ligger i en katalog som heter ”images”.

cannon
När du har sparat bildfilen på rätt ställe så behöver du uppdatera ”spel-delen” av koden på följande vis:

# Här börjar själva spelet

cannon = Cannon('cannon', (WIDTH / 2, 560))

def update():
    pass

def draw():
    screen.clear()
    cannon.draw()

Här skapar vi ett objekt, eller en instans, av klassen Cannon och lagrar den i en variabel som heter just cannon. När vi skapar kanonen så skickar vi med namnet på den sprite som ska användas (ändelsen ”.png” behövs inte här) samt kanonens startposition. För att få positionen i sidled, eller x-led, så delar vi spelfönstrets bredd med 2, vilket blir 240. Kanonen kommer alltså att ritas 240 pixlar åt höger från fönstrets vänstra kant. Positionen i höjdled, eller y-led, sätts till 560, det vill säga 560 pixlar nedåt från fönstrets överkant. I metoden draw har två rader lagts till som först rensar fönstret på innehåll och sedan ritar ut kanonen. Spara och kör programmet igen och – voilà! – kanonen visas i fönstrets nedre del.

Än så länge är kanonen helt orörlig, men det är lätt åtgärdat. Först lägger vi till följande kod i metoden update:

def update():
    if keyboard.right:
        cannon.move_right()
    elif keyboard.left:
        cannon.move_left()

Det här är en så kallad if-sats som säger att om höger piltangent är nedtryckt så ska kanonens metod move_right anropas. Annars, om vänsterpilen är nedtryckt, så ska move_left anropas. För att det här ska funka så måste vi också lägga till de här metoderna i klassen Cannon:

class Cannon(Actor):
    def __init__(self, sprite, position):
        super(Cannon, self).__init__(sprite, position)
        self.speed = 5

    def move_right(self):
        self.x += self.speed
        if self.right >= WIDTH - 40:
            self.right = WIDTH - 40
    
    def move_left(self):
        self.x -= self.speed
        if self.left <= 40:
            self.left = 40

I koden ovan har även raden self.speed = 5 lagts till i __init__. Den talar om med vilken hastighet kanonen ska röra sig. I metoderna move_right och move_left så används hastigheten för att flytta kanonens x-position åt höger respektive vänster, med 5 pixlar åt gången. Fundera gärna en stund på hur if-satserna i metoderna fungerar. De gör så att kanonen alltid stoppas 40 pixlar från fönstrets kanter.

Nu kan vi styra kanonen. Men den måste ju kunna skjuta också, annars är det inte mycket till kanon. Vi behöver skapa en ny klass, som vi kan kalla för Bullet och som representerar just själva ”kulorna” eller skotten från kanonen. Lägg till följande kod, direkt efter klassen Cannon:

class Bullet(Actor):
    def __init__(self, sprite, position):
        super(Bullet, self).__init__(sprite, position)
        self.speed = 20
  
    def update(self):
        self.y -= self.speed

Som du ser så har skotten en betydligt högre hastighet än vår kanon. Ett skott ska inte gå att styra, utan när det har avfyrats så ska det röra sig uppåt automatiskt. Det ordnas i metoden update, där skottets y-position minskas med 20 pixlar varje gång metoden anropas. Vi behöver också göra flera tillägg i koden efter klassdefinitionerna:

# Här startar själva spelet

cannon = Cannon('cannon', (WIDTH / 2, 560))
bullets = []    
  
def update():
    if keyboard.right:
        cannon.move_right()
    elif keyboard.left:
        cannon.move_left()
  
    if keyboard.space:
        bullets.append(Bullet('bullet', cannon.pos))
      
    for bullet in bullets:
        bullet.update()
    
    
def draw():
    screen.clear()
    cannon.draw()
  
    for bullet in bullets:
        bullet.draw()

Här finns en hel del att förklara. För det första vill vi inte ha ett enda skott, utan vi vill kunna hantera en hel drös med skott på samma gång. Därför så skapar vi, med koden bullets = [], en lista där vi kan lägga till och ta bort skott lite som vi vill. I metoden update ser vi till att ett nytt skott skapas och lagras i listan varje gång spelaren trycker på space-tangenten. Och i både update och draw så går vi igenom hela listan med skott med hjälp av så kallade for-loopar, och ser till så att vart och ett av skotten uppdateras och ritas ut på skärmen. Verkar for-looparna skumma? Tänk dig att du i stället skulle ge datorn muntliga instruktioner. Då skulle du säga något i stil med: ”För varje kula i listan med kulor, uppdatera/rita kulan.” Och om du provar att översätta koden så ser du att det är ju precis vad den säger.

Åh, just det. Du måste också ladda ner den pyttelilla skott-bilden nedan och spara den i ”images” som ”bullet.png”.

bullet

Ta sedan och spara och provkör programmet.

space_invaders

Wow, vilken kulsvärm! Rymdvarelserna kommer inte ha en chans! Fast… det kanske inte blir så kul? Spel ska ju helst vara lagom svåra för att man inte ska tröttna direkt. Vi får ta och sätta ett tak för hur många skott per sekund som kanonen kan avfyra. Vi ska också se till så att skott som är ”döda”, det vill säga har hamnat utanför fönstret, plockas bort ur listan. Annars kommer de där for-looparna, där listan med skott gås igenom, att ta onödigt lång tid när man har spelat ett tag och avfyrat några tusen skott.

För att kunna mäta hur lång tid det har gått sedan ett skott fyrades av så behöver vi importera en färdig metod från Pygame-biblioteket. Den här metoden heter get_ticks och varje gång som den anropas så talar den om hur många millisekunder, alltså tusendels sekunder, som har förflutit sedan spelet startades. Lägg till denna rad allra överst i programmet:

from pygame.time import get_ticks

Vidare så behöver vi ett par nya variabler i klassen Cannon

class Cannon(Actor):
    def __init__(self, sprite, position):
        super(Cannon, self).__init__(sprite, position)
        self.speed = 5
        self.last_fire = 0
        self.firing_interval = 300

… och så behöver vi lägga till metoden is_dead i klassen Bullet:

class Bullet(Actor):
    def __init__(self, sprite, position):
        super(Bullet, self).__init__(sprite, position)
        self.speed = 20
  
    def update(self):
        self.y -= self.speed
    
    def is_dead(self):
        return self.bottom <= 0

Till sist så måste vi lägga till lite kod i spel-delen, i metoden update:

def update():
    if keyboard.right:
        cannon.move_right()
    elif keyboard.left:
        cannon.move_left()
  
    if keyboard.space:
        if get_ticks() - cannon.last_fire > cannon.firing_interval:
            bullets.append(Bullet('bullet', cannon.pos))
            cannon.last_fire = get_ticks()
      
    for bullet in bullets[:]:
        bullet.update()
        if bullet.is_dead():
            bullets.remove(bullet)

I den nya if-satsen under if keyboard.space så kontrolleras nu om det har gått mer än 300 millisekunder innan det förra skottet avfyrades. Om så är fallet så avfyras ett nytt skott och tidpunkten lagras i variabeln cannon.last_fire. Observera att det egentligen inte ska vara någon radbrytning i den rad som börjar med if get_ticks(), utan hela villkoret i if-satsen ska stå på en och samma rad. Men här på bloggen fick all kod inte plats. Lägg också märke till den sista if-satsen, som kontrollerar om kulan är ”död”. Man kan se det som att spelet frågar kulan om den har hamnat utanför fönstret. Om så är fallet, om dess nederkant har en y-position som är mindre än 0, så svarar metoden is_dead genom att returnera värdet True. I annat fall så svarar den False.

Sådär, nu är vi redo för själva rymdinvasionen! Fast den får vänta till nästa blogginlägg. Tills dess så får du som har frågor eller synpunkter gärna höra av dig, antingen i kommentarsfältet nedan eller på Twitter.

/Mats

Häromdagen stötte jag på ett riktigt trevligt projekt som jag känner att jag måste tipsa om: Pygame Zero. Det är ett litet ramverk som bygger på Pygame, ett spelbibliotek som används för att skapa spel i Python, och som främst är tänkt att användas för undervisning. Som upphovsmannen Daniel Pope själv beskriver det: Pygame Zero gör det möjligt för lärare att lära ut programmeringskoncept utan att först behöva förklara saker som spelloopar och händelseköer. De här sakerna, och mycket annat, är dolda för den som använder ramverket.

Jag har lekt lite grann med Pygame Zero och kan konstatera att det på många sätt liknar Gosu, det ramverk för spelprogrammering i Ruby som jag har tjatat om i många tidigare inlägg här på bloggen. Men Pygame Zero tar det här med att abstrahera bort ”krångliga” saker ytterligare några steg längre. Till exempel: För att öppna ett grafikfönster så behöver du inte skriva en enda klass. Faktum är att du inte behöver skriva någon kod alls. Du kan bara skapa en tom fil, döpa den till t ex ”game.py” och köra den med ett speciellt program som ingår i ramverket. Ett fönster med en rityta på 800 gånger 600 pixlar framträder på skärmen.

Skärmbild_2015-08-05_18-33-43

Är det här bra eller dåligt, att så pass mycket sker i bakgrunden utan att användaren kommer i kontakt med koden? Det är en fråga som man kan vrida och vända på länge. Den som på allvar vill lära sig att programmera i Python måste förstås, förr eller senare, även greppa saker som import av moduler och iteration över listor. Men det kanske inte är det allra första man behöver lära sig. Jag är inte pedagog, men jag tror att det är viktigt att programmeringen snabbt ger roliga resultat för att inte motivationen hos eleven ska tryta. Och en rymdvarelse som svävar i ett grafikfönster är definitivt roligare än en while-loop. Har du en åsikt i frågan, eller andra synpunkter, hör gärna av dig i kommentarsfältet nedan.

För att kunna använda Pygame Zero måste du först installera Python 3 och Pygame. Installationsanvisningar hittar du här. Jag har utan större problem installerat ramverket på tre olika datorer: En med Xubuntu 15.04, en med Windows 8.1 och en Raspberry Pi med Raspbian. I skrivande stund har gänget bakom Pygame Zero precis släppt en ny version, som förutom buggfixar och nya funktioner innehåller ett knippe exempelspel i form av implementationer av klassiska spel som Snake, Pong och Lunar Lander.

Själv tänker jag försöka skriva om en liten Space Invaders-klon som jag en gång kodade i Ruby till Python och använda Pygame Zero. Min förhoppning är att detta projekt så småningom ska mynna ut i en liten tutorial. Den som lever får se.

/Mats

Okej, här kommer första blogginlägget på riktigt, riktigt länge. Och det handlar, är jag rädd, inte direkt om kodande. Tror ändå att raderna nedan kan vara av intresse för er som tycker att det här med datorer och programmering verkar kul och spännande, men som än så länge inte har särskilt stora kunskaper. Eller som kan en hel del men som aldrig har använt ett annat operativsystem än de som de där stora företagen i Redmond respektive Cupertino tillhandahåller.

Till saken. Har du, eller någon du känner, en trött gammal dator som ligger och samlar damm på någon hylla eller i ett förråd? Funderar du/de rentav (gud förbjude) på att slänga den? I så fall: Hejda dig/dem! Förutsatt att datorn inte har mer än, säg, tio år på nacken så finns en god chans att du kan blåsa nytt liv i den. Kanske inte till den grad att den klarar några tyngre uppgifter, men väl så att den kan tjäna som surf-, skriv- och koddator i ytterligare några år. Tricket är att låta datorn starta på helt ny kula – radera innehållet på hårddisken och installera ett nytt och fräscht operativsystem.

Själv har jag på det här viset tagit vara på flera gamla datorer som jag fått eller köpt billigt från vänner och bekanta, som ratat dem till förmån för nyare och kraftfullare doningar. De här datorerna har både jag och mina ungar haft stor nytta av. En av dem blev räddningen när jag två gånger på kort tid tvingades lämna in min ordinarie koddator på service. Ungarna har använt dem till saker som att göra Scratch-spel, göra läxor, kolla på Youtube och lyssna på Spotify. Och det har funkat förvånansvärt bra, med tanke på hur sega burkarna har varit innan ”renoveringen”.

Det system som jag har använt för detta ändamål är en variant av Linux kallad Xubuntu. Om du nu skruvar oroligt på dig och tycker att ”Xubuntu” låter väl exotiskt – det finns ingen anledning till oro. Xubuntu är, liksom många andra Linuxdistributioner, i min mening minst lika lättanvänt som de kommersiella system jag har provat på. Och min erfarenhet är att det lämpar sig riktigt väl för datorer i pensionsåldern. Anekdotisk bevisföring: När jag installerade Xubuntu på mitt senaste renoveringsobjekt, en bärbar Dell med Windows XP och ett decennium bakom sig, så minskade tiden för att starta datorn och öppna en webbläsare med över två tredjedelar, från drygt tre minuter till knappt en minut. Ja, och så fick ju datorns skrivbord ett rejält utseendemässigt lyft:

skrivbord

Beroende på dina förkunskaper/fördomar så kanske du fortfarande tänker att det här med Linux inte är något för dig. Kanske har du just nu scener från filmer som War Games på näthinnan. Slå i så fall bort de tankarna. Nedan redogör jag, steg för steg, för hur du installerar Xubuntu, och tipsar om olika resurser som kan vara till hjälp när det gäller användningen. Innan dess vill jag bara understryka att Xubuntu är långt ifrån det enda system som kan ge din gamla dator ett nytt, friskare liv. I olika nätforum förs en livlig diskussion om vilket operativsystem som egentligen är allra bäst i denna gren. Oftast är det Xubuntu eller andra Linuxdistributioner av det mer obskyra slaget som förespråkas. Men även Windows 7 har ett visst stöd i dessa kretsar. Så om du råkar sitta på en Windows 7-licens kan det vara värt att prova att göra en ominstallation. Annars, läs vidare för att ta klivet in i Linuxvärlden.

1. Ladda ner Xubuntu

Det första du behöver göra är att ladda ner en så kallad ISO-fil från Xubuntus webbplats. Jag rekommenderar starkt att du väljer version 14.04, som är en så kallad LTS-version (LTS står för Long Term Support) som kommer att underhållas fram till och med våren 2017. Förmodligen, om det är just en äldre dator du ska installera systemet på, så ska du välja 32-bitarsversionen (den fil vars namn slutar med ”i386.iso”. Om du inte vet hur man gör med så kallade torrents så är det bara att bläddra ner till ”Mirror downloads” och klicka på ”Sweden” och därefter ”PC (Intel x86) desktop image” (eller den andra länken för 64-bitarsversionen) för att starta nedladdningen av en vanlig ISO-fil.

2. Skapa bootbar USB-sticka

Nästa steg är att lägga över ISO-filen på ett USB-minne som är ”bootbart”, det vill säga som datorn kan starta ifrån. Det finns flera olika verktyg för att skapa bootbara USB-stickor. Ett alternativ för Windows är LiLi, eller Linux Live USB Creator. Macanvändare kan till exempel använda UNetbootin. Närmare instruktioner hittar du här.

3. Ändra i bootinställningarna

När du väl har ditt bootbara USB-minne och har pluggat in det i den gamla datorn så gäller det att få datorn att starta just från detta minne. För att detta ska funka behöver du troligen ändra i inställningarna så att datorn provar att starta från USB innan det vanliga alternativet, hårddisken. Hur det här går till skiljer sig mellan olika datorer, men det gäller att ta sig in i datorns så kallade BIOS genom att trycka på en viss tangent (ofta F2, escape eller delete) under uppstartsprocessen. Information om vilken tangent som ska användas brukar visas precis i början av uppstarten, ofta i anslutning till en bild av tillverkarens logga. Väl inne gäller det att hitta en meny som heter ”Boot order” eller något liknande, ändra prioriteringsordningen och därefter spara inställningarna och avsluta. På vissa datorer kan man nå bootmenyn direkt, genom att trycka på en särskild tangent.

4. Installera Xubuntu

Om du nu har lyckats så startar datorn från USB-minnet. Först visas en rund liten symbol i skärmens nederkant, och efter en stund öppnas installationsmenyn. Har du otur har du i stället fått ett kryptiskt felmeddelande om någonting med ”PAE”. I så fall beror det på att den processor som sitter i datorn inte är fullt ut kompatibel med den här versionen av Xubuntu. Men det går att lösa, detaljerade instruktioner finns här. Om du fortfarande är det minsta osäker på det här med Linux så kan du i installationsmenyn välja alternativet ”Prova Xubuntu”, och bekanta dig med miljön innan du eventuellt går vidare och installerar systemet. Annars väljer du ”Installera Xubuntu”. Därefter är det bara att följa anvisningarna. Obs! Se till att du, några steg framåt, verkligen väljer alternativet ”Radera disken och installera Xubuntu”. I annat fall kommer ditt befintliga operativsystem att lämnas kvar på hårddisken och Xubuntu installeras ”vid sidan om”.

5. Ladda ner program

Har installationsprocessen slutförts, och du sitter och tittar på en ren skrivbordsyta som den här ovan, med en stiliserad bild av en mus – Xubuntus symbol/maskot – i mitten? Grattis! Du är nu officiellt Linuxanvändare. För att komma igång och utforska systemet behöver du bara klicka på den andra lilla musen, uppe i övre vänstra hörnet, för att öppna programmenyn. Där hittar du saker som webbläsare, ordbehandlare, kalkyl- och bildbehandlingsprogram. Kort sagt, de olika verktyg som brukar finnas på en standardutrustad dator. Om du känner att något saknas så är det bara att öppna Programcentralen, som också nås direkt via menyn. Via den kan du hitta och ladda ned tusentals olika program såsom spel, mediaspelare, verktyg för ljud- och videoredigering, kontorsprogram, utvecklingsmiljöer för olika programspråk, etc.

6. Bli terminal-ninja

När du väl har blivit lite varm i kläderna och vant dig vid den grafiska miljön så kan det vara dags att prova på att använda Linux på det mer traditionella sättet – via terminalen, eller kommandofönstret. I terminalen så kommunicerar du med datorn genom att skriva textkommandon, i stället för att klicka på olika grafiska ikoner. En fördel med detta är att det, när du har blivit van, går väldigt snabbt att utföra uppgifter som att kopiera och flytta filer och mappar, kompilera och köra egna datorprogram, och så vidare. Terminalen nås antingen via menyn, eller med snabbkommandot ctrl + alt + T. Så här ser ett terminalfönster ut:

terminal

Det där som slutar med ett dollartecken och en markör är den så kallade prompten. När den visas är datorn redo att ta emot ett kommando. Du kan se det som att datorn säger ”Till din tjänst!” och sedan väntar på din befallning. (Nu är det helt okej att börja tänka på War Games igen.) Några av de enklaste och vanligaste kommandona är ”cd” för att byta katalog, ”ls” för att visa innehållet i katalogen, ”mv” för att flytta eller byta namn på en fil/katalog och ”cp” för att kopiera filer/kataloger. Men möjligheterna är i det närmaste oändliga. Om du vill få koll på grunderna så rekommenderar jag denna tutorial, som även finns i bokform. För tips om mer spektakulära saker som man kan göra via terminalen, följ kontot Command Line Magic (@climagic) på Twitter.

7. Lär dig Python

En av de bästa sakerna med Xubuntu, och många andra Linuxdistributioner, har jag sparat till sist: Systemet kommer med ett av de mest nybörjarvänliga programspråken som jag har stött på, Python, förinstallerat. Och det är ruskigt enkelt att komma igång och koda: Det är bara att öppna terminalen och skriva ”python”. Då startar en loop som låter dig mata in Python-instruktioner och direkt se resultatet på skärmen (för att komma ur loopen, tryck ctrl + Z). För att skriva längre program så behöver du dock någon form av kodeditor/utvecklingsmiljö. Två exempel på enkla editorer som lämpar sig väl för Pythonkodande är IDLE och Geany. Båda kan laddas ner via Programcentralen (eller från terminalen med kommandot sudo apt-get install idle respektive sudo apt-get install geany). Ute på nätet finns mängder av bra gratisresurser för den som vill lära sig Python. Jag kan till exempel rekommendera gratisböckerna på sajten Invent With Python. I inlägg med taggen Python på denna blogg hittar du flera andra tips.

Det var allt för denna gång. Hoppas och tror att nästa bloggpost inte ska dröja fullt lika länge som den här gjorde. Om du har frågor om eller synpunkter på inlägget ovan får du som vanligt gärna höra av dig, antingen i kommentarsfältet, via mejl eller på Twitter.

/Mats

Att skapa webbsidor på det gamla hederliga sättet, genom att skriva HTML, är kul. Men är det programmering? Det beror på vem du frågar. Att åsikterna kraftigt går isär framgår till exempel av den här diskussionstråden. Själv lutar jag, i likhet med författaren av detta blogginlägg, nog åt nej, bland annat eftersom HTML och andra märkspråk saknar grundläggande konstruktioner som loopar och villkorssatser. Men jag tillstår att även jasidan har hyfsade argument. Och även om diskussionen är lite intressant känns det inte särskilt viktigt att ha en tydlig åsikt.

Vad jag däremot är helt övertygad om, det är att HTML är en alldeles utmärkt väg in i programmeringens värld. För det första är det lätt att komma igång. Om du har tillgång till en vanlig dator, då har du redan allt du behöver för att kunna knacka HTML: en texteditor och en webbläsare. ”Riktiga” programspråk kräver däremot installation av mjukvara som en kompilator eller programtolk och ibland särskilda utvecklingsverktyg. Ofta är det ett helvete, rent ut sagt, att sätta upp en fungerande utvecklingsmiljö.

För det andra: Genom att skriva HTML lär du dig att använda en texteditor och du blir snabbt varse fördelarna med att skriva på ett snyggt och strukturerat sätt, med indrag etc, samt vikten av att stava rätt och sätta konstiga tecken på precis rätt ställen. Det kommer du att ha nytta av om du senare går vidare till annat kodande. För det tredje är HTML ett lämpligt, för att inte säga nödvändigt, första steg för dig som vill lära dig att skapa exempelvis spel och grafik med Javascript.

Och så är det ju skojigt att skapa en alldeles egen, personlig hemsida som till skillnad från exempelvis denna blogg eller de sociala mediernas profilsidor inte följer några givna mallar. Nu är det ju långt ifrån alla som har tillgång till serverutrymme för en hemsida, men hos Neocities kan du snabbt, enkelt och alldeles gratis skapa en egen webbplats på upp till 20 megabyte. (Om du väljer att stödja detta finfina projekt med en struntsumma per månad får du ett mycket större utrymme.)

Dessvärre är det ont om bra, svenskspråkiga nätresurser för dig som vill lära dig HTML från grunden. I synnerhet om du är ett barn. Men du som förstår engelska kan välja och vraka mellan olika interaktiva verktyg varav flera är omnämnda här och här. Och om du inte har något emot pappersböcker kan jag återigen rekommendera smått klassiska HTML och CSS-boken av Rolf Staflin.

Lycka till med programmeringen! Eller vad man nu ska kalla det.

/Mats

Mitt i den kanske sömnigaste sommarveckan hittills fick jag plötsligt anledning, blev tvungen rentav, att skriva en blänkare här på bloggen. För nu är Scratch Jr här!

Som namnet antyder är Scratch Jr en version av Scratch, det fina verktyget/språket för visuell programmering, som är avsedd för lite yngre barn. Den uttalade målgruppen är 5-7-åringar. Projektet har förverkligats genom en framgångsrik insamlingskampanj på Kickstarter.

Till att börja med finns Scratch Jr tillgängligt som Ipad-app. Har själv precis laddat ner den och tänker ägna kvällen åt att testa. (Karin Nygårds har både hunnit testa och skrivit en kort recension.) Senare i år släpps en Androidapp och nästa år kommer en version som, precis som vanliga Scratch, är tillgänglig direkt via webbläsaren.

Missade du chansen att stödja Scratch Jr på Kickstarter? Du kan främja den fortsatta utvecklingen av Scratch genom att skänka en slant till Code-to-Learn-stiftelsen.

/Mats

Följ

Få meddelanden om nya inlägg via e-post.

Gör sällskap med 26 andra följare