Primitivas de dibujo: gráficos vectoriales (graphic)

A la hora de incluir representaciones visuales estáticas donde sea necesario un grado de libertad a la hora de crearlas o que no incremente el uso de recursos de la aplicación, es posible hacer uso de los gráficos vectoriales.

Figura 4-13. Aspecto y otras propiedades de un objeto tipo "graphic" (gráfico).

Veamos algunos ejemplos de subtipos de un gráfico, en concreto el primero y el cuarto siguiendo el orden de aparición en la barra de "Menu Bar":

Objetos animados con gráficos vectoriales

Se puede aprovechar un objeto de tipo gráfico para realizar sencillas animaciones por que sus valores se pueden generar en tiempo de ejecución, pero para el caso que nos ocupa, se ha realizado de forma estática sin pérdida de generalidad en la exposición.

El caso expuesto ilustra cómo se puede plantear el movimiento de un objeto que recorre el contorno de un texto en pantalla. Se puede ver una ilustración del mismo en la Figura 4-16. Reaprovechamos así lo aprendido en el primer tutorial de MetaCard

#button "Pren"
on mouseUp
  set the loc of graphic "xispes" to the first line of the points of  graphic "camiMetxa"
  show graphic "xispes"
  move the graphic "xispes" to the points of graphic "camiMetxa" in 6 seconds
  hide graphic "xispes"
end mouseUp
             

Figura 4-16. Ejemplo de gráfico vectorial (primera aproximación).

A partir de este caso sencillo se puede plantear la construcción de un efecto de "mecha" sobre el mismo texto. Se deja a la consideración del lector añadir los elementos decorativos (imágines y seguramente sonido) que permitan similar una mecha. El caso, visto de forma simplificada, de querer simular una mecha que se va consumiendo conforme se deplaza el objeto animado obliga a recurrir a un bucle como el que se ha utilizado en el segundo de los tutoriales sobre MetaCard. Puesto que ahora es necesario realizar acciones conforme se realiza el moviento principal.

#button "Pren2"
on mouseUp
  show graphic "metxa"
  set the points of graphic "metxa" to the the points of  graphic "camiMetxa"
  set the loc of graphic "xispes" to the first line of the points of  graphic "camiMetxa"
  show graphic "xispes"
  repeat with i = 1 to the number of lines in the points of  graphic "camiMetxa"
    move graphic "xispes" to line (i+1) of the points of  graphic "camiMetxa"
    set the points of graphic "metxa" to \
             line i to  (the number of lines of the points of  graphic "camiMetxa") of \
             the points of  graphic "camiMetxa"
  end repeat
  hide graphic "metxa"
  hide graphic "xispes"
end mouseUp
             

Para ilustrar lo que está pasando se ha dejado comentada una posible modificación relativa al tiempo que hay que emplear en cada moviento para que el lector pueda comprobar de modo experimental que es lo que está pasando en cada iteracción del bucle y que se muestra en la Figura 4-17.

Figura 4-17. Ejemplo de gráfico vectorial (segunda aproximación).

Aviso

Descriure este "rellontge"

Figura 4-18. Ejemplo de reloj de "varillas".

Aviso

I este atre "rellontge"

Figura 4-19. Ejemplo de gráfico vectorial (segunda aproximación).

La pila inicializa una propiedad importante en este caso, debido a la variabilidad que estamos introduciendo en el contenido de la ventana: alwaysBuffer. La tarjeta permite parar y reiniciar el proceso. Finalmente, el código que nos trae a este caso es el del objeto gráfico.

# En la pila
#
on openCard
  set the alwaysBuffer of this stack to true
end openCard
local idMensatge, numId


#
# La tarjeta
local varAux

on mouseUp quinBoto
  if quinBoto = 1
  then
    set the startAngle of graphic "segons" to 90
    set the arcAngle of graphic "segons" to 360
      else
    send "paraLo" to graphic "segons"
  end if
end mouseUp


