Koda spel i Ruby

Lektion 8: Ladda laserkanonen

Vårt rymdskepp är redan utrustat med en laserkanon – du ser de två piporna sticka ut framtill på skeppet. Men kanonen har ingen ammunition, och kan därför inte skjuta. Så nu ska vi ta och ladda den med ett laserskott! (Här bortser vi helt från hur laser fungerar i verkligheten.)

Vi ska alltså lägga till ännu ett objekt i spelet. Och vid det här laget vet du nog hur det går till. Först måste vi skapa ytterligare en klass, den här gången med namnet Laser. Så direkt efter Asteroid-klassen skriver vi:


class Laser
  def initialize(window, hero)
    @window = window
    @hero = hero
  end
  
  def update
  end
  
  def draw
  end
end	  
	  

Laserskottet måste veta både i vilket spelfönster det ska ritas, och vilket rymdskepp det hör till. Det fixar vi i initialize-metoden.

Som vanligt behöver vi en bild, och den ligger här nedanför. Jag vet att den ser konstig ut, men det blir bra det här, jag lovar! Högerklicka, spara och flytta bilden till rätt mapp.

Laserskottet ska få en startposition precis under rymdskeppets laserkanon. Och dit ska det återvända varje gång kanonen "laddas om". I stället för att skriva samma kod på olika ställen i klassen så skapar vi en speciell metod, laser_home, som vi använder för att skicka skottet "hem" till kanonen. Denna metod ska vi anropa dels i initialize, dels i update:


class Laser
  def initialize(window, hero)
    @window = window
    @hero = hero
    @icon = Gosu::Image.new(@window, "laser.png", true)
    laser_home
  end
  
  def laser_home
    @x = @hero.x + 46
    @y = @hero.y + 3
  end
  
  def update
    laser_home
  end
  
  def draw
    @icon.draw(@x, @y, 1)
  end
end
      

Koden i laser_home gör så att skottet hamnar 46 pixlar till höger och 3 pixlar nedåt i förhållande till skeppets övre, vänstra hörn. Det vill säga precis i nivå med laserkanonen. Ovan har jag också sett till så att datorn vet vilken bild som ska användas för att visa skottet, och att bilden ritas ut när draw anropas.

Om du studerar draw-metoden noga så kanske du märker en skillnad jämfört med motsvarande metoder i Hero- och Asteroid-klasserna? Det står @icon.draw(@x, @y, 1) i stället för @icon.draw(@x, @y, 2). Siffran i slutet bestämmer i vilket lager bilden ska ritas. Så laserskottet ritas i lager ett, under rymdskeppet som ritas i lager två.

Vi måste också lägga till vår laser i Window-klassen. Först ordnar vi så att laserskottet skapas i initialize-metoden, genom att lägga till följande rad:


@laser = Laser.new(self, @hero)	  
	  

Sedan gör vi detta lilla tillägg i Window-klassens update-metod:


@laser.update	  
	  

I draw-metoden skriver vi:


@laser.draw	  
	  

Och så ska vi se till så att laserskottet kan läsa av rymdskeppets x- och y-position. Direkt efter class Hero skriver vi:


attr_reader :x, :y
      

Efter de här små tilläggen så ska hela programmet se ut så här:


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)}
    @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
      
      @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)
    @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

class Laser
  def initialize(window, hero)
    @window = window
    @hero = hero
    @icon = Gosu::Image.new(@window, "laser.png", true)
    laser_home
  end
  
  def laser_home
    @x = @hero.x + 46
    @y = @hero.y + 3
  end
  
  def update
    laser_home
  end
  
  def draw
    @icon.draw(@x, @y, 1)
  end
end

window = Window.new
window.show	  
	  

Om du nu provkör spelet är risken att du blir besviken – du kommer nämligen inte att märka någon som helst skillnad jämfört med förra lektionen. Laserkanonen är visserligen laddad, men det syns inte utanpå. Och kanonen går inte att fyra av.

Men håll ut, i nästa lektion blir det betydligt mer action!