Aprendiendo ReactJS

Hace bastante que tenía pendiente ponerme a aprender algo de ReactJS, así que me puse manos a la obra y comencé a reescribir una versión online de mi Curriculum Vitae utilizando React . Hasta ahora va bastante bien, aprendí a crear componentes y un poco como trabaja el ciclo de vida de los componentes. Cargar datos asincrónicos y demás. Esto lo estoy haciendo sobre una aplicación web escrita desde 0 en Go, que puede encontrarse aquí. Eventualmente probablemente le agregue una interfáz administrativa básica, o quizás directamente lo integre con este blog.

El Blog

Como pueden ver renové un poco el look del blog, quería algo más funcional y original. Ahora tiene un toque más personalizado sin dejar de ser un diseño simple y legible. Por otra parte instalé OwnCloud para poder sincronizar los archivos desde mi PC a mi laptop, con suerte me ayudará a mantener los proyectos más ordenadamente.

Cosas personales

En este mes me tengo que mudar, por lo cual no se cual sea el tiempo que disponga para seguir publicando actualizaciones.


un vicio nuevo, eBooks

El sábado por la mañana me levanté tarde (aunque no tan tarde como de costumbre) a atender la puerta y me sorprendió que había llegado el lector de libros que encargué (en teoría debía de llegar hoy). Hace mucho quería comprar uno, la realidad es que soy un poco adicto a los libros, lo cual me esta costando un dineral y cada vez me complica más por varias cuestiones. Una es el dinero que cuestan hoy en día los libros, las ediciones impresas son realmente caras, las peores ediciones son de 120$, ni hablar de que si es un libro medianamente largo que se va de lo que es literatura clásica, los precios saltan de manera exorbitante; por otra parte ya no tengo espacio físico en mis dos bibliotecas y por último (aunque esto no puedo solucionarlo más que con organización y voluntad) se me han acumulado varios libros que no he podido terminar de leer.

fit-width

Ni hablar de que hay libros que no se consiguen por estos lados, no solo de programación, sino otros como la saga “Discworld” de Terry Pratchet (que me esta gustando mucho).

En lo que va del fin de semana, estoy a punto de terminar “El Color de la Magia” de Terry Pratchet y también el ensayo “Dios y Estado” de Mijail Bakunin, así que de momento le estoy sacando mucho provecho al aparatito. También he encontrado varios sitios donde bajar libros gratuitos, en especial recomendaría “Lectulandia” y “La biblioteca anarquista” (the anarchist library)

Para quienes sean fanáticos de la lectura, les recomiendo considerar adquirir uno, no son tan caros y con el valor actual de los libros impresos seguramente recuperaran la inversión en poco tiempo.


Varias cosas

En las ultimas semanas hice varias cosas:

Juego

Tengo que hacer una entrada sobre las ultimas funcionalidades añadidas al juego. En las últimas iteraciones le he agregado animaciones, proyectiles, detección de daño, y soporte para varios jugadores. Igualmente falta mucho por hacer (menues, IA básico, scrolling de pantalla), aquí un video de como esta quedando:

Extensión de Nemo

Una de las cosas que me gustan del open source es que cuando encontramos cosas que no se adaptan exactamente a nuestro flujo de trabajo podemos ensuciarnos un poco las manos y tomar el código existente para lidiar con eso. En este caso encontré que Nemo estaba utilizando File-Roller de Gnome, mientras que yo suelo utilizar engrampa de MATE. Luego de buscar y ver que había gente que pedía esta funcionalidad decidí hacer un fork muy básico, ya que ambas herramientas utilizan los mismos flags en la linea de comandos (solo tuve que cambiár el nombre del binario)


sobre el #NiUnaMenos y el #NadieMenos

El dia de ayer fue un día bastante reflexivo debido a la protesta contra la violencia de género, me llego mucho al alma el reclamo, por lo espontáneo y la repercusión que tuvo (aunque para mi una hora de paro es medio flojito, pero bueno yo soy medio criticón por naturaleza) y sobre todo la causa que lo disparó.

