Bienvenidos sean a este post, hasta el post anterior hicimos un juego con enemigos y plataformas donde tambien recibimos daño pero siempre estatico, hoy veremos como dar el factor al azar.
Para comenzar con esto vamos a eliminar todo lo que creamos en nuestro room salvo una plataforma que debemos dejarla en el centro del eje X y lo mas abajo que se pueda en nuestro room, una vez hecho quedara de la siguiente manera

Nuestro siguiente paso sera modificar los sprites de la plataforma y del enemigo, lo que modificaremos es como sera su origen, si van a la ventana principal de los sprites en el rincon superior derecho tendran esta opcion

Tanto en el sprite de la plataforma como del enemigo debemos cambiar el valor de Top Left por Middle Centre, esto nos cambiara el valor de Origin para que utilice los valores medio de los sprites, en el caso del enemigo lo dejaran como se asigne pero en el caso de la plataforma cambien el valor de Y del valor que tenga a 0, esto lo haremos para que siga poniendo todo por arriba de ella, debera quedar de la siguiente manera

Recuerden que enemigo debe quedar como se asigne mediante el cambio del menu, con esto ya tenemos las modificaciones de nuestros sprites que nos serviran mas adelante, si verifican ahora la plataforma se nos habra cambiado de posicion quedando como se ve a continuacion

Como pueden ver ahora se nos ubico de otra forma, borren esta plataforma y una vez hecho vamos a crear un nuevo room, otra vez vamos a Asset Browser, buscamos la carpeta Rooms, le hacemos click con el boton derecho y seleccionamos Create -> Room, haremos un procedimiento similar al proyecto anterior donde crearemos un room para iniciar algunos valores de manera global, a este nuevo room lo llamaremos rm_setup y a nuestro room anterior lo renombraremos a rm_main, quedando de la siguiente manera

