Desarrollando el juego Snake en Sketchup con Ruby

Sketchup es un software de modelado 3D muy fácil de usar, y permite dsearrollo secundario mediante Ruby.

Hoy he creado un pequeño juego de Snake dentro de Sketchup. A continuación, los pasos seguidos.

Demostración

Las reglas del juego son las siguientes:

  • El jugador controla la serpiente con las teclas de dirección.
  • Al comer la comida, la serpiente crece y la putnuación aumenta.
  • Si la cabeza choca contra el borde del mapa o contra su propio cuerpo, el juego termina.

Archivo principal

# Punto de entrada
require 'json'
require_relative 'ayuda.rb'

module SerpienteIngenua
  $juego = nil  # Variable para instancia de la serpiente

  class Indice
    @@dialogo = nil
    @@dialogoConfig = nil
    @@dialogoRanking = nil

    def initialize
      cmd = UI::Command.new("SerpienteIngenua") {
        @@dialogo = UI::WebDialog.new("SerpienteIngenua", false, "", 229, 172, 5, 90, false)
        @@dialogo.set_file(__dir__ + '/index.html')

        @@dialogoConfig = UI::WebDialog.new("SerpienteIngenua_config", false, "", 262, 315, 235, 90, false)
        @@dialogoConfig.set_file(__dir__ + '/set.html')

        @@dialogoRanking = UI::WebDialog.new("SerpienteIngenua_ranking", false, "", 290, 320, 5, 260, false)
        @@dialogoRanking.set_url('http://00a00.com/snake/')

        @@dialogo.show unless @@dialogo.visible?
        @@dialogo.add_action_callback('iniciar') do
          if Sketchup.active_model.active_entities.length > 0
            respuesta = UI.messagebox("Hay un modelo existente, ¿desea limpiarlo?\n(Nota: debe limpiarse para jugar)", MB_YESNO)
            if respuesta == 6
              Sketchup.active_model.active_entities.clear!
              load __dir__ + '/Base.rb'
              require_relative 'Camara.rb'
              require_relative 'Mapa.rb'
              load __dir__ + '/Comida.rb'
              load __dir__ + '/Serpiente.rb'
              load __dir__ + '/HerramientaSerpiente.rb'

              Camara.new        # Inicializar cámara
              Mapa.new.mostrar  # Mostrar escenario
              Comida.new.mostrar # Mostrar comida
              $juego = Serpiente.new # Instancia de la serpiente
              $juego.iniciar       # Iniciar juego

              Sketchup.active_model.select_tool(HerramientaSerpiente.new)
              @@dialogo.close if @@dialogo.visible?
              @@dialogoConfig.close if @@dialogo.visible?
              @@dialogoRanking.close if @@dialogo.visible?
            end
          end
        end

        @@dialogo.add_action_callback('listo') do
          # Generar nombre aleatorio
          longitud = 8
          caracteres = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
          nombre = ""
          1.upto(longitud) { |i| nombre << caracteres[rand(caracteres.size - 1)] }
          config('apodo', nombre)
        end

        @@dialogo.add_action_callback('configurar') { @@dialogoConfig.show }

        @@dialogo.add_action_callback('ranking') { @@dialogoRanking.show }

        @@dialogoConfig.add_action_callback('ok') do |dialogo, json|
          hash = JSON.parse(json)
          hash.each { |k, v| config(k, v) }
          dialogo.close
        end

        @@dialogoConfig.add_action_callback('restaurar') do |dialogo, json|
          json = configRec()
          @@dialogoConfig.execute_script("fill(#{json})")
        end

        @@dialogoConfig.add_action_callback('inicio') do |dialogo, json|
          json = configAll()
          @@dialogoConfig.execute_script("data = #{json}")
        end
      }

      cmd.small_icon = cmd.large_icon = __dir__ + "/assets/icon.png"
      cmd.menu_text = "SerpienteIngenua"

      barra = UI::Toolbar.new("SerpienteIngenua")
      barra = barra.add_item(cmd)
      barra.show

      menu = UI.menu('Plugins').add_submenu('SerpienteIngenua (Snake)')
      menu.add_item
    end
  end
end

SerpienteIngenua::Indice.new

Clase base

