Haciendo un puzzle - Tercera parte: Uniendo las piezas

1

Index

Tagged

Attached Files

The following files have been attached to this tutorial:

.capx

tutorial-puzzle-2.capx

Download now 2.56 MB
.capx

tutorial-puzzle-2b-2.capx

Download now 2.56 MB

Stats

5,023 visits, 5,837 views

Tools

License

This tutorial is licensed under CC BY 4.0. Please refer to the license text if you wish to reuse, share or remix the content contained within this tutorial.

Published on 17 Nov, 2013. Last updated 25 Feb, 2019

#Detectando piezas encajadas

Probablemente el lector se esté preguntando: "Muy bien, pero lo que yo quiero es que las piezas encajen unas con otras". Esa es la parte complicada.

Lo primero de todo es añadir algunas variables de instancia (instance variables) al objeto Actualpiece:

"PlaceX": Columna lógica de la pieza dentro del puzzle. Valor inicial: 0.

"PieceY": Fila lógica de la pieza dentro del puzzle. Valor inicial: 0.

"Group": Grupo de piezas "encajadas" al que pertenece esta pieza. Valor inicial: 0.

Añadimos también las "behaviours" "Flash" y "Pin" al objeto "ActualPiece. "Flash" se usará para hacer pruebas, y "Pin" para agrupar piezas.

Lo primero, hay que rellenar cada objeto ActualPiece con los valores adecuados. Al final del evento "for tilev" (justo detrás de "Paste Object PieceMask into Canvas"), añadimos

First of all, we have to fill each ActualPiece with the right values. At the end of the "for tilev" event (just after "Paste Object PieceMask into canvas), incluimos las siguientes acciones:

    Set PlaceX to loopindex("tileh")
    Set PlaceY to loopindex("tilev")
    Set Group to self.PlaceY*(LogicalW)+self.PlaceX

Ahora hay que comparar las piezas cuando se "suelta" un objeto ActualPiece arrastrado. Esta es la parte más complicada.

Comencemos haciendo un evento Actualpiece.OnDrop.

Añadimos un sub-evento vacío debajo.

Entre el evento ActualPiece.Ondrop y el sub-evento vacío, añadimos cuatro variables locales:

    Local number CompareX=0 (Almacena Actualpiece.PositionX)
    Local number CompareY=0 (Almacena Actualpiece.PositionY)
    Local number IdealX=0 (Almacena la X ideal de una pieza vecina).
    Local number IdealY=0 (Almacena la Y ideal de una pieza vecina).

Dentro del sub-evento vacío, daremos a las variables estos valores:

    (Evento vacío)=> Set MyX to ActualPiece.X
    (Evento vacío)=> Set MyY to ActualPiece.Y
    (Evento vacío)=> Set CompareX to ActualPiece.PlaceX
    (Evento vacío)=> Set CompareY to ActualPiece.PlaceY

Debajo, añadimos otro sub-evento (así que estaremos trabajando en un sub-subevento). Ese subevento tendrá tres condiciones:

Primera condición: System.Pick all ActualPiece.

Segunda condición: System.For each actualpiece

Comprobando las piezas de arriba, abajo, izquierda y derecha en una sola condición

La tercera condición será una única condición buscando piezas encima, debajo, a la izquierda y a la derecha. Para crear la condición seguimos esta lógica:

- Las piezas de encima y debajo son piezas con la misma coordenada X que nuestra pieza, pero 1 más p 1 menos en la coordenada Y. Esto puede ser representado por:

    (ActualPiece.PlaceX=CompareX) & abs(CompareY- ActualPiece.PlaceY)=1)

Las piezas a izquierda y derecha son piezas con la misma coordenada Y, pero 1 más o 1 menos en la coordenada X. Se puede representar como:

    (ActualPiece.PlaceY=CompareY) & (abs(CompareX- ActualPiece.PlaceX)=1)

Si mezclamos ambas condiciones, tenemos:

    ((ActualPiece.PlaceX=CompareX) & (abs(CompareY- ActualPiece.PlaceY)=1))  |((ActualPiece.PlaceY=CompareY) & (abs(CompareX- ActualPiece.PlaceX)=1))

Mezclar todas las condiciones nos evitará tener que repetir las mismas o similares una y otra vez (evitando tener que repetir acciones o subeventos).

Ahora, añadimos la siguiente condición dentro del sub-evento:

System.Compare two values:

First value: ((ActualPiece.PlaceX=CompareX) & (abs(CompareY- ActualPiece.PlaceY)=1)) | ((ActualPiece.PlaceY=CompareY) & (abs(CompareX- ActualPiece.PlaceX)=1))

Comparison: Is different

Second value: 0

(Comparar dos valores, el primero es la condición que pusimos arriba, la comparación es el signo de distinto, y el segundo valor es cero).

Probemos nuestra condición. Añadimos la siguiente acción al evento de tres condiciones:

    ActualPiece.Flash

Ejecutemos el layout. Si la condición está bien formada, cuando se arrestre y suelte una pieza comenzarán a parpadear las que había alrededor del "hueco" que ha formado, diciéndonos que quiere comprobar dónde están estas piezas.

Y eso es lo que nos queda por hacer: comprobar dónde están.

Están las piezas en el lugar adecuado?

Ahora tenemos que probar si estas piezas están suficientemente cerca como para encajar con la que se acaba de soltar. Añadamos las siguientes acciones bajo ActualPiece.Flash:

Set IdealX to (ActualPiece.PlaceX-CompareX)*PieceStepX+MyX

Set IdealY to (ActualPiece.PlaceY-CompareY)*PieceStepY+MyY

Estas dos variables almacenarán dónde se espera que estén los vecinos de la pieza que se acaba de soltar.