Nota: Si no recuerdan como dejarlo como Home al nuevo room, les recomiendo visitar este post donde explico como hacerlo.
Una vez creado y establecido como home a nuestro nuevo room agregaremos el siguiente codigo a traves del boton Creation code, pero antes deben modificar el tamaño del room a 512×768, tal como hicimos con el otro room y una vez hecha esta modificacion continuamos con el boton Creation Code, con lo cual nos hara aparecer el cuadro para agregar el codigo, dentro de este agregaremos las siguientes lineas:
randomize();
room_goto(rm_main);
El primero nos permitira generar elementos al azar y el segundo hara que una vez ejecutada todas las lineas pasemos a nuestro room principal o del juego en si, antes de continuar vamos a hablar un poco sobre lo que usaremos como son los arrays, si bien lo mencionamos en este post hoy veremos otra forma de trabajar con ellos.
Cuando hablamos anteriormente sobre este tema vimos lo que se llama un array unidimensional donde le indicabamos un solo indice para saber en que posicion guardabamos dicha informacion y su sintaxis es:
varArray[posicion] = informacion Ejemplo: varArray[0] = "El Tinchicus";
Pero hoy hablaremos sobre los bidimensionales donde podremos almacenar informacion en una especie de coordenadas, su sintaxis es:
varArray[posicion1,posicion2] = informacion Ejemplo: varArray[0,0] = "El Tinchicus"; varArray[0,1] = "Guerrero";
En general si tuvieramos que transformar esto en una tabla la primera posicion representa a la linea de la tabla y la segunda posicion a la columna de esta, lo ideal sera solamente agregar mas informacion pero variando la segunda posicion y cuando cambiemos de elemento pasamos a la siguiente linea cambiando la primer posicion y asi sucesivamente, parece complicado (y lo es) pero una vez que le toman la mano es muy practico, y si bien los arrays pueden tener N dimensiones que es lo mismo que decir infinitas o todas las que necesitemos, en la vida real rara vez pasaran mas alla de la bidimensional y en el 99% seran unidimensionales pero para nuestro caso de hoy necesitaremos de la primera pero les repito que es muy raro que se usen mas alla de las bidimensionales o 2D, para nuestro caso vamos a crear una variable global llamada patron_inicial la cual sera un array bidimensional, para ello debemos agregar el siguiente bloque entre la linea randomize y la que nos lleva al otro room:
randomize();
global.patron_inicial[0,0] = obj_Plataforma;
global.patron_inicial[0,1] = 64;
global.patron_inicial[0,2] = 700;
global.patron_inicial[1,0] = obj_Plataforma;
global.patron_inicial[1,1] = 384;
global.patron_inicial[1,2] = 680;
global.patron_inicial[2,0] = obj_Plataforma;
global.patron_inicial[2,1] = 324;
global.patron_inicial[2,2] = 110;
global.patron_inicial[3,0] = obj_Plataforma;
global.patron_inicial[3,1] = 248;
global.patron_inicial[3,2] = 630;
global.patron_inicial[4,0] = obj_Plataforma;
global.patron_inicial[4,1] = 464;
global.patron_inicial[4,2] = 590;
global.patron_inicial[5,0] = obj_Plataforma;
global.patron_inicial[5,1] = 104;
global.patron_inicial[5,2] = 540;
global.patron_inicial[6,0] = obj_Plataforma;
global.patron_inicial[6,1] = 424;
global.patron_inicial[6,2] = 490;
global.patron_inicial[7,0] = obj_Plataforma;
global.patron_inicial[7,1] = 240;
global.patron_inicial[7,2] = 430;
global.patron_inicial[8,0] = obj_Plataforma;
global.patron_inicial[8,1] = 146;
global.patron_inicial[8,2] = 410;
global.patron_inicial[9,0] = obj_Plataforma;
global.patron_inicial[9,1] = 240;
global.patron_inicial[9,2] = 290;
global.patron_inicial[10,0] = obj_Plataforma;
global.patron_inicial[10,1] = 124;
global.patron_inicial[10,2] = 200;
room_goto(rm_main);
En esta ocasion creamos un array 2D de tipo global, esto es gracias a la palabra global que antecede a nuestro array llamado patron_inicial, y como su nombre lo indica sera la ubicacion predeterminada de nuestras plataformas cuando inicia el juego, si observan bien notaran que son tres lineas que se repiten durante todo el bloque, analicemos las primeras tres:
global.patron_inicial[0,0] = obj_Plataforma;
global.patron_inicial[0,1] = 64;
global.patron_inicial[0,2] = 700;
Observen como la primera posicion sera la encargada de representar al objeto y la segunda posicion almacenara los datos de dicho objeto, veamos cada uno:
- La posicion 0 sera el objeto que mostraremos
- La posicion 1 sera para el eje X de dicho objeto
- La posicion 2 sera para el eje Y del objeto informado
Como pueden ver es bastante simple, una vez completo los tres parametros incrementamos la primera posicion para pasar al siguiente objeto y asi sucesivamente lo haremos hasta crear 11 plataformas, pero como se daran cuenta solamente pasamos los parametros pero todavia no las creamos en el room que corresponde, el siguiente paso es saltar a rm_main y aca viene lo divertido.
Nota: Los datos que pase son subjetivos y no son estrictos, pueden modificarlos a como quieran o inclusive agregar mas elementos pero respeten los valores y el orden de cada posicion, tambien aclaro que no es lo mas recomendable pero si es mas practico porque de otra forma haria demasiado complejo a este proyecto.
Si aun tienen curiosidad de como es un array bidimensional les muestro una tabla que equivale a esto:
Pos1 \ Pos 2 | 0 | 1 | 2 |
0 | obj_Plataforma | 64 | 725 |
1 | obj_Plataforma | 224 | 675 |
2 | obj_Plataforma | 354 | 700 |
Con esto realizado nuestro siguiente paso sera ir al room que sera del juego, es decir rm_main, donde pulsaremos Creation Code y agregaremos el siguiente codigo:
for(i = 0; i < array_height_2d(global.patron_inicial); i++)
{
instance_create_depth(
global.patron_inicial[i,1],
global.patron_inicial[i,2],
0,
global.patron_inicial[i,0]);
}
Este es un ciclo for donde por medio de array_height_2d obtenemos el total de elementos de la variable global que construimos antes, global.patron_inicial, en realidad no pasa por todos los elementos sino mas bien cuenta el total de filas del array bidimensional, dentro del bucle usaremos a la funcion instance_create_depth para crear las instancias de los objetos del array, observen como pasamos los parametros que necesita en base a la segunda posicion, salvo la profundidad (depth) pero el resto lo sacaremos del array que creamos anteriormente, si lo compilamos y probamos quedara de la siguiente manera

