Välkommen till den näst sista lektionen i spelskolan! I den ska vi uträtta två saker. Dels ska vi se till så att laserkanonen, som nu är laddad, går att fyra av. Dels ska vi fixa så att asteroiderna försvinner om de blir träffade ett visst antal gånger.
För att kunna avlossa laserskottet så ska vi lägga till en ny variabel i Laser
-klassen. Låt oss kalla den för @shooting
. Från början ska @shooting
ha värdet false
. Det ordnar vi i initialize
-metoden:
def initialize(window, hero)
@window = window
@hero = hero
@icon = Gosu::Image.new(@window, "laser.png", true)
laser_home
@shooting = false
end
Sedan lägger vi till en metod, shoot
, som bara gör en enda sak: ändrar värdet på @shooting
till true
:
def shoot
@shooting = true
end
Och så ska vi göra så att skottet flyger iväg om @shooting
är true
och stannar kvar om @shooting
är false
. Vi lägger till två stycken if-satser i laserskottets update
-metod:
def update
if @shooting
@x = @x + 30
if @x > @window.width
@shooting = false
end
else
laser_home
end
end
Den första if-satsen kollar värdet på @shooting
. Om det är true
så avfyras kanonen. Notera att skottet flyttas 30 pixlar åt gången, och därmed flyger mycket snabbare än både asteroider och rymdskepp. Om @shooting
är false
händer ingenting alls, utan skottet håller sig "hemma" under laserkanonen, tack vare metoden laser_home
.
Den andra if-satsen ligger inuti den första och kontrollerar om @x
har blivit större än fönstrets bredd, det vill säga om skottet har flygit ut ur fönstret. I så fall ändras värdet på @shooting
till false
, och nästa gång update
-metoden körs kommer skottet därför att flyttas tillbaka till ursprungsläget.
Vi måste också se till så att metoden shoot
anropas när man trycker på en viss tangent, förslagsvis space. Så vi går till Window
-klassens update
-metod. Under de if-satser som kontrollerar om piltangenterna är nedtryckta skriver vi:
if button_down? Gosu::Button::KbSpace
@laser.shoot
end
Nu kan rymdskeppet skjuta laserstrålar! Prova så får du se. Men fortfarande händer ingenting när asteroiderna träffas. Vi går till Asteroid
-klassen och skriver en hit_by_laser?
-metod. Så här ska den se ut:
def hit_by_laser?
@laser.shooting && Gosu::distance(@x, @y, @laser.x, @laser.y) < 50
end
Den här metoden ska vi använda för att kontrollera två saker: om laserskottet har fyrats av och om avståndet mellan skottet och asteroiden är mindre än 50 pixlar. När man vill kolla om två saker är sanna används som du ser två stycken och-tecken, "&&".
För att hålla reda på hur många gånger asteroiden har träffats ska vi använda en ny variabel, @hits
, som i initialize
-metoden får värdet 0:
def initialize(window, laser)
@window = window
@laser = laser
@icon = Gosu::Image.new(@window, "asteroid.png", true)
@x = @window.width + rand(1000)
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
@hits = 0
end
I metoden ovan har vi också lagt till lite laser-kod på första och tredje raden. Det är för att asteroiden ska "lära känna" laserskottet, och kunna läsa av dess position.
Dessutom ska vi göra ett par ändringar i Asteroid
-klassens update
-metod:
def update
if hit_by_laser?
@hits = @hits + 1
end
@x = @x - @speed
if @x < -@icon.width || @hits >= 7
@x = @window.width
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
@hits = 0
end
end
Den första if-satsen anropar hit_by_laser?
-metoden. Om asteroiden träffats ökas värdet på @hits
med 1.
Och så har vi ändrat den andra if-satsen lite grann. Den kontrollerar nu om asteroiden har åkt ut ur fönstret eller om antalet träffar är större än, eller lika med, 7. När man vill göra en sådan kontroll används två lodräta streck, "||". Om minst ett av dessa påståenden är sanna så får asteroiden ny startposition och hastighet och @hits
nollställs.
Det här innebär att om en asteroid blir träffad av laserskottet sju gånger så kommer den att försvinna! Detta ska strax bevisas. Vi måste bara göra ytterligare två små tillägg först. I Laser
-klassen ska vi, direkt efter class Laser
lägga till följande rad:
attr_reader :x, :y, :shooting
Den gör så att variablerna @x
, @y
och @shooting
kan läsas av utifrån.
Till sist en liten justering i Window
-klassens initialize
-metod. När asteroidsvärmen bildas ska vi även skicka med en hänvisning till vårt laserskott, så här:
@asteroids = 5.times.map {Asteroid.new(self, @laser)}
Sådär! Vårt spel ska nu, i sin helhet, se ut så som följer:
require 'gosu'
class Window < Gosu::Window
def initialize
super(640, 480, false)
@hero = Hero.new(self)
@laser = Laser.new(self, @hero)
@asteroids = 5.times.map {Asteroid.new(self, @laser)}
@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
if button_down? Gosu::Button::KbSpace
@laser.shoot
end
@laser.update
@asteroids.each {|asteroid| asteroid.update}
if @hero.hit_by?(@asteroids)
@running = false
end
end
end
def draw
@hero.draw
@laser.draw
@asteroids.each {|asteroid| asteroid.draw}
end
end
class Hero
attr_reader :x, :y
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, laser)
@window = window
@laser = laser
@icon = Gosu::Image.new(@window, "asteroid.png", true)
@x = @window.width + rand(1000)
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
@hits = 0
end
def hit_by_laser?
@laser.shooting && Gosu::distance(@x, @y, @laser.x, @laser.y) < 50
end
def update
if hit_by_laser?
@hits = @hits + 1
end
@x = @x - @speed
if @x < -@icon.width || @hits >= 7
@x = @window.width
@y = rand(@window.height - @icon.height)
@speed = 3 + rand(5)
@hits = 0
end
end
def draw
@icon.draw(@x, @y, 2)
end
end
class Laser
attr_reader :x, :y, :shooting
def initialize(window, hero)
@window = window
@hero = hero
@icon = Gosu::Image.new(@window, "laser.png", true)
laser_home
@shooting = false
end
def shoot
@shooting = true
end
def laser_home
@x = @hero.x + 46
@y = @hero.y + 3
end
def update
if @shooting
@x = @x + 30
if @x > @window.width
@shooting = false
end
else
laser_home
end
end
def draw
@icon.draw(@x, @y, 1)
end
end
window = Window.new
window.show
Dags att testa spelet! Det ska nu gå att skjuta bort asteroiderna. Om du tycker att det är för svårt kan du minska antalet träffar som krävs för att en asteroid kan försvinna. Jag är säker på att du klarar det på egen hand!
Vårt spel är nu i stort sett färdigt. I den tionde och sista lektionen ska vi bara ägna oss åt finputsning. Till exempel ska vi lägga till poängräkning, så att man kan sätta rekord i spelet!