fit-width

También hubo una contracara, un #NadieMenos que me puso a pensar. Entiendo en parte el fundamento de querer hacer una protesta más abarcativa, pero eso no desacredita el derecho a hacer una protesta por causas específicas, no dudo que la intención de la gente posteando #NadieMenos no sea sincera. Pero creo que nos falta entender que si bien, todos queremos que la violencia se acabe, tenemos que tomar cartas en el asunto por nuestras causas y no esperar que otros adapten sus lemas para cambiarlas.

Algunas cifras

Datos sacados de acá, basados en datos del INDEC

Los homicidios en Argentina se se cuentan en 2746 victimas del genero masculino por año (7 hombres x día) y 524 mujeres asesinadas al año . De esa cifra 277 mujeres son asesinadas por violencia de genero(1 cada 30 horas) mientras que de la altisima cifra de hombres asesinados al año solo una cifra de 30 al año son por violencia de género (lo cual es igualmente trágico, pero decididamente menor en cantidad). Entonces claramente tenemos un sesgo , claramente las mujeres tienen una causa identificable de muerte predominante , mientras que los hombres (si bien esos 30 importan mucho) somos minoría en violencia de género.

La marcha, y protesta fue en contra de esas muertes. Una causa clara e identificada. Entonces los invito a hacer una reflexión ¿cada vez que haya una marcha en contra de los asesinatos violentos, vemos un #TambiénNiUnaMenos ? Cuando se protesta por las muertes por accidentes viales ¿vamos a reclamar que también se pida por la gente que muere de sobredosis o asesinada?

Entonces invitate a pensar ¿por qué una contraprotresta? contra algo que realmente no te perjudica. A mi me encantaría que se reduzcan todas las muertes, pero ayer se peleo por esto ¿para que lo voy a boicotear? ¿en que parte estas en desacuerdo?

¿Qué hacer?

Si realmente nos calienta el #NadieMenos, y no queremos simplemente “hacer callar a las de #NiUnaMenos”, tal vez en realidad deberíamos seguir el ejemplo y protestar, tenemos todo el resto de los días del año para organizarnos. Para protestar contra los pibes asesinados por la policia en las villas, protestar contra las muertes políticas, contra la represión, la desigualdad social detras de muchos casos de violencia. Como hombre, tengo que decir que que hasta me da un poquito de verguenza ver que las mujeres se animan a protestar y nosotros temerosos (adoctrinados como el supuesto “sexo fuerte”) les estamos diciendo “no se quejen, no protesten”, mientras no hacemos nada por nuestros muertos ni nuestras tragedias. Nos volvemos un instrumento del status quo que no beneficia a nadie, ni a nosotros mismos.

Mientras tanto incluso si no querés marchar o protestar, simplemente no obstaculizes, porque si realmente te interesa que no muera nadie más, entonces por definición también te interesa que ellas dejen de morir y deberías estar a favor de la protesta, no en contra.


Paveando con elixir

Este fin de semana estuve jugando un poco con Elixir. Elixir es un lenguaje de programación funcional muy similar a Ruby implementado sobre la VM de Erlang. Es un lenguaje interesante y aunque la sintaxis es algo similar a Ruby apenas rascamos un poco la superficie y las diferencias se notan muchísimo.

Elixir es puramente funcional, esta diseñado para manejar de manera eficiente la programación concurrente.

Entre las cosas que me llamaron la atención son:

  • la utilidad mix

  • los guards (guardias?)

  • pattern matching

mix

Mix es una utilidad de linea de comandos, al parecer extensible, permite instalar nuevas funcionalidades, crear proyectos y demás. Parece ser muy flexible y potente, y me llamo la atención que sea incluído por defecto en el lenguaje. Debo admitir que no estoy todavía interiorizado con la mayoría de sus comandos (solo lo utilize para instalar hex y subsecuentemente Phoenix un framework web que aún no he probado).