Esto nos va a generar un patron de inicio de nuestras plataformas, nuestra siguiente modificacion sera en el objeto de la plataforma, obj_Plataforma, a la cual le agregaremos un evento de Step -> Step, una vez creado agregaremos el siguiente codigo:
if (y > room_height)
instance_destroy();
if (obj_Pepe.vspeed < 0)
{
vspeed = -1 * obj_Pepe.vspeed;
}
else
{
vspeed = 0;
}
El primer condicional hara desaparecer a cada una de las instancias que se crean desde este objeto cuando el valor de y sea mayor al alto del room, room_height, si se preguntan como se va a lograr esta condicion no se preocupen porque para eso esta nuestro segundo condicional donde verificamos si la velocidad vertical, vspeed, del objeto obj_Pepe sea menor a 0, hara que la velocidad vertical de nuestro objeto sea inverso a la velocidad del objeto de nuestro personaje, esto hara el efecto de que nuestro personaje esta subiendo de lo contrario la velocidad vertical de nuestro objeto sera igual a 0, con esto tenemos incorporado un mecanismo para mover a nuestras plataformas pero como generamos las nuevas, ahi vamos a hacer un procedimiento similar al patron inicial pero mas reducido, volvemos a nuestro room de configuracion mas conocido como rm_setup y agregaremos las siguientes lineas despues del bloque de nuestra primer array pero antes de la funcion que nos envia al otro room:
global.patron_uno[0,0] = obj_Plataforma;
global.patron_uno[0,1] = 128;
global.patron_uno[1,0] = obj_Plataforma;
global.patron_uno[1,1] = 268;
global.patron_uno[2,0] = obj_Plataforma;
global.patron_uno[2,1] = 428;
global.patron_dos[0,0] = obj_Plataforma;
global.patron_dos[0,1] = 64;
global.patron_dos[1,0] = obj_Plataforma;
global.patron_dos[1,1] = 194;
global.patron_dos[2,0] = obj_Plataforma;
global.patron_dos[2,1] = 354;
global.patron_tres[0,0] = obj_Plataforma;
global.patron_tres[0,1] = 96;
global.patron_tres[1,0] = obj_Plataforma;
global.patron_tres[1,1] = 256;
global.patron_tres[2,0] = obj_Plataforma;
global.patron_tres[2,1] = 436;
Esto es muy similar a lo que vimos anteriormente pero en lugar de tres parametros usaremos dos, el primero para el objeto y el segundo para el eje X y en este caso no usaremos al eje Y, si observan creamos tres patrones con distintas ubicaciones para tres objetos, pronto entenderan como lo usaremos, nuestro siguiente paso sera crear un script para poder generar nuestras nuevas plataformas, como siempre debemos ir al Asset Browser, buscan la carpeta Scripts, le hacen click con el boton derecho y seleccionen Create -> Script, a este script lo llamaremos scr_GeneraPlat, una vez creado nuestro script agregaremos el siguiente codigo dentro de la funcion generada:
function scr_GeneraPlat(){
var rectangulo = collision_rectangle(
0,
0,
room_width,
75,
all, false, false);
if (rectangulo == noone)
{
switch(irandom_range(1,3))
{
case 1:
var a = array_height_2d(global.patron_uno) - 1;
var ult_obj_creado = instance_create_depth(
global.patron_uno[a,1],
0,0,global.patron_uno[a,0]);
--a;
while(a > -1)
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_uno[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_uno[a,0]);
--a;
}
break;
case 2:
var a = array_height_2d(global.patron_dos) - 1;
var ult_obj_creado = instance_create_depth(
global.patron_dos[a,1],
0,0,global.patron_dos[a,0]);
--a;
while(a > -1)
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_dos[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_dos[a,0]);
--a;
}
break;
case 3:
var a = array_height_2d(global.patron_tres) - 1;
var ult_obj_creado = instance_create_depth(
global.patron_tres[a,1],
0,0,global.patron_tres[a,0]);
--a;
while(a > -1)
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_tres[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_tres[a,0]);
--a;
}
break;
}
}
}
En este caso primero crearemos una variable que contendra un rectangulo de colision pero con un par de curiosidades, la primera sera que es un rectangulo en la parte superior de la pantalla, tendra un ancho del total de la pantalla o room y una altura de 75, la segunda es que no usaremos un objeto sino el comodin all para que la colision sea sobre todos los objetos, como esto nos incluye a nosotros debemos setear a notme como false y la precision en false, con esto podemos pasar al primer condicional donde chequeamos que este rectangulo sea igual a noone, en caso de ser verdadero crearemos un switch donde chequeara el resultado de un rango al azar del 1 al 3, para eso crearemos tres que trabajan de la misma forma, vamos a tomar uno como ejemplo:
case 1:
var a = array_height_2d(global.patron_uno) - 1;
var ult_obj_creado = instance_create_depth(
global.patron_uno[a,1],
0,0,global.patron_uno[a,0]);
--a;
while(a > -1)
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_uno[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_uno[a,0]);
--a;
}
break;
Lo primero que haremos sera crear una variable donde almacenara la altura o el total de nuestro array 2D llamado global.patron_uno y a su vez le restaremos uno, esto es debido a que cuenta el total de lineas pero recuerden que siempre comienza a contar desde cero por lo cual la ultima posicion no coincidira con el total y por eso le restamos uno, recuerdan cuando nombramos a estos arrays con patron seguido de un numero, eso era para vincularlo a cada uno de los case que tenemos en este switch, continuando con el codigo tenemos una variable que crea una instancia del objeto contenido en el patron de nuestro case, la unica diferencia va a estar en el caso de y donde le asignaremos el valor 0, pero a x lo sacamos de la posicion 1 de la segunda posicion y lo mismo con el objeto, despues restaremos en uno al valor de a, lo siguiente sera un bucle while que haremos mientras a sea mayor a -1, esto es asi porque un array jamas puede tomar un valor menor a 0, primero crearemos una variable que almacena un numero al azar en el rango entre 50 y 80, despues volveremos a usar a ult_obj_creado para crear una instancia pero en esta ocasion usaremos el valor de y del objeto creado anteriormente y le restaremos el valor almacenado en dist_crear, el resto es de la misma variable a cuando creamos el objeto anterior, luego restamos en uno a la variable a, esto se repetira hasta que termine el ciclo, esto es una muy buena manera de poder agregar mas elementos sin tener que alterar nuestro codigo, pero esto lo veremos en un rato, con esto terminamos el case 1, el case 2 y case 3 son exactamente lo mismo pero en lugar de usar patron_uno usaremos a patron_dos y patron_tres respectivamente, por ultimo debemos hacer una modificacion mas pero en este caso en el objeto obj_Pepe.
En obj_Pepe vamos al evento Step y al final de todo el codigo dentro de este evento agregaremos la siguiente linea:
scr_GeneraPlat();
Es decir que por cada paso que demos se llamara a la funcion que definimos recien, si lo probamos deberemos tener un personaje que salta por infinitas plataformas, pero y nuestros enemigos? Ahora pasaremos a ver este tema.
Como dijimos hace muy poco trabajar de la forma que lo hacemos en el case nos permite agregar nuevos elementos dentro del patron para el switch, nuestro siguiente paso sera modificar esos ultimos bloques de la siguiente manera:
global.patron_uno[0,0] = obj_Enemigo;
global.patron_uno[0,1] = 128;
global.patron_uno[1,0] = obj_Plataforma;
global.patron_uno[1,1] = 128;
global.patron_uno[2,0] = obj_Plataforma;
global.patron_uno[2,1] = 268;
global.patron_uno[3,0] = obj_Enemigo;
global.patron_uno[3,1] = 428;
global.patron_uno[4,0] = obj_Plataforma;
global.patron_uno[4,1] = 428;
global.patron_dos[0,0] = obj_Plataforma;
global.patron_dos[0,1] = 64;
global.patron_dos[1,0] = obj_Plataforma;
global.patron_dos[1,1] = 194;
global.patron_dos[2,0] = obj_Plataforma;
global.patron_dos[2,1] = 354;
global.patron_tres[0,0] = obj_Plataforma;
global.patron_tres[0,1] = 96;
global.patron_tres[1,0] = obj_Enemigo;
global.patron_tres[1,1] = 256;
global.patron_tres[2,0] = obj_Plataforma;
global.patron_tres[2,1] = 256;
global.patron_tres[3,0] = obj_Plataforma;
global.patron_tres[3,1] = 436;
Esto es muy similar solo que ahora intercalamos algunos enemigos, para ello simplemente cambiamos al objeto obj_Plataforma por obj_Enemigo, lo ubicamos en otra posicion pero le pasamos el valor del eje X de una plataforma, como nuestro bucle va de mayor a menor el enemigo debe ir despues de creada la plataforma, vamos a tomar este ejemplo:
global.patron_uno[3,0] = obj_Enemigo;
global.patron_uno[3,1] = 428;
global.patron_uno[4,0] = obj_Plataforma;
global.patron_uno[4,1] = 428;
Aca podemo ver como en orden esta antes que la plataforma que vamos a asignarle pero para nuestro bucle vendra despues, en mi caso para el patron uno asigne dos enemigos en las puntas, para el patron dos no pase ninguno y para el patron tres pase uno solo en el medio, si quieren prueben de hacerlo de otra forma, continuando con nuestras modificaciones debemos cambiar el script que creamos de la siguiente manera:
function scr_GeneraPlat(){
var rectangulo = collision_rectangle(
0,
0,
room_width,
75,
all, false, false);
if (rectangulo == noone)
{
switch(irandom_range(1,3))
{
case 1:
var a = array_height_2d(global.patron_uno) - 1;
var ult_obj_creado = instance_create_depth(
global.patron_uno[a,1],
0,0,global.patron_uno[a,0]);
--a;
while(a > -1)
{
if (global.patron_uno[a,0] == obj_Enemigo)
{
instance_create_depth(global.patron_uno[a,1],
ult_obj_creado.y - 16, 0,
global.patron_uno[a,0])
}
else
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_uno[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_uno[a,0]);
}
--a;
}
break;
case 2:
var a = array_height_2d(global.patron_dos) - 1;
var ult_obj_creado = instance_create_depth(
global.patron_dos[a,1],
0,0,global.patron_dos[a,0]);
--a;
while(a > -1)
{
if (global.patron_dos[a,0] == obj_Enemigo)
{
instance_create_depth(global.patron_dos[a,1],
ult_obj_creado.y - 16, 0,
global.patron_dos[a,0])
}
else
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_dos[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_dos[a,0]);
}
--a;
}
break;
case 3:
var a = array_height_2d(global.patron_tres) - 1;
var ult_obj_creado = instance_create_depth(
global.patron_tres[a,1],
0,0,global.patron_tres[a,0]);
--a;
while(a > -1)
{
if (global.patron_tres[a,0] == obj_Enemigo)
{
instance_create_depth(global.patron_tres[a,1],
ult_obj_creado.y - 16, 0,
global.patron_tres[a,0])
}
else
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_tres[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_tres[a,0]);
}
--a;
}
break;
}
}
}
Les paso nuevamente el codigo porque es mas simple, la unica modificacion va a estar en los case de nuestro switch, mas exactamente en el bucle while, tomemos uno de ejemplo:
while(a > -1)
{
if (global.patron_uno[a,0] == obj_Enemigo)
{
instance_create_depth(global.patron_uno[a,1],
ult_obj_creado.y - 16, 0,
global.patron_uno[a,0])
}
else
{
var dist_crear = irandom_range(50,80);
ult_obj_creado = instance_create_depth(
global.patron_uno[a,1],
ult_obj_creado.y - dist_crear,
0, global.patron_uno[a,0]);
}
--a;
}
Ahora dentro de nuestro bucle tenemos un condicional que verifica si la posicion 0 de nuestro patron posee un valor igual a obj_Enemigo, en caso de ser verdadero crea una instancia donde pasa el valor del eje X, luego haremos el valor del eje Y de nuestro ultimo objeto creado menos 16, la mitad de nuestro sprite, la profundidad sera de 0 y por ultimo pasamos el objeto, como pueden ver por esta razon debemos crear primero la plataforma y luego el enemigo, tambien le restamos 16 porque nuestros sprites ahora se generan desde la mitad del mismo, luego esta el caso contrario donde volvera a hacer lo que vimos antes para crear la plataforma, esto mismo lo hacemos en los otros case pero para el patron correspondiente, por ultimo debemos modificar a nuestro objeto obj_Enemigo.
En este caso en particular debemos agregar la misma actualizacion que usamos para las plataformas, por ende vamos al objeto, agregamos el evento Step y una vez creado le asignamos este codigo:
if (y > room_height)
instance_destroy();
if (obj_Pepe.vspeed < 0)
{
vspeed = -1 * obj_Pepe.vspeed;
}
else
{
vspeed = 0;
}
Hacen exactamente lo mismo, el primer condicional destruye la instancia cuando se llega al fondo de la pantalla, la otra moviliza a los enemigos o los detiene segun corresponda, con todas estas modificaciones podemos probarlo, compilemos y veamos que sucede mediante el siguiente video
Como podemo ver en el video ya tenemos un juego mucho mas completo, entretenido y desafiante, el dark souls de los proyectos xD, fuera de broma tenemos un 90% del juego hecho ya solo nos faltan un par de detalles.
En resumen, hoy hemos visto como generar plataformas «al azar», tambien hemos visto como crear una plantilla inicial y como despues en base a unos patrones podemos ir generando una serie de plataformas para poder ir hacia arriba, despues hemos agregado los enemigos para poder agregar un poco mas de dificultad, espero les haya gustado sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donación
Es para mantenimento del sitio, gracias!
$1.00
