Introducción a GML Parte 2

Expresiones.

Funciones.

Arreglos.

Expresiones

En esta sección discutiremos los diversos tipos de expresiones del lenguaje GML.


Una expresión puede ser un número real (3.4), un número hexadecimal comenzando con el signo $ ($00FFAA), cadenas entre comillas sencillas o dobles ('hola' o "hola") o expresiones más complejas. Los siguientes operadores binarios están disponibles para construír expresiones (en orden de prioridad):


= - Usado para asignar un valor a una variable. En GameMaker:Studio tambiénes usado para comparar variables, esto podrías verlo en ejemplos y códigos de otras presonas. Esto es un legado de versiones viejas de GameMaker, pero se debería de usar el operador == para comparar y = únicamente para asignar, como se muestra a continuación:

a = 12; speed = 5; val = (old_val + 5);



&&, ||, ^^ (and, or and xor) - Operadores boleanos para evaluar una expresión como verdadera o falsa. Si en cualquiera de los siguientes ejemplos la expresión es verdadera trueel código es ejecutado:

if (a == b && c == d) {do something...}// and
if (a == b || c ==d) {do something...}// or
if (a == b ^^ c == d) {do something...} // xor



<, <=, ==, !=, >, >= - Comparaciones entre valores, sólo se pueden devolver como resultado verdadero (true) o falso (false) result (donde verdadero se puede interpretar como 1, y falso como 0). Ejemplo:

if (a < b) {do something...}
if (a != b) {do something...}



|, &, ^, <<, >>: Se pueden realizar operaciones bit a bit mediante estos operadores. | = or bit a bit, & = and bit a bit, ^ = xor bit a bit, << = desplazamiento hacia la izquierda, >> = desplazamiento a la derecha. Se usan de esta manera:

x = (x & $ffffffe0) + 32;
if (y ^ $1f) > 0 {do something...};



+, -, *, / - Suma, resta, multiplicación y división.

c = a * b;
str = a + "world";



++, -- - Incrementar en uno y restar uno respectivamente. No está de más aclarar que el resultado varía ligeramente dependiendo de si el operador se coloca antes o después de la variable. Por ejemplo:

De tal manera, que si tienes algo como esto:

var a = 1;
show_debug_message(string(a++));
show_debug_message(string(++a));

La salida del depurador será 1 y 3. Aquí hay un ejemplo de uso muy común del operador de incremento:

for (i = 0; i < 10; i++) {do something...};
if hit == true score--;



div, mod (%) - División y módulo, donde div da el cociente en número entero, sin residuo. mod, por su parte, sólo devuelve el residuo de una división. Ejemplo:

secs = time mod 60;
time_str = string(time div 60);



También existen los siguientes operadores:

Como valores en una expresión, se pueden usar números, variables o funciones que devuelvan un valor. las subexpresiones se pueden encerrar entre llaves. Todos los operadores funcionan con valores reales, pero las comparaciones también funcionan con cadenas y éstas se pueden concatenar mediante el operador +.

Nota: A diferencia de otros lenguajes, los dos argumentos en una operación boleana se calculan siempre, a pesar de que el primer argumento pueda por sí solo determinar el resultado.

Para cerrar esta sección, presentamos unos cuantos ejemplos de distintas expresiones:

{
x = 23;
color = $FFAA00;
str = 'hello world';
y += 5;
x *= y;
x = y << 2;
x = 23 * ((2 + 4) / sin(y));
str = 'hello' + " world";
b = (x < 5) && !(x==2 || x==4);
}




Funciones

Se explica qué es una función en GameMaker:Studio


Una definición popular de una función es que es un bloque de código que admite una entrada y arroja una salida, y la salida está relacionada de alguna manera con la entrada. En GameMaker:Studio las funciones se usan como parte de un programa para realizar muy diversas tareas, y hay un gran número de funciones GML disponibles, todas ellas explicadas en el manual.

En GML, una función tiene la forma de un nombre de función, seguido por los argumentos de entrada entre paréntesis y separados por comas (si la función no tiene argumentos de entrada, se usan sólo los paréntesis). La estructura general de una función tiene la forma:

<function>(<arg0>, <arg1> ,... <arg15>);


Hay dos tipos de funciones. Una es la vasta colección de funciones predefinidas usadas para controlar casi cualquier aspecto del juego, y la otra son los scripts creados por el usuario, que en ocasiones (no siempre) pueden ser usados como una función.