# Clase base
module SerpienteIngenua
  class Base
    @@modelo = Sketchup.active_model   # Modelo
    @@vista  = @@modelo.active_view     # Vista
    @@entidades = @@modelo.active_entities # Entidades
    @@materiales = @@modelo.materials       # Materiales

    @@anchoMapa     = config('anchoMapa').to_i    # Ancho del mapa
    @@altoMapa      = config('altoMapa').to_i     # Alto del mapa
    @@altoSerpiente = config('altoSerpiente').to_i # Altura de la serpiente
    @@anchoSerpiente = config('anchoSerpiente').to_i # Ancho de la serpiente
    @@numSegmentos  = config('numSegmentos').to_i    # Número de segmentos
    @@nivel         = config('nivel')                 # Nivel de dificultad
    @@direccion     = ARRIBA     # Dirección inicial
    @@serpienteActual = []      # Cuerpo actual de la serpiente
    @@serpienteArr    = []      # Cuerpo inicial de la serpiente
    @@comida         = []       # Almacena la comida
    @@listaNegra     = []       # Lista negra de posiciones de comida
    @@idTemporizador = nil      # ID del temporizador
    @@puntuacion     = 0        # Puntuación
  end
end

Clase de cámara

# Cámara
module SerpienteIngenua
  class Camara < Base
    def initialize
      iniciar
    end

    def iniciar
      ojo = [0, 0, 1300]; objetivo = [0, 0, 0]; arriba = [0, 1, 0]
      camara_inicio = Sketchup::Camera.new(ojo, objetivo, arriba)
      @@vista.camera = camara_inicio
    end
  end
end

Clase del mapa

# Mapa
module SerpienteIngenua
  class Mapa < Base
    def mostrar
      cesped # Crear césped
    end

    def cesped
      archivo = __dir__ + '/assets/cesped.skm'
      material = @@materiales.load(archivo)

      pt1 = Geom::Point3d.new(-@@anchoMapa/2, -@@altoMapa/2, 0)
      pt2 = Geom::Point3d.new(@@anchoMapa/2, -@@altoMapa/2, 0)
      pt3 = Geom::Point3d.new(@@anchoMapa/2, @@altoMapa/2, 0)
      pt4 = Geom::Point3d.new(-@@anchoMapa/2, @@altoMapa/2, 0)
      grupo = @@entidades.add_group
      grupo.entities.add_face(pt1, pt2, pt3, pt4)
      grupo.material = material
    end
  end
end

Clase de la serpiente