#
# Gráfico "segons"
#
on mouseUp
  send "pasaElTemps" to me in 1 millisecond
end mouseUp

on pasaElTemps
  set the arcAngle of me to (the arcAngle of me - 1)
  if the arcAngle of me <> 0 then
    delete first line of idMensatge
    send "pasaElTemps" to me in 1 second 
#round(60/1000) milliseconds
    put the result & return after idMensatge
  end if
end pasaElTemps

on paraLo
  repeat with numId = the number of lines of idMensatge down to 1
    cancel line numId of idMensatge
  end repeat
end paraLo
                        

Vamos a desarrollar un comecocos, bueno sólo una de las piezas del juego : el protagonista. Es un ejercicio para introducir las posibilidades de emplear gráficos vectoriales frente a mapas de bits. Primero que nada deberíamos planificar nuestras acciones, nuestros objetivos y diseñar nuestros modelos para que la implementación no sea un mar de dudas. El objetivo final es algo parecido a la Figura 4-20. Aquí hay que considerar que hay resolver:

Figura 4-20. Apariencia de una pantalla típica del juego del Comecocos (del trabajo de Rafael Salguero y Jose V. Sebastiá).

Para realizar un juego de tablero o de casillas, lo cómodo es crear un tablero de forma dinámica sobre el que se disponen en cada una de sus casillas un elemento. Si este es de naturaleza estática, estará asignado de forma fija en todo momento. Si es móvil, hay que actualizar en cada instante la posible variación de los mismos.

La creación del tablero la abordaremos en la la sección de nombre Generación y uso dinámico de multiples objetos en Capítulo 6. Hecho esto, sólo restará asignar un valor a cada casilla de libre o ocupado. Esto resuelve que un objeto se pueda mover (en una de las cuatro direcciones posibles) o no.

Sólo puede haber un caso de "colisión" entre objetos móbiles, que resuelven las reglas del juego, para decidir quién pasa a ocupar la nueva posición y en cuyo caso se ha de determinar la modificación de la puntuación. Ahora cabe distinguir que entre los objetos dinámicos, los que pueden aparecer en algún lugar de la ruta recorrible, existiran comportamientos fijos y variables: los fijos son los que permiten recoger útiles (puntos, vidas, energía, ...) y los variables son los oponentes que tendrán un comportamiento más o menos inteligente, hudizo, atrevido, ...

Dejemos algo para los ejercicios y centrémonos en una cosa: las posibilidades de un gráfico cuyas propiedades permiten alterar su apariencia de forma dinámica. Hecho para uno establecermos las bases para los otros. El protagonista de nuestra aventura es el más complejo y vamos a encararlo aquí. Lo vamos a realizar en tres etapas utilizando sólo gráficos vectoriales:

  1. Para ello en primera intancia (Figura 4-21a) crearemos un objeto gráfico de tipo oval, lo rellenamos y le asignamos un color (Figura 4-21b).

    Figura 4-21. Desarrollando un objecto gráfico animado (parte 1).

    a)b)

  2. Después se puede incluir (Figura 4-21) la característica boca y ponerle un ojo. Ya sólo falta que la boca se abra y cierre de una manera rápida o lenta, pero a voluntad del programador y que se puedaprogramar .

    Figura 4-22. Desarrollando un objecto gráfico animado (parte 2).

    a)b)

  3. Casi está terminado, queda rellenar el ojo de color y agrupar los dos elementos bajo un mismo nombre. El código hará el resto.

    Figura 4-23. Desarrollando un objecto gráfico animado (final).

    a)b)