Algunas funciones devuelven valores que pueden ser usados en expressiones, otras simplemente ejecutan una serie de comandos. Se debe notar el hecho de que no es posible usar una función como el miembro izquierdo de una asignación, es decir, no puedes escribir algo como:

instance_nearest(x, y, obj).speed = 0;


pues el valor devuelto (de la función) es un número real, y por lo tanto debe encerrarse entre paréntesis (para mayor información, consulta la sección Accediendo a variables desde otras instancias), por lo que el código debe ser reescrito como:

(instance_nearest(x,y,obj)).speed = 0;




Arreglos


Los arreglos pueden parecer confusos en principio, pero resultan sumamente útiles y son una parte esencial en el desarrollo de juegos. ¿Por qué? Aquí algunas cosas que serían difíciles de lograr sin arreglos:

Son tan sólo una muestra ya que los arreglos son una de las herramientas fundamentales a nuestra disposición. Conozcamos más sobre ellos...

Arreglos unidimensionales (1D)

Comencemos con las preguntas básicas ¿qué es un arreglo? ¿Cómo luce uno? Un arreglo es algo así...

array[0] = 1.25;


Esto es un arreglo unidimensional. A continuación examinaremos cómo está conformado.

array


Es el nombre del arreglo, como el nombre de cualquier otra variable, puede ser cualquier cosa, como "a" o "mi_arreglo".

[0]


Se trata de la posición dentro del areglo que estamos consultando o cambiando. Un arreglo es básicamente un contenedor con cierto número de espacios para almacenar valores. Cada posición en el contenedor está identificada con un número especifico, que es el número que ponemos dentro de []. Observa que la posición siempre comienza en 0 y ¡no puede ser negativa! Para expandir el arreglo, de manera que incluya más posiciones, hacemos...

array[2] = 0;
array[1] = 0;
array[0] = 0;


Ahora el arreglo contiene tres posiciones (0, 1 and 2) y hemos iniciado el arreglo a 0. ¿Qué significa esto? Un arreglo tiene que iniciarse antes de poder usare, de lo contrario GameMaker:Studio dará un error. Iniciar un arreglo simplemente significa que a cada posición se le da un valor inicial como preparación para que sea usado posteriormente en el objeto o el código. Esto es importante de recordar ya que deberá haber cierta planeación antes de usar arreglos, pero es tan fácil como usar un bucle repeat para iniciarlos, es decir...

var i;
i = 9;
repeat(10)
   {
   array[i] = 0;
   i -= 1;
   }


Este pequeño ejemplo initializará un arreglo de diez posiciones (0-9) a 0, por lo que cada posición contendrá un valor de 0. Se puede notar que el arreglo en realidad ha sido iniciado a la inversa, definiendo en primer lugar la última poscición. Esto no es estritctamente necesario pero es la manera más adecuada ya que así se reserva en memoria un espacio exactamente del tamño del arreglo. Si el arreglo se inicia de 0 en adelante, la memoria se tiene que reposicionar por cada valor que se agregue. La diferencia de velocidad es despreciable para arreglos pequeños, pero los grandes deberían ser optimizados de esta manera siempre que se pueda.

Qué tal si queremos iniciar el arreglo con valores distintos para cada posición? En tal caso es necesario especificar manualmente cada posición, pero podemos hechar mano de un útil truco para "rastrear" su valor de manera sencilla.

count = 3;
array[count] = "you?"
count -= 1;
array[count] = "are "
count -= 1;
array[count] = "How "
count -= 1;
array[count] = "Hello!"
count -= 1;


Como se puede ver, no se han usado números, sino una variable para contar la posición de los valores. Esto conlleva dos beneficios - Uno, no hay que preocuparse por errores de escritura cuando se especifican las posiciones del arreglo. y dos, disponemos de la variable "count" la cual puede ser usada posteriormente para saber el número de posiciones que contiene el arreglo.

Definido lo anterior, ¿cómo usamos un arreglo en la práctica? De la misma manera en que usaríamos una variable normal, como lo muestran los siguientes ejemplos:

total = array[0] + array[5]; //Sumar dos valores de un arreglo

if array[9] = 10 //Probar cada valor del arreglo
   {
   //do something
   }

draw_text(32, 32, array[3]); //dibujar el valor de un arreglo


Como los arreglos están numerados consecutivamente, Esto quiere decir que es posible recorrerlo mediante un bucle para realizar operaciones tal como se hizo al iniciarlo.:

