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


Jueguitos en Ruby

Hace un tiempo en una de las meetups menos concurridas de Ruby Lit estuvimos viendo varios juegos hechos por @jjconti en Python. Ahí creo que surgió la pregunta si había algo parecido a PyGame en Ruby. Creería que no sabíamos de alternativas en su momento. Así que en un momento de aburrimiento me puse a investigar y me encontre con Gosu, una librería con la que es muy sencillo hacer juegos 2D en Ruby.

La creación de juegos no es ciertamente mi area de experiencia pero me resulto fácil crear un pequeño entorno, y un bastante precario sistema de gravedad. Aún estoy peleando un poco con como calcular y responder a las colisiones, pero eso lo hace aún más interesante para mí.

fit-width

enlace al ejemplo

Gosu

Edit: Por lo que veo en la página para física recomiendan Chipmunk, y hay también recomendaciones para frameworks sobre Gosu, los cuales seguramente voy a investigar.


Varias cosas II

Hola, hace un tiempo que no actualizo mi blog, no obstante han pasado varias cosas desde la ultima vez que escribí.

Nuevas funcionalidades

Añadí una precaria funcionalidad de reply a los comentarios, que aunque el blog no tenga muchos, sin duda el software ha tomado un rumbo a un CMS algo más genérico y me pareció necesario. Por ahora solo permite 1 nivel de anidado, ya que estoy pensando en la mejor forma de limitarlo (n-niveles rompería el layout) y debido a las características del sistema de templates de Go no encontré una buena forma de hacerlo que no involucre JS.

También hay una funcionalidad para añadir páginas estáticas, lo cual lo hace un poco más versátil.

fit-width

Fuera de esto estoy pensando en mover el desarrollo del blog a un repositorio aparte, ponerle un nombre (¿ideas?) y difundirlo un poco más, quizás encuentre gente que le sirva y quiera colaborar con un mini CMS hecho en Go.

Docker

Durante el fin de semana he estado estudiando un poco de Docker, un poco motivado por la meetup de DevOps Litoral y la charla de Germán y Manuel. La verdad avance mucho menos de lo que quería pero leí bastante sobre containers y demás.

The Force Awakens

El otro fin de semana me di el gusto de ver la nueva peli de Star Wars, sinceramente iba preparado para una decepción pero no fue así, me gustó mucho. No voy a contar muchos detalles fuera de eso :).


Blog update

I’ve added some features to this little piece of software. Mainly you will notice you can now get a proper RSS feed for the blog posts, for this I created a little small Go library which just uses the encoding/xml package of go STL. Also now it’s possible to access posts by pretty urls rather than by id, which is something I had to do a long time ago but forgot to do it far too many times.

I’m also working on a prettier admin interface based on SB admin bootstrap template.


Frase #2

I never let my schooling get in the way of my education. – Mark Twain


La mezquindad de los condenados

Sinceramente nunca me fue mucho la retórica del anti-chori sobre los que “van a tener que volver a trabajar”, será porque mi paladar poco delicado esta más cerca de disfrutar un chori que comida gourmet, y porque durante todos estos años solo he conocido y me he relacionado gente que labura. Y algo que he descubierto, es que hay cierta rabia, cierto odio cuando cierta persona accede a algo “inmerecido”, porque el trabajo que vale solo es el mío, y el del resto es una boludez, una trivialidad.

fit-width

La mente básica y la lógica tribal de algunas personas les hacen odiar al vecino en sus tiempos de bonanza (sin importar como nos vaya a nosotros mismos) porque estamos tan acostumbrados a la escasez. Silvio decía “…dirán que la gente es mala y no merece…”, y claramente es el caso, el rabiar porque el otro no merece nada bueno si no nos demostró que lo gano con un sufrimiento desmedido (y en cuyo caso se convierte en un panfleto del “vieron que se puede salir adelante con esfuerzo?” para dejar las cosas como están).

Es doloroso que la gente haya votado con esto en la cabeza. Es doloroso ver los colmillos de la mezquindad y el revanchismo contra gente que no le ha hecho nada y simplemente tuvo el atrevimiento, la osadía de disfrutar un poco más de la vida cuando la oportunidad se le presentó.

Por que el odio ya no es contra el dirigente del otro partido, es contra la gente, es odio de ver gente con la panza llena, con zapatillas nuevas o accediendo a servicios que deberían ser solo de la élite y de ciertos colores de piel.

Es doloroso ver que en el fondo no cambio nada una mierda ni va a cambiar nunca.

(Este es un posteo de facebook que como le interesó a varias personas decidí guardarlo acá y compartirlo x twitter)


