Du har nu klarat av hela sex lektioner i den här spelskolan. Det tyder på att du är både uthållig och har talang för programmering. Nu återstår att se om du är en lika talangfull spelare – när den här lektionen är över kommer vårt spel att vara betydligt svårare!
Än så länge är det bara en enda asteroid som svävar omkring i spelfönstret. Vi ska nu lägga till ytterligare ett antal asteroider, så att det blir en hel svärm. Det är inte särskilt svårt, eftersom vi redan har en färdig beskrivning av en asteroid: vår Asteroid
-klass.
Men hur ska vi skriva i programmet för att skapa, säg, fem olika asteroider? Ett sätt vore att skapa dem en och en, på precis samma sätt som vi skapade den första:
def initialize
super(640, 480, false)
@hero = Hero.new(self)
@asteroid1 = Asteroid.new(self)
@asteroid2 = Asteroid.new(self)
@asteroid3 = Asteroid.new(self)
@asteroid4 = Asteroid.new(self)
@asteroid5 = Asteroid.new(self)
@running = true
end
Men det är inte ett särskilt bra sätt. Då skulle vi få upprepa all kod som rör asteroiderna fem gånger om. Till exempel skulle vi, i stället för bara ett @asteroid.update
, få skriva @asteroid1.update, @asteroid2.update, @asteroid3.update
och så vidare. Det skulle bli rörigt!
Nej, i stället ska vi ta och lägga alla asteroiderna i samma "låda", det vill säga samma variabel. Så i Window
-klassen tar vi och döper om variabeln @asteroid
(som betyder just "asteroid") till @asteroids
(som betyder "asteroider"). Och så skapar vi alla fem asteroiderna på en enda rad, på det här viset:
def initialize
super(640, 480, false)
@hero = Hero.new(self)
@asteroids = 5.times.map {Asteroid.new(self)}
@running = true
end
De där konstiga parenteserna, "{" och "}", runt Asteroid.new(self)
brukar kallas för klammerparenteser, krullparenteser eller måsvingar. Här ska det vara just sådana – det funkar inte med vanliga parenteser.
Sedan ska vi ändra i Window
-klassens metoder update
och draw
. Först i update
, där vi i stället för @asteroid.update
skriver så här:
@asteroids.each {|asteroid| asteroid.update}
"Each" betyder "varje", och den här koden gör just så att varje asteroid i svärmen uppdateras. Vi måste också göra motsvarande ändring i draw
-metoden:
@asteroids.each {|asteroid| asteroid.draw}
Dessutom måste vi ändra @asteroid
till @asteroids
i den if-sats i update
-metoden som kontrollerar om rymdskeppet har träffats av en asteroid:
if @hero.hit_by?(@asteroids)
@running = false
end
Bara en ändring till innan vi testar spelet! Vi går till Player
-klassen och skriver om metoden hit_by?
på följande sätt:
def hit_by?(asteroids)
asteroids.any? {|asteroid| Gosu::distance(@x, @y, asteroid.x, asteroid.y) < 50}
end
(Observera att du måste scrolla till höger för att se all kod!)
Sådär. Spara och provkör. Om allting funkar kommer det att se ut ungefär så här:
Hm. Jag vet inte vad du säger, men jag är inte helt nöjd. Det här liknar ju mer en vägg av asteroider än en svärm. Det beror på att alla asteroider har samma position i sidled när de startar, 640, och att de rör sig med samma hastighet, fem pixlar i taget.
Låt oss testa att ge asteroiderna olika startpositioner, och olika hastigheter, så att de inte kommer in i fönstret samtidigt. För att göra det ska vi återigen ta hjälp av slumpen. Vi går till Asteroid
-klassen och gör några ändringar i initialize
-metoden:
def initialize(window)
@window = window
@icon = Gosu::Image.new(@window, "asteroid.png", true)
@x = @window.width + rand(1000)
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
end
Tidigare har vi använt rand
-metoden för att slumpa fram ett värde på @y
, som bestämmer asteroidens position i höjdled. Nu använder vi samma metod för att få ett värde på @x
.
När man skriver rand(1000)
så levererar metoden ett slumptal mellan 0 och 999. Det slumptalet lägger vi ihop med 640, som är fönstrets totala bredd. Så @x
kommer som minst att få ett värde på 640, vilket ger en position precis utanför högerkanten, och som mest ett värde på 1 639, vilket motsvarar en position en bra bit utanför fönstret.
Och så har vi lagt till en ny variabel, @speed
("hastighet"), som med hjälp av rand
får ett värde mellan 3 och 7. Denna variabel ska vi även använda i Asteroid
-klassens update
-metod, på det här viset:
def update
@x = @x - @speed
if @x < -@icon.width
@x = @window.width
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
end
end
Provkör nu spelet igen. Nu får vi verkligen en svärm av asteroider. Jag hoppas du håller med mig om att spelet blev bättre på det här sättet. Är det inte tillräckligt svårt? Prova gärna att lägga till ännu fler asteroider! Det är bara att byta ut siffran i 5.times.map
i Window
-klassens initialize
-metod.
Vårt program börjar nu bli riktigt långt. Här har du det i sin helhet:
require 'gosu'
class Window < Gosu::Window
def initialize
super(640, 480, false)
@hero = Hero.new(self)
@asteroids = 5.times.map {Asteroid.new(self)}
@running = true
end
def update
if @running
if button_down? Gosu::Button::KbRight
@hero.move_forward
end
if button_down? Gosu::Button::KbLeft
@hero.move_back
end
if button_down? Gosu::Button::KbUp
@hero.move_up
end
if button_down? Gosu::Button::KbDown
@hero.move_down
end
@asteroids.each {|asteroid| asteroid.update}
if @hero.hit_by?(@asteroids)
@running = false
end
end
end
def draw
@hero.draw
@asteroids.each {|asteroid| asteroid.draw}
end
end
class Hero
def initialize(window)
@window = window
@icon = Gosu::Image.new(@window, "spaceship.png", true)
@x = 100
@y = 215
end
def move_forward
@x = @x + 5
if @x > @window.width - @icon.width
@x = @window.width - @icon.width
end
end
def move_back
@x = @x - 5
if @x < 0
@x = 0
end
end
def move_up
@y = @y - 5
if @y < 0
@y = 0
end
end
def move_down
@y = @y + 5
if @y > @window.height - @icon.height
@y = @window.height - @icon.height
end
end
def draw
@icon.draw(@x, @y, 2)
end
def hit_by?(asteroids)
asteroids.any? {|asteroid| Gosu::distance(@x, @y, asteroid.x, asteroid.y) < 50}
end
end
class Asteroid
attr_reader :x, :y
def initialize(window)
@window = window
@icon = Gosu::Image.new(@window, "asteroid.png", true)
@x = @window.width + rand(1000)
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
end
def update
@x = @x - @speed
if @x < -@icon.width
@x = @window.width
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
end
end
def draw
@icon.draw(@x, @y, 2)
end
end
window = Window.new
window.show
Ännu en lektion är avklarad – bravo! Det här börjar verkligen likna ett riktigt spel. Fast visst vore det rätt coolt om man inte behövde undvika asteroiderna, utan kunde skjuta sönder dem? I nästa lektion ska vi ladda laserkanonen!