Ir al contenido principal

Haciendo un juego de plataformas con Ruby y Gosu

Como les comentaba en el post anterior, comencé a programar un pequeño juego en mis ratos libres. Para esto utilicé la librería Gosu, que hace que esto sea una tarea bastante sencilla.

Comenzando

Para mi juego dividí la aplicación en 5 clases, una para definir el entorno o sea el juego en sí, otra el mundo donde esta el código encargado de manejar la gravedad y eventualmente las colisiones entre objetos; luego esta la clase actor, donde defino cosas comunes a todos los actores que aparecen en el juego (bloques, personajes, NPCs? ) y dos clases que derivan de esta, player y block. La clase esta comentada explicando que hace cada cosa:

require "gosu"

require_relative "./lib/player"
require_relative "./lib/block"
require_relative "./lib/world"

class GameWindow < Gosu::Window

  def initialize
    super 800, 600
    self.caption =  "Game test"

    @world = World.new() # aquí inicializamos la clase mundo y le asignamos un par de valores 
    @world.viewport_height = self.height
    @world.viewport_width = self.width

    #Creamos un jugador y lo añadimos a nuestro mundo

    @player = Player.new
    @player.warp(200,@world.horizon ) 
    @world.add_actor(@player)    

    # Creamos un bloque y lo añadimos a nuestro mundo
    @block = Block.new
    @block.place(300,@world.horizon + @block.height)
    @world.add_actor(@block)

    # seteamos un fondo 
    @background_image = Gosu::Image.new("assets/images/bg.png", :tileable => true)
  end

 # El método update se encarga de capturar los distintos eventos de teclado
 # también llamamos al metodo de mundo gravity, que se encargará de que los objetos caigan
 # por ultimo el método move de la clase player se encargara de que el jugador se mueva

  def update
    if Gosu::button_down? Gosu::KbLeft 
       @player.accelerate :left
    end

    if Gosu::button_down? Gosu::KbRight 
        @player.accelerate :right
    end

    if Gosu::button_down? Gosu::KbUp 

        if !@player.falling
          @player.jump
        end

    end

    @world.gravity

    @player.move    
  end

# El método draw se encarga de dibujar todos los actores del mundo así como la imágen de fondo que hemos escogido

  def draw
    @world.show
    @background_image.draw(0, 0, 0)    
  end
end

#Instanciamos la ventana de juego y mostramos 
window = GameWindow.new

window.show

La clase actor

La clase actor define propiedades comunes de los actores del juego. Por ahora no hace mucho más que proveer un método comun de acceder a las propiedades del ancho y alto de la sprite de cada actor

class Actor
  attr_accessor :sprite, :x, :y, :angle, :mass, :falling, :mid_air, :height

  def width
    @sprite.width
  end

  def height
    @sprite.height
  end 

end

La clase player

Nuestra clase player hereda de actor, tiene los métodos necesarios para: posicionar el jugador, iniciar la aceleración cuando el jugador realiza un movimiento

require_relative "./actor"
require "pp"

class Player < Actor
  attr_accessor :vel_y, :vel_x, :acc, :x,:y
  def initialize
    super 
    @sprite = Gosu::Image.new("assets/images/player.png")

    @x = @y = @vel_x = @vel_y =  0.0

    @acc = 0.5
    @mass = 50
  end

  def warp(x,y)
    @x,@y = x,y
  end

  # este metodo se encarga de acelerar el jugador, dado que queremos disminuir la velocidad de aceleracion cuando estamos en el aire
  # el flag @midair determina si estamos en el aire o no

  def accelerate(angle)
    acc =  @mid_air ? 0.2 : @acc

    case angle
    when :right
      @vel_x += Gosu::offset_x(90, acc)
    when :left
      @vel_x += Gosu::offset_x(-90, acc)
    end

  end


  # Movemos el actor a las coordenadas deseadas
  def move
    @x += @vel_x
    @y += @vel_y


    @vel_x *= 0.95
    @vel_y *= 0.95

  end

  # Al saltar, definimos que estamos en el aire, y en tanto no llegemos a cierto punto permitimos acelerar el actor
  # esto permite graduar la fuerza del salto sin que el jugador pueda volar y permite activar la gravedad una vez que se llega al punto máximo

  def jump
    @mid_air = true
    if @vel_y.abs < 6.0
      @vel_y += Gosu::offset_y(1, 3.5)
    else
      @falling = true
    end
  end  

  # dibujamos el actor en la posicion indicada
  def draw
    @sprite.draw(@x,@y, 1 )
  end
end

La clase World

La clase world controla la interacción entre los actores del juego, asi como efectos como la gravedad que afectan a los actores

require "json"


class World
  attr_reader :actors,:gravity,:friction, :horizon
  attr_accessor :viewport_height, :viewport_width
  def initialize
    @actors = [] #arreglo que contiene los actores del juego
    @gravitational_force = 0.85 # constante de la fuerza de gravedad
    @gravity_acceleration = 0.0 # aceleración generada por fuerza de gravedad
  end

  # el horizonte sería nuestro suelo ¿tal vez debería llamarlo suelo?
  def horizon
    @viewport_height - 140    
  end

 # agrega un actor
  def add_actor(actor)
    @actors << actor
  end


  #define nuestras reglas de gravedad
  def gravity

    @actors.each {|actor|

        if actor.y >= horizon #Si nuestro actor se encuentra en el suelo detenemos la caida y toda aceleración vertical.
          if actor.falling  
            actor.vel_y = 0
          end
          @gravity_acceleration = 0
          actor.y = horizon
          actor.falling = false
          actor.mid_air = false
        elsif actor.vel_y.abs > 0.0 # aplicamos la fuerza de gravedad siempre y cuando el jugador no se encuentre en el suelo
          @gravity_acceleration = Gosu::offset_y(1, @gravitational_force)
          actor.vel_y -= @gravity_acceleration
        end

    }

  end

  def show
    @actors.each { |actor|
      actor.draw
    }


  end

end

El juego en acción

Como ya mencioné el código de este programa se puede descargar de github. Lo que quedaría es agregar un bloque, y programar las colisiones, aunque quizás no lo haga manualmente sino que cambie mi sistema de física por Chipmunk que parece algo mucho más completo y bien hecho

Comentarios

Comments powered by Disqus