Tillbaka till turtle

turtle game

Här kommer en guide för hur man bygger ett spel i turtle och det byggs i repl.it’s miljö. Spelet vi skall bygga är ett spel där din spelare skall fånga bollar för att samla poäng inne i en avgränsad yta. Spelet kommer byggas steg för steg tills vi har ett fungerande spel som går att bygga vidare på.

Det färdiga spelet ser ut så här:

Nu gäller det att bygga spelet steg för steg och följande saker behöver vi bygga upp.

  • Fixa till spelplanen
  • Skapa en spelare som vi kan styra
  • Se till att spelaren håller sig inom spelplanen
  • Slumpa poängbollar
  • Ta hand om kollision mellan spelare och boll
  • Beräkna poäng och skriv ut dessa

Fixa spelplanen

Här vill vi bygga en färgad bakgrund och sedan rita en ram i denna. Ramen skall sedan bli en begränsning så att spelet pågår inne i ramen.

# Nödvändiga importer
import turtle

window = turtle.Screen()        # Namnger vår screen
window.bgcolor("light blue")    # Bakgrundsfärg på vår spelplan
window.tracer(3)                # Låter var tredje "bild" visas, risk att det hackar annars

# Ritar upp en ram på spelplanen
border = turtle.Turtle()        # Skapar en turtle
border.speed(50)                # Ritar upp ytan snabbt
border.penup()
border.setposition(-300,-300)
border.pendown()
border.pensize(3)
for side in range (4):          # Ritar fyra sidor
	border.forward(600)
	border.left(90)

border.hideturtle()             # Skall gömma turtle border men gör inte det just nu.

# Tvingar fönstret att vara öppet (inte obligatoriskt på repl.it)
turtle.done()

Skapa spelaren

Nu skapar vi spelaren, här har jag testat att ändra på kodraden window.tracer(3) för att se hur fort jag vill att spelaren skall gå. window.tracer() handlar om hur ofta spelet skall ritas ut på skärmen.

# Nödvändiga importer
import turtle

window = turtle.Screen()        # Namnger vår screen
window.bgcolor("light blue")    # Bakgrundsfärg på vår spelplan
window.tracer(1)                # Testar att rita ut varje "bild" på skärmen

# Ritar upp en ram på spelplanen
border = turtle.Turtle()        # Skapar en turtle
border.speed(50)                # Ritar upp ytan snabbt
border.penup()
border.setposition(-300,-300)
border.pendown()
border.pensize(3)
for side in range (4):          # Ritar fyra sidor
	border.forward(600)
	border.left(90)

border.hideturtle()             # Skall gömma turtle border men gör inte det.

# Skapa spelaren
player = turtle.Turtle()  
player.color('red')
player.shape('turtle')
player.penup()                  # Eftersom vi inte vill rita ett spår
#player.speed(10)                  #defined when keybinding for speed; animation speed, not movement speed, this is fastest speed

# Players hastighet, ändrar till window.tracer(1) för att inte minhastigheten skall bli för snabb.
speed = 1

# Funktioner för att svänga vår turtle
def turnLeft():
	player.left(30)           # 30 grader vänster
def turnRight():
	player.right(30)          # 30 grader höger


#Keyboard binding
window.onkey(turnLeft, "Left")
window.onkey(turnRight, "Right")
window.listen()

# Make player move
while True:
  player.forward(speed)       # Hastigheten bestämmer hur fort vår player går

# Tvingar fönstret att vara öppet (inte obligatoriskt på repl.it)
turtle.done()

Håll spelaren innanför spelplanen

Nu skall vi bygga en funktion, checkBoundary() för att kontrollera att player inte hamnar utanför spelplanen. Vi bygger denna funktion så att den kan påverka vilken turtle som helst, både en spelare men också de bollar som vi skall skapa senare. Studsen är 180grader vilket innebär att alla turtles kommer studsa tillbaka precis på det sätt som de kom ifrån. Det ser inte så verkligt ut men vi kör på det sättet först.