El código que impulsa a este elemento está compuesto, por una parte por el movimiento de la boca y por otro por el del conjunto. Preparemos un par de botones para poner el marcha ("Mou-lo") al "monstruito" y para pararlo ("Para'l"). Si llevamos todo el código relativo a la animación al objeto podremos después clonarlo para hacer un pequeño ejército y el conteo de las puntuaciones que las centralice otra parte del juego. Concentrémos en mover al bicho en horizontal y después ya le dejo para que piense cómo le da la vuelta. En tres pasos:

  1. Los dos botones que hemos creado enviaran un mensaje al grupo para iniciar el movimiento y para pararlo (bueno, de paso lo llevo a una posición conocida de antemano para que esté listo para la siguiente prueba), nada más. El primer paso es ver cómo hacemos para que se mueva la boca. Recordará que hemos creado un gráfico y le hecho una "boca" utilizando las propiedades de startAngle y arcAngle. Con estas vamos a jugar ahora.

    #
    # Botón "Mou-lo"
    #
    on mouseUp
      send "mouTe" to group "comecocos"
    end mouseUp
    
    #
    # Botón "Para'l"
    #
    on mouseUp
      send "paraT" to group "comecocos"
    end mouseUp
    
    #
    # Grupo "comecocos"
    #
    on mouTe
       send "mouTe" to graphic "cara" in 250 milliseconds
    end mouTe
    
    on paraT
       send "paraT" to graphic "cara"
       set the loc of me to 102,144
    end parat
                 
  2. Ahora es todo cuestión del gráfico "cara", si tiene la boca abierta que la cierre y si no que la abra. De paso, que se recuerde a sí mismo que tiene que seguir haciéndolo. Los valores numéricos están puestos a mi gusto.

    local idMensatge
    
    
    on paraT
      cancel idMensatge
    end parat
    
    on mouTe
      if the arcAngle of me = 358
      then  set the arcAngle of me to 317
      else  set the arcAngle of me to 358
      send mouTe to me in 250 milliseconds
      put the result into idMensatge
    end mouTe
                 
  3. Si ya funciona esto, sólo queda que avance. Es decir, que todo el grupo se deplace en un sentido y que siga así hasta que se le de la orden de parar. Esto implica que el grupo también ha de reenviarse un mensaje para acordarse de que está moviéndose. Incluiré también una prueba de que no "tropieza" para ilustrar el control de colisiones.

    local idMensatge
    
    on paraT
       cancel idMensatge
       send "paraT" to graphic "cara"
       set the loc of me to 102,144
    end parat
    
    on mouTe
       send "mouTe" to graphic "cara" in 250 milliseconds
       send "menejat" to me in 500 milliseconds
    end mouTe
    
    on menejat
       if not (the first item of the loc of me > the width of this card-(the width of me / 2))
        then
         move me rel 10,0
         send "menejat" to me in 500 milliseconds
         put the result into idMensatge
        end if
    end menejat
                 

Nota: Dependiendo de los tiempos en que se programen los eventos y la de veces que se pulsen los botones podemos tener problemas con este código. El problema radica en que sólo se almancena el último mensaje en uno y otro lado, así que sólo podemos cancelar el mismo número de eventos pendientes.

La solución, como ya habrá imaginado puede venir (aunque hay otras maneras) de guardar cada identificador en una línea de la variable que los guarda, consumiendo (borrando) el primero antes de reenviarse el mensaje. A la hora de parar el movimiento hay que "cancelar", uno por uno, todos los identificadores almacenados.

local idMensatge

 on paraT
  repeat with i = 1 to the number of lines of idMensatge
   cancel line i of idMensatge
  end repeat
 end parat

 on mouTe
  if the arcAngle of graphic "cara" = 358
    then  set the arcAngle of graphic "cara" to 317
    else  set the arcAngle of graphic "cara" to 358
   send mouTe to me in 250 milliseconds
   put the result & return after idMensatge
 end mouTe
             

En la implementación final del juego, como en casi todos los de "tablero", habría que diseñar un tablero con dos columnas y dos filas más de las visibles: los bordes del tablero. Estas, siempre puestas a un valor de "obstáculo", facilitarían la comprobación de que el movimiento que se va a intentar no lleva a un posición que no se puede ocupar; algo similar a lo que hemos implementado.