var i, total;
i = 0;
repeat(10)
   {
   total += array[i];
   draw_text(32, 32 + (i * 32), array[i]);
   i += 1;
   }
draw_text(32, 32 + (i * 32), total);


El código anterior sumará todos los valores en el arreglo y dibujará cada uno de ellos. Al finalizar el bucle también se dibuja el total de la suma.

Arreglos bidimensionales (2D)

Ahora que sabemos cómo se "ve" un arreglo "normal", veamos un arreglo de dos dimensiones.

array[0,0] = 5;


Como antes, cada número apunta a una posición dentro del vector, pero esta vez cada posición tiene una coordenada "a" y una coordenada "b": se trata de una nueva dimension para nuestro contenedor, que ahora posee altura y anchura mientras que el arreglo 1D sólamente tiene altura. Aquí tenemos un ejemplo de esto:
nta

array[1,2] = 1;
array[1,1] = "hello";
array[1,0] = 55.5;
array[0,2] = sprite_index;
array[0,1] = "world";
array[0,0] = -67.89;


Tal como sucede con los arreglos 1D, un arreglo 2D requiere ser iniciado antes de usarse, y puede contener números reales, cadenas y constantes, como cualquier otra variable, haciéndolos candidatos ideales paracualquier juego que necesite almacenar grandes cantidades de datos de una manera accesible. A continuación se presenta un ejemplo final de cómo se podría aplicar esto en un juego de verdad... Supongamos que queremos generar cuatro enemigos de manera aleatoria en cuatro puntos distintos en el juego. Una manera de usar esto sin escribir una gran cantidad de código es mediante un arreglo 2D:

Primero iniciamos el arreglo en el evento [CREATE] de nuestro objeto controlador:

enemy[3,2] = 448; //y position
enemy[3,1] = 32; //x position
enemy[3,0] = obj_Slime; //Object
enemy[2,2] = 448;
enemy[2,1] = 608;
enemy[2,0] = obj_Skeleton;
enemy[1,2] = 32;
enemy[1,1] = 608;
enemy[1,0] = obj_Knight;
enemy[0,2] = 32;
enemy[0,1] = 32;
enemy[0,0] = obj_Ogre;


Ya tenemos definidos los objetos a partir de los cuales generar instancias así como sus correspondientes coordenadas de generación x e y almacenadas en el arreglo. Esto se puede usar de la siguiente manera en otro evento del objeto controlador (el evento alarma, por ejemplo, o el evento key press):

var i;
i = irandom(3); //get a random number from 0 to 4
instance_create(enemy[i,1], enemy[i,2], enemy[i,0]); //Usar el arreglo para crear la instancia


Este breve código generará un enemigo de manera aleatoria en la habitación y emplea menos código que una estructura "if / then / else", incluso menos que un "switch", y como el arreglo es iniciado totalmente en el evento [CREATE] resulta MUCHO más fácil editar y cambiar cualquiera de los valores ya que no están mezclados con el resto de código en otros objetos.

Funcionalidad avanzada de arreglos

Al igual que las variables, es posible usar arreglos como argumentos de entrada para los scripts y tener otro arreglo devuelto a la instancia que llamó al script. Para hacer esto, sólo se necesita especificar la variable (nombre) del arreglo (no hay necesidad de indicar las entradas individuales ni los corchetes) y así esos valores serán copiados a un arreglo temporal del script. ¡Observa el uso de la palabra temporal! En realidad no se le estará pasando el arreglo al script (como ocurriría con una variable), sino que se estaría solicitando que el script creara una copia de dicho arreglo. Esto quiere decir que siempre se debe devolver el arreglo desde el script para modificar cualquier valor o valores del arreglo si se usa este método.

NOTA: Debido al modo de operación interno, pasar arreglos a scripts puede afectar el desempeño, especialmente si el arreglo es muy grande, así que esta funcionalidad se debe usar con cui-da-do

Considera el siguiente código de ejemplo. Primero creamos e iniciamos el arreglo, y luego lo pasamos al script:

for (var i = 9; i > -1; i--;)
   {
   a[i] = i;
   }

scr_Return_Array(a); //pasamos el arreglo al script


El script de ejemplo es bastante sencillo:

for (var i = 9; i > -1; i--;)
   {
   a[i] = i * 100; //multiplicar cada valor del arreglo por 100
   }