# Nödvändiga importer
import turtle

window = turtle.Screen()        # Namnger vår screen
window.bgcolor("light blue")    # Bakgrundsfärg på vår spelplan
window.tracer(1)                # Låter var tredje "bild" visas, risk att det hackar annars

# Ritar upp en ram på spelplanen
border = turtle.Turtle()        # Skapar en turtle
border.speed(50)                # Ritar upp ytan snabbt
border.penup()
border.setposition(-300,-300)
border.pendown()
border.pensize(3)
for side in range (4):          # Ritar fyra sidor
	border.forward(600)
	border.left(90)

border.hideturtle()             # Gömmer turtle

# Skapa spelaren
player = turtle.Turtle()  
player.color('red')
player.shape('turtle')
player.penup()                  # Eftersom vi inte vill rita ett spår
#player.speed(10)                  #defined when keybinding for speed; animation speed, not movement speed, this is fastest speed

# Players hastighet, ändrar till window.tracer(1) för att inte minhastigheten skall bli för snabb.
speed = 1

# Funktioner för att svänga vår turtle
def turnLeft():
	player.left(30)           # 30 grader vänster
def turnRight():
	player.right(30)          # 30 grader höger

# Funktion för att studsa mot kanten
def checkBoundary(turtle):
	if turtle.xcor() > 290 or turtle.xcor() < -290: 
		turtle.right(180)
	if turtle.ycor() > 290 or turtle.ycor() < -290:
		turtle.right(180)

#Keyboard binding
window.onkey(turnLeft, "Left")
window.onkey(turnRight, "Right")
window.listen()

# Make player move
while True:
  player.forward(speed)       # Hastigheten bestämmer hur fort vår player går

  # Kolla så att vi inte hamnar utanför spelplanen
  checkBoundary(player)


# Tvingar fönstret att vara öppet (inte obligatoriskt på repl.it)
turtle.done()

Slumpa poängbollar

Bollarna skapas och rör sig. Vi använder också funktionen checkBoundary() för att kolla att bollarna inte hamnar utanför spelplanen och att de studsar på rätt sätt.

# Nödvändiga importer
import turtle
import random

window = turtle.Screen()        # Namnger vår screen
window.bgcolor("light blue")    # Bakgrundsfärg på vår spelplan
window.tracer(3)                # Låter var tredje "bild" visas, risk att det hackar annars

# Ritar upp en ram på spelplanen
border = turtle.Turtle()        # Skapar en turtle
border.speed(50)                # Ritar upp ytan snabbt
border.penup()
border.setposition(-300,-300)
border.pendown()
border.pensize(3)
for side in range (4):          # Ritar fyra sidor
	border.forward(600)
	border.left(90)

border.hideturtle()             # Gömmer turtle border

# Skapa spelaren
player = turtle.Turtle()  
player.color('red')
player.shape('turtle')
player.penup()                  # Eftersom vi inte vill rita ett spår
#player.speed(10)                  #defined when keybinding for speed; animation speed, not movement speed, this is fastest speed

# Players hastighet, ändrar till window.tracer(1) för att inte minhastigheten skall bli för snabb.
speed = 1

# skapar bollar
maxBalls = 5
balls = [] 

# Bygger upp listan med alla bollar.
for b in range(maxBalls):
  balls.append(turtle.Turtle())     # Skapar en ny boll
  balls[b].color('yellow')          # Ger bollen en färg
  balls[b].shape('circle')          # Ger bollen ett utseende
  balls[b].speed(0)                 # Ger bollen en hastighet
  balls[b].penup()                  # Lyfter pennan
  balls[b].right(random.randint(0,360)) # Ger bollen en slumpad riktning
  balls[b].setposition(random.randint(-300,300), random.randint(-300,300))  # Ger bollen en slumpad position


# Funktioner för att svänga vår turtle
def turnLeft():
	player.left(30)           # 30 grader vänster