El comando mix new por ejemplo nos dejará hacer un nuevo proyecto ya listo con sus carpetas de test y configuración


 elixir $ mix new example2
* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/example2.ex
* creating test
* creating test/test_helper.exs
* creating test/example2_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd example2
    mix test

Run "mix help" for more commands.

Guards

Al definir las funciones en Elixir podemos utilizar guards, que básicamente hacen que ese método solo sea utilizado en caso de cumplirse x condición (is_list, is_integer). También podemos definir nuevos guards a traves de macros ( no se pueden llamar funciones externas/remotas) desde un guard. He aquí un ejemplo bastante trivial y probablemente demasiado complicado para lo que hace, que toma edades desde la linea de comandos y determina si son mayores de edad o no.


defmodule Example do
  def main(args) do
    Enum.each(args, fn(arg) -> determine( elem( Integer.parse( arg ),0)  ) end ) 
  end

  defmacro is_adult(age) do
    quote do: 18 <= unquote(age) 
  end

  defmacro is_minor(age) do
    quote do: 18 > unquote(age)
  end

  def determine(arg) when is_adult(arg) do
    IO.puts "Mayor de edad"
    IO.puts "Edad ingresada: #{arg}"
  end


  def determine(arg) when is_minor(arg) do
    IO.puts "Menor de edad"
    IO.puts "Edad ingresada: #{arg}"
  end

  
end

Pattern Matching

En elixir la expresion 5 = x por ejemplo realiza una operación de matching, es por eso que si hacemos esto:


iex(1)> x = 5 
5
iex(2)> 5 = x
5
iex(3)> 3 = x
** (MatchError) no match of right hand side value: 5

Arroja un error del tipo MatchError. Raro no?

En fin. He visto muy poquito aún de Elixir, pero sin dudas parece un bicho lo suficientemente extraño como para analizarlo, eventualmente me gustaría ver bien como trabaja con multiples hilos y probar el framework Phoenix.


Agregando un Popover a una aplicación Ruby Gtk

La aplicación Pomarola es un poquito distinta a los demás relojes pomodoro que he visto, básicamente la idea era generar un diario de trabajo basado en la cantidad de pomodoros trabajados por día. Es por eso que al fin me puse manos a la obra para poder guardarlos en un formato JSON. Para ello quería añadir un menu a mi aplicación. Resulta que los nuevos menues gtk son algo poco intuitivo de cara al desarrollo y por eso me costo bastante encontrarle la vuelta. Es por eso que aquí hay un pequeño tutorial de como agregar un menú tipo popover a nuestra aplicación.

fit-width

El boton de menu

Es conveniente utilizar el botón de menu provisto por GTK (GtkMenuButton), esto nos permitirá definir si lo que queremos es un menu popover. Para ello en las opciones del MenuButton seleccionamos “Ventana Emergente” y haciendo click en el pequeño icono de la entrada de texto creamos una nueva ventana emergente (nuestro popover). Este nos servirá en el código, más no podremos editarlo directamente desde aquí.

fit-width

Los archivos UI