Quizás se esperaría que el arreglo original tuviera los valores 900, 800, 700, etc... pero NO ES ASÍ ya que no devolvimos el arreglo desde el script, por lo que lo único que cambiamos fue la copia temporal que se creó al momento de pasar el arreglo como argumento al script. para corregir esto, debimos haber escrito el código de la siguiente manera:

for (var i = 9; i > -1; i--;)
   {
   a[i] = i;
   }

a = scr_Return_Array(a);


Y el script lo debemos de reescribir así:

for (var i = 9; i > -1; i--;)
   {
   argument0[i] = i * 100;
   }

return argument0; // Devolver el arreglo "actual"


También es posible borrar de manera sencilla un arreglo al re-asignarle un valor sencillo a la variable que lo define. Esto libera la memoria asociada con todas las entradas y valores del arreglo. Por ejemplo:

//Crear el arreglo:
for (var i = 9; i > -1; i--;)
   {
   a[i] = i;
   }
//Borrar el arreglo:
a = 0;

Funciones de arreglo

Hay unas cuantas funciones asociadas al uso de arreglos. Están diseñadas para ofrecer flexibilidad en el código y permiten crear arreglos más dinámicos y funcionales. Estás funciones son:

  1. is_array
  2. array_length_1D
  3. array_length_2D
  4. array_height_2D
is_array

Determina si una variable un arreglo o no.


Sintaxis:

is_array(variable);


ArgumentoDescripción
variable La variable a revisar.

Devuelve: Boolean


Descripción:

Esta función puede ser usada para saber si una variable contiene un arreglo (devolverá true) o no (en tal caso devolverá false).

Ejemplo:

if is_array(a)
   {
   a = 0;
   }

El código anterior revisa una variable para saber si es un arreglo, en cuyo caso el arreglo es eliminado.




array_length_1d

Devuelve la longitud de la primera dimensión de un arreglo.


Sintaxis:

array_length_1d(arreglo);


ArgumentoDescripción
arreglo El arreglo a revisar.

Devuelve: Real

Descripción

Esta función determina el largo (número de entradas) de un arreglo. El arreglo puede ser tanto unidimensional, en donde el número de entradas será devuelto, o bidimensional, en cuyo caso se devolverá la longitud de la primera dimensión (si necesitas encontrar la longitud de la segundadimensión debes usar la función array_length_2d). Esta función devuelve 0 si la variable proporcionada no es un arreglo.



Ejemplo:

for (var i = array_length_1d(a); a > -1; i--;)
   {
   a[i] = -1;
   }

El código anterior recorrerá un arreglo e iniciará cada entrada a -1




array_length_2d

Devuelve la longitud de una dimensión determinada en un vector bidimensional.


Syntaxis:

array_length_2d(arreglo, n);


Argumento Description
arreglo El arreglo a verficar.
n La entrada del arreglo de la que se quiere saber la longitud.

Devuelve: Real


Descripción

Mediante esta función se puede obtener la longitud (número de entradas) de la segunda dimensión de un arreglo. Es necesario que a esta función se le pase el número de entrada de la primera dimensión y la función devolverá el número de entradas (longitud) de la segunda dimensión que el arreglo tenga. Para encontrar la longitud de la primera dimensión se debe usar antes la función array_height_2D. Esta función devolverá 0 si la variable proporcionada no es un arreglo o si es un arreglo de una dimensión.


Ejemplo

for (var i = array_height_2d(a); i > -1; i--;)
   {
   for (var j = array_length_2d(a, i); j > -1; i--;)
      {
      a[i, j] = -1;
      }
   }

El código anterior recorrerá todo el arreglo biodimensional e iniciará cada entrada a -1.



array_height_2d

Devuelve la 'altura' o número de filas de un arreglo bidimenisonal (2D).


Sintaxis:

array_height_2d(array);


ArgumentoDescripción
arreglo El arreglo a verificar.

Devuelve: Real


Descripción

Con esta función obtendrás la altura (número de entradas) de la primera dimensión de un arreglo bidimensional (2D). Se proporciona el arreglo a revisar. La salida de la función indicará cuantas entradas iniciales contiene el arreglo en cuestión. Se puede obtener el número de entradas de la segunda dimensión del arreglo mediante la función array_length_2d.


Ejemplo:

for (var i = array_height_2d(a); i > -1; i--;)
   {
   for (var j = array_length_2d(a, i); j > -1; i--;)
      {
      a[i, j] = -1;
      }
   }

Este código recorrerá todo el arreglo bidimensional e iniciará cada entrada a -1.