def turnRight():
	player.right(30)          # 30 grader höger

# Funktion för att studsa mot kanten, 
# studsar tillbaka på precis samma sätt, hur ändrar vi det?
def checkBoundary(turtle):
	if turtle.xcor() > 290 or turtle.xcor() < -290:
		turtle.right(180)
	if turtle.ycor() > 290 or turtle.ycor() < -290:
		turtle.right(180)

#Keyboard binding
window.onkey(turnLeft, "Left")
window.onkey(turnRight, "Right")
window.listen()

# Make player move
while True:
  player.forward(speed)       # Hastigheten bestämmer hur fort vår player går
  # Kolla så att vi inte hamnar utanför spelplanen
  checkBoundary(player)

  # Loopa igenom alla bollarna
  for b in range(maxBalls):
    balls[b].forward(3)       # Flytta bollen 3 pixlar framåt
    checkBoundary(balls[b])   # Kolla om bollen studsar


# Tvingar fönstret att vara öppet (inte obligatoriskt på repl.it)
turtle.done()

Här finns en tydlig bugg och det är att en boll som skapas på kanten blir kvar i en oändlig turn-around eftersom den bara byter riktning men inte hinner flyttas så långt som behövs för att den skall “hinna” ifrån kanten innan nästa riktningsförändring skall göras. Här kan vi se till att bollar inte kan skapas så nära kanten.

Kollision mellan spelare och poängbollar

Jag skapar en funktion för att kolla om två turtels kolliderar. Det görs med avståndsformeln, som kollar hur långt det är mellan två turtels och om de är tillräckligt nära så anses de kollidera, och vid kollision så får bollen en ny position och en ny riktning. Alternativet är att ta bort bollen och skapa en ny, men nu låter jag samma boll få en ny position istället.

# Nödvändiga importer
import turtle
import random
import math

window = turtle.Screen()        # Namnger vår screen
window.bgcolor("light blue")    # Bakgrundsfärg på vår spelplan
window.tracer(3)                # Låter var tredje "bild" visas, risk att det hackar annars

# Ritar upp en ram på spelplanen
border = turtle.Turtle()        # Skapar en turtle
border.speed(50)                # Ritar upp ytan snabbt
border.penup()
border.setposition(-300,-300)
border.pendown()
border.pensize(3)
for side in range (4):          # Ritar fyra sidor
	border.forward(600)
	border.left(90)

border.hideturtle()             # Gömmer turtle när ramen är färdigritad.

# Skapa spelaren
player = turtle.Turtle()  
player.color('red')
player.shape('turtle')
player.penup()                  # Eftersom vi inte vill rita ett spår
#player.speed(10)                  #defined when keybinding for speed; animation speed, not movement speed, this is fastest speed

# Players hastighet, ändrar till window.tracer(1) för att inte minhastigheten skall bli för snabb.
speed = 1

# skapar bollar
maxBalls = 5
balls = [] 

# Bygger upp listan med alla bollar.
for b in range(maxBalls):
  balls.append(turtle.Turtle())     # Skapar en ny boll
  balls[b].color('yellow')          # Ger bollen en färg
  balls[b].shape('circle')          # Ger bollen ett utseende
  balls[b].speed(0)                 # Ger bollen en hastighet
  balls[b].penup()                  # Lyfter pennan
  balls[b].right(random.randint(0,360)) # Ger bollen en slumpad riktning
  balls[b].setposition(random.randint(-300,300), random.randint(-300,300))  # Ger bollen en slumpad position


# Funktioner för att svänga vår turtle
def turnLeft():
	player.left(30)           # 30 grader vänster
def turnRight():
	player.right(30)          # 30 grader höger

# Funktion för att studsa mot kanten, 
# studsar tillbaka på precis samma sätt, hur ändrar vi det?
def checkBoundary(turtle):
	if turtle.xcor() > 290 or turtle.xcor() < -290:
		turtle.right(180)
	if turtle.ycor() > 290 or turtle.ycor() < -290:
		turtle.right(180)

