Hacer un juego de Match-3 en Construct 2: Movimiento de Bloques
Spanish (Español) translation by Elías Nicolás (you can also view the original English article)
En la parte anterior de esta serie, hicimos algunos pequeños pero importantes cambios en muchos de los sistemas que creamos para nuestro juego de Match-3. Con estas mejoras implementadas, ahora vamos a volver a ponernos en marcha e implementar uno de los dos últimos sistemas principales para el juego: el sistema de Movimiento de Bloques.
Este tutorial le llevará a través de todo el desarrollo del sistema que permite que los bloques se eleven a la parte superior de la pantalla, y también cubrirá la creación de todos los sistemas más pequeños que necesitaremos implementar para soportar el sistema de movimiento. Aunque los temas que abarco en este tutorial no son demasiado complejos, hay mucho por recorrer, así que vamos a llegar a él.
Demostración final del juego
Aquí está una demostración del juego que estamos trabajando hacia a través de esta serie:
1. Movimiento de bloques hacia arriba
Antes de empezar a mover los bloques, tenemos que hacer un pequeño cambio a los eventos que generan los bloques. Vaya al evento System> On Start of Layout y cambie el bucle Y for
para ir de 0
a 3
, en lugar de 0
a 7
como lo hizo originalmente.
El evento debería tener el siguiente aspecto:



La razón por la que hicimos este cambio es porque queremos que el juego comience con menos bloques en la pantalla, de modo que no termine tan rápido cuando añadamos un Game Over en el próximo tutorial.
A continuación, crearemos una variable que representará la velocidad de los bloques:
Global Variable: Name = CurrentSpeed Type = Number Value = 0.2
Ahora crearemos el evento que realmente mueve los bloques:
Event: Condition: System>Every X Seconds Interval (seconds) = CurrentSpeed Action: Block>Move at Angle Angle = -90 Distance = 1
El evento debería tener este aspecto:



Si ejecuta el juego después de agregar este evento, lo primero que debe ver es que los bloques se caigan, debido a la gravedad que implementamos en un tutorial anterior. Después de eso, los bloques deben subir lentamente hasta que estén en su posición original, y luego caer de nuevo. Esto se repetirá infinitamente siempre y cuando no haga nada a los bloques.
Esto sucede porque los bloques se están moviendo más allá del punto donde la gravedad se supone para golpear con el pie, y están descubriendo que no hay bloques debajo de ellos, haciendo que todos caen. Si bien esto es un problema, no es el primero que quiero mirar.
2. Arreglando el Intercambio
Ejecutar el juego, e intentar hacer un intercambio de cualquier tipo. Cuando hagas esto, deberías ver que los bloques empiezan a atascarse uno detrás de otro, quedando atascados en posiciones que no están alineadas con la cuadrícula, y generalmente se comportan mal. Hay dos razones para este problema.
La
primera cuestión es que, a pesar de que estamos moviendo los bloques en
sí, no estamos moviendo los objetos LeftBlock
, RightBlock
, TopBlock
y
BottomBlock
con ellos, lo que significa que los bloques que usas para
detectar swaps no se mueven con la rejilla del bloque Son sólo sentarse en la posición que se establecen en la primera vez que recoger un bloque.
Por
lo tanto, cuando intenta hacer un intercambio, los bloques se están
poniendo fuera de lugar porque los bloques de detección de intercambio
no se han ajustado en absoluto a la cuadrícula. (Ésta
es también la razón de la segunda edición que estamos teniendo, que es
que no estamos modificando las posiciones que almacenamos en la matriz
de BlockPositions
tampoco.)
El GIF a continuación demuestra este problema:

Como se puede ver en el GIF, los bloques de detección de intercambio no se mueven, aunque los bloques sí lo son.
Para solucionar ambos problemas, agregaremos algunas acciones más al evento que acabamos de crear:
Action: BottomBlock>Move at Angle Angle = -90 Distance = 1 Action: LeftBlock>Move at Angle Angle = -90 Distance = 1 Action: RightBlock>Move at Angle Angle = -90 Distance = 1 Action: TopBlock>Move at Angle Angle = -90 Distance = 1 Action: BlockPositions>Set at XY X = 0 Y = 1 Value = BlockPositions.At(0,1) - 1 Action: BlockPositions>Set at XY X = 1 Y = 1 Value = BlockPositions.At(1,1) - 1
El Evento ahora debe verse así:



Las
primeras cuatro acciones que acabamos de agregar ajustan las posiciones
de los objetos LeftBlock
, TopBlock
, RightBlock
y BottomBlock
para que
permanezcan en línea con la cuadrícula de bloques. Los
segundos dos eventos ajustan los valores de Y que hemos almacenado en
la matriz BlockPositions
para que también permanezcan en línea con la
cuadrícula de bloques.
Si vuelves a probar el juego en este punto, el intercambio debería ser en su mayoría arreglado.
En este punto, todavía hay un otro tema que necesitamos tratar de hacer el intercambio de trabajo correctamente. Ejecutar el juego e intentar hacer un intercambio hacia abajo con cualquiera de los bloques en la fila inferior, mientras que la fila está parcialmente por debajo de la zona inferior del campo de juego:



Si lo hizo correctamente, debería ver que no sucedió nada y los bloques no se intercambiaron. Si esperó demasiado tiempo, los bloques pueden haber cambiado porque se habían movido por encima de la frontera del campo de juego de nuevo, por lo que si esto sucedió, vuelva a intentarlo una vez que se caigan y debería ver este problema ocurrir.
Esta cuestión es bastante simple de resolver y de entender. Si
observa el código de los intercambios descendentes, debe encontrar el evento
que agregamos en el tutorial anterior, lo que evita que el jugador
realice intercambios descendentes que causen que el bloque caiga en la parte
inferior del campo de juego. Dado
que esta instrucción impide que el jugador realice intercambios descendentes
cuando el objeto BottomBlock
es inferior a la posición Y inicial del
bloque, impide que los bloques se intercambien una vez que hayan caído y
sólo le permita hacer intercambios de nuevo una vez que hayan pasado de su
posición original de nuevo.
Para corregir esta declaracion vamos a hacer un pequeño cambio a la condición:
Condition: BottomBlock>Compare Y Comparison = Less or equal Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)
La condición ahora debería verse así:



Esta
modificación significa que un intercambio hacia abajo sólo puede ocurrir
mientras el objeto BottomBlock
es como máximo un medio bloque por debajo
de la posición Y en la que los bloques comienzan. Esto también
significa que, una vez que comencemos a generar nuevas filas de bloques y
empujándolos a la pantalla desde La parte
inferior, estos bloques sólo podrán ser intercambiados de esta manera
una vez que al menos la mitad del bloque es visible.
También vamos a poner una restricción similar en todos nuestros eventos de intercambio para asegurarnos de que todos ellos se conviertan en utilizables al mismo tiempo y que un bloque no pueda ser intercambiado en absoluto hasta que al menos la mitad sea visible. De nuevo, esto también ayudará cuando integremos el sistema que genera nuevas filas de bloques. Para ello, agregaremos una nueva condición a cada uno de los otros tres eventos de intercambio.
Las
condiciones que agregamos serán exactamente iguales a las que acabamos
de modificar en el evento BottomBlock
, excepto que harán referencia a
los objetos TopBlock
, RightBlock
y LeftBlock
en lugar del objeto
BottomBlock
, dependiendo del evento en el que se encuentre.
La nueva condición para el evento TopBlock
debe ser:
Condition: TopBlock>Compare Y Comparison = Less or equal Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)
La nueva condición para el evento LeftBlock
debe ser:
Condition: LeftBlock>Compare Y Comparison = Less or equal Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)
La nueva condición para el evento RightBlock
debería ser:
Condition: RightBlock>Compare Y Comparison = Less or equal Y co-ordinate = SPAWNY + ((Block.Width + 2)/2)
Todo el evento de On DragDrop drop debería verse así:



Con estas nuevas condiciones, hemos fijado nuestra mecánica de intercambio y hemos comenzado a preparar los sistemas existentes para el siguiente sistema que estamos agregando: el que generará nuevas filas de bloques.
3. Generar más bloques
Ahora
que tenemos los bloques moviéndonos a una velocidad constante,
necesitamos hacer que las nuevas filas de bloques aparezcan en el
momento correcto, y permitir que el jugador continúe jugando por el
tiempo que quieran. Vamos a utilizar una función
para generar las nuevas filas de bloques, y vamos a utilizar un evento
que detecta cuando los bloques están en línea con SPAWNY
para activar
esa función.
Así que primero, hagamos la función misma.
Event: Condition: Function>On function Name = "SpawnNewBlocks" Condition: System>For Name = "X" Start Index = 0 End Index = 7 Action: System>Create object Object = Block Layer = 1 X = SPAWNX + (loopIndex("X"))*(Block.Width+2) Y = SPAWNY + (Block.Width+2) Action: Block>Set value Instance Variable = Color Value = floor(Random(1,7)) Action: System>Add to Variable = NumBlocks Value = 1
Tu nuevo Evento debería tener este aspecto:



Cuando se utiliza, esta función creará una fila de bloques debajo de la fila inferior de bloques en el campo de juego. En este momento, sin embargo, en realidad no utilizamos esta función en ningún momento, así que hagamos el Evento que hace eso:
Event: Condition: System>Every X seconds Interval(seconds) = CurrentSpeed Condition: Block>Compare Y Comparison = Equal to Y = SPAWNY Condition: Invert: Block>Is Dragging Action: Function>Call function Name = "SpawnNewBlocks"
Tu nuevo Evento debería tener este aspecto:



El Evento que acabamos de crear comprueba la posición Y de los bloques cada vez que se mueven. Si encuentra los bloques que están en línea con SPAWNY
, activa la función SpawnNewBlocks()
como discutimos anteriormente. También comprueba para asegurarse de que el bloque que encuentra no es el que es arrastrado por el jugador.
Si prueba su juego en este punto, funcionará, pero debería notar un problema extraño. En el momento en que comiences el juego, tus bloques caerán como si no hubiera bloques debajo de ellos, pero después de ese punto todo funciona perfectamente, y nuevos bloques se generan cuando son necesarios.
Esto sucede porque, cuando el juego comienza por primera vez, procesa el código de gravedad antes del código que genera nuevas filas de bloques. Para corregir esto vamos a hacer un pequeño ajuste al código que genera el grupo inicial de bloques para que se generen por debajo del punto donde una nueva fila sería necesario. Esto le permite evitar ejecutar el código de gravedad inmediatamente, y le permite crear la nueva fila de bloques una vez que los bloques existentes están en la ubicación correcta.
Vaya al Evento que genera el grupo inicial de bloques y modifique la Acción que realmente crea el bloque. Cambie la Acción a esto:
Action: System>Create object Object = Block Layer = 1 X = SPAWNX + (loopIndex("X"))*(Block.Width+2) Y = SPAWNY - (loopIndex("Y"))*(Block.Width+2) + 5
El Evento ahora debe verse así:



Esta modificación significa que los bloques generarán cinco píxeles por debajo de SPAWNY
. Esto
significa que los bloques realmente tendrán que subir cinco veces antes
de que aparezca una nueva fila, y resuelve nuestro problema.
4. Un poco de animación
En este punto nuestros bloques se están moviendo, y tenemos nuevas filas que se están creando. Además de eso, recuerde que antes impedimos al jugador usar cualquier bloque hasta que al menos la mitad del bloque sea visible. Si bien esto es una buena característica, el jugador puede no entender por qué un bloque no puede ser utilizado inmediatamente cuando se hace visible, aunque no mucho de lo que es visible en ese momento.
Debido a este potencial problema de interfaz de usuario, vamos a hacer que cada bloque utilice el sprite de bloque gris (al principio de los cuadros de animación del bloque) cuando se encuentre en este estado inutilizable. Esto le dejará claro al jugador cuando un bloque se convierte en utilizable, y nos dará la oportunidad de finalmente usar nuestra última imagen de bloque.
Puede ver un ejemplo de cómo se verá cuando los Bloques pasen de estar inactivos a activos en el GIF a continuación:

El Evento que creamos también incluirá una segunda Condición que comprueba que el bloque que está viendo no está siendo arrastrado. Esta condición nos permite asegurar que cuando el jugador arrastra un bloque por debajo del punto donde los bloques se vuelven utilizables, no cambiará su imagen para que quede gris, y permanecerá el color que se supone que es.
Para que esta animación funcione, primero tenemos que añadir un nuevo Evento:
Event: Condition: Block>Compare Y Comparison = Greater than Y = SPAWNY + ((Block.Width + 2)/2) Condition: Invert: Block>Is Dragging Action: Block>Set frame Frame number = 0
El nuevo evento debería tener este aspecto:



Ahora deberías ser capaz de probar tu juego y deberías ver que los bloques están usando la imagen gris cuando están por debajo del punto en que se vuelven utilizables.
5. Activar y desactivar arrastrar / soltar
Si ejecuta el juego ahora, notará que aunque los bloques no se pueden intercambiar entre sí cuando están en gris, los bloques grises pueden arrastrarse y manipularse. Esto se debe a que nunca desactivamos las capacidades de arrastrar / soltar del bloque cuando impedimos al jugador intercambiar con ellos.
Para evitar que los bloques grises se puedan mover, modificaremos el Evento que creamos en la sección anterior. Primero agregaremos una nueva acción que apaga el arrastrar cuando el bloque está por debajo del punto en que se convierte en utilizable.
Agregue esta Acción al Evento que creamos anteriormente:
Action: Block (DragDrop)>Set enabled State = Disabled
También vamos a añadir una declaración Else para este Evento que permite que el bloque sea arrastrado de nuevo una vez que está por encima del punto en que el bloque se convierte en utilizable:
Event: Condition: Else Action: Block (DragDrop)>Set enabled State = Enabled
Con estos dos cambios, el Evento debería tener este aspecto:



Si prueba el juego en este punto, los bloques ya no se pueden utilizar cuando son grises, y deben funcionar de la misma manera que siempre tienen cuando no lo son.
6. Cambios de velocidad
La última cosa que quiero cubrir en este artículo es el sistema que nos permitirá cambiar la velocidad del juego con el tiempo. Específicamente, este es el sistema que hará que los bloques se muevan más rápido, ya que el jugador elimina más de ellos.
El sistema que vamos a crear es relativamente simple: cada vez que el jugador obtiene un cierto número de puntos, la velocidad del juego aumentará basado en un modificador que vamos a crear, y el número de puntos que el jugador necesita para obtener El siguiente aumento de velocidad cambiará en función de un segundo modificador.
Antes de que podamos empezar a realizar los eventos para este sistema, crearemos un par de variables globales para manejar las nuevas funciones para nosotros:
Global Variable: SPEEDMOD Type = Number Initial value = 0.8 Constant = Yes Global Variable: PointsForSpeedUp Type = Number Initial value = 400 Constant = No Global Variable: PointsBetweenSpeedUps Type = Number Initial value = 400 Constant = No Global Variable: POINTSFORSPEEDUPMOD Type = Number Initial value = 1.4 Constant = Yes
Sus nuevas variables deberían tener este aspecto:



Ahora que tenemos las variables en su lugar, voy a explicar lo que cada uno hace.
-
SPEEDMOD
es la variable que multiplicaremos la velocidad para modificarla cada vez que el jugador alcance el número de puntos que necesitan para provocar un aumento de velocidad. -
PointsForSpeedUp
es el número de puntos que el jugador necesita para alcanzar la siguiente velocidad. -
PointsBetweenSpeedUps
representa cuánto aumentará la variablePointsForSpeedUp
cuando el jugador obtiene una aceleración, para ajustarla de modo que la próxima velocidad aumente aún más puntos. En este momento es 400, comoPointsForSpeedUp
, pero cuando el jugador realmente obtiene una aceleración se multiplicará porPOINTSFORSPEEDUPMOD
antes de que se agregue aPointsForSpeedUp
. - Por
último,
POINTSFORSPEEDUPMOD
es la variable que vamos a utilizar para modificar el número de puntos que el jugador necesita para llegar a aumentar su velocidad otra vez más allá de la que más recientemente tuvo.
Junto con la configuración de las variables, también tenemos que crear un nuevo objeto sprite que actuará como la alerta para el jugador cuando la velocidad aumenta.
Vaya a Layout 1 y siga estos pasos para crear el nuevo sprite:
- Inserte un nuevo objeto Sprite en el Layout 1.
- Con el Editor de animación, abra la imagen
SpeedIncImage.png
.- Establezca el nombre en
SpeedIncreaseIndicator
. - Establezca Layer a
Game Field
. - Establezca la Position en
188, 329
. - Establezca Initial visibility a
Invisible
.- Agregue un comportamiento Fade al Sprite.
- Establecer Active at Start a
No
. - Ajuste el Fade out time a
2.5
. - Establecer Destroy a
No
.
- Establezca el nombre en
Su diseño debería verse así:



Ahora vamos a crear el evento que cambia la velocidad:
Event: Condition: Function>On function Name = "CheckForSpeedUp" Condition: System>Compare variable Variable = Score Comparison = Greater or equal Value = PointsForSpeedUp Action: SpeedIncreaseIndicator>Set visible Visibility = Visible Action: SpeedIncreaseIndicator>Start fade Action System>Set value Variable = CurrentSpeed Value = CurrentSpeed * SPEEDMOD Action System>Set value Variable = PointsBetweenSpeedUp Value = PointsBetweenSpeedUp * POINTSFORSPEEDUPMOD Action: System>Add to Variable = PointsForSpeedUp Value = PointsBetweenSpeedUp
Su evento debe tener este aspecto:



Cuando se llama a esta función, comprueba si el jugador ha anotado suficientes puntos para garantizar un aumento de velocidad. Si tienen, entonces:
- Activa el sprite que le dice al jugador que la velocidad ha aumentado haciéndolo visible y comenzando el Fade
- Aumenta la velocidad multiplicándola por el modificador
- Determina el número de puntos necesarios antes de la siguiente aceleración, y
- Agrega ese valor al número total de puntos que el jugador necesitará tener antes de que la velocidad aumente de nuevo.
Con esta función completa, sólo tenemos que asegurarnos de que se llama. Vaya a la función GivePoints()
y agregue esta Acción al final del evento principal y el sub-evento:
Action: Function>Call function Name = "CheckForSpeedUp"
La función GivePoints()
debería tener el siguiente aspecto:



Con ese evento completo, debería ser capaz de probar su juego y ver la aceleración del sistema en acción.
Sugerencia: Mientras jugaba con él más, descubrí que estos valores se sentían un poco apagados, así que le sugiero que tome algún tiempo para experimentar con el sistema, y encontrar los valores que se sienten más cómodos con.
Conclusion
Hemos cubierto muchos temas diferentes en este artículo, pero todo lo que tratamos estaba relacionado directa o indirectamente con conseguir que el sistema de movimiento funcionara de la manera que queríamos. Mientras que tomó un cierto tiempo, y nos requirió hacer más sistemas que podríamos haber anticipado al principio, la recompensa valió la pena y terminamos para arriba con un sistema muy fuerte en el extremo.
Debido a lo mucho que ya hemos cubierto, creo que este es un buen lugar para terminar este artículo. El próximo artículo debe ser el tutorial final de esta serie y vamos a cubrir un montón de temas más pequeños dentro de él, pero lo más importante que estamos cubriendo es definitivamente la eliminación de los partidos pre-hechos.
Si desea empezar a tratar de averiguar cómo vamos a eliminarlos, eche un vistazo a cómo detectar los partidos para empezar. El sistema que creamos será muy similar a ese sistema, excepto que utilizará las coincidencias que encuentre de una manera diferente. Comienza a pensar en ello y verás lo que puedes imaginar, y te veré aquí la próxima vez para el último gran tutorial de la serie.