Helm Dash

fit-width

Frecuentemente tengo que consultar documentación así que busqué un modulo de emacs que me permita hacerlo. Helm Dash permite fácilmente consultar la documentación tanto en el punto actual como buscar dentro de los docsets (fácilmente instalables utilizando M-x helm-dash-install-docset ) para conveniencia defini 2 shortcuts

(global-set-key "\C-hp" 'helm-dash-at-point) ;; busca la documentación de la función señalada por el cursor
(global-set-key "\C-hs" 'helm-dash) ;; inicia el modo busqueda de helms dash

La instalación esta explicada en la web del proyecto aunque a mi los paquetes de melpa/marmalade no me funcionaron y tuve que instalar helm manualmente.

en acción


PyDay Recap

El sábado tuve la suerte de poder asistir al PyDay organizado por gente del LugLi y PyAr. La verdad es que hacía un tiempo que no asistía a una jornada tan interesante. Si bien de momento no me encuentro programando en Python (Ruby y Go) son ya demasiado para aprender a la vez, disfruté muchísimo de las charlas.

Llegue medio tarde y me perdí la charla de Juanjo Conti ( aunque pude ver los slides y se ve que estuvo muy buena), pero pude asistir a la de Django un framework web que me resulto un poco raro, pero interesante y seguramente estaré mirando cuando tenga tiempo. Luego vino la emotiva charla de Argentina en Python por humitos en la que me pude enterar del estado de la gran movida que estan llevando a cabo (que conocí el año pasado pero se ve que han hecho progresos enormes).

fit-width

Disfrute mucho de ambas charlas de Machine Learning, principalemente porque había empezado a estudiar eso a mitad de año pero no pude continuar por falta de tiempo (y agotamiento). Estuve chusmeando alternativas para utilizar [OpenCV] en Ruby, al menos hasta que encuentre tiempo de arrancar más de lleno con Python.

Pude escuchar algo de la charla de Flask, pero ya estaba corto de tiempo y me tuve que ir. Al parecer es un micro-framework para Python, definitivamente algo útil para tener en la caja de herramientas.

Otras yerbas

Estuve probando PRY para ruby, una versión de irb en esteroides, muy interesante y cómodo de usar.

También estuve mirando Lotus, un framework que promete ser más liviano que Rails, pero no un micro-framework como Sinatra o Cuba. Definitivamente algo interesante para tener en cuenta en un futuro.

Y nada que ver…

En la última feria del libro me compré el primer tomo de los libros de Terramar, que había oído que eran muy recomendables para lectores de Tolkien. La verdad es que no es ni parecido en mi opinión, pero esto no significa que sea malo, solo que el foco de la narrativa parece pasar por otro lado. Cuando lo termine (si tengo tiempo quizás haga una breve reseña)


Ayreon - Into The Electric Castle

Mi banda favorita sin duda es Ayreon, y algunos otros proyectos del genio musical Arjen Lucassen. Si bien tengo la discografía completa, un album que siempre había pasado por alto es “Into The Electric Castle”. Principalmente por estar desconectado de la linea de tiempo de Ayreon (sobre la cual seguramente hare un post más adelante). Hace una semana que lo estoy escuchando y simplemente no puedo creer de lo que me había perdido.

pull-left

Es díficil definir que canción es la mejor de este trabajo ya que la mayoría tienen una marca distintiva, desde los riffs pegadizos de Amazing Flight a la ominosidad de The Castle Hall, con unas líricas increibles de Fish que lamentablemente desaparece en la mitad del album, y un desarrollo de personajes increible.

En fin, me sorprende haber dejado pasar por alto esta genialidad, altamente recomendable.


Proceso secuencial

Me tope con un problema en el cual debía ejecutar un proceso en varias partes y ejecutarlo nuevamente si es que había finalizado antes de terminar la tarea que necesitaba terminar, por suerte con un poco de ayuda de germán armé este pequeño script salvaje que resuelve el problema:

#!/usr/bin/bash
COMMAND="process name";
    
$COMMAND &
PID=$!
FINISH=0

echo 0 > process_finished
while true
do
    if ! ps -p $PID >/dev/null;
    then
    FINISH=`cat process_finished`;
    if [ $FINISH -eq 1 ]
    then
        echo "batch finished running";
        break;
    else
        echo "running again";       
        $COMMAND &
        PID=$!
    fi
    fi
    sleep 1;
done

Básicamente el comando se ejecuta nuevamente si no esta siendo ejecutado a menos que se encuentre 1 en el archivo process_finished .

Quizás no es una solución elegante pero muy util y eficaz para resolver un problema puntual.