# Funktion som kollar om player krockar med bollen
def isCollision(turtle1, turtle2):
	d = math.sqrt(math.pow(turtle1.xcor() - turtle2.xcor(),2) + math.pow(turtle1.ycor() - turtle2.ycor(),2))        # Avståndsformeln
	if d < 20:                      # Om avståndet mellan player och boll är mindre än 20px så anser vi att de har kolliderat.
		return True                 # Returnerar True, kollision har inträffat
	else:
		return False                # Returnerar False, ingen kollision


#Keyboard binding
window.onkey(turnLeft, "Left")
window.onkey(turnRight, "Right")
window.listen()

# Make player move
while True:
  player.forward(speed)       # Hastigheten bestämmer hur fort vår player går
  # Kolla så att vi inte hamnar utanför spelplanen
  checkBoundary(player)

  # Loopa igenom alla bollarna
  for b in range(maxBalls):
    balls[b].forward(3)       # Flytta bollen 3 pixlar framåt
    checkBoundary(balls[b])   # Kolla om bollen studsar

    # Tar hand om en kollision
    if(isCollision(player, balls[b])):
      balls[b].setposition(random.randint(-300,300), random.randint(-300,300))  # Ger bollen en ny position (tar alltså inte bort den)
      balls[b].right(random.randint(0,360))   # Ger bollen en ny riktning


# Tvingar fönstret att vara öppet (inte obligatoriskt på repl.it)
turtle.done()

Nu upptäcker vi ett problem med att spelarens fart är konstant, och ganska så långsam, vilket gör att vi får planera hur vi skall nå bollarna då vi inte kan jaga ikapp dem. Detta kan du gärna ändra om du vill, men vi behöver nog också se över hur vi skall kunna öka och minska farten på spelaren lite senare.

Räkna poäng och skriv ut dessa

Nu skall vi skapa en poängberäkning och även se till att denna skrivs ut i spelet. Tänk på att vi inte kan använda svenska tecken, å/ä/ö, när vi skriver ut i repl.it.

# Nödvändiga importer
import turtle
import random
import math

window = turtle.Screen()        # Namnger vår screen
window.bgcolor("light blue")    # Bakgrundsfärg på vår spelplan
window.tracer(3)                # Låter var tredje "bild" visas, risk att det hackar annars

# Ritar upp en ram på spelplanen
border = turtle.Turtle()        # Skapar en turtle
border.speed(50)                # Ritar upp ytan snabbt
border.penup()
border.setposition(-300,-300)
border.pendown()
border.pensize(3)
for side in range (4):          # Ritar fyra sidor
	border.forward(600)
	border.left(90)

border.hideturtle()             # Gömmer turtle när ramen är färdigritad.

# Skapa spelaren
player = turtle.Turtle()  
player.color('red')
player.shape('turtle')
player.penup()                  # Eftersom vi inte vill rita ett spår
#player.speed(10)                  #defined when keybinding for speed; animation speed, not movement speed, this is fastest speed

# Players hastighet, ändrar till window.tracer(1) för att inte minhastigheten skall bli för snabb.
speed = 1

# En variabel som lagrar poängen
score = 0

# Vi behöver skapa en turtle för att skriva ut poängen
penna = turtle.Turtle()
penna.hideturtle()
penna.penup()
penna.setundobuffer(1000)           # Behövs för att inte texterna skall skrivas på varandra.

# skapar bollar
maxBalls = 5
balls = [] 

# Bygger upp listan med alla bollar.
for b in range(maxBalls):
  balls.append(turtle.Turtle())     # Skapar en ny boll
  balls[b].color('yellow')          # Ger bollen en färg
  balls[b].shape('circle')          # Ger bollen ett utseende
  balls[b].speed(0)                 # Ger bollen en hastighet
  balls[b].penup()                  # Lyfter pennan
  balls[b].right(random.randint(0,360)) # Ger bollen en slumpad riktning
  balls[b].setposition(random.randint(-300,300), random.randint(-300,300))  # Ger bollen en slumpad position