Un popover no es simplemente un menú tradicional (en cuyo caso simplemente utilizariamos un TreeModel y lo poblaríamos. Sino más bien un widget contenedor con widgets hijos. Es por eso que debemos diseñar y guardar en un archivo ui aparte el contenedor de la siguiente manera:

fit-width

El código

Creamos nuestro popover en Glade, así como el archivo UI necesario, es por eso que ahora deberemos indicar en nuestro código que queremos utilizar este archivo para poblar los contenidos de nuestro popover:


  def generate_popover()
    @popover = @ui.get_object "popover1"
    @popover_ui = Gtk::Builder.new(:file => "./ui/menu.ui")
    @popover_contents = @popover_ui["popover"]
    
    @popover_ui.connect_signals do |handler|
        method(handler)
    end

   
    @popover.add @popover_contents
  end

Paso a paso

Obtenemos de nuestro archivo UI principal el objeto con el ID “popover1”, este es el widget que mostrará el GtkMenuButton al pulsarlo, pero ahora mismo se encuentra vacío

    @popover = @ui.get_object "popover1"

Cargamos nuestro archivo de menu, y seleccionamos el widget con el ID “popover” dentro de el (quizás debería haber utilizado nombres distintos para evitar confusión)

    @popover_ui = Gtk::Builder.new(:file => "./ui/menu.ui")
    @popover_contents = @popover_ui["popover"]

Conectamos las señales de los widgets del popover (no es necesario hacerlo de esta forma)

    @popover_ui.connect_signals do |handler|
        method(handler)
    end

Añadimos los contenidos de este nuevo archivo UI a nuestro popover.

    @popover.add @popover_contents

Y listo! Como dije anteriormente el contenido de los popover es básicamente un Container Gtk (creo que más especificamente un Gtk::bin), por lo tanto podremos añadir dentro de el grillas, cajas, barras de progreso y botones esto posibilita menus complejos como los nuevos menues de gEdit y los de otras nuevas aplicaciones de Gnome.


Pomarola, evil-mode y otras cosas

Hola!

Despues de tanto tiempo vuelvo a bloguear. Sinceramente extrañaba mucho escribir por acá (ahora en nuevo dominio), ya que es una herramienta que me mantiene motivado a seguir aprendiendo cosas y mantiene un buen registro de las cosas que he aprendido previamente (lo cual me ha servido más de una vez).

Emacs, I3, RSI y Evil

Tanto joder con atajos de teclado, hace un tiempo me empezó a molestar la mano izquierda y me di cuenta que entre Tmux, i3 y Emacs estaba haciendo mucho uso de atajos de teclado con esta misma. Así que lamentablemente (a pesar de que me encontraba muy a gusto con ese manejador de ventanas) tuve que abandonar i3 y volví al tradicional escritorio MATE. No pensaba dejar Emacs tan fácilmente, pero si tengo que admitir que el lado oscuro (VIM) tiene atajos que no cansan tanto las muñecas, y que los modos, una vez que te acostumbrás son más fáciles. Es por eso que ahora mismo me encuentro utilizando evil-mode y god-mode que me emulan bastante el workflow de Vim sin perder todas las ventajas de Emacs (la indentación, paquetes, org-mode, etc)

Pomarola

Hace un tiempo que quiero implementar la técnica pomodoro y vi que la app para linux solo soporta el escritorio GNOME. Es así que me decidi a hacer una aplicación en Ruby para aplicar esta técnica a mi trabajo (inicialmente iba a ser en Go, pero la librería esta rota para gtk3.20). No es la gran cosa, pero lo bueno es que al finalizar un pomodoro, guarda la entrada debajo así podemos editar el nombre. La idea es eventualmente poder exportarla a diversos formatos y hacer una suerte de work log. Ahora mismo está muy verde (no se puede guardar permanentemente), pero como timer pomodoro básico funciona bastante bien (con notificaciones de escritorio y toda la cosa).

fit-width

Libros

Estoy leyendo la Trilogía del Asesino de Robin Hobb, y debo decir que me gusta bastante. Es muy similar (pero anterior) a Canción de Hielo y Fuego de GRRM, aunque bastante más centrado en un solo personaje. También comencé la saga de la Torre Obscura, pero realmente debo admitir que me cuesta muchísimo seguir la escritura de Stephen King.

Una canción

Esta canción la escuche en el CD Incompleto de Callejeros y la verdad me encantó:

Un Minuto


Frase #3

Que es un buen hombre sino un maestro para el hombre malo?

Que es un hombre malo sino un trabajo para el hombre bueno?

Si no entiendes esto te perderás, sin importar lo inteligente que seas

–Tao Te Ching, ch. 27


Haciendo un juego de plataformas con Ruby y Gosu (Parte II )

En el post anterior comente como comenzar a hacer juegos con la librería Gosu y Ruby. El ejemplo mostraba como implementar una función muy primitiva para simular la gravedad, pero dejaba a resolver cuestiones como cálculo de colisiones lo cual es muy importante. Para simplificar el trabajo encontré la librería Chipmunk que básicamente se encarga de hacer el trabajo pesado de calcular la física de nuestro juego, y además nos permite detectar y responder a distintas colisiones de una manera muy sencilla.

En este tutorial voy a mostrar como añadir Chipmunk al ejemplo anterior ( y ya que estamos cambie el tileset del juego con la ayuda de opengameart )

Conceptos de Chipmunk

Chipmunk añade varias abstracciones a nuestro juego en este ejempo hacemos uso de:

  • Cuerpos
  • Figuras de colisión
  • Espacios
  • Vectores

Cuerpos

El cuerpo es una representación de la estructura física de una entidad o actor de nuestro juego, básicamente contiene información como la posicion, fuerza y velocidad de un objeto, así como otras propiedades como masa, momento, elasticidad, fricción y demás.

Figuras de colisión

Las figuras de colisión básicamente manejan la forma en la que chipmunk representará nuestra figura estas pueden ser

  • Circulos
  • Segmentos
  • Poligonos

Espacio

El espacio cumple una función muy similar a nuestra clase mundo, básicamente se encarga de hacer interactuar las distintas figuras y cuerpos entre si, también permite manipular las colisiones y activar callbacks o bloques en caso de producirse colisiones específicas.

Revisando lo que ya se hizo

Estos son los cambios a realizar:

La clase Actor pierde gran parte de sus propiedades que estarán ahora manejadas por CP::Body y CP::Shape, he escrito también algunos metodos de conveniencia como vec_from_size que permite establecer una forma (CP::Shape) CP::Vec2 a partir de un tamaño arbitrario o bien del tamaño del sprite . Le agregamos además la función draw, que dibuja el sprite a partir de un cuerpo, warp también ha cambiado, directamente alterando la posición del cuerpo.

require 'chipmunk'

class Actor
  attr_accessor :sprite, :angle, :mass, :falling, :mid_air, :height
  attr_reader :shape, :body

  def vec_from_size
    @width = @width ? width : @sprite.width
    @height = @height ? height : @sprite.height
    half_width = @width / 2
    half_height = @height / 2
    
    [CP::Vec2.new(-half_width,-half_height), CP::Vec2.new(-half_width, half_height), CP::Vec2.new(half_width, half_height), CP::Vec2.new(half_width,-half_height)]

  end

  def width
    @width ? @width : @sprite.width 
  end

  def height
    @height ? @height: @sprite.height
  end  
  

  def draw
    @sprite.draw_rot(@body.p.x , @body.p.y  , 1, @shape.body.a)
  end
  
  def mid_air
    @body.v.y.abs > 0
  end
  
  def warp(x,y)
    @body.p.x = x
    @body.p.y = y
  end
  
end

La clase player

La clase player cambia bastante, el constructor se encarga de establecer un cuerpo y una forma a partir de nuestro sprite ( que ha cambiado por este simpatico amigo por cierto!). El método accelerate ahora solo incrementa un poco la velocidad del cuerpo hacia la izquierda o derecha. Y saltar hace lo mismo detectando que el actor no este en el aire (para evitar el doble salto)

require_relative "./actor"
require 'chipmunk'
require 'pp'

class Player < Actor

  def initialize
    @sprite = Gosu::Image.new("assets/images/player.png")    

 # agregamos un cuerpo dandole masa y
 # momento le damos CP::INFINITY ya que no queremos que gire

    @body = CP::Body.new(10, CP::INFINITY)  

# Creamos la forma
    @shape = CP::Shape::Poly.new(@body,vec_from_size,CP::Vec2.new(0,0) )
    @shape.collision_type = :player #el tipo de colisión servirá para determinar que accion tomar ante distintas colisiones
    @shape.e = 0.0 # Le quitamos elasticidad así nuestro personaje no rebota por todos lados
    @shape.u = 1 # Le damos friccion
    @shape.surface_v  = CP::Vec2.new(1.0,1.0) #Velocidad de superficie

    @body.w_limit = 0.5

  end


  def accelerate(angle)
     case angle
     when :right
       @body.v.x = 3 * 0.85
     when :left
       @body.v.x = -3 * 0.85
     end
  end

  def jump
    if !mid_air
      @body.v.y = -20 * 0.95
    end
  end  
  

end


Mundo

El mundo ahora tiene menos atributos, conserva los actores, y añade uno nuevo, :space, lo inicializa determinando el damping ( una fuerza global de desaceleración, que evitara que nuestros objetos se aceleren indefinidamente ) y la gravedad

El método add actor ahora agrega la capacidad de añadir “rogue bodies”, básicamente cuerpos que no serán manipulados por el espacio, esto es util para hacer cosas como el suelo o plataformas fijas

require "chipmunk"
class World
  attr_reader :actors, :space

  def initialize
    @space = CP::Space.new()
    @actors = []

    @space.damping = 0.9
    @space.gravity.y = 0.5
  end


  
  def add_actor(actor, rogue = false)
    @actors << actor
    if rogue #adds static shape to have a rogue body
      @space.add_static_shape(actor.shape) 
    else
      @space.add_body(actor.body)      
      @space.add_shape(actor.shape)
    end
  end

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


  end

end

Clase Platform

Esta clase la cree para crear plataformas donde nuestro personaje se pueda subir, básicamente es igual a las demas solo que cuenta con 3 sprites para definir inicio, medio y final. También es una de las únicas done definimos arbitrariamente el tamaño en vez de tomarlo del tamaño del sprite, es por ello que sobrecargamos luego el metodo draw para poder dibujar correctamente la plataforma completa.

require_relative "./actor.rb"
require "chipmunk"

class Platform < Actor
  attr_accessor :height
  
  def initialize(width, height, angle = nil)
    @body = CP::Body.new_static()
    @width = width
    @height = height
    @sprite_start = Gosu::Image.new("assets/images/platform_start.png")
    @sprite = Gosu::Image.new("assets/images/platform_body.png")
    @sprite_end = Gosu::Image.new("assets/images/platform_end.png")

    @shape = CP::Shape::Poly.new(@body,vec_from_size,CP::Vec2.new(0,0) )

    if angle
      @body.a = angle
    end
    
    @shape.collision_type = :platform

  end

  def draw
     tiles = (@width / @sprite.width) / 2 
     (-tiles..tiles).each do |i|
       if i == -tiles
         @sprite_start.draw_rot(@body.p.x + (@sprite.width  * i  ) + 32 ,@body.p.y    , 1, @body.a)
       elsif i > -tiles && i < tiles -1
         @sprite.draw_rot(@body.p.x + (@sprite.width * i ) + 32  ,@body.p.y    , 1, @body.a)
       elsif i == tiles -1
         @sprite_end.draw_rot(@body.p.x + (@sprite.width * i ) + 32 ,@body.p.y    , 1, @body.a)
       end
     end
   end
  
end

Clase Ground

Ahora que tenemos física necesitamos un lugar a donde caer. La clase ground es muy similar a platform aunque un poco más simple. (quizas platform la hace obsoleta)

require_relative "./actor.rb"
require "chipmunk"

class Ground < Actor
  attr_accessor :height
  
  def initialize
    @body = CP::Body.new_static()
    @sprite = Gosu::Image.new("assets/images/ground.png")
    @width = 1200
    @height = 84
    @shape = CP::Shape::Poly.new(@body,vec_from_size,CP::Vec2.new(0,0) )
    
    @shape.collision_type = :ground

  end


  def draw
    tiles = (@width / @sprite.width) / 2
    (-tiles..tiles).each do |i|
      @sprite.draw_rot(@body.p.x + (@sprite.width * i ) ,@body.p.y    , 1, @body.a)
    end
  end

  
end

Actualizando nuestro juego

Ahora es momento de editar nuestro archivo principal game.rb y hacer que las cosas interactuen entre sí. Afortunadamente ahora esto es muy sencillo ya que la mayoría de nuestras clases manejan todo lo necesario, lo único que cambia es que ahora en vez de llamar a distintos metodos de World para la gravedad y demás, simplemente llamamos al método step de @world.space con parametro 1, lo cual avanzara la simulación una unidad de tiempo.

Ah dado que cambiamos el tileset, la funcion de dibujar el fondo también cambia un poquito.

require "gosu"
require_relative "./lib/player"
require_relative "./lib/crate"
require_relative "./lib/platform"
require_relative "./lib/world"
require_relative "./lib/ground"

class GameWindow < Gosu::Window
  
  def initialize
    super 1024, 768
    self.caption =  "Game test"

    @world = World.new()

    
    @player = Player.new
    @player.warp(200,128) #position the player
    @world.add_actor(@player)

    
    @ground = Ground.new
    @ground.warp(600,726) #position the ground
    @world.add_actor(@ground,true)    

    @platform = Platform.new(256,64)
    @platform.warp(256,128)
    @world.add_actor(@platform,true)

    @platform = Platform.new(256,64)
    @platform.warp(640,128)
    @world.add_actor(@platform,true)        

    @platform = Platform.new(256,64)
    @platform.warp(512,256)
    @world.add_actor(@platform,true)    

    @platform = Platform.new(256,64)
    @platform.warp(256,512)
    @world.add_actor(@platform,true)    

    @platform = Platform.new(256,64)
    @platform.warp(512,640)
    @world.add_actor(@platform,true)    

    
    @crate = Crate.new
    @crate.warp(640,128)
    @world.add_actor(@crate)


    @crate = Crate.new 3
    @crate.warp(256,128)
    @world.add_actor(@crate)

    

    @crate = Crate.new 2
    @crate.warp(600,350)
    @world.add_actor(@crate)        
    
    @background_image = Gosu::Image.new("assets/images/bg.png", :tileable => true)
  end

  def update
    if Gosu::button_down? Gosu::KbLeft #or Gosu::button_down? Gosu::GpLeft then
      @player.accelerate :left
    end
    
    if Gosu::button_down? Gosu::KbRight #or Gosu::button_down? Gosu::GpRight then
      @player.accelerate :right
    end

    if Gosu::button_down? Gosu::KbUp #or Gosu::button_down? Gosu::GpRight then

      @player.jump        
    

    end

    @world.space.step 1
  end

  def draw
    @world.show
    tiles_x = 1024 / @background_image.width
    tiles_y = 768 / @background_image.height
    tiles_x.times { |i|
      tiles_y.times {|j|
              @background_image.draw(i * @background_image.width, j * @background_image.height, 0)
      }

    }

  end
end


window = GameWindow.new

window.show

El resultado un simpático robot en una fábrica que puede empujar cajas y otros objetos. También subi un video de etapas más tempranas del desarrollo usando el antiguo tileset.

fit-width

Consideraciones

Hay ciertas cosas a recordar trabajando con chipmunk:

  • Chipmunk y gosu expresan los angulos y vectores de manera distinta, chipmunk simplemente indica puntos en un eje relativo al cuerpo, gosu lo expresa en función de un angulo y distancia.
  • CP::INFINITY es un valor que representa infinito, y es útil en algunos casos como por ejemplo cuando no queremos que un actor gire sobre si mismo.
  • Chipmunk no maneja fricción con objetos en rotación, eso hace que sea más importante CP::INFINITY
  • Si añadis un cuerpo al espacio simulado este va a ser afectado por la simulación, si solo añadís la forma, esta va a afectar a los demás pero el propio cuerpo no se vera afectado ( a menos que arbitrariamente se modifique como en caso de ascensores y demás) esto sería un “rogue body”

Recuerden que pueden descargar el juego aquí


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