Como se escriben las sentencias en GML
Hoy vamos a aprender casi lo más importante que usaremos cuando programemos en GML. El 95% de nuestro código serán diferentes sentencias o declaraciones. Vamos a ver que es.
¿Qué es una sentencia?
Hemos aprendido a crear variables con sus operaciones, pero nos falta poder tomar decisiones cuando programamos.
Hemos visto con las acciones que pueden tomar estas decisiones cuando hacemos controles o preguntas. En programación también es necesario hacerlo. Así que veremos las diferentes opciones que tenemos para tomar decisiones.
Sentencia if
Una de las tomas de decisiones más comunes cuando programamos es con la declaración if. Por ejemplo, podemos decir en un objeto enemigo que si está cerca del jugador que le dispare
if cerca_jugador disparar();
Tal y como lo hemos escrito sería válido en Game Maker, pero yo prefiero poner paréntesis en la pregunta y las llaves en el resultado. Esto es equivalente:
if (cerca_jugador) { disparar; }
cerca_jugador puede ser una variable que detecte el cambio. En Game Maker existe una función que lo detecta. Si además disparar es crear un objeto bala, es más habitual ver en GML este if
if (instance_nearest(x, y, obj_player))
{
instance_create(x, y, obj_bullet);
}
En resumen, si se cumple la condición que hay dentro del paréntesis, ejecutaremos la siguiente instrucción. Recordemos que cumplir la condición nos devuelve un valor true.
Sentencia if…else
Muchas veces, en la condición anterior nos interesa controlar que no se ha cumplido. Se podría poner de esta manera:
if (instance_nearest(x, y, obj_player) == true)//esta línea es la misma que el ejemplo anterior
{
instance_create(x, y, obj_bullet);
}
if (instance_nearest(x, y, obj_player) == false) //No se cumple la condición
{
show_debug_message(‘Estamos aún lejos del jugador’);
}
Los lenguajes de programación ya tienen en cuenta esta situación, por eso es posible añadir en la sentencia if un control cuando no se cumple la condición. Se hace con la palabra especial else, y el ejemplo anterior quedaria así:
if (instance_nearest(x, y, obj_player))
{
instance_create(x, y, obj_bullet);
}
else
{
show_debug_message(‘Estamos aún lejos del jugador’);
}
Mucho más claro así, ¿verdad? Por cierto, a mi me gusta más escribirlo de esta manera:
if (instance_nearest(x, y, obj_player)) {
instance_create(x, y, obj_bullet);
} else {
show_debug_message(‘Estamos aún lejos del jugador’);
}
¿Qué os parece? Es otro estilo a la hora de escribir, aunque Game Maker lo interpreta de la misma manera. También se puede escribir así:
if (instance_nearest(x, y, obj_player)) { instance_create(x, y, obj_bullet); } else { show_debug_message(‘Estamos aún lejos del jugador’); }
Lo importante a la hora de escribir código es que quién lo lea pueda entenderlo. No irá más rápido si lo escribimos de una manera o de otra.
Encadenar sentencias if..else
Dentro de una sentencia if, es posible que cuando salte a la instrucción else debamos hacer otra pregunta más. Por ejemplo:
if (x < 50) {
x += 4;
} else {
if (x >= 50) and (x < 200) {
x -= 4;
} else {
x = room_width + 50;
}
}
El código que hemos visto lo podemos encadenar dentro del mismo else, de manera que no haga falta incluirlo dentro. Mejor ver el ejemplo:
if (x < 50) {
x += 4;
} else if (x >= 50) and (x < 200) {
x -= 4;
} else {
x = room_width + 50;
}
nos ahorramos algunas llaves y queda más claro. Podemos encadenar tantos como creamos necesarios.
if (condicion1) {
sentencia1;
} else if (condicion2) {
sentencia2;
sentencia3;
} else if (condicion3) {
sentencia4;
} else {
sentencia5;
}
Aquí hemos encadenado 3 condiciones, pero pueden ser muchas más.
Sentencia switch
Imaginas que tenéis que poner esto:
if (x == 1) {
sentencia1;
} else if (x == 2) {
sentencia2;
} else if (x == 3) {
sentencia3;
} else if (x == 4) {
sentencia4;
} else {
sentencia5;
}
Existe una sentencia equivalente, que nos hará escribir un poquito menos, y es la declaración switch. Quedaría de esta manera:
switch (x) {
case 1: sentencia1; break;
case 2: sentencia2; break;
case 3: sentencia3; break;
case 4: sentencia4; break;
default: sentencia5; break;
}
Vamos a verlo en detalle. Al principio, en el switch, ponemos en este caso la variable x. Luego vamos enumerando las distintas opciones que puede valer x: puede ser 1, o 2, o 3… En cada caso decimos que tiene que hacer. Una vez hecho, hay que poner la palabra especial break para que no compruebe el resto de casos. Por cierto, también se puede escribir debajo:
switch (x) {
case 1:
sentencia1;
break;
case 2:
sentencia2;
break;
...
O con llaves, gracias a la flexibilidad de GML
switch (x) {
case 1:
{
sentencia1;
break;
}
case 2:
{
sentencia2;
break;
}
...
También tenemos la palabra especial default: si no se ha cumplido ninguno de los casos, ejecutará el que hemos puesto en default siempre. Poner default es optativo, si queremos que ejecute algo por defecto lo ponemos, si no podemos saltarlo.
La sentencia switch se utiliza para controlar los casos que tengamos claro que van a suceder. No podemos poner condiciones como en un if, sino datos concretos. Un último ejemplo, que devuelve un texto dependiendo del idioma detectado:
switch (global.language) {
case "es": {text_string = "MODO HISTORIA"; break;}
case "en": {text_string = "STORY MODE"; break;}
default: {text_string = "STORY MODE"; break;}
}
Sentencia repeat
Empezamos con las sentencias de bucle o repetición. Ya vimos con las acciones como hacer una sentencia repeat. Por ejemplo, en acciones podemos tener esto
y en código GML sería así
repeat (6) {
instance_create(random(300), random(300), obj_enemy);
}
Si queremos evitar copiar el mismo código…
instance_create(random(300), random(300), obj_enemy);
instance_create(random(300), random(300), obj_enemy);
instance_create(random(300), random(300), obj_enemy);
instance_create(random(300), random(300), obj_enemy);
instance_create(random(300), random(300), obj_enemy);
instance_create(random(300), random(300), obj_enemy);
Queda más limpio con la sentencia repeat. También se usa para recorrer una array (ya que habitualmente se recorren enteras) o para contar una serie de operaciones. Otro ejemplo:
repeat(total_enemies) {
score ++;
draw_text(50, 100, score);
}
Sentencia while
La sentencia while es muy parecida a la sentencia repeat. La diferencia es que en repeat ponemos un número de veces de repetición, mientras que en while ponemos una condición. Mientras esa condición se cumpla, ejecutará lo que hay dentro del código todas las veces hasta que esa condición sea false. Un ejemplo:
while (!place_free(x, y)) {
x = random(room_width);
y = random(room_height);
}
Aprovechamos para decir que place_free() es una función de sistema que detecta que en la posición X/Y esté libre. Como hemos puesto un operador no delante (recordemos que es el operador !), este código significa que
mientras (no haya una lugar libre en X/Y) {
x = busca otro sitio aleatorio(en el ancho de la room)
y = busca otro sitio aleatorio(en la altura de la room)
}
Un último apunte, poner !place_free(x, y)
es lo mismo que place_free(x, y) == false
.
Sentencia do
Otra sentencia parecida a while. Veamos un ejemplo:
do {
x = random(room_width);
y = random(room_height);
} until (place_free(x, y));
Si cumple la condición de until, volverá a ejecutar el código. La diferencia con while es que aquí siempre ejecutará lo que hay dentro del do al menos una vez, ya que la condición se comprueba al final. Con while si no cumple la condición desde el principio ya no entra.
Sentencia for
Una sentencia for suele tener esta pinta:
var i;
for (i = 0; i < 10; i ++) {
draw_text(32, 32 + (i * 32), string(i) + ". " + string(scr[i]));
}
Vamos a explicarlo poco a poco. Dentro del for necesitamos una variable (en este caso la hemos declarado con var para que sea temporal) donde le damos un valor inicial, valor 0. El segundo parámetro le decimos hasta cuando podrá ejecutar el bucle, en ese caso i < 10. Se ejecutará 10 veces porque el tercer parámetro decimos cada cuánto se incrementa (casi siempre se incrementará de uno en uno).
Dentro del for ya tenemos el código que repetirá. Es habitual usar un for para aprovechar el valor de la variable i en cada momento (otra vez funciona muy bien para recorrer arrays). Además, en el ejemplo aprovechamos el valor de i para desplazar el texto mostrado en draw_text en el parámetro y, de manera que escribirá diez textos colocados verticalmente (lo que muestra será el valor de cada índice de la array scr).
Sentencia break
Ya vimos en la sentencia switch que usamos un break para saltar entre cada case. Pero también podemos usarla para cualquier sentencia bucle que hemos visto: while, repeat, for y do. Así podríamos interrumpir el bucle en cualquier momento. Un ejemplo:
repeat (random(10)) {
temp += array[i];
if (temp > total) {
break;
} else { i++; }
}
Sentencia exit
Con la sentencia exit podemos hacer algo un poco más radical que con break: saldremos del código actual. Si es un script, saldremos del script para volver hacia dónde se ha llamado, si es de un evento saldrá del todo del evento, aunque tengamos varios Execute code añadidos.
Lo habitual es ponerlo al principio del código para comprobar errores y no ejecutar el código a continuación.
if (error_string) exit;
Construcción with
Hemos visto algunos ejemplos con with, sobretodo con el uso de variables. Ahora lo detallamos.
Ya vimos que es posible cambiar los valores de una variable de un objeto desde otro, poniendo delante de la variable el objeto o la instancia (al igual que ponemos global para referirnos a variables globales). Vamos a ver un uso incorrecto:
obj_ball.y +=8;
¿Esto que hace? ¿Que todos los objetos obj_ball incremente su y en 8? Pues no, si tenemos varias instancias de obj_ball. Al referirnos al objeto, le dará el mismo valor a todos los objetos, cogerá la primera, le dará valor y hará lo mismo con todas las instancias a continuación.
Está claro que no queremos hacer eso. Queremos que cada instancia incremente en 8 el valor de y, respetando el valor que tiene cada una. La única manera que tenemos de hacerlo es con with.
with (obj_ball) {
y += 8;
}
Con with irá a cada instancia individualmente y le incrementará su valor. ¿Que una tiene y = 64 y otra y = 25? Pues tendrán y = 68, y = 33. Con obj.ball y += 8 valdría y = 64, y = 64.
También podemos usar with con funciones que devuelven objetos. Por ejemplo:
inst = instance_create(x, y, obj_ball);
inst.y += 8;
en este caso creamos una instancia de obj_ball y modificamos la y. Este código hace lo mismo:
with (instance_create(x, y, obj_ball)) {
y += 8;
}
No haría falta crear una variable para usarla con with.
Otro uso de with es con la palabra other. Hemos comentado que other se utiliza en eventos de colisión para saber el otro objeto que está colisionando. Si lo usamos dentro de un with, se referirá al objeto que estamos programando fuera del with.
Mejor con un ejemplo: imaginamos un obj_player, que tiene una variable point, y además en su código modificamos obj_ball.
point = 20;
with (obj_ball) {
y += 8;
}
Sabemos que y se refiere al de obj_ball, no al de obj_player. ¿Como podemos accede a point?
point = 20;
with (obj_ball) {
y += 8;
obj_player.point++;
}
Así sabemos que si solo hay una instancia no hay problemas, pero nos estamos refiriendo al objeto (todas las instancias activas). Si nos queremos referir solamente a la instancia que tenemos el código pondríamos
point = 20;
with (obj_ball) {
y += 8;
self.point++;
}
Resumen
Con lo que sabemos de variables y lo que hemos visto hoy, ya podemos programar con todo el potencial en GML (no exagero). Solo falta conocer las funciones que viene en el sistema para poder aprovecharlas al máximo. En el próximo post explicaré de una manera sencilla que es una función y que podemos hacer con ellas.
¡Podéis preguntar cualquier cosa en los comentarios!
Buenas!!
En la conclusión haces referencia a un post sobre el uso de las funciones de sistema como paso siguiente.
En el índice no lo veo…
¿está por algún otro sitio?
Gracias!!!
He añadido el enlace. ¡gracias por el aviso!
Gracias (y)
Hola!
Una pregunta, ¿como se lograría que el sprite del jugador, si el mouse esta viendo en cierta dirección dentro de un rango de grados, cambie el sprite?
Me explico.
Por ejemplo, si el mouse esta en una posición entre 0 y 45 grados el sprite sea el de el personaje viendo hacia la derecha y arriba, si el mouse esta entre 45 y 135 grados sea el personaje viendo hacia arriba… etc.
Si estoy equivocado respecto a los grados me disculpo, que no me llevo con las mates u.u
Piensa como podrías hacerlo… 🙂
Si quieres hacer cambios cada 45 grados, haz esa división. Por ejemplo:
0 / 45 = 0
43 / 45 = 0
45 / 45 = 1
46 / 45 = 1
130 / 45 = 1
etc.
¿Cómo saber esos grados del ratón? Con la función, point_direction(). Así que el código debe ser algo así:
grados = point_direction(x, y, mouse_x, mouse_y);
//giramos el sprite
image_angle = (grados / 45) * 45;
Seguro que tienes que hacer algún ajuste. Ya me lo dices.
Gracias por la respuesta!
Eso me ayuda, pero me referial al efecto que logran los de DodgeRoll en Enter The Gungeon con el sprite del jugador…
Eh investigado un poco y ya tengo una idea de como seria:
if dir > 0 and dir < 45 // dir es el point_direction
{
draw_sprite(spr_jugador_arribaderecha,0,obj_judador.x,obj_judador.y);
}
Pero pongo esto en un evento step dentro del jugador y no funciona, quiza no puedo poner un draw en un evento step?
Las funciones de draw solo funcionan dentro del evento Draw. Copia tu código tal cual y debería funcionar.