# Funktioner för att svänga vår turtle
def turnLeft():
	player.left(30)           # 30 grader vänster
def turnRight():
	player.right(30)          # 30 grader höger

# Funktion för att studsa mot kanten, 
# studsar tillbaka på precis samma sätt, hur ändrar vi det?
def checkBoundary(turtle):
	if turtle.xcor() > 290 or turtle.xcor() < -290:
		turtle.right(180)
	if turtle.ycor() > 290 or turtle.ycor() < -290:
		turtle.right(180)

# Funktion som kollar om player krockar med bollen
def isCollision(turtle1, turtle2):
	d = math.sqrt(math.pow(turtle1.xcor() - turtle2.xcor(),2) + math.pow(turtle1.ycor() - turtle2.ycor(),2))        # Avståndsformeln
	if d < 20:                      # Om avståndet mellan player och boll är mindre än 20px så anser vi att de har kolliderat.
		return True                 # Returnerar True, kollision har inträffat
	else:
		return False                # Returnerar False, ingen kollision

#Keyboard binding
window.onkey(turnLeft, "Left")
window.onkey(turnRight, "Right")
window.listen()

# Make player move
while True:
  player.forward(speed)       # Hastigheten bestämmer hur fort vår player går
  # Kolla så att vi inte hamnar utanför spelplanen
  checkBoundary(player)

  # Loopa igenom alla bollarna
  for b in range(maxBalls):
    balls[b].forward(3)       # Flytta bollen 3 pixlar framåt
    checkBoundary(balls[b])   # Kolla om bollen studsar

    # Tar hand om en kollision
    if(isCollision(player, balls[b])):
      balls[b].setposition(random.randint(-300,300), random.randint(-300,300))  # Ger bollen en ny position (tar alltså inte bort den)
      balls[b].right(random.randint(0,360))   # Ger bollen en ny riktning
      # Har vi kolliderat så skall vi få poäng
      score = score + 1

      # Hanterar utskriften av poängen
      penna.undo()                          # Tar bort tidigare text
      penna.setposition(-290,310)
      utskrift = "Poang: {}".format(score)
      penna.write(utskrift, False, align="left", font = ("Arial", 14, "normal"))



# Tvingar fönstret att vara öppet (inte obligatoriskt på repl.it)
turtle.done()

Nu kommer poängen skrivas ut så fort vi får vår första poäng. Här skulle vi behöva skriva ut poängen även när spelet startar. Då behöver vi göra det utanför while-loopen för annars kommer det skrivas ut så många gånger att spelet inte hänger med och spelet börjar lagga. Eftersom vi skriver ut samma sak från två olika ställen i programmet så kan det vara värt att skapa en funktion som anropas när vi behöver skriva ut poängen. Vi kommer också bara skriva ut poängen när det har uppdaterats. Vi skall undvika att göra onödiga saker tar extra kraft för programmet.

Buggar & features

Nu har vi ett fungerande spel om än något buggigt och med behov av att förbättras. Här nedan listar jag några punkter som du kan jobba vidare med.

  • Bollar som skapas på kanten och fastnar där.
  • Studsen som borde vara mer verklighetstrogen och inte bara studsa tillbaka i exakt motsatt riktning som den kom ifrån. Går det att använda turtle.towards() eller turtle.heading() för att läsa ut infallsvinkel och sedan beräkna en reflektionsvinkel, se bild nedan.
  • Spelaren har en konstant och låg fart för tillfället. Ett alternativ är att höja farten, det är ganska lätt gjort, eller så får vi skapa en möjlighet att öka, och minska, farten med tangenter. Detta kan göras på flera olika sätt men det kan också vara bra att sätta en begränsning så att farten inte kan bli hur hög som helst, då finns det risk att vi skapar andra buggar som behöver tas om hand.
reflektionsvinkel