Si el vecino está a la izquierda (ActualPiece.PlaceX-CompareX=-1), la pieza estará PieceStepX píxeles a la izquierda de MyX; por lo tanto, sus coordenadas serán (PieceStepX * -1) + MyX = -PieceStepX + MyX = MyX-PieceStepX.

En cambio, Si el vecino está a la derecha, la pieza estará PiecestepX píxeles a la derecha (ActualPiece.PlaceX-CompareX=+1, estaremos sumando en lugar de restar).

Si el vecino está encima o debajo, se realizarán cálculos similares para IdealY.

Añadimos otro sub evento (un sub-subevento) con la siguiente condición:

Distance(ActualPiece.X, ActualPiece.Y, Ideal.X, Ideal.Y) <5

Así, se comprueba si las piezas están, como mucho, a 5 píxeles de distancia. Puede cambiarse "5" por el margen de error que se desee. Cuanto mayor, más fácil será el juego.

En el cálculo anterior se usa distance() como una manera fácil de calcular distancias entre piezas. Personalmente creo que usar abs() y restas será algo más rápido (distance() calcula una raíz cuadrada) pero, después de todos los cálculos anteriores, creo que será un respiro para el lector esta fórmula más simple.

Finalmente, movemos la acción "Flash" al último subevento y ejecutamos el proyecto. Ahora, las piezas deberían parpadear solamente cuando "encajen" en el lugar correcto.

Encajando

El punto anterior sebería ser más difícil que lo que queda por hacer. Ya solo queda unir las piezas con la "behaviour" Pin. Pero, antes de usar "Pin", hay que mover un poco las piezas para llevarlas al lugar donde encajen mejor. ¿Cuál es el mejor lugar? Ya lo hemos calculado antes: (IdealX, IdealY).

Así, bajo la acción "Flash" añadimos:

    ActualPiece=>Set position (IdealX, IdealY)

Queda otro obstáculo. Imaginemos que añaimos el siguiente código al proyecto:ct:

    ActualPiece=>Pin to another object =>...

Enseguida aflorarán dos dificultades:

La primera, que no sabemos a qué objeto "Pinchar" (pin) el objeto ActualPiece. Es necesario almacenar una UID a la instancia de ActualPiece que acaba de ser soltada. ¡Así que "Pinchar" dos instancias del mismo objeto no es fácil!

La segunda, que incluso si se "pinchan" (pin) dos objetos ActualPiece juntos, el primero moverá el segundo, pero el segundo no moverá el primero (por lo menos, en las pruebas que hice tiempo atrás). Por eso son necesarios los "grupos".

Añadimos lo siguiente al código:

Entre el bloque de variables locales bajo "Actualpiece.On dragdrop drop", añadimos una variable local llamada "MyGroup", con valor 0.

En el evento vacío que hay tras las variables locales, bajo "Set compareY to ActualPiece.PlaceY", añadimos:

    System=>Set  Value MyGroup to ActualPiece.Group

Después, en el subevento de abajo, tras la acción "ActualPiece.SetPosition", añadimos esta otra:

    ActualPiece=>Set Group to ActualPiece.Group.

Ahora vayamos a la vista de Layout e insertemos un sprita vacío (sin imagen) llamado Pin. En el ejemplo, hemos hecho que fuera visible y de color azul para poder verlo, pero en un entorno de producción debería ser invisible para el usuario.

Añadimos el comportamiento ("behaviour") "Pin" al sprite "Pin".

Volvamos después al evento "ActualPiece.On Drag" y añadamos:

    Pin.Destroy
    System.Create Object Pin at (ActualPiece.X,ActualPiece.Y)
    Pin Pin to ActualPiece (Position and angle)

Destruimos el objeto Pin antes de pincharlo a la pieza porque, de esa manera, si ya tenemos algo "pinchado" en él, lo "soltaremos" (es más rápido destruirlo y volverlo a crear que averiguar qué hay "pinchado" en él).

Debajo, añadimos un sub-evento vacío. Encima del sub-evento vacío, añadimos las siguientes variables locales:

MyUID (valor:0). Aquí guardaremos la UID del objeto ActualPiece actual.

MyGroup (valor:0). Aquí guardaremos el grupo del objeto ActualPiece actual.

En el subevento vacío, añadimos:

    Set MyUID to ActualPiece.UID
    Set MyGroup to ActualPiece.Group

Añadimos un sub-subevento al subevento vacío, con estas condiciones:

    System.Pick all Actualpiece
    ActualPiece. Compare instance Variable Group with MyGroup
    System. Pick ActualPiece by evaluating ActualPiece.UID<>MyUID.

Pongamos en él las siguientes acciones:

    ActualPiece=>Pin to Pin

Si ejecutamos ahora el proyecto, veremos que se puede arrastrar una pieza y pegarla a otra. Pero hay conductas indeseadas, como que en el grupo arrastrado sólo se "pegará" la pieza que estábamos arrastrando. Para arreglarlo, debemos emplear un bucle para comprobar todas las piezas del grupo "arrastrado" contra las piezas de fuera del grupo.

Intentaremos hacerlo en un tutorial posterior.

Siguiente en la serie

Haciendo un puzzle- Cuarta parte: Manejando grupos de piezas explica cómo comprobar el grupo entero en busca de piezas encajadas y cómo unir grupos entre sí. Asemás, incorpora pequeños detalles como la adición de sonido, el "barajado" de piezas o el salto a una rutina especial cuando se completa el puzzle.

.CAPX

tutorial-puzzle-2.capx

Download now 2.56 MB
.CAPX

tutorial-puzzle-2b-2.capx

Download now 2.56 MB
  • 0 Comments

Want to leave a comment? Login or Register an account!