# Serpiente
module SerpienteIngenua
  class Serpiente < Base
    def initialize
      @@serpienteArr = inicializarSerpiente(@@numSegmentos)
      @arriba = 38; @abajo = 40; @izquierda = 37; @derecha = 39
      @direccion = @@direccion
      @posX = rand(-@@anchoMapa..@@anchoMapa) / 2 / 10 * 10
      @posY = rand(-@@altoMapa..@@altoMapa) / 2 / 10 * 10
    end

    def iniciar
      crearSerpiente # Dibujar serpiente
      velocidad = 0.5
      case @@nivel
      when 'Normal'; velocidad = 0.4
      when 'Valiente'; velocidad = 0.1
      when 'Infierno'; velocidad = 0.03
      when 'Pesadilla'; velocidad = 0.006
      end
      tiempo = 1
      @@idTemporizador = UI.start_timer(velocidad, true) do
        mover(@direccion)
        tiempo += 1
      end
    end

    def establecer(direccion)
      @direccion = direccion
    end

    # Dibujar serpiente
    def crearSerpiente
      @@serpienteActual.clear # Limpiar estado actual
      @@serpienteArr.each do |cuadro|
        xBase = @@anchoSerpiente / 2
        yBase = @@anchoSerpiente / 2
        punto1 = Geom::Point3d.new(cuadro[0]-xBase, cuadro[1]-yBase, 0)
        punto2 = Geom::Point3d.new(cuadro[0]+xBase, cuadro[1]-yBase, 0)
        punto3 = Geom::Point3d.new(cuadro[0]+xBase, cuadro[1]+yBase, 0)
        punto4 = Geom::Point3d.new(cuadro[0]-xBase, cuadro[1]+yBase, 0)
        grupoCuadro = @@entidades.add_group
        cara = grupoCuadro.entities.add_face(punto1, punto2, punto3, punto4)
        cara.reverse!.pushpull(@@altoSerpiente)
        @@serpienteActual.push(grupoCuadro)
      end
    end

    # Mover
    def mover(direccion)
      arregloTmp = [] # Contenedor temporal
      cabeza = @@serpienteArr.last # Cabeza de la serpiente

      # Verificar colisión con lista negra
      @@listaNegra.each do |pos|
        if cabeza == pos
          finJuego
          return
        end
      end

      # Verificar colisión con bordes
      if cabeza[0].abs >= @@anchoMapa/2 || cabeza[1].abs >= @@altoMapa/2
        finJuego
        return
      end

      # Verificar si come comida
      if cabeza == @@comida
        @@puntuacion += 10
        @@listaNegra.push(@@comida)
        @posX = rand(-(@@anchoMapa-50)..(@@anchoMapa-50)) / 2 / 10 * 10
        @posY = rand(-@@altoMapa..@@altoMapa) / 2 / 10 * 10
        @@serpienteArr.push([@posX+@@anchoSerpiente/2, @posY+@@altoSerpiente/2])
        Comida.new.mostrar
      end

      # Desplazar el array
      ultimoValor = @@serpienteArr.last.clone
      (0...@@serpienteArr.size-1).each do |i|
        arregloTmp[i] = @@serpienteArr[i+1]
      end
      arregloTmp.push(ultimoValor)

      case direccion
      when @arriba;   arregloTmp.last[1] += @@anchoSerpiente
      when @abajo;    arregloTmp.last[1] -= @@anchoSerpiente
      when @derecha;  arregloTmp.last[0] += @@anchoSerpiente
      when @izquierda; arregloTmp.last[0] -= @@anchoSerpiente
      end

      @@serpienteArr = arregloTmp

      eliminarSerpiente
      crearSerpiente
    end

    def eliminarSerpiente
      @@serpienteActual.each do |ent|
        ent.entities.each { |e| @@entidades.erase_entities(e) }
      end
    end

    def finJuego
      UI.stop_timer(@@idTemporizador)
      UI.messagebox("¡Juego terminado!\nPuntuación: " + @@puntuacion.to_s)
      actualizarPuntuacion
    end

    def actualizarPuntuacion
      hash = {
        apodo: config('apodo'),
        puntuacion: @@puntuacion,
        nivel: config('nivel'),
        token: 'qwertyuioplkjhgfdsazxcvbnm'
      }
      solicitud = Sketchup::Http::Request.new("http://00a00.com/snake/guardar.php", Sketchup::Http::POST)
      solicitud.body = construirQuery(hash)
      solicitud.start do |req, res|
        puts 'Ranking actualizado' if res
      end
    end

    def construirQuery(hash)
      cadena = ''
      hash.each { |k, v| cadena += k.to_s + '=' + v.to_s + '&' }
      cadena.chop!
    end

    def inicializarSerpiente(n)
      arreglo = []
      n.times do |i|
        arreglo.push([@@anchoSerpiente/2 + @@anchoSerpiente*i, @@anchoSerpiente/2])
      end
      arreglo
    end
  end
end

Clase de la comida

# Comida
module SerpienteIngenua
  class Comida < Base
    def initialize
      @posX = rand(-@@anchoMapa..@@anchoMapa) / 2 / 10 * 10
      @posY = rand(-@@altoMapa..@@altoMapa) / 2 / 10 * 10
    end

    def mostrar
      archivo = __dir__ + '/assets/comida.skm'
      material = @@materiales.load(archivo)

      puntos = []
      unless estaLlena?
        puntos[0] = [@posX, @posY, 0]
        puntos[1] = [@posX+@@anchoSerpiente, @posY, 0]
        puntos[2] = [@posX+@@anchoSerpiente, @posY+@@anchoSerpiente, 0]
        puntos[3] = [@posX, @posY+@@anchoSerpiente, 0]

        grupo = @@entidades.add_group
        @@comida = [@posX+@@anchoSerpiente/2, @posY+@@altoSerpiente/2]
        cara = grupo.entities.add_face(puntos)
        cara.material = material
        cara.reverse!.pushpull(@@altoSerpiente)
        @comida = cara
      else
        @posX = rand(-@@anchoMapa..@@anchoMapa)
        @posY = rand(-@@altoMapa..@@altoMapa)
      end
    end

    def estaLlena?
      @@serpienteArr.include?([@posX, @posY])
    end
  end
end

Etiquetas: Sketchup Ruby Game Development 3D modeling macro

Publicado el 6-8 00:24