Programando con Game Maker Si no tienes el programa bajo de www.yoyogames.com Estructura general del GML. Como habrás leído antes, el Game Maker contiene un lenguaje de programación interno. Este lenguaje te da mucha más flexibilidad y control que las acciones estándar. Nos referiremos a este lenguaje como el GML (de Game Maker Language). Hay diferentes lugares en los que puedes escribir programas con este lenguaje. El primero, cuando defines scripts. Un script es un programa en GML. Segundo, cuando agregas una acción de código a un evento. En una acción de código debes escribir un programa en GML. Tercero, en el room creation code. Y finalmente, en cualquier momento que necesites especificar algún valor en una acción, puedes también emplear una expresión en GML. Una expresión, como veremos más adelante no es un programa completo, sino una pieza de código que devuelve un resultado.
En este capítulo describiremos la estructura básica de los programas en GML. Cuando desees usar programas en GML, se debe tener cuidado con ciertos aspectos. Primero que nada, para todos tus recursos (sprites, objetos, sonidos, etc.) debes emplear nombres que inicien con una letra y que sólo consistan de letras, números y el guión bajo "‘_". De otra forma no podrás referirte a ellas desde el programa. Mantente seguro que todos los recursos tengan nombres diferentes, también ten cuidado de no nombrar a tus recursos self, other, global o all porque estas son palabras que tienen un significado especial dentro del lenguaje. Tampoco debes usar ninguna de las palabras reservadas indicadas a continuación. La estructura básica del GML se trata con detalle en los siguientes capítulos: Un programa. Un programa consiste de un sistema de instrucciones, llamados sentencias. Un programa debe comenzar con el símbolo ‘{‘ y terminar con el símbolo ‘}’ . Las sentencias deben separarse con el símbolo ';'. La estructura global de todo programa es:
{ <sentencia >; <sentencia >; ... }
Hay un número de diferentes tipos de sentencias, de las cuales vamos a ver más abajo.
Variables Como en cualquier lenguaje de programación, el GML contiene variables. Las variables son las posiciones de memoria que guardan la información. Las variables pueden almacenar valores reales o cadenas de texto. Las variables no necesitan ser declaradas como en otros lenguajes. Hay un gran número de variables internas. Algunas son generales, como mouse_x y mouse_y, las cuales indican la posición actual del cursor, mientras otras son locales para la instancia
del objeto para el cual se ejecuta el programa, como “x” e “y” que indican la posición actual de la instancia. Una variable tiene un nombre que debe iniciar con una letra, y puede contener sólo letras, números, y el símbolo ‘_’ (La longitud máxima es de 64 caracteres). Cuando haces uso de una nueva variable, ésta es local para la instancia actual y no es conocida en los programas de otras instancias (aún del mismo objeto), aunque existe la posibilidad de hacer referencia a variables de otras instancias; mira más abajo para mayor información.
Asignaciones Una asignación pasa el valor de una expresión a una variable. Una asignación tiene la siguiente forma:
= <expresión>;
Una expresión puede ser un simple valor pero también puede ser más complicada. Además de asignar un valor a una variable, también se le puede sumar usando +=, restar usando -=, multiplicarla usando *=, dividirla usando /=, o usando |=, &\ o ^=.
Expresiones Las expresiones pueden ser números reales (p. Ej. 3.4), números hexadecimales, comenzando con el signo ‘$’ (p. Ej. $00FFAA), cadenas entre comillas simples o dobles (p. Ej. ‘hola’ o “hola”) u otras más complicadas. Para las expresiones, existen los siguientes operadores binarios (en orden de prioridad):
•
&&, ||: funciones Booleanas (&& para la función and, || para la función)
•
<, <=, ==, !=, >, >=: comparaciones, el resultado es true (1) o false (0)
•
| & ^: operadores de bit (| = bitwise or, & = bitwise and, ^ = bitwise xor)
•
<< >>: operadores de bit (<< = shift left, > > = shift right)
•
+, -: adición, sustracción
•
*, /, div, mod: multiplicación, división, división entera y módulo.
Nota que el valor operador
x div y
es el valor de
x/y redondeado en la dirección de cero al número entero más cercano. El
mod devuelve el resto obtenido dividiendo sus operandos. En otras palabras, x mod y =
x – (x
div y) * y. También, los operadores de bit existen: •
!: not, convierte un valor verdadero en falso y uno falso en verdadero
•
-: cambio de signo
•
~: cambio de signo de bit
Como valores se pueden emplear números, variables o funciones que devuelvan algún valor. Las sub-expresiones se pueden colocar entre paréntesis. Todos los operadores funcionan para valores reales. Las comparaciones también funcionan para las cadenas y el + concatena cadenas.
Ejemplo Aquí hay un ejemplo con algunas asignaciones
{ x = 23; color = $FFAA00; str = 'hola mundo'; y += 5; x *= y; x = y << 2; x = 23*((2+4) / sin(y)); str = 'hola' + " mundo"; b = (x < 5) && !(x==2 || x==4); }
Variables extra Puedes crear nuevas variables al asignándoles un valor (no es necesario declararlas antes). Si simplemente usas un nombre de variable, la variable será almacenada sólo para la instancia actual. Por lo que no esperes encontrarla cuando manejes otro objeto (u otra instancia del mismo objeto). También se puede cambiar y leer variables de otros objetos colocando el nombre del objeto con un punto antes del nombre de la variable.
{ if (global.hacer) { // hacer cualquier cosa global.hacer = false; } }
A veces quieres variables que solo estén dentro del actual piece of code o de un script. De esta manera evitas perder memoria y estás seguro que no hay ningún conflicto con los nombres. Esto es también más rápido que usar variables globales. Para hacer esto debes declarar las variables en el comienzo del código, usando la palabra “var”. Esta declaración se ve así:
var <nombrevariable1>,<nombrevariable2>,<nombrevariable3>, ...
Por ejemplo, puedes escribir:
{ var xx,yy; xx = x+10; yy = y+10; instance_create(xx,yy,pelota); }
Accediendo a variables en otras instancias Como se dijo antes, puedes alterar variables en la instancia actual usando sentencias como:
x = 3;
Pero en ciertos casos querrás acceder a variables en otra instancia. Por ejemplo, para detener el movimiento de todas las pelotas, o para mover al personaje principal a cierta posición, o, en el caso de una colisión, cambiar el sprite de la otra instancia involucrada. Esto puede lograrse antecediendo el nombre del objeto y un punto al nombre de la variable. Así por ejemplo, puedes escribir:
pelota.speed = 0;
Esto cambiará la velocidad de todas las instancias del objeto pelota. Hay ciertos “objetos” especiales.
•
self: La instancia actual para la que estamos ejecutando la acción
•
other: La otra instancia involucrada en un evento de colisión
•
all: Todas las instancias
•
noone: Ninguna instancia (tal vez te parezca raro pero puede ser útil como veremos más adelante)
•
global: : No es precisamente una instancia, sino un contenedor que almacena variables globales
Por ejemplo, puedes usar las siguientes sentencias:
other.sprite_index = sprite5; all.speed = 0; global.message = 'Un buen resultado'; global.x = pelota.x;
Ahora tal vez te estés preguntando lo que la última tarea realiza cuando hay más de una pelota. Bien, se toma la primera y su valor x es asignado al valor global.
Pero qué tal si deseas establecer la velocidad de una pelota en particular, en lugar de la de todas ellas. Esto es un poco más difícil. Cada instancia tiene un id único. Cuando colocas instancias en un cuarto en el diseñador, este id se muestra cuando colocas el ratón sobre la instancia. Estos números son mayores o iguales a 100000. Puedes emplear estos números como la parte a la izquierda del punto. Pero ten cuidado, el punto será interpretado como el punto decimal en el número. Para evitarlo, colócalo entre paréntesis. Así por ejemplo, asumiendo que el id de la pelota es 100032, puedes escribir:
(100032).speed = 0;
Cuando creas una instancia en el programa, la llamada devuelve su id. Una pieza de programa válido es:
{ nnn = instance_create(100,100,pelota); nnn.speed = 8; }
Esto crea una pelota y establece su velocidad. Nota que hemos asignado el id de la instancia a una variable y usamos esta variable como indicación antes del punto. Esto es completamente válido. Déjame explicarlo un poco mejor. Un punto es de hecho, un operador. Toma un valor como el operador de la izquierda y una variable (dirección) como el operador de la derecha, y devuelve la dirección de esta variable en particular para el objeto o instancia indicados. Todos los nombres de objetos, y los objetos especiales nombrados antes representan valores y pueden ser tratados como con cualquier otro valor. Por ejemplo, el siguiente programa es válido:
{ obj[0] = pelota; obj[1] = bandera; obj[0].alarm[4] = 12; obj[1].id.x = 12; }
La última sentencia debiera interpretarse como sigue. Tomamos el id de la primera bandera. Para la instancia con ese id establecemos a 12 su coordenada x. Los nombres de objetos, objetos especiales y los id de las instancias pueden también emplearse en otros programas.
Arrays Puedes emplear arrays de una o dos dimensiones en el GML. Simplemente coloca el índice entre corchetes cuadrados para un array unidimensional, y los dos índices con una coma entre ellos para los arrays bidimensionales. En el momento en que emplees un índice el array es generado. Cada array inicia en el índice 0. Por lo que debes tener cuidado al usar índices muy grandes ya que se ocupará memoria para un array grande. Nunca emplees índices negativos. El sistema coloca un límite de 32000 para cada índice y 1000000 para el tamaño total. Por ejemplo, puedes escribir lo siguiente:
{ a[0] = 1; i = 1; while (i < 10) { a[i] = 2*a[i-1]; i += 1;} b[4,6] = 32; }
Sentencia If Una sentencia If tiene esta forma
if (<expresión>) <sentencia >
o
if (<expresión>) <sentencia> else <sentencia >
La sentencia también puede ser un bloque. La expresión se evaluará. Si el valor (redondeado) es <=0 (false) se ejecuta la sentencia después del else, de otra forma (true) se ejecuta la otra sentencia. Es un buen hábito colocar siempre corchetes a las sentencias en la sentencia if. Por lo que mejor usa
if (<expresión>) { <Sentencia > } else { <Sentencia > }
Ejemplo El siguiente programa mueve el objeto hacia el medio de la pantalla.
{ if (x<200) {x += 4} else {x -= 4}; }
Sentencia Repeat Una sentencia repeat tiene esta forma
repeat (<expresión>) <sentencia >
La sentencia es repetida el numero de veces indicado por el valor redondeado de la expresión.
Ejemplo El siguiente programa crea 5 pelotas en posiciones aleatorias.
{ repeat (5) instance_create(random(400),random(400),pelota); }
Sentencia While Una sentencia While tiene esta forma
while (<expresión>) <sentencia>
Mientras la expresión sea verdadera, la sentencia (que puede también ser un bloque) es ejecutada. Ten cuidado con tus ciclos while. Puedes fácilmente hacer que se repitan eternamente, en cuyo caso el juego se bloqueará y ya no responderá a los comandos del usuario.
Ejemplo El siguiente programa trata de colocar el objeto actual en una posición libre (esto es casi lo mismo que la acción para mover un objeto a una posición aleatoria).
{ while (!place_free(x,y)) { x = random(room_width); y = random(room_height); } }
Sentencia Do La sentencia Do tiene esta forma:
do <sentencia> until(<expresión>)
La sentencia (que puede también ser un bloque) es ejecutada hasta que la expresión sea verdadera. La sentencia se ejecuta por lo menos una vez. Ten cuidado con los ciclos do. Puedes fácilmente crear uno que se repita indefinidamente, en cuyo caso el juego se bloqueará y ya no responderá a los eventos generados por el usuario.
Ejemplo El siguiente programa intenta colocar el objeto actual en una posición libre (esto es lo mismo que mover un objeto en una posición aleatoria)
{ do { x = random(room_width); y = random(room_height); } until (place_free(x,y)) }
Sentencia For Una sentencia For tiene esta forma:
for (<sentencia1> ; <expresión> ;<sentencia2>) <sentencia3>
Funciona de la manera siguiente. Primero se ejecuta la sentencia1. Entonces se evalúa la expresión. Si es verdadera, se ejecuta la sentencia3; entonces la sentencia2 y luego se evalúa nuevamente la expresión. Esto continúa hasta que la expresión sea falsa.
Puede sonar complicado. Debes interpretarlo de la manera siguiente. La primera sentencia inicializa el ciclo for. La expresión prueba si el ciclo debiera terminar. La sentencia2 es la sentencia de paso hacia la evaluación del siguiente ciclo.
El uso más común es para llevar un contador hasta cierto valor.
Ejemplo El siguiente programa inicializa un array llamada “lista” de longitud 10 con los valores 1-10.
{ for (i=0; i<=9; i+=1) lista[i] = i+1; }
Sentencia Switch En ciertas situaciones querrás llevar a cabo alguna acción dependiendo de un valor en particular. Puedes lograrlo empleando varias sentencias if pero es más sencillo si empleas la sentencia switch. Una sentencia switch tiene la siguiente forma:
switch (<expresión>) { case <expresión1>: <statement1>; ... ; break; case <expresión2>: <statement2>; ... ; break; ... default: <statement>; ... } Funciona así: primero se ejecuta la expresión. Después se compara con los resultados de las diferentes expresiones delante de las sentencias case. La ejecución continúa después de la sentencia case con el valor correcto, hasta que se encuentre una sentencia break. Si no se encuentra una sentencia case con el valor correcto, la ejecución continúa después de la sentencia default. (No es necesaria la sentencia default. Nota que se pueden colocar múltiples sentencias
case para la misma sentencia. También, no es necesaria la sentencia break. Si no existe una sentencia break, la ejecución simplemente continúa con el código para la siguiente sentencia case.
Ejemplo El siguiente programa lleva a cabo una acción según la tecla que se presione.
switch (keyboard_key) { case vk_left: case vk_numpad4: x -= 4; break; case vk_right: case vk_numpad6: x += 4; break; }
Sentencia Break La sentencia Break tiene esta forma:
break
Si se emplea en un ciclo for, while, repeat, en una sentencia switch o with, finaliza el ciclo o sentencia. Si es empleada fuera de una de estas sentencias finaliza el programa no el juego).
Sentencia Continue La sentencia Continue tiene esta forma
continue
Si se emplea dentro de un ciclo for, while, repeat o con una sentencia with, continua con el siguiente valor del ciclo for o de la sentencia with.
Sentencia Exit La sentencia Exit tiene esta forma:
exit
Simplemente termina la ejecución del programa/script actual. (¡No termina la ejecución del juego! Para ello necesitas la función game_end(); ver más abajo)
Funciones Una función tiene la siguiente estructura: nombre de la función, seguido por uno o varios argumentos entre paréntesis, separados por comas (también puede no incluir ningún argumento).
(<arg1>,<arg2>,...)
Hay dos tipos de funciones. En primer lugar, tenemos una gran cantidad de funciones internas, para controlar todos los aspectos del juego. Después, cualquier scipt que definas en el juego puede ser usado como una función.
Nota que para una función sin argumentos aún se necesitan los paréntesis. Algunas funciones devuelven valores y pueden ser empleadas en expresiones. Otras simplemente ejecutan órdenes.
Nota que es imposible usar una función como el lado izquierda de una asignación. Por ejemplo, no puedes escribir
instante_nearest(x,y,obj).speed = 0.
En lugar, debes escribir
(instance_nearest(x,y,obj)).speed = 0.
Scripts Cuando creas un script, querrás tener acceso a los argumentos enviados a él (ya sea cuando uses una acción script, o cuando llames al script como una función desde un programa u otro, o inclusive desde el mismo script). Estos argumentos se almacenan en las variables argument0, argument1, …, argument15. Por lo que puede haber como máximo 16 argumentos. (Nota: cuando se llama un script desde una acción, sólo se pueden especificar los primeros 5 argumentos). Pueden usar también argument[0], etc.
Los scripts también pueden devolver un valor, por lo que pueden ser empleados en expresiones. Para ello debes emplear la sentencia return:
return <expresión>
¡La ejecución del script termina en la sentencia return!
Ejemplo Aquí esta la definición de un script que calcula el cuadrado del argumento:
{ return (argument0*argument0); }
Para llamar un script desde una pieza de código, solo hazlo como cuando se hacen las llamadas a funciones. Esto es, escribe el nombre del script con sus argumentos entre paréntesis.
Construcciones With Como se indicó antes, es posible leer y cambiar el valor de las variables en otras instancias. Pero en ciertos casos querrás hacer mucho más con esas otras instancias. Por ejemplo, imagina que deseas mover todas las pelotas 8 píxeles hacia abajo. Pudieras pensar que eso se logra con el siguiente código
pelota.y = pelota.y + 8;
Pero no es correcto. El valor a la derecha de la asignación obtiene la coordenada y de la primera pelota y le suma 8. Entonces este nuevo valor se toma como la coordenada y para todas las pelotas. Por lo que el resultado es que todas las pelotas tienen la misma coordenada y. La sentencia
pelota.y += 8;
tendrá exactamente el mismo efecto porque es simplemente una abreviatura de la primera declaración. Entonces, ¿cómo logramos esto? Para ello existe la declaración with. Su forma general es
with (<expresión>) <sentencia>
<expresión> indica una o más instancias. Para esto puedes emplear el id de la instancia, o el nombre de un objeto (para indicar todas las instancias de este objeto) o uno de los objetos especiales (all, self, other, noone). <declaración> se ejecuta para cada una de las instancias indicadas, como si la instancia fuera la instancia (self) actual. Así, para mover todas las pelotas 8 píxeles hacia abajo, puedes escribir
with (pelota) y += 8;
Si deseas ejecutar múltiples declaraciones, colócalas entre corchetes. Por ejemplo, para mover todas las pelotas a una posición aleatoria, puedes usar
with (pelota) { x = random(room_width); y = random(room_height); }
Nota que, dentro de las sentencias, la instancia indicada se ha vuelto la instancia self. Entonces, la instancia self original ahora es la instancia other. Así, por ejemplo, para mover todas las pelotas a la posición de la instancia actual, puedes usar
with (pelota) { x = other.x; y = other.y; }
El uso de la declaración with es muy poderoso. A continuación te muestro unos cuantos ejemplos más. Para destruir todas las pelotas usas
with (pelota) instance_destroy();
Si una bomba explota y tu quieres destruir todas las instancias cercanas a ella puedes usar
with (all) { if (distance_to_object(other) < 50) instance_destroy(); }
Comentarios Puedes agregar comentarios a tus programas. Todo en una línea después de // no se ejecuta. Puedes hacer también una multi-línea de comentarios colocando el texto entre /* y */.
Funciones y variables en GML El GML contiene un gran número de funciones y variables internas. Con ellas puedes controlar cualquier parte del juego. Para todas las acciones existe una función correspondiente por lo que de hecho no necesitas emplear ninguna acción si prefieres emplear código. Pero hay muchas más funciones y variables que controlan aspectos del juego que no se pueden acceder sólo empleando acciones. Por lo que si deseas crear juegos más avanzados se te recomienda leer los siguientes capítulos para tener un panorama general de todo lo que es posible lograr. Por favor nota que estas variables y funciones pueden también emplearse cuando se envían valores para las acciones. Por lo que aún si no planeas emplear código o escribir algún script, aún obtendrás beneficios de esta información.
En los capítulos siguientes se emplean las siguientes convenciones. Los nombres de variables marcados con un * son sólo de lectura, es decir, no se puede cambiar su valor.
Los nombres de variables con [0...n] después de ellos son arrays. Se da el intervalo posible de sus índices.
Haciendo cálculos Game Maker contiene un gran número de funciones para realizar determinadas tareas. Aquí tienes una lista completa de estas funciones.
Esta sección está dividida en los temas: Constantes Game Maker incluye las siguientes constantes matemáticas:
true equivale a 1. false
equivale a 0.
pi equivale a 3.1415...
Funciones de valores reales Estas son las funciones disponibles para trabajar con números reales:
random(x) Devuelve un valor entre 0 y X. El valor devuelto es siempre menor que X. choose(val1,val2,val3,...) Devuelve uno de argumentos de forma aleatoria. La función acepta un máximo de 16 argumentos.
abs(x) Devuelve el valor absoluto de X. sign(x) Devuelve el signo de X (-1, 0 o 1). round(x) Devuelve el valor de X
redondeado al valor entero más cercano.
floor(x) Devuelve el valor de X redondeado hacia abajo. ceil(x) Devuelve el valor de X redondeado hacia arriba. frac(x) Devuelve la parte fraccional de X, sqrt(x) Devuelve la raíz cuadrada de X.
que es la parte situada tras el punto decimal.
El valor no debe ser negativo.
sqr(x) Devuelve el cuadrado de X. power(x,n) Devuelve X elevado a la potencia N. exp(x) Devuelve
E elevado a X.
ln(x) Devuelve el logaritmo neperiano (natural) de X. log2(x) Devuelve el logaritmo en base 2 de X. log10(x) Devuelve el logaritmo en base 10 de X. logn(n,x) Devuelve el logaritmo en base N de X. sin(x) Devuelve el seno de X (X en radianes). cos(x) Devuelve el coseno de X (X en radianes). tan(x) Devuelve la tangente de X (X en radianes). arcsin(x) Devuelve el arcoseno de X. arccos(x) Devuelve el arcocoseno de X.
arctan(x) Devuelve la arcotangente de X. arctan2(y,x)
Calcula la arcotangente de (Y/X), y devuelve un ángulo en el cuadrante correcto.
degtorad(x) Convierte grados a radianes. radtodeg(x) Convierte radianes a grados. min(val1,val2,val3,...) Devuelve el menor de los valores. La función soporta 16 argumentos. Deben ser todos números reales o cadenas de texto.
max(val1,val2,val3,...) Devuelve el mayor de los valores. La función soporta 16 argumentos. Deben ser todos números reales o cadenas de texto.
mean(val1,val2,val3,...) Devuelve el promedio de los valores. La función soporta 16 argumentos. Deben ser todos números reales.
median(val1,val2,val3,...) Devuelve el valor intermedio de los argumentos introducidos. (Cuando el número de argumentos es parejo, el menor de los dos valores intermedios, es el que devuelve la función. La función soporta 16 argumentos. Deben ser todos números reales.
point_distance(x1,y1,x2,y2) Devuelve la distancia existente entre el punto situado en (x1,y1) y el situado en (x2,y2).
point_direction(x1,y1,x2,y2) Devuelve la dirección desde el punto (x1,y1) hacia el punto (x2,y2) en grados.
lengthdir_x(len,dir) Devuelve la componente horizontal (x) del vector determinado por
la longitud y
dirección indicadas.
lengthdir_y(len,dir) Devuelve la componente vertical (y) del vector determinado por
la longitud y
dirección indicadas.
is_real(x) Averigua cuando X es un valor real.
(Diferenciándolo de una cadena de texto).
is_string(x) Averigua cuando X es una cadena de texto.
(Diferenciándolo de un número real).
Funciones de cadenas de texto Estas son las funciones disponibles para trabajar con cadenas de texto:
chr(val) Devuelve una cadena con el carácter al que hace referencia el código asci VAL. ord(str) Devuelve el código asci del primer carácter de la cadena de texto STR. real(str) Convierte una cadena de texto
en un número real. STR puede contener signos negativos, puntos
decimales o una parte exponencial.
string(val) Convierte el número real en una cadena de texto utilizando el formato estándar (sin decimales cuando se trata de un número entero y un máximo de dos dígitos decimales en cualquier otro caso).
string_format(val,tot,dec) Convierte VAL en una cadena de texto utilizando nuestro propio formato: TOT indica el máximo de dígitos y DEC el número de dígitos decimales.
string_length(str) Devuelve el número de caracteres de la cadena. string_pos(substr,str) Devuelve la posición de SUBSTR en STR (0 No encontrado).
string_copy(str,index,count) Devuelve una subcadena de STR, partiendo de la posición INDEX y de una longitud definida por COUNT.
string_char_at(str,index) Devuelve el carácter
situado en la posición INDEX de la cadena STR.
string_delete(str,index,count) Devuelve una copia de la cadena STR con una parte borrada, que empieza en INDEX y de una longitud definida por COUNT.
string_insert(substr,str,index) Devuelve una copia de la cadena STR con la subcadena SUBSTR añadida en la posición INDEX.
string_replace(str,substr,newstr) Devuelve una copia de STR con la primera ocurrencia de SUBSTR reemplazada por NEWSTR.
string_replace_all(str,substr,newstr) Devuelve una copia de STR con todas las ocurrencias encontradas de SUBSTR reemplazadas por la subcadena NEWSTR.
string_count(substr,str) Devuelve el número de ocurrencias de la subcadena SUBSTR existentes en STR.
string_lower(str) Devuelve una copia en minúsculas de la cadena STR. string_upper(str) Devuelve una copia en mayúsculas de la cadena STR. string_repeat(str,count) Devuelve una cadena con un número de copias de la cadena STR definido por COUNT.
string_letters(str) Devuelve una cadena de texto que solo contiene las letras de la cadena STR. string_digits(str) Devuelve una cadena que solo contiene los números de la cadena STR. string_lettersdigits(str) Devuelve una cadena que solo contiene los números y las letras de la cadena STR.
Trabajando con el tiempo y la fecha Game Maker dispone de varias funciones para trabajar con fechas y horas. La fecha y la hora se almacenan como un número real. La parte entera es el número de días que han pasado desde 12/30/1899 y parte decimal de este valor es la fracción de un día de 24 horas que ha transcurrido hasta el momento. Estas son las funciones disponibles:
date_current_datetime()Devuelve fecha y hora actual. date_current_date()Devuelve fecha actual ignorando la hora .date_current_time()Devuelve hora actual ignorando la fecha.
date_create_datetime(year,month,day,hour,minute,second) Crea un valor fecha-hora correspondiente a la fecha y hora indicados.
date_create_date(year,month,day) Crea un valor fecha-hora correspondiente a la fecha indicada. date_create_time(hour,minute,second) Crea un valor fecha-hora correspondiente a la hora indicada.
date_valid_datetime(year,month,day,hour,minute,second) Muestra si la hora y fecha indicados son válidos.
date_valid_date(year,month,day) Muestra si la fecha indicada es válida. date_valid_time(hour,minute,second) Muestra si la hora indicada es válida.
date_inc_year(date,amount) Devuelve una nueva fecha N años después de la fecha indicada. N debe ser un número entero.
date_inc_month(date,amount) Devuelve una nueva fecha N meses después de la fecha indicada. N debe ser un número entero.
date_inc_week(date,amount) Devuelve una nueva fecha N semanas después de la fecha indicada. N debe ser un número entero.
date_inc_day(date,amount) Devuelve una nueva fecha N días después de la fecha indicada. N debe ser un número entero.
date_inc_hour(date,amount) Devuelve una nueva fecha
N horas después de la fecha indicada. N debe
ser un número entero.
date_inc_minute(date,amount) Devuelve una nueva fecha N minutos después de la fecha indicada. N debe ser un número entero.
date_inc_second(date,amount) Devuelve una nueva fecha N segundos después de la fecha indicada. N debe ser un número entero.
date_get_year(date) Devuelve el año actual. date_get_month(date) Devuelve el mes actual. date_get_week(date) Devuelve la semana actual. date_get_day(date) Devuelve el día actual. date_get_hour(date) Devuelve la hora actual. date_get_minute(date) Devuelve el minuto actual. date_get_second(date) Devuelve el segundo actual. date_get_weekday(date) Devuelve el día de la semana actual. date_get_day_of_year(date) Devuelve el día del año especificado. date_get_hour_of_year(date) Devuelve la hora del año especificado. date_get_minute_of_year(date) Devuelve el minuto del año especificado. date_get_second_of_year(date) Devuelve el segundo del año especificado. date_year_span(date1,date2) Devuelve el número de años que hay entre las dos fechas. Reporta los años incompletos como una fracción.
date_month_span(date1,date2) Devuelve el número de meses que hay entre las dos fechas. Reporta los meses incompletos como una fracción.
date_week_span(date1,date2) Devuelve el número de semanas que hay entre las dos fechas. Reporta las semanas incompletas como una fracción.
date_day_span(date1,date2) Devuelve el número de días que hay entre las dos fechas. Reporta los días incompletos como una fracción.
date_hour_span(date1,date2) Devuelve el número de horas que hay entre las dos fechas. Reporta las horas incompletas como una fracción.
date_minute_span(date1,date2) Devuelve el número de minutos que hay entre las dos fechas. Reporta los minutos incompletos como una fracción.
date_second_span(date1,date2) Devuelve el número de segundos que hay entre las dos fechas. Reporta los segundos incompletos como una fracción.
date_compare_datetime(date1,date2) Compara los dos valores fecha-hora. Devuelve -1, 0, ó 1 dependiendo en si la primera fecha es anterior, igual, o posterior que la segunda.
date_compare_date(date1,date2) Compara los dos valores fecha-hora tomando en cuenta sólo la parte de la fecha. Devuelve -1, 0, ó 1 dependiendo en si la primera es anterior, igual, o posterior que la segunda.
date_compare_time(date1,date2) Compara los dos valores fecha-hora tomando en cuenta sólo la parte de la hora. Devuelve -1, 0, ó 1 dependiendo en si la primera es anterior, igual, o posterior que la segunda.
date_date_of(date) Devuelve la parte de la fecha del valor fecha-hora indicado, estableciendo la hora a 0. date_time_of(date) Devuelve la hora del valor fecha-hora indicado, estableciendo la fecha a 0. date_datetime_string(date) Devuelve una cadena indicando la fecha y hora definidos, en el formato predeterminado para el sistema.
date_date_string(date) Devuelve una cadena indicando la fecha definida en el formato predeterminado para el sistema.
date_time_string(date) Devuelve una cadena indicando la hora definida en el formato predeterminado para el sistema.
date_days_in_month(date) Devuelve el número de días que hay en el mes indicado. date_days_in_year(date) Devuelve el número de días que hay en el año indicado. date_leap_year(date) Define si el año indicado es un año bisiesto. date_is_today(date) Define si la fecha indicada es la actual.
Game play Hay una gran cantidad de variables y funciones que puedes emplear para definir el game play (jugabilidad). Estas en particular influyen en el movimiento y creación de instancias, el timing, y el manejo de los eventos.
La información sobre el game play se puede encontrar en las siguientes secciones: Moviéndose Obviamente, un aspecto importante de los juegos es el movimiento de las instancias de los objetos. Cada instancia tiene dos variables internas
x e y que indican la posición de la instancia. (Para ser precisos, indican el lugar donde se
encuentra el punto de origen del sprite). La posición (0,0) es la esquina superior izquierda del cuarto. Puedes cambiar la posición de la instancia al cambiar los valores de sus variables
x e y. Es lo que debes hacer si deseas movimientos
más complicados. Este código normalmente se coloca en el evento step del objeto.
Si el objeto se mueve con velocidad y dirección constantes, hay una manera más fácil de lograrlo. Cada instancia tiene una velocidad horizontal (hspeed) y vertical (vspeed). Ambas se indican en píxeles por paso (step). Una velocidad horizontal positiva indica movimiento a la derecha, una velocidad horizontal negativa indica movimiento a la izquierda. La velocidad vertical positiva es movimiento hacia abajo y la negativa indica movimiento hacia arriba. Por lo
que sólo debes establecer estos valores una vez (por ejemplo en el evento de creación) para dar al objeto un movimiento constante.
Hay otra manera muy diferente de especificar el movimiento, usando dirección (en grados 0-359), y velocidad (no debe ser negativa). Puedes configurar y leer estas variables para especificar un movimiento arbitrario. (Internamente se convierte a valores de
hspeed y vspeed). También tenemos la fricción y la gravedad, y la dirección de la
gravedad.
Finalmente, tenemos la función
motion_add(dir,speed) para agregar movimiento al actual.
Para concluir, cada instancia tiene las siguientes variables y funciones referentes a su posición y movimiento:
x
Su posición x.
y
Su posición y.
xprevious Su posición x anterior. yprevious xstart
Su posición y previa.
Su posición x inicial en el cuarto.
ystart Su posición y inicial en el cuarto. hspeed
Componente horizontal de la velocidad.
vspeed Componente vertical de la velocidad. direction Su dirección actual (0-360, contra las manecillas del reloj, 0 = a la derecha). speed Su velocidad actual (píxeles por step). friction Fricción actual (píxeles por step). gravity Cantidad actual de gravedad (píxeles por paso). gravity_direction Dirección de la gravedad (270 es hacia abajo). motion_set(dir,speed) Establece el movimiento a la velocidad speed y la dirección dir. motion_add(dir,speed) Agrega el movimiento al movimiento actual (como una suma vectorial).
Existe un gran número de funciones para ayudarte a definir el movimiento:
place_free(x,y) Devuelve si la instancia colocada en la posición (x, y) está libre de colisión. Normalmente se emplea para revisar antes de mover la instancia a la nueva posición.
place_empty(x,y) Devuelve si la instancia colocada en la posición (x, y) no se encuentra con nadie. Esta función también toma en cuenta las instancias no sólidas.
place_meeting(x,y,obj) Devuelve si la instancia colocada en la posición (x,y) se encuentra con un el objeto obj. obj puede ser un objeto en cuyo caso la función devuelve verdadero si se encuentra con una instancia de ese objeto. También puede ser el
id de una instancia, o la palabra especial other.
place_snapped(hsnap,vsnap) Devuelve si la instancia está alineada con los valores de snap hsnap y vsnap.
move_random(hsnap,vsnap) Mueve la instancia a una posición libre, y la alinea con los valores hsnap y vsnap,
al igual que la acción correspondiente.
move_snap(hsnap,vsnap) Alinea la instancia, como la acción correspondiente. move_wrap(hor,vert,margin) Teleporta la instancia cuando sale del room al lado opuesto. hor indica si debe teleportarse horizontalmente y
vert indica si debe teleprotarse verticalmente. margin indica cuánto debe
salir el origen de la instancia del room para teleportarse (es decir, un margen alrededor del room). Esta función se usa normalmente el evento Outside.
move_towards_point(x,y,sp) Mueve la instancia con velocidad sp hacia el punto (x,y). move_bounce_solid(adv) Rebotar contra objetos sólidos, como la acción correspondiente. adv indica si se emplea rebote avanzado, que toma en cuenta las paredes inclinadas.
move_bounce_all(adv) Rebotar contra todas las instancias, en lugar de sólo con las sólidas. move_contact_solid(dir,maxdist) Mover la instancia en la dirección dir hasta que haya contacto con un objeto sólido. Si no hay collision en la posición actual, la instancia es colocada justo antes de donde ocurre una colisión. Si ya hay una colisión en la posición actual, la instancia no se mueve. Puedes especificar la distancia máxima a mover la instancia maxdist (emplea un número negativo para indicar distancia arbitraria).
move_contact_all(dir,maxdist) Igual que la función anterior pero esta vez se detiene hasta que haya contacto con cualquier objeto, no solo sólidos.
move_outside_solid(dir,maxdist) Mueve la instancia en la dirección dir hasta que no esté al alcance de un objeto sólido. Si no hay collision en la posición actual, no se mueve la instancia. Puedes especificar la distancia máxima a mover (usa un valor negativo para indicar una distancia arbitraria).
move_outside_all(dir,maxdist) Igual que la anterior pero se mueve hasta estar fuera de alcance de cualquier objeto, no solo objetos sólidos.
distance_to_point(x,y) Devuelve la distancia de la caja límite de la instancia actual hacia el punto (x,y). distance_to_object(obj) Devuelve la distancia de la instancia actual a la instancia más cercana del objeto obj.
position_empty(x,y) Indica si no hay nada en la posición (x,y). position_meeting(x,y,obj) Indica si en la posición (x,y) hay una instancia obj. obj puede ser un objeto, una
id de una instancia, o las palabras clave self, other o all.
Paths
En Game Maker puedes definir caminos o trayectorias (paths) y ordenar a las instancias que los sigan. Aunque puedes usar las acciones para esto, existen funciones que te dan más flexibilidad:
path_start(path,speed,endaction,absolute) Comienza un path para la instancia actual. path
es el nombre del path que deseas iniciar.
speed es la velocidad
con la que la instancia debe moverse por el
path (una velocidad negativa indica que la instancia se moverá al revés sobre el path).
endaction indica que
debería ocurrir cuando la instancia llegue al final del camino. Puedes usar los siguientes valores para esto:
0 : parase 1: continuar desde la posición inicial del path (s el path no está cerrado saltamos a la posición inicial) 2: continuar desde la posición inicial 3: recorrer el path al revés (cambia el signo de la velocidad) El argumento
absolute debe ser true o false. Cuando es true se usan las coordenadas absolutas del path. Cuando
es false el path es relativo a la posición actual de la instancia. Para ser más precisos, si la velocidad es positiva el punto inicial del path se colocará en la posición actual de la instancia y se seguirá desde ahí. Cuando la velocidad es negativa, el punto final del path se colocará en la posición de la instancia y el path se seguirá al revés desde ahí.
path_end() Termina el path para la instancia actual. path_index* Índice del path que la instancia sigue. No se puede cambiar directamente, debes utilizar la función path_start(path,speed,endaction,absolute). path_position Posición en el path actual. 0 es el principio del path y 1 es el final. Los valores deben estar entre 0 y 1.
path_positionprevious Posición previa en el path. Esto se puede usar en eventos de colisión para colocar la instancia en la posición anterior antes de una colisión.
path_speed Velocidad (en píxels por paso) con la que la instancia sigue el path. Con una velocidad negativa el path se recorre en sentido inverso.
path_orientation Orientación (antihoraria) en la que se realiza el path. 0 es la orientación normal del path. path_scale Escala del path. Auméntala para hacer el path más grande. 1 es el valor normal del path. path_endaction La acción que se debe ejecutar al finalizar el path. Puedes indicar los valores explicados más arriba.
Planificación del movimiento
La planificación del movimiento te ayuda a mover una instancia de un punto a otro esquivando otras instancias que pudiera encontrarse por el camino (por ejempo, paredes). Resulta imposible dar funciones generales que funcionen en cualquier situación. Así mismo, las operaciones necesarias para calcular un camino libre de colisiones consumen bastantes recursos, así que debes usar estas funciones con criterio. Ten todo esto en cuenta cuando uses las siguientes funciones.
Game Maker dispone de diferentes formas de planificar el movimiento. La más simple consiste en hacer que una instancia de un paso hacia la posición final, intentando ir en línea recta pero tomando otra dirección si esto último resulta imposible. Estas funciones deben usarse en el evento step de la instancia y se corresponden a las acciones ya comentadas:
mp_linear_step(x,y,stepsize,checkall) Esta función hace que la instancia de un paso hacia la posición (x,y). La longitud del paso se indica con el parámetro posición no se moverá. Si
stepsize. Si la instancia ya ha llegado a esa
checkall es true la instancia se parará cuando choque con una instancia de cualquier
objeto. Si es false, sólo se parará al chocar con un objeto sólido. Esta función no propone un camino alternativo, simplemente se parará si encuentra un obstáculo. La función devuelve si se ha alcanzado el destino.
mp_linear_step_object(x,y,stepsize,obj) Igual que la anterior, pero esta vez sólo se tienen en cuenta las instancias del objeto
obj. obj puede ser un objeto o una id de una instancia particular.
mp_potential_step(x,y,stepsize,checkall) Igual que las anteriores, pero en este caso la instancia intentará esquivar los obstáculos que encuentre. Cuando la instancia se choque con un obstáculo cambiará su dirección para tratar de esquivar el objeto, moviéndose alrededor de él. Puede que no siempre se consiga llegar a la meta, pero la función siempre intentará acercar lo más posible a la instancia. Devuelve true si se llega a la meta.
mp_potential_step_object(x,y,stepsize,obj) ) Igual que la anterior, pero esta vez sólo se tienen en cuenta las instancias del objeto
obj. obj puede ser un objeto o una id de una instancia particular.
mp_potential_settings(maxrot,rotstep,ahead,onspot) La función anterior hace su trabajo usando un número de parámetros que pueden ser cambiados con esta función. El método funciona como sigue: primero la instancia intenta moverse en línea recta hacia la meta. Para ello, mira un número de pasos adelante para ver si hay algún obstáculo. Este número de pasos corresponde al valor
ahead (por defecto 3). Reduciendo este valor la
instancia comenzará a cambiar su dirección más tarde si encuentra un obstáculo. Aumentándolo cambiará antes de dirección. Si detectamos una colisión, la función mira a la derecha y a la izquierda para encontrar un camino libre. Esto se realiza en pasos de tamaño
rotstep (por defecto 10). Reduciéndolo conseguimos que la instancia tenga más
posibilidades para moverse pero la función será más lenta. El parámetro
maxrot
(por defecto 30) indica cuánto
puede cambiar como máximo la dirección en un paso. Así que aunque pueda moverse en línea recta hacia la meta no lo hará si debe girar más de lo indicado por este parámetro. Aumentándolo conseguimos que la instancia pueda girar más en cada paso, haciendo que sea más fácil encontrar un camino aunque éste será menos uniforme. Disminuyendo su valor el camino será más suave pero la instancia realizará giros más largos, haciendo que a veces no pueda llegar exactamente a la meta. Cuando la instancia no se puede mover en ninguna dirección el comportamiento dependerá del valor de
onspot. Si onspot es true la instancia rotará en su posición la cantidad indicada por maxrot. Si es
false se parará (esto es útil para coches, por ejemplo, pero reduce las posibilidades de encontrar un camino hacia la meta).
Observa que el acercamiento potencial sólo usa información local. Así que sólo encontrará un camino si la información es suficiente para determinar la dirección correcta. Por ejemplo, normalmente no podrá encontrar el camino para escapar de un laberinto.
El segundo tipo de funciones calcula un camino libre colisiones. Una vez que el camino se ha calculado puedes asignárselo a la instancia para que se mueva hacia la meta como si fuera un path normal que tú hubieras creado. El cálculo del camino tarda un poco pero una vez hecho la ejecución del path es muy rápida. Por supuesto, esto es válido si la situación no cambia (por ejemplo, si los obstáculos se mueven). Entonces necesitarás volver a calcular el path. De nuevo, estas funciones pueden fallar en algunas circunstancias. Estas funciones sólo están disponibles en la versión registrada de Game Maker.
Las dos primeras funciones usan el acercamiento por movimiento lineal y potencial que se usan en las funciones anteriores.
mp_linear_path(path,xg,yg,stepsize,checkall) Calcula un path en línea recta para la instancia desde su posición hasta (xg,yg) usando el paso especificado en
stepsize. Usa pasos como en la función
mp_linear_step(). El path indicado debe existir con anterioridad a la llamada de la función y será sobreescrito por el nuevo path (consulta el capítulo sobre cómo crear y destruir paths). La función devuelve si se ha encontrado un path. Si no consigue encontrar un camino, la función devolverá un path hasta la posición donde la instancia quedó bloqueada.
mp_linear_path_object(path,xg,yg,stepsize,obj) Igual que la anterior, pero esta vez sólo se tienen en cuenta las instancias del objeto
obj. obj puede ser un objeto o una id de una instancia particular.
mp_potential_path(path,xg,yg,stepsize,factor,checkall) Esta función calcula un camino para instancia desde su posición actual y orientación hasta (xg,yg) usando el paso especificado en
stepsize e intentando evitar colisionar con los obstáculos. Utiliza pasos potenciales como la función mp_potential_step() y los parámetros se pueden configurar con mp_potential_settings(). El path indicado debe existir con anterioridad a la llamada de la función y será sobreescrito por el nuevo path (consulta el capítulo sobre cómo crear y destruir paths). La función devolverá si se ha encontrado un camino. Para evitar que la función continúe calculando para siempre debes especificar un
factor mayor que 1. La función se detendrá y
devolverá un mensaje de error si no puede encontrar un camino que sea más corto que la distancia del origen a la meta multiplicada por
factor. Un factor de 4 es normalmente suficiente pero si crees que la instancia tendrá un
camino largo puedes aumentarlo. Si la función falla se crea el camino en dirección a la meta pero la instancia no llegará la meta.
mp_potential_path_object(path,xg,yg,stepsize,factor,obj) Igual que la anterior, pero esta vez sólo se tienen en cuenta las instancias del objeto
obj. obj puede ser un objeto o una id de una
instancia particular.
Las demás funciones usan un mecanismo mucho más complejo basado en rejillas (un algoritmo A*). Tiene más sexito a la hora de encontrar caminos y hacerlos más cortos, pero requiere más trabajo por tu parte. Además, también puede fallar en algunas ocasiones. El funcionamiento es como sigue: primero situamos una rejilla sobre la parte del cuarto afectada. Puedes usar si quieres usar una rejilla fina (más lento) o más espaciada. Después, determinamos las celdas de la rejilla ocupadas por objetos relevantes (usando colisión precisa o la caja de contorno) y marcamos estas celdas como prohibidas. Así que una celda estará prohibida si parte de un obstáculo la está ocupando. Finalmente especificamos la posición inicial y final, que deben estar en celdas libres de la rejilla y la función calcula el camino más corto entre ellas. El camino irá de centro a centro de las celdas libres. Así que las celdas deben ser lo suficientemente grandes como para que la instancia entre totalmente dentro de ellas. Ahora puedes asignar el path a una instancia y hacer que lo siga.
Este sistema es muy potente (se usa en muchos juegos profesionales) pero requiere que lo planifiques con cuidado. Debes determinar la zona del cuarto sobre la que situar la rejilla y el tamaño de las celdas con la mayor precisión posible. También debes decidir qué objetos deben tomarse en cuenta y si es necesaria la colisión precisa o no. Todos estos parámetros afectan de manera muy notable a la eficiencia del método.
En particular, el tamaño de las celdas es crucial. Recuerda que las celdas deben lo suficientemente grandes como para que la instancia que se mueve entre totalmente dentro de ellas (ten cuidado con la posición del origen de la instancia y recuerda que puedes mover el path para hacer que el centro del objeto coincida con el centro de la celda). Por otro lado, cuanto menores sean las celdas más caminos diferentes podrás encontrar. Si haces las celdas demasiado grandes puede que unos pocos objetos ocupen todas las celdas cerrando todos los caminos posibles.
Las funciones para el método de rejilla son:
mp_grid_create(left,top,hcells,vcells,cellwidth,cellheight) Esta función crea la rejilla. Devuelve un índice que debe ser usado en las demás funciones. Puedes crear y mantener varias rejillas al mismo tiempo.
left y top indican la posición de la esquina superior izquierda de la rejilla y hcells y
vcells indican el número de celdas horizontales y verticales respectivamente. Finalmente, cellwidth y cellheight indican la anchura y altura de las celdas. mp_grid_destroy(id) Destruye la rejilla indicada y libera la memoria usada. No olvides llamar a esta función cuando no necesites usar más la rejilla.
mp_grid_clear_all(id) Marca todas las celdas como libres. mp_grid_clear_cell(id,h,v) Marca la celda indicada como libre (la primera celda es la 0,0). mp_grid_clear_rectangle(id,left,top,right,bottom) Marca como libres todas las celdas que intersectan el rectángulo definido en coordenadas absolutas del cuarto.
mp_grid_add_cell(id,h,v) Marca ls celdas indicadas como prohibidas. mp_grid_add_rectangle(id,left,top,right,bottom) Marca todas las celdas que intersectan el rectángulo como prohibidas.
mp_grid_add_instances(id,obj,prec) Marca todas las celdas que intersectan una instancia del objeto indicado como prohibidas. También puedes especificar una id de una instancia concreta, o la palabra clave para indicar todas las instancias.
all
prec indica si hay que usar colisión precisa (sólo funcionará si en el sprite de la
instancia está activada la misma opción).
mp_grid_path(id,path,xstart,ystart,xgoal,ygoal,allowdiag) Calcula el path a través de la rejilla. El path indicado debe existir con anterioridad a la llamada de la función y será sobreescrito por el nuevo path (consulta el capítulo sobre cómo crear y destruir paths). path y
xgoal
e
xstart e ystart indican el comienzo del
ygoal las coordenadas de la meta. allowdiag indica si se permiten movimientos diagonales
entre celdas o sólo horizontales y verticales. La función devuelve si consiguió calcular un path (observa que el path es independiente de la instancia actual).
mp_grid_draw(id) Esta función dibuja la rejilla marcando las celdas libres y prohibidas (muy útil para buscar errores).
Detección de colisiones
Al planificar movimientos o decidir ciertas acciones es importante comprobar si ocurren colisiones con otras instancias en otras posiciones. Las funciones siguientes se utilizan para esto. Todas ellas tienen 3 argumentos en común: el argumento
obj puede ser un objeto, la palabra clave all, o la id de una instancia. El argumento prec indica si se
debe usar colisión precisa o la caja de contorno de la instancia (la colisión precisa sólo funciona si el sprite de la instancia tiene activada la misma opción). El argumento
notme indica si no se debe tener en cuenta a la instancia que
llama a la función. Todas estas funciones devuelven la id de una de las instancias con las que se detecta colisión. Si no hay colisión devuelven un valor negativo.
collision_point(x,y,obj,prec,notme) Comprueba si hay una colisión en el punto (x,y) con instancias del objeto obj.
collision_rectangle(x1,y1,x2,y2,obj,prec,notme) Comprueba si hay una colisión rectángulo (sólido) con las esquinas indicadas e instancias del objeto
entre el
obj. Por ejemplo, puedes usar esta función para
ver si un área está libre de obstáculos.
collision_circle(xc,yc,radius,obj,prec,notme) Comprueba si hay una colisión circunferencia (sólido) con centro (xc,yc) y radio r e instancias del objeto
entre la
obj. Puedes usar esta función para ver si
un objeto está cerca de una posición.
collision_ellipse(x1,y1,x2,y2,obj,prec,notme) Comprueba si hay una colisión elipse (sólida) con las esquinas indicadas e instancias del objeto
obj.
collision_line(x1,y1,x2,y2,obj,prec,notme) Comprueba si hay una colisión que va de (x1,y1) a (x2,y2) e instancias del objeto
entre la
entre la línea
obj. Esta función es muy poderosa. Puedes usarla para
comprobar si una instancia puede ver a otra chequeando si entre ellas hay una pared
Instancias
Las unidades básicas del juego son las instancias. Durante el juego, puedes cambiar varios aspectos de estas instancias. También puedes crear o destruir instancias. Además de las variables de movimiento y las de dibujo cada instancia posee las siguientes variables:
object_index* Índice del objeto del cual ésta es una instancia. No se puede cambiar. id* La id única de la instancia (>= 100000) (Al definir cuartos la id de la instancia bajo el puntero del ratón es indicada). No se puede cambiar.
mask_index Índice de l sprite usado como máscara para las colisiones. Si indicas -1 la máscara será igual al sprite de la instancia.
solid Indica si la instancia es sólida o no. persistent Indica si la instancia es persistente y reaparecerá al moverse a otro cuarto. A veces puedes querer volver a ponerlo a 0 (por ejemplo, al volver al primer cuarto).
Al trabajar con instancias hay un problema: no es fácil identificar una instancia concreta. No tienen un nombre. Cuando sólo hay una instancia de un objeto puedes acceder a ella usando el nombre del objeto pero normalmente necesitas conocer la id de la instancia. Este identificador único se puede usar en construcciones with y para identificar la instancia. Afortunadamente, las siguientes variables te ayudan a localizar la id de una instancia:
instance_count* Número de instancias que existen en el cuarto. instance_id[0..n-1]* La id de la instancia número n. Observa que la asignación de las instancias al
instance_id[] cambia en cada step, así que debes actualizar este
valor. Por ejemplo: imagina que cada unidad en tu juego tiene un poder y quieres encontrar la más poderosa de todas. Puedes hacerlo con el siguiente código:
{ maxid = -1; maxpower = 0; for (i=0; i maxpower) {maxid = iii; maxpower = iii.power;} } } }
Después del bucle maxid contendrá la id de la instancia más podersa (No destruyas instancias durante un bucle como éste porque se eliminarán inmediatamente y te saltarás instancias existentes).
instance_find(obj,n) Devuelve la id de la instancia n+1 de tipo obj. obj puede ser un objeto o la palabra clave
all. Si no existe se devuelve el objeto especial noone. Recuerda que el orden de las instancias
cambia en cada step así que no puedes usar valores de steps anteriores.
instance_exists(obj) Devuelve si existe alguna instancia del objeto obj. obj puede ser un objeto, la id de una instancia o la palabra clave
all.
instance_number(obj) Devuelve el número de instancias de tipo obj. obj puede ser un objeto o la palabra clave
all.
instance_position(x,y,obj) Devuelve la id de la instancia de tipo obj en la posición (x,y). Cuando hay varias instancias en esa posición se devuelve la id de la prtimera. no existe se devuelve el objeto especial
noone
obj puede ser un objeto o la palabra clave all. Si
instance_nearest(x,y,obj) Devuelve la id de la instancia de tipo obj que esté más cercana en ese momento a (x,y).
obj puede ser un objeto o la palabra clave all.
instance_furthest(x,y,obj) Devuelve la id de la instancia de tipo obj que esté más lejana en ese momento a (x,y).
obj puede ser un objeto o la palabra clave all.
instance_place(x,y,obj) Devuelve la id de la instancia de tipo obj encontrada cuando la instancia actual se coloca en la posición (x,y). especial
obj puede ser un objeto o la palabra clave all. Si no existe se devuelve el objeto
noone.
Las siguientes funciones se usan para crear y destruir instancias:
instance_create(x,y,obj) Crea una instancia de obj en la posición (x,y). La función devuelve la id de la nueva instancia creada.
instance_copy(performevent) Crea una copia de la instancia actual. El argumento indica si se debe ejecutar el evento create en la nueva instancia. La función devuelve la id de la nueva copia.
instance_destroy() Destruye la instancia actual. instance_change(obj,perf) Cambia la instancia a una del tipo obj. perf indica si se deben ejecutar los eventos de destrucción y creación.
position_destroy(x,y) Destruye toda las instancias cuyo sprite pasa por el punto (x,y). position_change(x,y,obj,perf) Cambia todas las instancias en la posición indicada a otras del tipo obj. perf indica si se deben ejecutar los eventos de destrucción y creación.
Desactivando instancias
Cuando creas un cuarto muy grande, por ejemplo en juegos de plataformas, con una vista (view) muy pequeña, muchas instancias se quedan fuera de la vista. Aunque no sean visibles, estas instancias siguen ejecutando sus eventos. También, al efectuar chequeos de colisión son tomadas en cuenta. Esto puede hacer que el juego se ralentice. Para remediar esto, Game Maker contiene unas funciones para desactivar o activar instancias. Pero antes de usarlas debes entender cómo funcionan.
Cuando desactivas instancias es como si las eliminaras del juego. No son visibles, no ejecutan sus eventos,…así que para todas las funciones y acciones estas instancias ya no existen y no son tomadas en cuenta. Así consigues que el juego sea más rápido. Pero ten cuidado, ya que esto puede generar errores en tu juego. Por ejemplo, al eliminar todas las instancias de un objeto, las instancias que estén desactivadas no serán eliminadas! Así, una llave que recoja el jugador no podrá abrir una puerta que esté desactivada, por ejemplo.
El error más crucial que puedes hacer es el de desactivar la instancia que se encarga de activar las demás instancias. Para evitar esto algunas funciones permiten especificar si la instancia que desactiva a las demás debe ser desactivada o no.
Las rutinas disponibles son las siguientes:
instance_deactivate_all(notme) Desactiva todas las instancias del cuarto. Si notme es true la instancia actual no es desactivada (normalmente es lo que se desea).
instance_deactivate_object(obj) Desactiva todas las instancias en el cuarto del objeto especificado. También puedes indicar
all para desactivar todas las instancias o la id de una instancia concreta para desactivarla.
instance_deactivate_region(left,top,width,height,inside,notme) Desactiva todas las instancias en la región indicada (es decir, todas aquellas cuya caja de contorno está parcial o completamente dentro de la región indicada). Si desactivadas. Si
inside es igual a false las instancias completamente fuera de la región son
notme es true la instancia actual no es desactivada (normalmente es lo que se desea).
instance_activate_all() Activa todas las instancias del cuarto. instance_activate_object(obj) Activa todas las instancias en el cuarto del objeto especificado. También puedes indicar
all para activar todas las instancias o la id de una instancia concreta para activarla.
instance_activate_region(left,top,width,height,inside) Activa las instancias dentro de la región especificada. Si
inside es false las instancias fuera de la región son activadas.
Por ejemplo, para desactivar todas las instancias fuera de la vista y activar las que estén dentro podemos poner este código en el evento step del personaje del jugador:
{ instance_activate_all(); instance_deactivate_region(view_xview[0],view_yview[0], view_wview[0],view_hview[0],false,true); }
Normalmente es mejor usar una región ligeramente mayor que la vista.
Timing
Los buenos juegos requirieron de cuidado especial de los tiempos en que las cosas se llevaban a cabo (timing). Afortunadamente el Game Maker se ocupa de la mayor parte del timing por ti. Se asegura de que las cosas ocurran con un ritmo constante. Este ritmo es definido al definir los cuartos. Pero puedes cambiarlo usando la variable global room_speed. Así por ejemplo, puedes incrementar lentamente la velocidad del juego, haciéndolo más difícil, agregando una muy pequeña cantidad (como 0.001) a room_speed en cada step. Si tu máquina es lenta la velocidad del juego pudiera no alcanzarse. Esto puede comprobarse usando la variable fps que monitorea constantemente el número actual de cuadros por segundo. Finalmente, para un timing avanzado puedes usar la variable current_time que te da el número de milisegundos desde que la computadora fue iniciada. Aquí está la colección completa de variables disponibles (sólo la primera puede ser cambiada):
room_speed Velocidad del juego en el cuarto actual (en steps por segundo). fps* Número de cuadros que son dibujados por segundo. current_time* Número de milisegundos que han pasado desde que el sistema fue iniciado. current_year* El año actual. current_month* El mes actual. current_day* El día actual. current_weekday* El día actual de la semana (1=domingo, …, 7=sábado). current_hour* La hora actual. current_minute* El minuto actual. current_second* El segundo actual.
Algunas veces querrás detener el juego por un corto periodo. Para esto, usa la función sleep:
sleep(numb) Pausa el juego durante numb milisegundos.
Como debes saber, cada instancia tiene 12 diferentes alarmas que puedes configurar. Para cambiar los valores (u obtener los valores) de las diferentes alarmas usa la siguiente variable:
alarm[0..11] Valor de la alarma indicada. (Nota: ¡las alarmas solo se actualizan cuando el evento de alarma para el objeto contiene acciones!)
Hemos visto que para los problemas de un timing complejo puedes usar el recurso de las líneas de tiempo (time lines). Cada instancia puede tener un recurso time line asociado con ella. Las siguientes variables están relacionadas con esto:
timeline_index Índice de la time line asociada con la instancia. Puedes establecerlo a una time line en particular para usarla. Ponlo en –1 para dejar de usar la time line para la instancia.
timeline_position Posición actual dentro de la time line. Puedes cambiarla para saltar o repetir ciertas partes.
timeline_speed Normalmente, en cada step la posición en la time line se incrementa en 1. Puedes cambiar esta cantidad configurando esta variable a un valor diferente. Puedes usar números reales, por ejemplo 0.5. Si el valor es mayor que uno, varios momentos pueden ocurrir dentro del mismo tiempo del step. Se realizarán en el orden correcto, por lo que no se saltará ninguna acción.
Rooms
Los juegos funcionan en cuartos. Cada cuarto tiene un índice que se indica por el nombre del cuarto. El cuarto actual es almacenado en la variable room. No puedes asumir que los cuartos están numerados en un orden consecutivo. Por lo que nunca sumes o restes un número de la variable room. En lugar de ello usa las funciones y variables indicadas abajo. Por lo que una típica pieza de código que usarás sería:
{ if (room != room_last) { room_goto_next(); } else { game_end(); } }
Las siguientes variables y funciones se relacionan con los cuartos (rooms).
room Índice del cuarto actual; puede cambiarse para ir a un cuarto diferente, pero mejor usa las rutinas listadas abajo.
room_first* Índice del primer cuarto en el juego. room_last* Índice del ultimo cuarto en el juego. room_goto(numb) Ir al cuarto con indice numb. room_goto_previous()Ir al cuarto anterior. room_goto_next()Ir al siguiente cuarto.
room_restart() Reiniciar el cuarto actual. room_previous(numb)Devuelve el índice del cuarto anterior a numb (-1 = ninguno) pero no va a él. room_next(numb) Devuelve el índice del cuarto posterior a numb (-1 =ninguno). game_end() Finaliza el juego. game_restart() Reinicia el juego.
Los cuartos tienen varias propiedades adicionales:
room_width* Ancho del cuarto en píxeles. room_height* Alto del cuarto en píxeles. room_caption Título de la ventana del cuarto. room_persistent Indica si el cuarto es persistente.
Muchos juegos ofrecen al jugador la posibilidad de guardar el juego y cargar un juego guardado. En el Game Maker esto ocurre automáticamente cuando el jugador presiona para guardar y para cargar. También puedes guardar y cargar juegos desde una pieza de código (nota que la carga sólo se lleva a cabo al final del step actual).
game_save(string) Guarda el juego al archivo con nombre string. game_load(string) Carga el juego del archivo con nombre string.
Ten en cuenta que sólo los datos básicos del juego son guardados. Por ejemplo, si guardas cuando una canción está sonando, al cargar el juego la canción no sonará desde ese momento. Los recursos editados tampoco son guardados, ni las partículas, los contenidos de las estructuras de datos ni la configuración multijugador.
Score
Otro aspecto importante de muchos juegos es el score (marcador), la energía, y el número de vidas. El Game Maker mantiene el
score en la variable global score y el número de vidas en la variable global lives. Puedes
cambiar el score simplemente cambiado el valor de esta variable. Lo mismo se aplica para la energía y las vidas. Si la variable
lives es mayor que 0 y se vuelve menor o igual a 0 se ejecuta el evento no-more-lives para todas las
instancias. Si no quieres mostrar el score y las vidas en el título, pon la variable
show_score, etc., a falso.
También puedes cambiar el título del marcador, de las vidas o de la energía. Para juegos más complicados mejor muestra el score tú mismo.
score El marcador actual. lives El número de vidas. health La energía actual (0-100). show_score Indica si se muestra el marcador en el título de la ventana. show_lives Indica si se muestra el número de vidas en el título de la ventana. show_health Indica si se muestra la energía en el título de la ventana. caption_score El título empleado para el marcador. caption_lives El título empleado para el número de vidas. caption_health El título para la energía.
Generando eventos
Como sabes, el Game Maker está completamente manejado por eventos. Todas las acciones ocurren como resultado de eventos. Hay una gran cantidad de eventos diferentes. Los eventos de creación y destrucción ocurren cuando una instancia es creada o destruida. En cada step, el sistema maneja primero los eventos de alarma. Después los eventos de teclado y ratón, y luego el siguiente evento step. Después de esto las instancias son colocadas en su nueva posición después de lo cual se maneja el evento de colisión. Finalmente el evento draw se usa para dibujar las instancias (nota que cuando empleas múltiples vistas el evento draw es llamado varias veces en cada step). También puedes aplicar un evento a la instancia actual desde una pieza de código. Se tienen las siguientes funciones:
event_perform(type,numb) Realiza el evento numb del tipo type para la instancia actual. Se pueden emplear los siguientes tipos de eventos:
ev_create ev_destroy ev_step ev_alarm ev_keyboard ev_mouse ev_collision ev_other ev_draw ev_keypress ev_keyrelease Cuando hay varios eventos del tipo dado, numb puede usarse para especificar el evento preciso. Para el evento de alarma
numb puede tener un valor de 0 a 11. Para el evento de teclado puedes usar el código de tecla para la tecla.
Para los eventos de ratón puedes usar las siguientes constantes:
ev_left_button ev_right_button ev_middle_button
ev_no_button ev_left_press ev_right_press ev_middle_press ev_left_release ev_right_release ev_middle_release ev_mouse_enter ev_mouse_leave ev_mouse_wheel_up ev_mouse_wheel_down ev_global_left_button ev_global_right_button ev_global_middle_button ev_global_left_press ev_global_right_press ev_global_middle_press ev_global_left_release ev_global_right_release ev_global_middle_release ev_joystick1_left ev_joystick1_right ev_joystick1_up ev_joystick1_down ev_joystick1_button1 ev_joystick1_button2 ev_joystick1_button3 ev_joystick1_button4 ev_joystick1_button5 ev_joystick1_button6 ev_joystick1_button7 ev_joystick1_button8 ev_joystick2_left ev_joystick2_right ev_joystick2_up ev_joystick2_down ev_joystick2_button1 ev_joystick2_button2
ev_joystick2_button3 ev_joystick2_button4 ev_joystick2_button5 ev_joystick2_button6 ev_joystick2_button7 ev_joystick2_button8 Para el evento de collision proporcionas el índice del otro objeto. Finalmente, para el evento other puedes usar las siguientes constantes:
ev_outside ev_boundary ev_game_start ev_game_end ev_room_start ev_room_end ev_no_more_lives ev_no_more_health ev_animation_end ev_end_of_path ev_user0 ev_user1 ev_user2 ev_user3 ev_user4 ev_user5 ev_user6 ev_user7 ev_user8 ev_user9 ev_user10 ev_user11 ev_user12 ev_user13 ev_user14 ev_user15
Para el evento step puedes dar el índice usando las siguientes constantes:
ev_step_normal ev_step_begin ev_step_end event_perform_object(obj,type,numb) Esta función funciona igual que la anterior pero esta vez puedes especificar eventos en otro objeto. Nota que las acciones en estos eventos se aplican a la instancia actual, no a las instancias del objeto dado.
event_user(numb)
En los eventos other también puedes definir 16 eventos definidos por el usuario. Estos son
ejecutados solo si llamas esta función. Numb debe tener valores de 0 a 11.
event_inherited()
Ejecuta el evento heredado. Esto sólo funciona si la instancia tiene un objeto padre.
Puedes obtener información sobre el evento actualmente ejecutado usando las siguientes variables de sólo lectura:
event_type* El tipo del evento que se está ejecutando. event_number* El número del evento que se está ejecutando. event_object* El índice del objeto para el cual se está ejecutando el evento actual. event_action* El índice de la acción que está siendo ejecutada (0 es la primera en el evento, etc.)
Otras variables y funciones
Aquí puedes ver algunas variables y funciones que se refieren a los errores.
error_occurred Indica si ha ocurrido un error error_last Cadena de texto que indica el último mensaje de error show_debug_message(str) Muestra la cadena str en modo debug
Las siguientes funciones te permiten saber si ciertas variables existen, darles un valor o leerlo. En todas ellas el nombre de la variable se pasa como una cadena de texto:
variable_global_exists(name) Devuelve si la variable global con el nombre especificado existe. variable_local_exists(name) Devuelve si la variable local con el nombre especificado existe para la instancia actual.
variable_global_get(name) Devuelve el valor de la variable global indicada. variable_global_array_get(name,ind) Devuelve el valor de índice ind del array global con el nombre indicado.
variable_global_array2_get(name,ind1,ind2) Devuelve el valor de índice ind1, ind2 del array bidimensional global con el nombre indicado.
variable_local_get(name) Devuelve el valor de la variable local indicada para la instancia actual. variable_local_array_get(name,ind) Devuelve el valor de índice ind del array locall con el nombre indicado.
variable_local_array2_get(name,ind1,ind2) Devuelve el valor de índice ind1, ind2 del array bidimensional global con el nombre indicado.
variable_global_set(name,value) Otorga el valor indicado a la variable global especificada. variable_global_array_set(name,ind,value) Otorga el valor indicado al elemento ind del array global especificado.
variable_global_array2_set(name,ind1,ind2,value) Otorga el valor indicado al elemento ind 1, ind2 del array bidimensional global especificado.
variable_local_set(name,value) Otorga el valor indicado a la variable local especificada. variable_local_array_set(name,ind,value) Otorga el valor indicado al elemento ind del array local especificado.
variable_local_array2_set(name,ind1,ind2,value) Otorga el valor indicado al elemento ind 1, ind2 del array bidimensional local especificado.
Por ejemplo, puedes escribir:
{ if variable_global_exists('ammunition') global.ammunition += 1 else global.ammunition = 0 }
También puees usar estas funciones para pasar variables a los scripts usando referencias, utilizando el nombre de las variables como cadenas de texto.
Puedes cambiar la prioridad del programa usando la función :
set_program_priority(priority) Cambia la prioridad del programa. Debes indicar un valor comprendido entre -3 y +3. Usando -3 el programa se ejecutará sólo si no hay otro proceso que requiera tiempo de computación. -2 y -1 son valores por debajo de lo normal, así que otros procesos tendrán prioridad sobre el juego. 0 es el valor normal. +1 y +2 son valores de alta prioridad, que pueden hacer que el juego se ejecute más suavemente y a mayor velocidad. Otros procesos tendrán menos tiempo de computación. +3 indica ejecución en tiempo real: todo el tiempo de computación se pone en disposición del juego. Esto puede crear problemas con otras aplicaciones que se estén ejecutando en el ordenador. Además, los eventos de teclado o pinchar en el botón para cerrar la ventana pueden dejar de ser atendidos por Windows. Así que sólo debes usar este valor si realmente necesitas todo el procesamiento posible. Se cuidadoso y no olvides guardar tu juego antes de ejecutarlo.
Interacción con el usuario No hay juego sin interacción con el usuario. La manera estándar de interactuar con el usuario en el Game Maker es colocando acciones en los eventos del ratón o del teclado. Pero en ocasiones se necesita más control. Desde una pieza de código puedes chequear la posición del ratón o si alguno de sus botones es presionado. Normalmente esto se chequea en el evento step de algún objeto controlador y llevas a cabo las acciones adecuadas. La información sobre este tema se encuentra en las secciones: El teclado Para interacción con el teclado (keyboard) las siguientes funciones y variables existen:
keyboard_lastkey Código de la última tecla presionada. Mira más las constantes para los códigos de tecla. Puedes cambiarlo, p. ej, ponerlo a 0 si tu lo manipulaste.
keyboard_key Código de tecla de la tecla presionada actualmente (mira mas abajo; 0 si no se presiona ninguna) keyboard_lastchar Último carácter presionado (como string) keyboard_string Cadena de caracteres de los últimos 1024 caracteres tipeados. Esta cadena solo contendrá caracteres imprimibles en la pantalla. También responde a la tecla de retroceso borrando el último carácter.
En ocasiones es útil mapear una tecla a otra. Por ejemplo pudieras permitir al jugador emplear tanto las teclas del cursor como las del teclado numérico. En lugar de duplicar las acciones puedes mapear el teclado numérico a las teclas del cursor. También pudieras implementar un mecanismo en el que el jugador pueda seleccionar las teclas a usar. Para este fin, contamos con las siguientes funciones:
keyboard_set_map(key1,key2) Mapea la tecla con el código de tecla key 1 a key2. keyboard_get_map(key) Devuelve el mapeado actual para una tecla keyboard_unset_map() Restablece todas sus teclas a su mapa original.
Para chequear si una tecla o botón del ratón en particular han sido presionados puedes emplear las siguientes funciones. Esto es útil particularmente cuando se presionan varias teclas simultáneamente.
keyboard_check(key) Indica si la tecla con el código de tecla particular está presionado. keyboard_check_pressed(key) Indica si la tecla con el código de tecla particular fue presionado desde el último step.
keyboard_check_released(key) Indica si la tecla con el código de tecla particular dejó de presionarse desde el último step.
keyboard_check_direct(key) Indica si la tecla con el código de tecla es presionada chequeando el hardware directamente. El resultado es independiente de la aplicación enfocada. Esta función permite algunos chequeos más. En este caso puedes emplear los códigos vk_lshift, vk_lcontrol, vk_lalt, vk_rshift, vk_rcontrol y vk_ralt para checar si se presiona la tecla shift, control o alt, ya sea izquierda o derecha
Las siguientes rutinas puedes ser usadas para manipular el estado del teclado:
keyboard_get_numlock()Indica si BloqNum está activada. keyboard_set_numlock(on) Activa (on=true) o desactiva (on=false) BloqNum.
keyboard_key_press(key) Simula presionar la tecla con el código de tecla keyboard_key_release(key) Simulates a release of the key with the indicated keycode.
Las siguientes constantes para los códigos de tecla son:
vk_nokey Código de tecla que representa que no hay teclas presionadas vk_anykey Código de tecla que representa que cualquier tecla ha sido presionada. vk_left Código para tecla de la flecha izquierda vk_right Código para tecla de la flecha derecha vk_up Código para tecla de la flecha arriba vk_down Código para tecla de la flecha abajo vk_enter Tecla Enter o Intro vk_escape Tecla Escape vk_space Tecla Espacio vk_shift Tecla Shift vk_control Tecla Control vk_alt Tecla Alt vk_backspace Tecla Backspace o Retroceso vk_tab Tecla Tab vk_home Tecla Inicio vk_end Tecla Fin vk_delete Tecla Suprimir vk_insert Tecla Insertar vk_pageup Tecla Re Pag vk_pagedown Tecla Av Pag vk_pause Tecla Pausa/Inter vk_printscreen Tecla Impr Pant/Pet Sis vk_f1 ... vk_f12 Códigos de tecla para las las teclas funcionales F1 hasta F12 vk_numpad0 ... vk_numpad9 Teclas numéricas en el teclado numérico vk_multiply Tecla de multiplicación en el teclado numérico vk_divide Tecla de división en el teclado numérico vk_add Tecla de suma en el teclado numérico vk_subtract Tecla de substracción en el teclado numérico vk_decimal Tecla de punto decimal en el teclado numérico
Para las letras usa por ejemplo
ord('A'). (Letras Mayúsculas). Para los dígitos usa por ejemplo ord('5') para
obtener la tecla <5> (no en el teclado numérico). Las siguientes constantes solo sirven para
keyboard_check_direct: vk_lshift Tecla Shift de la Izquierda vk_lcontrol Tecla Control de la Izquierda vk_lalt Tecla Alt de la Izquierda vk_rshift Tecla Shift de la Derecha vk_rcontrol Tecla Control de la Derecha vk_ralt Tecla Alt de la Derecha
Por ejemplo, asumiendo que tienes un objeto que el usuario puede controlar con las teclas del cursor puedes colocar el siguiente código en el evento step del objeto:
{ if (keyboard_check(vk_left))
x -= 4;
if (keyboard_check(vk_right)) x += 4; if (keyboard_check(vk_up))
y -= 4;
if (keyboard_check(vk_down))
y += 4;
}
Claro, esto es mucho más fácil si simplemente lo ponemos en los eventos del teclado.
Hay algunas funciones adicionales relacionadas con la interacción con el teclado
keyboard_clear(key) ‘Limpia’ el estado de la tecla mencionada en “key”. Esto significa que no generará eventos de teclado hasta que se vuelva a presionar.
io_clear() ‘Limpia’ todos los estados del teclado y del ratón. io_handle() Maneja la entrada y salida por parte del usuario, actualizando los estados del teclado y del ratón. keyboard_wait() Espera hasta que el usuario presione una tecla del teclado.
El ratón Para más interacción, las siguientes variables y funciones existes:
mouse_x* Coordenada X del ratón. No puede cambiarse. mouse_y* Coordenada Y del ratón. No puede cambiarse. mouse_button Botón del ratón presionado actualmente. Como valores puedes emplear mb_none (ningún botón),
mb_any (cualquier botón), mb_left (botón izquierdo), mb_middle (botón central) o mb_right
(botón derecho).
mouse_lastbutton último botón presionado del ratón.
Para chequear si un botón particular del ratón se presionó puedes usar estas funciones. Esto es muy útil cuando muchas teclas se presionan simultáneamente.
mouse_check_button(numb) Indica si se presiona el botón del ratón numb (como valores de numb emplea mb_none, mb_left, mb_middle, o mb_right).
mouse_check_button_pressed(numb) Indica si el botón del ratón fue presionado desde el ultimo step. mouse_check_button_released(numb) Indica si el botón del ratón se soltó desde el último step.
Hay funciones adicionales relacionadas con la interacción con el ratón:
mouse_clear(button) “limpia” el estado del botón del Mouse. Esto significa que no se generarán mas eventos del ratón hasta que se vuelva a presionar otra vez.
io_clear() ‘Limpia’ todos los estados del teclado y del ratón. io_handle() Maneja la entrada y salida por parte del usuario, actualizando los estados del teclado y del ratón. mouse_wait()Espera hasta que el usuario presione un botón en el ratón.
El joystick Tenemos algunos eventos asociados con los joysticks (mandos de control, controles, palancas de mando, palancas de juego, etc.) Pero para tener control total sobre los joysticks hay un grupo de funciones para tratarlos. El Game Maker soporta hasta dos joystick. Por lo que todas estas funciones reciben el id del joystick como argumento.
joystick_exists(id) Indica si el joystick id (1 o 2) existe. joystick_name(id) Devuelve el nombre del joystick. joystick_axes(id) Devuelve el número de ejes del joystick. joystick_buttons(id) Devuelve el número de botones del joystick. joystick_has_pov(id) Indica si el joystick tiene capacidades point-of-view. joystick_direction(id) Devuelve el código (vk_numpad1 a vk_numpad9) correspondiente a la dirección del joystick id (1 o 2).
joystick_check_button(id,numb) Indica si el botón del joystick id es presionado (numb está en el intervalo 1-32).
joystick_xpos(id) Devuelve la posición (-1 a 1) del eje-x del joystick id. joystick_ypos(id) Devuelve la posición y del joystick id. joystick_zpos(id) Devuelve la posición z del joystick id (si es que cuenta con eje z). joystick_rpos(id) Devuelve la posición del timón del joystick id (del cuarto eje). joystick_upos(id) Devuelve la posición u del joystick id (del quinto eje).
joystick_vpos(id) Devuelve la posición v del joystick id (del sexto eje). joystick_pov(id) Devuelve la posición del point-of-view del joystick id. Este es un ángulo entre 0 y 360 grados. 0 es adelante, 90 a la derecha, 180 atrás y 270 a la izquierda. Cuando no se especifica la dirección del point-ofview devuelve –1.
Gráficos del juego Una parte importante de un juego son los gráficos. Game Maker normalmente toma el control de casi todo y en juegos sencillos no hay necesidad de preocuparse. Pero a veces necesitas tomar mayor control sobre ellos. Para algunos aspectos hay acciones, pero mediante código puedes controlar muchos más aspectos. Este capítulo muestra todas las variables y funciones disponibles para esto y da más información sobre lo que está sucediendo realmente.
La información sobre la parte gráfica se encuentra en las secciones siguientes: Imágenes y sprites Cada objeto tiene un sprite asociado. Puede ser una imagen simple o compuesta por varias subimágenes. Para cada instancia del objeto, el programa dibuja la imagen correspondiente en la pantalla, con su origen (definido en las propiedades de sprite) en la posición (x,y) de la instancia. Cuando hay varias subimágenes, ésta se reproduce a través de las subimágenes para crear un efecto de animación. Hay varias variables que afectan en el modo en que se dibuja la imagen. Estas pueden sert utilizadas para cambiar los efectos. Cada instancia posee las siguientes variables:
visible : Si visible es cierto (1) la imagen se dibuja,
en caso contrario no se dibuja. Las instancias invisibles aun
son activas y crean eventos de colisión; Simplemente no puedes verlas. Definir la visibilidad como falso es útil para por ejemplo objetos controladores (hazlos también no sólidos para evitar eventos de colisión con ellos) o palancas ocultas.
sprite_index Este es el índice del sprite actual para la instancia. Puedes cambiarlo para asignar un sprite diferente a la instancia. Como valor puedes usar los nombres de los diferentes sprites que definas . Cambiando el sprite no cambias el índice de la subimagen actual.
sprite_width* Indica el ancho del sprite. Este valor no puede ser cambiado, pero puedes utilizarlo. sprite_height* Indica el alto del sprite. Este valor no puede ser cambiado, pero puedes utilizarlo. sprite_xoffset* Indica el offset horizontal del sprite, como esta definido en las propiedades del sprite. Este valor no puede ser cambiado, pero puedes utilizarlo.
sprite_yoffset* Indica el offset horizontal del sprite, como esta definido en las propiedades del sprite. Este valor no puede ser cambiado, pero puedes utilizarlo.
image_number* El numero de subimágenes del sprite actual de la instancia (no puede ser cambiado). image_index Cuando la imagen tiene varias subimágenes, el programa las recorre cíclicamente. Esta variable indica la subimagen dibujada actualmente (están numeradas desde 0). Puedes cambiar la imagen actual cambiando esta variable. EL programa continuará reproduciendo el ciclo de subimágenes, pero comenzando desde el nuevo índice. (El valor puede ser fraccional. En este caso, se redondea siempre hacia abajo para obtener el índice de la subimagen dibujada.
image_speed La velocidad con que se reproduce el ciclo de subimágenes. Un valor de 1 indica que en cada paso se muestra la siguiente subimagen . Valores menores, reducirán la velocidad de visualización de la animación, dibujando la misma subimagen varias veces. Valores mayores harán que se salten subimágenes para aumentar la velocidad de la animación. A veces quieres que una subimagen en particular sea visible y no quieres que el programa muestre todas las demás subimágenes. Puedes conseguir esto definiendo la velocidad igual a 0 y eligiendo la subimagen correcta. Por ejemplo, asumamos que tenemos un objeto que puede rotar y has creado un sprite que tiene varias subimágenes para las diferentes orientaciones (en sentido contrario a las agujas del reloj) Puedes introducir el siguiente código en el evento step:
{ image_index = direction * image_number/360; image_speed = 0; }
depth Normalmente las imágenes son dibujadas en el orden en que se crean las instancias. Puedes cambiar este orden cambiando la profundidad de la imagen. El valor por defecto es 0, hasta que le introduzcas un valor diferente en las propiedades del objeto. Cuanto mayor es el valor más al fondo se dibujará la instancia. (También puedes utilizar valores negativos.) Las instancias con mayor profundidad permanecerán debajo de las instancias con menor profundidad. Definiendo la profundidad garantizamos que las instancias son dibujadas en el orden que queremos. (Ej. El avión sobre la nube). Las instancias de fondo tendrán una alta profundidad (valor positivo) y las imágenes frontales tendrán una profundidad baja (valores negativos).
image_xscale Un valor de escala para hacer
mayores o menores las imágenes. Un valor de 1 indica el tamaño
normal. Debes separar la escala horizontal xscale y la vertical yscale. Cambiando la escala también cambian el ancho y el alto de la imagen e influye en los eventos de collisión, como podrías esperar. Cambiar la escala puede ser utilizado para dar un efecto 3-D. Puedes utilizar un valor de -1 para invertir horizontalmente la imagen.
image_yscale Escala vertical yscale. 1 no modifica el tamaño. Puedes utilizar un valor de -1 para invertir verticalmente la imagen.
image_angle El ángulo con que se rota la imagen. Se especifica en grados en sentido contrario a las agujas del reloj. Un valor de 0 indica que no hay rotación. Esta variable solo puede modificarse en la versión registrada!
image_alpha El valor de transparencia (alpha) que se aplica al dibujar la imagen. Un valor de 1 es la opacidad normal; un valor de 0 es totalmente transparente.
image_blend
Teñido de color que se aplica al dibujar la imagen. Un valor de color blanco (c_white) es el que se
utiliza por defecto. Cuando especificas un color diferente, la imagen se tiñe de dicho color. Esto puede ser utilizado para colorear el personaje en tiempo de ejecución. Esta variable solo puede modificarse en la versión registrada!
bbox_left* Lado izquierdo de la caja de contorno utilizada por la imagen de la instancia (Se toma en cuenta el escalado).
bbox_right* Lado derecho de la caja de contorno utilizada por la imagen de la instancia
bbox_top* parte superior de la caja de contorno utilizada por la imagen de la instancia. bbox_bottom*
parte inferior de la caja de contorno utilizada por la imagen de la instancia.
Fondos
Cada room (cuarto) puede tener hasta 8 fondos. Así mismo, también puede tener un color de fondo. Todos los aspecto de estos fondos se pueden cambiar con código usando las siguientes variables (observa que algunas son vectores con un rango de 0 a 7; este número indica el fondo al que se aplicarán los cambios):
background_color Color de fondo para el cuarto. background_showcolor Indica si se debe limpiar la pantalla con el color de fondo. background_visible[0..7] Indica si el fondo indicado es visible o no. Por ejemplo, para indicar que el fondo número 3 no debe mostrarse, deberíamos hacerlo así: background_visible[3]=false;
background_foreground[0..7] Indica si el fondo está en primer plano (se dibujará encima de todo lo demás, tapando todo lo que quede por debajo).
background_index[0..7] Imagen de fondo asignada al fondo indicado. background_x[0..7] Posición x del fondo. background_y[0...7] Posición y del fondo. background_width[0...7]* Anchura de la imagen del fondo. background_height[0...7]* Altura de la imagen del fondo. background_htiled[0..7] Indica si el fondo debe repetirse horizontalmente para llenar toda la pantalla. background_vtiled[0..7] Indica si el fondo debe repetirse verticalmente para llenar toda la pantalla. background_xscale[0..7] Factor de escalado horizontal del fondo: un número entre 0 y 1 hará la imagen más pequeña y un número mayor que 1 la hará más grande. (Debe ser un valor positivo)
background_yscale[0..7] Factor de escalado vertical del fondo: un número entre 0 y 1 hará la imagen más pequeña y un número mayor que 1 la hará más grande. (Debe ser un valor positivo)
background_hspeed[0..7] Velocidad horizontal de scrolling del fondo, en píxeles por step. El scrolling es el movimiento del fondo, por lo tanto esto se refiere al movimiento horizontal del fondo.
background_vspeed[0..7] Velocidad vertical de scrolling del fondo, en píxeles por step. El scrolling es el movimiento del fondo, por lo tanto esto se refiere al movimiento vertical del fondo.
background_blend[0..7] Color de teñido usado al dibujar el fondo. El valor por defecto es c_white. Esta variable sólo se puede usar en la versión registrada!
background_alpha[0..7] Factor de transparencia usado al dibujar el fondo. 1 es el valor normal (imagen totalmente opaca) y 0 es totalmente transparente (utiliza valores intermedios para dibujar fondos parcialmente transparentes).
Dibujando sprites y fondos Normalmente los objetos tienen un sprite asociado que se dibuja en la pantalla. Pero también puedes usar el evento draw. para dibujar otras cosas. Esta sección y las dos siguientes te darán información sobre este aspecto. Primero de todo, hay un grupo de funciones que sirven para dibujar sprites y fondos de distintas maneras. Estas funciones te dan un gran control sobre la apariencia gráfica del juego y te permiten hacer cosas como dibujar sólo un trozo de un fondo, rotar un sprite...
draw_sprite(sprite,subimg,x,y) Dibuja la subimagen subimg (-1 = subimagen actual) del sprite con índice
sprite
con su origen en la posición (x,y) sin teñirlo de ningún color y sin usar transparencia.
draw_sprite_stretched(sprite,subimg,x,y,w,h) Dibuja la subimagen subimg con índice
sprite
de forma que llene la región con esquina superior-izquierda en (x,y), anchura
del sprite
w y altura h.
draw_sprite_tiled(sprite,subimg,x,y) Dibuja el sprite repitiéndolo horizontal y verticalmente de forma que llene toda la pantalla. (x,y) es la posición donde se dibuja una de las copias del sprite.
draw_sprite_part(sprite,subimg,left,top,width,height,x,y) Dibuja la parte del sprite indicada con su esquina superior izquierda en (x,y). La parte del sprite que queremos dibujar se indica con
left
y
top (coordenadas de la esquina superior-izquierda) y width y height (anchura y altura del trozo que
queremos dibujar).
draw_background(back,x,y) Dibuja el fondo en la posición (x,y), sin teñirlo de ningún color y sin transparencia.
draw_background_stretched(back,x,y,w,h) Dibuja el fondo escalado de forma que ocupe la región indicada.
draw_background_tiled(back,x,y) Dibuja el fondo repitiéndolo en horizontal y vertical de forma que llene toda la pantalla.
draw_background_part(back,left,top,width,height,x,y) Dibuja el trozo indicado del fondo con su esquina superior-izquierda en la posición (x,y).
Las funciones siguientes son versiones extendidas de las funciones anteriores. Estas funciones sólo están disponibles en la versión registrada de Game Maker.
draw_sprite_ext(sprite,subimg,x,y,xscale,yscale,rot,color,alpha) Dibuja el sprite escalado con factores antihorario.
xscale (horizontal) e yscale
(vertical) y rotado
rot grados en sentido
color indica el color con el que queremos teñir el sprite (usa el color blanco c_white si no quieres teñir
el sprite de ningún color) y
alpha indica el factor de transparencia. Un valor igual a 0 hace la imagen totalmente
transparente y un valor igual a 1 la hace totalmente opaca. Con valores intermedios conseguirás que la imagen sea parcialmente transparente. Con esta función se pueden crear efectos muy espectaculares como explosiones semitransparentes.
draw_sprite_stretched_ext(sprite,subimg,x,y,w,h,color,alpha) Dibuja el sprite
escalado de forma que ocupe la región indicada: esquina superior izquierda en (x,y), anchura indica el color de teñido y
w y altura h. color
alpha el factor de transparencia.
draw_sprite_tiled_ext(sprite,subimg,x,y,xscale,yscale,color,alpha) Dibuja el sprite repetido de forma que cubra toda la pantalla con factores de escala, color de teñido y factor de transparencia.
draw_sprite_part_ext(sprite,subimg,left,top,width,height,x,y,xscale,ys cale,color,alpha) Dibuja la parte indicada del sprite con factores de escala, color de teñido y factor de transparencia.
draw_sprite_general(sprite,subimg,left,top,width,height,x,y,xscale,ysc ale,rot,c1,c2,c3,c4,alpha) La función más general para dibujar sprites. Dibuja la parte indicada de la subimagen
subimg (-1 = subimagen actual) del sprite con índice sprite situando su esquina superior-izquierda en la
posición (x,y) y con factores de escala, ángulo de rotación, un color de teñido para cada una de las 4 esquinas del sprite (en el orden arriba-izquierda, arriba-derecha, abajo-derecha y abajo-izquierda) y un factor de transparencia. Observa que el sprite se rotará sobre su esquina superior-izquierda y no sobre su origen.
draw_background_ext(back,x,y,xscale,yscale,rot,color,alpha)
Dibuja el fondo
escalado, rotado, teñido del color especificado (usa c_white si no quieres teñirlo) y con factor de transparencia
alpha (0-1). draw_background_stretched_ext(back,x,y,w,h,color,alpha) Dibuja el fondo escalado de forma que ocupe la región indicada con color de teñido y factor de transparencia.
draw_background_tiled_ext(back,x,y,xscale,yscale,color,alpha) Dibuja el fondo repetido de forma que ocupe todo el cuarto con factores de escala, color de teñido y factor de transparencia.
draw_background_part_ext(back,left,top,width,height,x,y,xscale,yscale, color,alpha) Dibuja la parte indicada del fondo situando su esquina superior-izquierda en la posición (x,y) con factores de escala, color de teñido y factor de transparencia.
draw_background_general(back,left,top,width,height,x,y,xscale,yscale,r ot,c1,c2,c3,c4,alpha) La función más general para dibujar fondos. Dibuja la parte indicada del fondo situando su esquina superior-izquierda en la posición (x,y) con factores de escala, ángulo de rotación, un color de teñido para cada una de las cuatro esquinas (en el orden arriba-izquierda, arriba-derecha, abajo-derecha y abajoizquierda) y un factor de transparencia. Observa que el sprite se rotará sobre su esquina superior-izquierda del trozo indicado del fondo.
Dibujando formas Game Maker dispone de una amplia colección de funciones para dibujar diferentes formas. También hay otras funciones para dibujar texto (consulta el capítulo siguiente). Estas funciones de dibujo sólo se pueden usar en el evento draw ya que no tienen sentido si se colocan en otro evento. Debes tener en cuenta que las colisiones entre las instancias se determinan según sus sprites y sus máscaras de colisión y no según lo que cada instancia dibuja en la pantalla. Las funciones siguientes sirven para dibujar las formas básicas:
draw_clear(col) Pinta todo el cuarto del color especificado. draw_clear_alpha(col,alpha) Pinta todo el cuarto del color especificado y con el factor de transparencia indicado (muy útil para superficies).
draw_point(x,y) Dibuja un punto en (x,y) en el color de dibujo actual. draw_line(x1,y1,x2,y2) Dibuja una línea desde (x1,y1) hasta (x2,y2). draw_rectangle(x1,y1,x2,y2,outline) Dibuja un rectángulo. outline indica si sólo debe dibujarse el borde (true) o si el rectángulo debe estar relleno (false).
draw_roundrect(x1,y1,x2,y2,outline) Dibuja un rectángulo redondeado. outline indica si sólo debe dibujarse el borde (true) o si el rectángulo debe estar relleno (false).
draw_triangle(x1,y1,x2,y2,x3,y3,outline) Dibuja un triángulo. outline indica si sólo debe dibujarse el borde (true) o si debe estar relleno (false).
draw_circle(x,y,r,outline) Dibuja un círculo con su centro en (x,y) y radio r. outline indica si sólo debe dibujarse el borde (true) o si debe estar relleno (false).
draw_ellipse(x1,y1,x2,y2,outline) Dibuja una elipse. outline indica si sólo debe dibujarse el borde (true) o si debe estar rellena (false).
draw_arrow(x1,y1,x2,y2,size) Dibuja una flecha desde (x1,y1) hasta (x2,y2). size indica el tamaño de la flecha en píxeles.
draw_button(x1,y1,x2,y2,up) Dibuja un botón. up indica si está pulsado (0) o no (1). draw_path(path,x,y,absolute) Con esta función puedes dibujar el path indicado en el cuarto con su comienzo en la posición (x,y). Si
absolute es true el path se dibuja en la posición en la que fue definido y los
valores de x e y son ignorados.
draw_healthbar(x1,y1,x2,y2,amount,backcol,mincol,maxcol,direction,show back,showborder) Con esta función puedes dibujar una barra de vida (o una barra que indique cualquier otra cosa como poder, magia,…). Los parámetros x1, y1, x2 e y2 indican el área total de la barra. porcentaje de la barra que debe estar relleno (debe estar entre 0 y 100).
mincol
y
amount indica el
backcol es el color de fondo de la barra.
maxcol indican el color cuando el porcentaje de llenado (parámetro amount) es 0 y 100
respectivamente. Para un porcentaje intermedio el color se interpola entre estos dos. Así puedes crear fácilmente una barra que vaya de verde a rojo, por ejemplo.
direction es la dirección en la que la barra se dibuja: 0 significa que
la barra está fijada a la izquierda, 1 a la derecha, 2 arriba y 3 abajo. Finalmente, una caja de fondo y
showback indica si debe mostrarse
showborder si la caja de fondo y la barra deben tener un borde negro.
Muchas de las funciones anteriores utilizan los colores y transparencia generales de dibujo que pueden ser cambiados con estas funciones:
draw_set_color(col) Indica el color que debe usarse para dibujar primitivas. draw_set_alpha(alpha) Indica el factor de transparencia que debe usarse para dibujar primitivas. Debe estar comprendido en el rango 0-1. 0 significa totalmente transparente y 1 totalmente opaco.
draw_get_color() Devuelve el color general de dibujo utilizado para dibujar primitivas. draw_get_alpha()Devuelve el factor de transparencia general de dibujo utilizado para dibujar primitivas.
Hay un gran rango de colores predefinidos:
c_aqua c_black c_blue c_dkgray c_fuchsia c_gray c_green c_lime c_ltgray c_maroon c_navy c_olive c_purple c_red c_silver c_teal c_white c_yellow
Sin embargo, también puedes usar las funciones siguientes para crear tus propios colores:
make_color_rgb(red,green,blue) Devuelve un color con los componentes indicados de rojo, verde y azul. Los valores indicados deben estar entre 0 y 255 (ambos inclusive).
make_color_hsv(hue,saturation,value) Devuelve un color con los componentes indicados de brillo, saturación y valor. Los valores indicados deben estar entre 0 y 255 (ambos inclusive).
color_get_red(col) Devuelve el componente de rojo del color. color_get_green(col) Devuelve el componente de verde del color. color_get_blue(col) Devuelve el componente de azul del color. color_get_hue(col) Devuelve el componente de brillo del color. color_get_saturation(col) Devuelve el componente de saturación del color. color_get_value(col) Devuelve el componente de valor del color. merge_color(col1,col2,amount) Devuelve un color resultante de mezclar los dos colores indicados. La mezcla se determina por el parámetro
amount: un valor igual a 0 corresponde a col1, un valor de 1 corresponde a
col2 y un valor intermedio hace que se mezclen los dos colores.
También puedes utilizar las siguientes funciones misceláneas:
draw_getpixel(x,y) Devuelve el color del píxel en la posición (x,y) del cuarto. Esta función no es muy rápida, así que úsala con cuidado.
screen_save(fname) Salva una imagen bmp de la pantalla en el archive especificado. Útil para crear screenshots.
screen_save_part(fname,x,y,w,h) Salva la parte de la pantalla indicada a un archive bmp.
Fuentes y texto A veces necesitas dibujar texto. Para ello, primero debes indicar la fuente que quieres usar. Las fuentes se pueden definir creando un recurso de fuente (directamente en Game Maker o usando las funciones de modificación de recursos). Para dibujar texto disponemos de varias funciones distintas. En todas ellas debes indicar el texto a ser dibujado y la posición del mismo. Además, existen dos funciones para definir la alineación del texto en horizontal y vertical respecto a esa posición.
Para dibujar texto disponemos de las funciones siguientes:
draw_set_font(font) Indica la fuente que debe usarse para dibujar texto. Puedes indicar el valor -1 para usar la fuente por defecto (Arial 12).
draw_set_halign(halign) Indica la alineación horizontal del texto. Debes indicar uno de los siguientes valores:
fa_left
izquierda
fa_center fa_right
centrada derecha
draw_set_valign(valign) Indica la alineación vertical del texto. Debes indicar uno de los siguientes valores:
fa_top
arriba
fa_middle
centrada
fa_bottom
abajo
draw_text(x,y,string) Dibuja el texto indicado en la posición (x,y) usando el color y el factor de transparencia de dibujo generales. Los símbolo #, chr(13) o chr(10) (tecla ENTER o de salto de línea) son interpretados como caracteres de salto de línea. De esta forma puedes dibujar textos de varias líneas (Usa \# para dibujar el símbolo #).
draw_text_ext(x,y,string,sep,w) Similar a la función anterior pero ahora puedes especificar 2 cosas más. Primero de todo,
sep indica la distancia de separación entre las líneas del texto si es que tiene varias líneas. Usa
-1 para obtener la distancia por defecto. Por último,
w indica la anchura del texto en píxeles. Las líneas más largas que
esta distancia se dividen en nuevas líneas cuando encuentren un espacio en el texto o el símbolo -. Utiliza el valor -1 si no quieres que el texto se divida automáticamente en varias líneas.
string_width(string) Anchura que el texto indicado ocuparía si se dibujara con la fuente actual usando la
función
draw_text(). Puedes usar esta función para posicionar los gráficos con precisión.
string_height(string) Altura que el texto indicado ocuparía si se dibujara con la fuente actual usando la función
draw_text(). Puedes usar esta función para posicionar los gráficos con precisión.
string_width_ext(string,sep,w) Anchura que el texto indicado ocuparía si se dibujara con la fuente actual usando la función
draw_text_ext(). Puedes usar esta función para posicionar los gráficos con precisión.
string_height_ext(string,sep,w) Altura que el texto indicado ocuparía si se dibujara con la fuente actual usando la función
draw_text_ext(). Puedes usar esta función para posicionar los gráficos con precisión.
Las siguientes funciones te permiten dibujar texto escalado o rotado e incluso aplicarle gradientes de color. Estas funciones sólo están disponibles en la versión registrada de Game Maker!
draw_text_transformed(x,y,string,xscale,yscale,angle) Dibuja el texto indicado en la posición (x,y) con factores de escala y rotado
angle grados en sentido antihorario.
draw_text_ext_transformed(x,y,string,sep,w,xscale,yscale,angle) Combina las funciones
draw_text_ext() y draw_text_transformed(). De esta forma es posible dibujar textos
multilíneas rotados o escalados.
draw_text_color(x,y,string,c1,c2,c3,c4,alpha) Dibuja el texto indicado aplicando a cada esquina el color indicado en el orden arriba-izquierda, arriba-derecha, abajo-derecha, abajo-izquierda y con factor de transparencia
alpha (0-1).
draw_text_ext_color(x,y,string,sep,w,c1,c2,c3,c4,alpha) Similar a draw_text_ext() pero con vértices coloreados. draw_text_transformed_color(x,y,string,xscale,yscale,angle,c1,c2,c3,c4 ,alpha) Similar a draw_text_transformed()pero con vértices coloreados. draw_text_ext_transformed_color(x,y,string,sep,w,xscale,yscale,angle,c 1,c2,c3,c4,alpha) Similar a draw_text_ext_transformed()pero con vértices coloreados.
Funciones avanzadas de dibujo Esta funcionalidad sólo está disponible en la versión registrada de Game Maker
En los capítulos anteriores hemos descrito las funciones básicas de dibujo. En este capítulo veremos funciones más avanzadas que te otorgan muchas más posibilidades para crear los gráficos de tus juegos. Primero, hay unas funciones que sirven para dibujar formas coloreadas con gradientes o efectos de difuminado. Después veremos las funciones que sirven para dibujar polígonos básicos y por último veremos la forma de dibujar polígonos texturizados.
Las siguientes funciones son versiones extendidas de las funciones de dibujo básicas. Todas ellas admiten parámetros para el color, por lo que ninguna de estas funciones usará el color general de dibujo.
draw_point_color(x,y,col1) Dibuja un punto en la posición (x,y) con el color indicado. draw_line_color(x1,y1,x2,y2,col1,col2) Dibuja una línea desde (x1,y1) hasta (x2,y2), interpolando el color entre
col1 y col2.
draw_rectangle_color(x1,y1,x2,y2,col1,col2,col3,col4,outline) Dibuja un rectángulo. Los cuatro colores indican los colores de los vértices superior-izquierdo, superior-derecho, inferior-derecho e inferior-izquierdo respectivamente.
outline indica si sólo debe dibujarse el contorno del rectángulo (true) o si
éste debe estar relleno (false).
draw_roundrect_color(x1,y1,x2,y2,col1,col2,outline) Dibuja un rectángulo redondeado.
col1 es el color en el centro del rectángulo y col2 en el borde. outline indica si sólo debe
dibujarse el contorno del rectángulo (true) o si éste debe estar relleno (false).
draw_triangle_color(x1,y1,x2,y2,x3,y3,col1,col2,col3,outline) Dibuja un triángulo. Los tres colores son los colores en los vértices del triángulo (el color se interpola en las demás zonas entre estos tres colores).
outline indica si sólo debe dibujarse el contorno del triángulo (true) o si éste debe estar relleno
(false).
draw_circle_color(x,y,r,col1,col2,outline) Dibuja un círculo en (x,y) con radio r. col1 es el color en el centro y
col2 en el borde. outline indica si sólo debe dibujarse el contorno del círculo (true) o si
éste debe estar relleno (false).
draw_ellipse_color(x1,y1,x2,y2,col1,col2,outline) Dibuja una elipse. col1 es el color en el centro y
col2 en el borde. outline indica si sólo debe dibujarse el contorno de la elipse (true) o si éste
debe estar rellena (false).
Dibujando superficies
Esta funcionalidad sólo está disponible en la versión registrada de Game Maker
En algunas situaciones puede que no quieras dibujar directamente en la pantalla sino en un lienzo que más tarde puedas usar para dibujar otras cosas. Por ejemplo, puedes querer que el usuario dibuje en la pantalla. En vez de dejar que el usuario dibuje directamente en la pantalla (lo que no funcionaría, ya que la pantalla se refresca en cada step) puedes hacer que dibuje en un lienzo a parte y copiar este lienzo a la pantalla en cada paso. También podrías querer usar una textura que cambie con el tiempo, crear un efecto de transiciones entre cuartos, efectos de iluminación…
Las superficies hacen posibles cosas como éstas. Las superficies son muy sencillas de usar: primero creas una superficie indicando su tamaño. Luego indicas que vas a dibujar sobre la superficie. Desde este momento, todas las funciones de dibujo se aplicarán a la superficie. Cuando termines de dibujar, indicas que ya no vas a dibujar más en la superficie. Ya tienes lista la superficie, ahora puedes dibujarla directamente en la pantalla, usarla como textura…Consulta el final del capítulo para conocer algunos detalles con los que debes tener cuidado al trabajar con superficies.
Las siguientes funciones sirven para tratar superficies:
surface_create(w,h) Crea una superficie con la anchura y altura indicadas. La función devuelve la id de la superficie que debe ser usada en las demás funciones. Observa que la superficie no se “limpia” (es decir, que no se pinta toda ella de un mismo color). Para esto, debes indicar que vas a dibujar en la superficie y llamar a la función apropiada, por ejemplo draw_clear(color).
surface_free(id) Libera la memoria utilizada por la superficie. surface_exists(id) Devuelve si la superficie con la id especificada existe.
Estas funciones nos dan información sobre la superficie:
surface_get_width(id) Devuelve la anchura de la superficie. surface_get_height(id) Devuelve la altura de la superficie. surface_get_texture(id) Devuelve la textura correspondiente a la superficie. Esto se puede usar para dibujar objetos texturizados con la imagen de la superficie.
Estas dos funciones manejan el mecanismo de dibujo:
surface_set_target(id) Indica que la superficie con la id correspondiente es el objetivo de dibujo. Esto significa que todas las funciones de dibujo que se llamen actuarán sobre la superficie. Simplemente resetea la proyección para que cubra sólo a la superficie.
surface_reset_target() Vuelve a fijar la pantalla como objetivo de dibujo. Es decir, las funciones de
dibujo que se llamen a partir de ahora funcionarán normalmente ¡No olvides llamar a esta función cuando termines de dibujar sobre la superficie!
Las funciones siguientes nos permiten manejar las superficies:
surface_getpixel(id,x,y) Devuelve el color del píxel en la posición (x,y) de la superficie. Esta función no es muy rápida así que úsala con moderación.
surface_save(id,fname) Guarda una imagen bmp de la superficie, creando para ello el archivo con el nombre indicado. Se puede usar para crear screenshots, por ejemplo.
surface_save_part(id,fname,x,y,w,h) Igual que la función anterior, pero esta vez sólo se copiará la parte de la superficie que indiques.
Para dibujar superficies hay muchas posibilidades:
draw_surface(id,x,y) Dibuja la superficie en la posición (x,y). draw_surface_stretched(id,x,y,w,h) Dibuja la superficie en la posición (x,y) y escalada de forma que tenga la anchura y altura indicadas.
draw_surface_tiled(id,x,y) Dibuja la superficie en la posición (x,y) y la repite una y otra vez para que ocupe todo el cuarto.
draw_surface_part(id,left,top,width,height,x,y) Dibuja la parte indicada de la superficie en la posición (x,y).
draw_surface_ext(id,x,y,xscale,yscale,rot,color,alpha) Dibuja la superficie en la posición (x,y), con factores de escala, rotación, transparencia y tiñiéndola con el color indicado (utiliza c_white si no quieres teñir la superficie).
draw_surface_stretched_ext(id,x,y,w,h,color,alpha) Dibuja la superficie en la posición (x,y) escalada para que ocupe la región indicada con factor de transparencia y tiñiéndola del color especificado.
draw_surface_tiled_ext(id,x,y,xscale,yscale,color,alpha) Dibuja la superficie repitiéndola para que ocupe todo el cuarto pero con factores de escala, transparencia y tiñiéndola del color indicado.
draw_surface_part_ext(id,left,top,width,height,x,y,xscale,yscale,color ,alpha) Dibuja la parte indicada de la superficie en la posición (x,y) pero con factores de escala, transparencia y color.
draw_surface_general(id,left,top,width,height,x,y,xscale,yscale,rot,c1 ,c2,c3,c4,alpha) La función más general de dibujo. Dibuja la parte indicada de la superficie con su origen en la posición (x,y), con factores de escala, transparencia y rotada
rot grados en sentido antihorario. Además debes
indicar 4 colores para cada una de las esquinas de la superficie en este orden: esquina superior izquierda, esquina superior derecha, esquina inferior derecha y esquina inferior izquierda.
Por último, existen dos funciones para copiar superficies:
surface_copy(destination,x,y,source) Copia la superfcie en la posición (x,y) en la superficie indicada por
destination.
surface_copy_part(destination,x,y,source,xs,ys,ws,hs) Copia la parte indicada de la superfcie en la posición (x,y) en la superficie indicada por
destination.
Observa que no hay funciones para copiar parte de la pantalla a una superficie. Esto es imposible debido a la diferencia de formato entre la pantalla y las superficies. Si necesitaras usar esto puedes indicar la superficie como objetivo de dibujo y dibujar todo el room. Luego usando las funciones para copiar superficies puedes copiar partes de ella a la pantalla.
También puedes crear sprites y fondos de superficies. Esto se explica con más detalle en la sección de modificación de recursos.
Al usar superficies debes tener algunas cosas en cuenta:
•
Nunca debes cambiar el objetivo de dibujo mientras dibujas estás dibujando sobre la pantalla. Es decir, nunca uses las funciones surface_set_target() ni surface_reset_target() en el evento draw. Esto causará graves problemas en la proyección.
•
Las superficies no funcionan correctamente en modo 3D. Puedes usarlas mientras no estés en modo 3D (llamando a la función d3d_end() antes de usarlas) pero una vez que empieces a usar el modo 3D de nuevo las superficies se destruirán automáticamente.
•
Por motivos de velocidad, las superficies se mantienen en la memoria de video constantemente. Como resultado de esto, podrías perder las superficies al cambiar la resolución de la pantalla o cuando salte el salvapantallas.
•
Las superficies no se guardarán al guardar un juego.
Tiles Como ya debes saber, puedes añadir tiles a los cuartos. Una tile es una parte de un fondo. En realidad, una tile es simplemente una imagen visible: no reaccionan ante eventos y no generan colisiones. Como resultado de esto, las tiles funcionan mucho más rápido que los objetos. Así pues, todo lo que no sufra colisiones o no reaccione ante eventos funcionará mucho mejor si lo haces con tiles. Incluso a veces es mejor utilizar un tile para mostrar los gráficos y poner por debajo un objeto invisible que se encargue de las colisiones. Para definir un tile necesitas un fondo. Sobre este fondo indicas la esquina superior (top) izquierda (left), la anchura (width) y la altura (height) de un rectángulo. La parte del fondo que queda dentro de este rectángulo pasa a ser una tile.
Puedes añadir tiles al diseñar el room, pero también puedes hacerlo mientras el juego se ejecuta. Puedes cambiar su posición, escalarlas o hacerlas transparentes. Una tile tiene las propiedades siguientes:
•
background. El fondo del que se crea la tile.
•
left, top, width, height. La parte del fondo usada en la tile (izquierda, arriba, anchura y altura).
•
x,y. Posición de la esquina superior izquierda del tile en el room.
•
depth. Profundidad del tile. Puedes escoger la profundidad que quieras, haciendo que las tiles aparezcan entre varias instancias distintas.
•
visible. Indica si es visible.
•
xscale, yscale. Factores de escalado horixontal y vertical (el tamaño normal es 1).
•
blend. Color de teñido usado al dibujar el tile.
•
alpha. Factor de transparencia utilizado al dibujar el tile.
Para cambiar las propiedades de una tile debes conocer su id. Cuando creas tiles desde el editor de rooms la id se muestra en la barra inferior de información. Pero también existen funciones para conocer la id de una tile en una posición particular.
Las funciones siguientes sirven para manejar tiles:
tile_add(background,left,top,width,height,x,y,depth) Añade una nueva tile al cuarto con las propiedades indicadas. Esta función devuelve la id de la nueva tile que se puede usar más tarde en el resto de funciones.
tile_delete(id) Elimina el tile con la id especificada. tile_exists(id) Devuelve si existe una tile con la id especificada.
Las funciones siguientes nos dan información sobre las tiles:
tile_get_x(id) Devuelve la posición x de la tile con la id especificada. tile_get_y(id) Devuelve la posición y de la tile con la id especificada. tile_get_left(id) Devuelve el valor de la propiedad left
(izquierda) de la tile con la id especificada.
tile_get_top(id) Devuelve el valor de la propiedad top (arriba) de la tile con la id especificada. tile_get_width(id) Devuelve la anchura de la tile con la id especificada. tile_get_height(id) Devuelve la altura de la tile con la id especificada. tile_get_depth(id) Devuelve la profundidad de la tile con la id especificada. tile_get_visible(id) Devuelve si la tile con la id especificada es visible o no. tile_get_xscale(id) Devuelve el factor horizontal de escalado de la tile con la id especificada. tile_get_yscale(id) Devuelve el factor vertical de escalado de la tile con la id especificada.
tile_get_background(id) Devuelve el fondo de la tile con la id especificada. tile_get_blend(id) Devuelve el color de teñido de la tile con la id especificada. tile_get_alpha(id) Devuelve el factor de transparencia de la tile con la id especificada.
Las funciones siguientes sirven para manipular las propiedades de las tiles:
tile_set_position(id,x,y) Coloca la tile con la id especificada en la posición x,y. tile_set_region(id,left,top,width,height) Cambia la región del tile con la id especificada sobre su fondo.
tile_set_background(id,background) Cambia el fondo de la tile con la id especificada. tile_set_visible(id,visible) Cambia la visibilidad de la tile con la id especificada. tile_set_depth(id,depth) Cambia la profundidad de la tile con la id especificada. tile_set_scale(id,xscale,yscale) Cambia los factores de escala de la tile con la id especificada. tile_set_blend(id,color) Cambia el color de teñido de la tile con la id especificada. Sólo disponible en la versión registrada!
tile_set_alpha(id,alpha) Cambia la transparencia de la tile con la id especificada.
Las funciones siguientes manejan capas de tiles, es decir, grupos de tiles que tienen la misma profundidad:
tile_layer_hide(depth) Oculta todas las tiles con la profundidad indicada. tile_layer_show(depth) Muestra todas las tiles con la profundidad indicada. tile_layer_delete(depth) Elimina todas las tiles con la profundidad indicada.. tile_layer_shift(depth,x,y) Mueve todas las tiles con la profundidad indicada siguiendo el vector (x,y). Esta función se puede usar para capas de tiles móviles.
tile_layer_find(depth,x,y) Devuelve la id de la tile con la profundidad indicada que se encuentra en la posición (x,y). Si no se encuentra ninguna tile la función devuelve -1. Cuando se encuentran varias tiles en esa posición con la misma profundidad se devuelve la primera.
tile_layer_delete_at(depth,x,y) Elimina la tile con la profundidad indicada que se encuentra en la posición (x,y). Si se encentran varias tiles en esa posición y con la profundidad indicada se eliminan todas.
tile_layer_depth(depth,newdepth) Cambia la profundidad de todas las tiles con la profundidad indicada a la nueva profundidad. Es decir, con esta función puedes mover capas enteras de tiles a otra profundidad.
El display El display es todo el área del monitor. Tiene varias características como el tamaño (típicamente 1024x768 ó 1280x1024), la profundidad de color (que es el número de bits que se utilizan para representar 1 píxel) y suele ser de 16 bits (color de alta densidad) ó 32 bits (color verdadero) y la frecuencia de refresco, que es el número de veces por segundo que se refresca el display, es decir, cuántas veces por segundo se vuelven a dibujar los píxeles en la pantalla (típicamente entre 60 y 120). Estos parámetros se pueden cambiar desde las propiedades de la pantalla en Windows. Pero para algunos juegos, sobre todo los que se ejecuten en pantalla completa, es importante poder controlar estos
aspectos. Todos ellos se pueden inicializar desde la pestaña Game Settings. Para cambiar estos parámetros durante el juego Game Maker dispone de un grupo de funciones que veremos a continuación. Debes tener en cuenta que al cambiar estos parámetros el sistema necesitará un poco de tiempo para volver a ajustarlo todo. Las funciones para cambiar el modo sólo están disponibles en la versión registrada de Game Maker.
display_get_width() Devuelve la anchura del display en píxeles. display_get_height()Devuelve la altura del display en píxeles. display_get_colordepth()Devuelve la profundidad de color en bits. display_get_frequency()Devuelve la frecuencia de refresco del monitor. display_set_size(w,h) Cambia la altura y anchura del display a los nuevos valores indicados. La función devuelve true si los cambios se aplicaron con éxito y false en caso contrario (Debes tener en cuenta que sólo algunas combinaciones están permitidas, por ejemplo 800x600, 1024x768 ó 1280x1024).
display_set_colordepth(coldepth) Cambia la profundidad de color a la especificada. En general sólo se permite usar profundidad de color de 16 ó 32 bits. La función devuelve true si los cambios se aplicaron con éxito y false en caso contrario.
display_set_frequency(frequency) Cambia la frecuencia de refresco del monitor a la especificada. Sólo unas pocas frecuencias están permitidas. Por ejemplo, puedes ajustar este valor a 60 y usar un room_speed del mismo valor para lograr animaciones fluidas de 60 frames por segundo. La función devuelve true si los cambios se aplicaron con éxito y false en caso contrario.
display_set_all(w,h,frequency,coldepth)
Cambia todos los valores a la vez. Para valores que
no quieras cambiar utiliza -1. La función devuelve true si los cambios se aplicaron con éxito y false en caso contrario.
display_test_all(w,h,frequency,coldepth)
Comprueba si los valores especificados están
permitidos. La función no aplica los valores al display, sólo chequea si son válidos. Para los valores que no quieras cambiar usa el valor -1. La función devuelve true si los cambios se pueden aplicar y false en caso contrario.
display_reset() Resetea las características del display a los originales, es decir, a los que estaban presentes cuando se inició el programa.
A veces es útil poder conocer la posición del puntero en el display o poder cambiarla. Para ello se usan las funciones siguientes:
display_mouse_get_x() Devuelve la coordenada x del puntero en el display. display_mouse_get_y()Devuelve la coordenada x del puntero en el display. display_mouse_set(x,y) Cambia la posición del puntero a los valores indicados.
La ventana
El juego se ejecuta en una ventana. Esta ventana tiene varias propiedades como si tiene borde, si ocupa toda la pantalla...Normalmente estos valores se fijan desde la opción Game Settings. Pero también puedes cambiarlos durante el juego. Para ello puedes usar las funciones siguientes:
window_set_visible(visible) Hace que la ventana se vuelva visible o invisible. Normalmente la ventana es visible durante todo el juego. Cuando la ventana sea invisible el programa no ejecutará ni recibirá los eventos del teclado.
window_get_visible() Devuelve si la ventana es visible. window_set_fullscreen(full) Hace que la ventana ocupe toda la pantalla (modo pantalla completa) o no.
window_get_fullscreen() Devuelve si la ventana ocupa toda la pantalla. window_set_showborder(show) Indica si el borde alrededor de la ventana debe mostrarse (si la ventana está a pantalla completa el borde no se muestra).
window_get_showborder() Devuelve si el borde de la ventana es visible cuando ésta no está a pantalla completa.
window_set_showicons(show) Indica si deben mostrarse los iconos de la ventana (minimizar, maximizar y cerrar). Si la ventana está a pantalla completa no se muestran.
window_get_showicons() Devuelve si los iconos de la ventana son visibles. window_set_stayontop(stay) Indica si la ventana de be mostrarse siempre por encima de otras ventanas que puedan existir.
window_get_stayontop() Devuelve si la ventana se mantiene siempre por encima de las demás. window_set_sizeable(sizeable) Indica si el jugador puede cambiar el tamaño de la ventana. El jugador sólo podrá hacer esto si el borde de la ventana es visible y la ventana no está a pantalla completa.
window_get_sizeable() Devuelve si el jugador puede cambiar el tamaño de la ventana. window_set_caption(caption) Indica el título de la ventana. Normalmente esto se especifica al definir el room y se cambia usando la variable room_caption. Por ello, esta función no es útil a no ser que dibujes tú mismo el room en lugar de dejar que Game Maker lo haga automáticamente. El título de la ventana sólo es visible cuando ésta tiene borde visible y no está a pantalla completa.
window_get_caption() Devuelve el título de la ventana. window_set_cursor(curs) Indica el puntero que se usará en la ventana. Puedes indicar una de las siguientes constantes:
cr_default cr_none cr_arrow cr_cross cr_beam cr_size_nesw cr_size_ns cr_size_nwse cr_size_we cr_uparrow cr_hourglass cr_drag cr_nodrop cr_hsplit cr_vsplit cr_multidrag cr_sqlwait
cr_no cr_appstart cr_help cr_handpoint cr_size_all
Por ejemplo, para hacer que no se vea el cursor o puntero usa window_set_cursor(cr_none).
window_get_cursor() Devuelve el cursor utilizado en la ventana. window_set_color(color) Indica el color de la parte de la ventana que no se usa para mostrar el room. window_get_color() Devuelve el color de la ventana. window_set_region_scale(scale,adaptwindow) Si la ventana es mayor que el cuarto actual el cuarto se muestra centrado en la ventana. Con esta función es posible indicar si queremos que el cuarto sea escalado para que ocupe toda la ventana o una parte concreta. Si usamos el valor 1 el cuarto no será escalado. Si usamos el valor 0 el cuarto se escalará para ocupar toda la ventana. Si usas un valor negativo el cuarto será escalado al máximo valor dentro de la ventana que le permita seguir conservando su relación de aspecto entre altura y anchura. El segundo parámetro, adaptwindow, indica si el tamaño de la ventana debe adaptarse al cuarto si éste, una vez escalado, no entra por completo dentro de la misma. Esto sólo suele ser útil cuando el primer parámetro (el que indica el escalado del cuarto) es positivo.
window_get_region_scale() Devuelve el factor de escala de la región de dibujo.
La ventana tiene una posición en la pantalla y un tamaño. Cuando hablamos de posición y tamaño siempre nos referimos a la ventana sin los bordes. Puedes cambiar estos valores aunque raramente querrás utilizarlo desde el juego. Normalmente, se determinan automáticamente o el jugador escoge los que le interesan. Las funciones siguientes te permiten cambiar estos aspectos de las ventanas. Observa que estas funciones sólo funcionarán si la ventana no está a pantalla completa. Si la ventana está a pantalla completa los cambios no se aplicarán hasta que la ventana vuelva al modo normal.
window_set_position(x,y) Indica la posición de la ventana. window_set_size(w,h) Indica el nuevo tamaño de la ventana. Observa que si el tamaño indicado es menor que la región de dibujo se mantendrá lo suficientemente grande como para que la región entre por completo dentro de él.
window_set_rectangle(x,y,w,h) Indica la nueva posición y medidas de la ventana (es como usar las dos funciones anteriores a la vez).
window_center() Centra la ventana en la pantalla. window_default() Da a la ventana los valores por defecto de tamaño y posición (centrada). window_get_x() Devuelve la coordenada x de la ventana. window_get_y()Devuelve la coordenada y de la ventana. window_get_width()Devuelve la anchura de la ventana. window_get_height()Devuelve la altura de la ventana.
Puede que nunca necesites utilizar las funciones de posicionamiento de l ventana ya que Game Maker lo realizará automáticamente.
En algunas ocasiones puede que quieras conocer la posición del ratón respecto a la ventana (normalmente se usa la posición del ratón respecto a un cuarto o a una vista). Las siguientes funciones sirven para esto:
window_mouse_get_x() Devuelve la coordenada x del puntero en la ventana. window_mouse_get_y()Devuelve la coordenada y del puntero en la ventana. window_mouse_set(x,y) Indica la nueva posición del puntero en la ventana.
Vistas
Como ya deberías saber puedes definir hasta 8 vistas diferentes al diseñar un cuarto. Una vista se define por su área en el cuarto y por su puerto o viewport en la pantalla (la región de dibujo dentro de la ventana). Usando vistas puedes mostrar diferentes partes del cuarto en diferentes lugares de la pantalla. Además, puedes asegurar que un objeto concreto siempre permanezca visible en una vista.
Puedes controlar las vistas con código. Puedes hacerlas visibles o invisibles, cambiar su posición o tamaño dentro de la región de dibujo o en el cuarto (lo cual es muy útil cuando no indicas ningún objeto que deba ser seguido por la vista). Puedes cambiar el tamaño de los bordes horizontal y vertical alrededor del objeto a ser seguido. Si el objeto que quieres que sea seguido cambia a otro tipo de objeto durante el juego (por ejemplo, si acumula mucha experiencia y evoluciona en un personaje más poderoso) puede que las vistas siguientes no lo sigan correctamente. Para solucionar esto, puedes indicar este código en el evento de creación de todos los objetos del personaje (en este ejemplo se supone que la vista principal es la vista 0):
{ view_object[0] = object_index; }
Las siguientes variables actúan sobre las propiedades de las vistas. Todas, excepto las dos primeras, son vectores con 8 elementos: el primer elemento (0) indica la primera vista y el último elemento (7) la última.
view_enabled Indica si las vistas están activadas en el cuarto actual. view_current* La vista que actualmente se está dibujando (0...7). Usa esta variable sólo en el evento draw. Por ejemplo, puedes chequear el valor de esta variable para dibujar algo sólo en unas vistas y no en otras. El valor de esta variable no se puede cambiar.
view_visible[0..7] Devuelve si la vista indicada es visible en la pantalla
view_xview[0..7] Posición X de la vista en el cuarto. view_yview[0..7] Posición Y de la vista en el cuarto. view_wview[0..7] Anchura de la vista en el cuarto. view_hview[0..7] Altura de la vista en el cuarto. view_xport[0..7] Posición X del puerto en la región de dibujo. view_yport[0..7] Posición Y del puerto en la región de dibujo. view_wport[0..7] Anchura del puerto en la región de dibujo. view_hport[0..7] Altura del puerto en la región de dibujo. view_angle[0..7] Ángulo de rotación usado para la vista en el cuarto (grados en sentido antihorario). view_hborder[0..7] Tamaño en píxeles del borde horizontal alrededor del objeto a seguir por la vista. view_vborder[0..7] Tamaño en píxeles del borde vertical alrededor del objeto a seguir por la vista. view_hspeed[0..7] Velocidad horizontal máxima de la vista. view_vspeed[0..7] Velocidad vertical máxima de la vista. view_object[0..7] Objeto cuya instancia debe permanecer visible en la vista. Si hay varias instancias de este objeto se usará la primera. Puedes asignar una id de una instancia a esta variable para indicar una instancia concreta que quieras seguir.
Observa que el tamaño de la imagen en la pantalla se calcula a partir de las vistas visible al comienzo del cuarto. Si cambias las vistas durante el juego puede que ya no entren correctamente en la pantalla. Para solucionar esto, puedes adaptar tú mismo el tamaño de la pantalla con las siguientes funciones:
window_set_region_size(w,h,adaptwindow) Indica la anchura y altura de la región de dibujo en la ventana. adaptwindow indica si el tamaño de la ventana debe adaptarse si la región de dibujo no cabe dentro de ella. La ventana siempre se adaptará si usas escalado fijo (Consulta la función window_set_region_scale() en el capítulo La ventana).
window_get_region_width() Devuelve la anchura actual de la región de dibujo. window_get_region_height()Devuelve la altura actual de la región de dibujo.
A veces necesitarás conocer la posición del ratón. Normalmente puedes hacer esto con las variables mouse_x y mouse_y. Cuando hay muchas vistas, estas variables dan la posición del ratón respecto a la vista en la que el ratón se encuentra. Pero puede ser que en alguna ocasión necesites conocer la posición del ratón respecto a una vista concreta, incluso cuando el ratón se encuentre fuera de esa vista. Para hacer esto, puedes usar las siguientes funciones:
window_view_mouse_get_x(id) Devuelve la coordenada x del ratón respecto a la vista indicada. window_view_mouse_get_y(id) Devuelve la coordenada y del ratón respecto a la vista indicada. window_view_mouse_set(id,x,y) Indica la posición del ratón respecto a la vista indicada. window_views_mouse_get_x()Devuelve la coordenada x del ratón respecto a la vista en la que se encuentra (es lo mismo que mouse_x).
window_views_mouse_get_y()Devuelve la coordenada y del ratón respecto a la vista en la que se
encuentra (es lo mismo que mouse_y).
window_views_mouse_set(x,y) Indica la posición del ratón respecto a la primera vista que es visible.
Transiciones Al moverse de una room a otra puedes seleccionar una transición. También puedes seleccionar la transición que se utilizará en el frame siguiente sin tener que cambiar de cuarto. Para esto usamos la variable transition_kind. Asignándole un valor entre 1 y 13 se usará la transición correspondiente. Si usamos un valor igual a 0 significa que no se aplicará ninguna transición. La transición sólo se aplicará la próxima vez que se dibuje un frame.
transition_kind Indica la transición al próximo frame: 0 = sin transición 1 = Crear desde la izquierda 2 = Crear desde la derecha 3 = Crear desde arriba 4 = Crear desde abajo 5 = Crear desde el centro 6 = Cambiar desde la izquierda 7 = Cambiar desde la derecha 8 = Cambiar desde arriba 9 = Cambiar desde abajo 10 = Mezclar desde la izquierda 11 = Mezclar desde la derecha 12 = Mezclar desde arriba 13 = Mezclar desde abajo
Observa que es muy sencillo crear tus propias transiciones. Por ejemplo, para crear un efecto de oscurecimiento puedes dibujar un rectángulo negro que tape toda la pantalla y hacer que su factor de transparencia vaya aumentando poco a poco. O puedes cambiar la posición y el tamaño de una vista para mover el cuarto fuera de la zona visible. Utilizando superficies y partículas puedes crear transiciones realmente asombrosas!
Redibujando la pantalla Normalmente al final de cada paso del juego (step) el cuarto se dibuja en la pantalla. Pero en algunas ocasiones puede que quieras dibujar el cuarto en otro momento. Esto ocurre cuando tu programa toma el control, por ejemplo: antes de que el programa se congele durante un largo período es recomendable dibujar el cuarto. O cuando el juego muestra un mensaje y espera a que el jugador presione un tecla para continuar es necesario dibujar el cuarto en medio de esta operación. Existen dos funciones para hacer esto:
screen_redraw() Redibuja la pantalla ejecutando todos los eventos de dibujo. screen_refresh() Refresca la pantalla usando la imagen del cuarto actual sin ejecutar los eventos de dibujo.
Para entender la segunda función es necesario ver cómo trabaja internamente el mecanismo de dibujo. Internamente existe una imagen donde se dibuja todo. Esta imagen no es visible en la pantalla. Sólo al final de un paso del juego, cuando se ha dibujado todo, la imagen de la pantalla se reemplaza por esta imagen interna (esta técnica se conoce como “double buffering”). La primera función scrren_redraw() dibuja la imagen interna y luego refresca la imagen de la pantalla. La segunda función screen_refresh() sólo refresca la imagen de la pantalla, pero no actualiza la imagen interna.
Ahora deberías entender por qué no puedes usar las acciones o funciones de dibujo en otros eventos a parte de los eventos de dibujo. Si lo haces así, dibujarías elementos en la imagen interna pero no en la pantalla. Cuando se ejecuta el evento draw, primero se dibuja el fondo del cuarto, tapando y borrando todo lo que habías dibujado en la imagen interna. Pero cuando usas screen_refresh() después de dibujar algo, la imagen se volverá visible en la pantalla. Por ejemplo, podemos crear un trozo de código que dibuje un texto en la pantalla, llame a la función de refresco y espere a que el jugador pulse una tecla:
{ draw_text(screen_width/2,100,'Pulsa cualquier tecla para continuar'); screen_refresh(); keyboard_wait(); }
Observa que al dibujar en un evento distinto del evento draw sólo puedes dibujar en la imagen interna, ¡nunca en una vista! Así que las coordenadas que usas serán las mismas que si no hubiera vistas. Ten cuidado al usar esta técnica: asegúrate de que has entendido bien como funciona y ten en cuenta que el refresco de la pantalla tarda un poco de tiempo.
Cuando estás dibujando el propio cuarto puede ser útil no dejar que se dibuje automáticamente. Por ejemplo, puedes querer que el cuarto sólo se dibuje cada 5 pasos. Para ello puedes usar la función siguiente:
set_automatic_draw(value) Indica si el cuarto se debe dibujar automáticamente (true por defecto) o no (false).
Finalmente, hay una función que te permite sincronizar el dibujo con la frecuencia de refresco del monitor:
set_synchronization(value) Indica si se debe sincronizar el dibujo con la frecuencia de refresco del monitor.
También puedes obligar a que el programa espere hasta la próxima sincronización vertical:
screen_wait_vsync() Espera hasta la siguiente sincronización vertical del monitor.
Música y sonido Actualmente, el sonido juega un papel crucial en los videojuegos de computadora. Estos deben ser agregados al juego en forma de recursos de sonido, pero asegúrate de que los nombres que uses sean nombres de variable válidos. Como ya habrás leído, puedes indicar cuatro diferentes tipos de sonido: normal, música de fondo, 3D, y sonidos que deben ser reproducidos a través de un reproductor multimedia.
Los sonidos normales son usados para efectos de sonido y por lo general, son archivos WAV. Puedes reproducir muchos del tipo NORMAL a la vez (incluso puedes reproducir múltiples instancias del mismo sonido a la vez) y se caracterizan por podérseles aplicar cualquier tipo de efectos en ellas.
El sonido de fondo comúnmente consiste en archivos MIDI pero algunas veces también pueden usarse WAV y a estos también se les puede aplicar efectos de sonido. La única diferencia que tienen con los sonidos NORMALES, es que los de fondo solo pueden reproducirse uno a la vez. Si reproduces un sonido, el que previamente se había estado reproduciendo es parado.
El uso y características del sonido 3D son descritos más adelante, en el subcapítulo “Sonidos 3D”. Son sonidos mono (WAV o MIDI). Finalmente, si quieres usar otro tipo de sonido, en partículas mp3, estos no pueden ser reproducidos por DirectX. En su lugar, el reproductor multimedia normal debe ser usado para esto, lo cual lo hace mucho más limitado, ya que solo puedes reproducir un sonido a la vez, no se le pueden aplicar efectos (ni siquiera cambio de volumen) y el tiempo (p.e. los sonidos rebobinados son de calidad pobre). También puede haber retardos en reproducirlos. Se recomienda altamente no usar este tipo de media, ya que hay algunas computadoras que probablemente no estén capacitadas para reproducirlos).
Puedes encontrar información sobre las funciones de audio en las secciones siguientes: Funciones básicas de sonido Existen cinco funciones básicas relacionadas con sonido: 2 para reproducir un sonido, una para ver si un sonido se está reproduciendo, y dos más para parar el sonido. La mayoría necesita el índice del sonido como argumento. El nombre del sonido representa su índice, pero también puedes almacenar el índice en una variable, y usarla como argumento.
sound_play(index) Reproduce el sonido indicado una vez. Si el sonido es música de fondo, la actual es parada.
sound_loop(index) Reproduce el sonido indicado, rebobinándolo continuamente. Si el sonido es música de fondo, la actual es parada.
sound_stop(index) Para el sonido indicado. Si hay varios sonidos con este índice reproduciéndose
simultáneamente, todos son parados.
sound_stop_all() Para todos los sonidos. sound_isplaying(index) Devuelve si (una copia de) el sonido indicado se esta reproduciendo. Nótese que esta función devuelve true cuando el sonido realmente esta reproduciéndose a través de los altavoces (línea de salida). Después de llamar la función de reproducir un sonido, no llega a los altavoces inmediatamente, lo que quiere decir que mientras esto no suceda, esta función devolverá false. Parecido es si cuando el sonido es parado y lo escuchas por un tiempo más (p.e. por el eco), la función devolverá true.
Es posible usar efectos de sonido. en lo particular puedes cambiar el volumen y pan (si el sonido viene del altavoz izquierdo o derecho). En estos casos el volumen solamente puede ser reducido. Estas funciones no trabajan con archivos que se reproducen a través del reproductor multimedia.
sound_volume(index,value) Cambia el volumen del sonido indicado (0 = bajo, 1 = alto,) sound_global_volume(value) Cambia el volumen global de todos los sonidos (0=bajo, 1=alto) sound_fade(index,value,time) Cambia el volumen del sonido indicado a el nuevo value (0=bajo, 1=alto) durante el tiempo indicado (en milisegundos). Esto puede ser usado para ascender o descender el sonido (p.e. para entradas).
sound_pan(index,value) Cambia el pan del sonido indicado (-1=izquierda, 0,centro, 1=derecha). sound_background_tempo(factor) Cambia el tempo de la música de fondo (si es un archivo midi). factor indica el factor a multiplicar por el tempo. Un valor de 1 corresponde al tempo normal. Valores mayores corresponden a un tempo más rápido, menores a un tempo más lento. Debe oscilar entre 0.01 y 100.
A demás de los archivos MIDI y WAV (y mp3) existe un cuarto tipo de archivo que puede ser reproducido: archivos direct music. Tienen la extensión .sgt. Dichos archivos frecuentemente se refieren a otros archivos describiendo p.e. banda o género. Para encontrar estos archivos, el sistema de sonido debe saber donde se localizan. Con este propósito, puedes usar las siguientes funciones para estableces el directorio de búsqueda de archivos. NOTA: Debes agregar los archivos tu mismo. Game Maker no incluye automáticamente dichos archivos adicionales.
sound_set_search_directory(dir) Establece el directorio en donde se deben buscar archivos direct music. El string DIR no debe incluir la diagonal al final.
Efectos de sonido Esta funcionalidad esta disponible únicamente en la versión registrada de Game Maker.
Los efectos de sonido pueden ser usados para cambiar la manera en que los sonidos normales y música de fondo, suena. Ten en cuenta que los efectos de sonidos solo aplican en archivos WAV y MIDI, no a los mp3. Esta sección describe las funciones que existen para usar y cambiar efectos de sonido. Ten también en cuenta que para poder usar
estas funciones necesitas una buena comprensión de cómo el sonido y los sintetizadores trabajan. No se han agregado explicaciones de los diferentes parámetros que se dan. Busca en la web o en libros para más información.
Para aplicar un efecto de sonido a un sonido en particular, puedes indicarlo cuando definas el sonido como recurso o puedes usar la siguiente función:
sound_effect_set(snd,effect) Establece una (combinación de) efecto(s) de sonido para el sonido indicado. EFFECT puede ser cualquiera de estos valores:
se_none se_chorus se_echo se_flanger se_gargle se_reverb se_compressor se_equalizer Puedes establecer una combinación de efectos, sumando los valores. Entonces, por ejemplo, puedes usar:
sound_effect_set(snd,se_echo+se_reverb); para obtener una combinación de los efectos echo y reverb.
Todos los efectos tienen distintas configuraciones, una vez que el efecto ha sido aplicado en un sonido. El orden en esto es crucial. Primero aplicas el efecto al sonido y después estableces sus parámetros. Una vez que reapliques los efectos al sonido, la configuración se borra y necesitas establecerla de nuevo. Nótese que todos los parámetros deben oscilar en un rango en particular, según se indique a continuación. Las siguientes funciones existen para cambiar la configuración de los efectos de sonido: *Al final de cada parámetro, entre paréntesis está el rango que se necesita usar p.e. (0 a 100,pred.50) pred.=predeterminado
sound_effect_chorus(snd,wetdry,depth,feedback,frequency,wave,delay,pha se) Establece los parámetros para el efecto de chorus al sonido indicado. Los siguientes parámetros son requeridos: WETDRY Radio de la señal wet (procesada) a la señal dry (no procesada).(0 a 100,pred.50) DEPTH Porcentaje con el cual el tiempo de espera es modulado por el oscilador de baja frecuencia, en cientos por punto de porcentaje. (0 a 100,pred.25) FEEDBACK Porcentaje de señal de salida para respaldar la entrada del sonido. (-99 a 100,pred.0) FREQUENCY Frecuencia del LFO (0 a 10,pred.0) WAVE Forma de la onda del LFO (0- triangular, 1-onda,pred.1) DELAY Tiempo (número) en milisegundos que la entrada debe esperar antes de ser reproducida de nuevo (0 a 20,pred.0) PHASE Fase diferencial entre LFOs izquierdos o derechos.(0 a 4,pred.2)
sound_effect_echo(snd,wetdry,feedback,leftdelay,rightdelay,pandelay) Establece los parámetros para el efecto de echo al sonido indicado. Los siguientes parámetros son requeridos: WETDRY Radio de la señal wet (procesada) a la señal dry (no procesada).(0 a 100,pred.50) FEEDBACK .Porcentaje de señal de salida para respaldar la entrada del sonido. (-99 a 100,pred.0) LEFTDELAY. Retraso para el canal izquierdo, en milisegundos. (1 a 2000,pred.333) RIGHTDELAY. Retraso para el canal derecho, en milisegundos. (1 a 2000,pred.333) PANDELAY. Indica si el LEFTDELAY y RIGHTDELAY deben intercambiarse con cada echo que sucede.(0 y 1,pred.0)
sound_effect_flanger(snd,wetdry,depth,feedback,frequency,wave,delay,ph ase) Establece los parámetros para el efecto de flanger al sonido indicado. Los siguientes parámetros son requeridos: WETDRY Radio de la señal wet (procesada) a la señal dry (no procesada).(0 a 100,pred.50) DEPTH Porcentaje con el cual el tiempo de espera es modulado por el oscilador de baja frecuencia, en cientos por punto de porcentaje. (0 a 100,pred.25) FEEDBACK Porcentaje de señal de salida para respaldar la entrada del sonido. (-99 a 100,pred.0) FREQUENCY Frecuencia del LFO (0 a 10,pred.0) WAVE Forma de la onda del LFO (0- triangular, 1-onda,pred.1) DELAY Tiempo (número) en milisegundos que la entrada debe esperar antes de ser reproducida de nuevo (0 a 20,pred.0) PHASE Fase diferencial entre LFOs izquierdos o derechos.(0 a 4,pred.2)
sound_effect_gargle(snd,rate,wave) Establece los parámetros para el efecto de gargle al sonido indicado. Los siguientes parámetros son requeridos:
RATE Variable de modulación,en Hertz. (1 a 1000, pred.1) WAVE Forma de la onda del LFO (0- triangular, 1-onda,pred.1)
sound_effect_reverb(snd,gain,mix,time,ratio) Establece los parámetros para el efecto de reverb al sonido indicado. Los siguientes parámetros son requeridos:
GAIN Señal ganada por la entrada, en decibels (dB) (-96 a 0,pred.0) MIX Mezcla de la repetición, en dB. (-96 a 0,pred.0) TIME Tiempo de la repetición, en milisegundos (0.001 a 3000,pred.1000) RATIO
Radio de frecuencia (0.001 a 0.999,pred.0.001)
sound_effect_compressor(snd,gain,attack,release,threshold,ratio,delay) Establece los parámetros para el efecto de compressor al sonido indicado. Los siguientes parámetros son requeridos:
GAIN Señal de salida ganada después de la compresión (-60 a 60, pred.0) ATTACK Tiempo antes de que la compresión termine (0.01 a 500,0.01) RELEASE Velocidad a la que la entrada es parada después de que la entrada llegue debajo del umbral.(50 a 3000,pred.50)
RATIO
Radio de frecuencia (0.001 a 0.999,pred.0.001)
DELAY Tiempo (número) en milisegundos que la entrada debe esperar antes de ser reproducida de nuevo (0 a 20,pred.0)
sound_effect_equalizer(snd,center,bandwidth,gain) Establece los parámetros para el efecto de equalizer al sonido indicado. Los siguientes parámetros son requeridos:
center Fecuencia central, en Hertz (80 a 16000) bandwidth Banda ancha, en semitonos (1 a 36) gain Ganancia. (-15 a 15)
Sonido 3D Esta funcionalidad esta disponible únicamente en la versión registrada de Game Maker.
Sonido 3D se refiera al sonido que tiene una posición (y velocidad) con respecto al oyente. A pesar de que es más prominente usarlo en juegos 3D, también puede ser efectivamente aplicado en juegos 2D. La idea es que el sonido tenga una posición en el espacio. En todas las funciones, se asume que el oyente está en una posición (0,0,0). El sistema calcula como el receptor estaría oyendo el sonido, y lo adapta acorde a esto. Este efecto es especialmente bueno cuando tienes un buen equipo de sonido. De cualquier manera, esto también funciona en altavoces pequeños.
Además de una posición, el sonido también puede tener una velocidad. Esto lleva al muy conocido efecto Doppler, que están correctamente modelado. Finalmente el sonido puede tener una orientación y, una vez más, el sonido es adaptado por consiguiente. Game Maker cuenta con la modalidad de sonido 3D, a través de las funciones que se indican abajo, pero solo funcionan con recursos de sonido que fueron indicados como 3D. (La desventaja es que los sonidos 3D son mono, no estéreo).
sound_3d_set_sound_position(snd,x,y,z)
Establece la posición al sonido indicado con respecto
a la posición del oyente en el espacio. Valores en el eje x incrementan de izquierda a derecha, en el eje y de arriba hacia abajo, y en el eje z de cerca a lejos. Estos valores son medidos en metros. El volumen con el que el sonido se debe oír depende en esta medida, de la misma manera a como ocurre en el mundo real.
sound_3d_set_sound_velocity(snd,x,y,z)
Establece la velocidad al sonido indicado con el vector
indicado en el espacio. Por favor note que estableciendo la velocidad no significa que la posición cambie. La velocidad es usada sólo para calcular efectos doppler. Entonces, si quieres mover un sonido debes cambiar tu mismo la posición del sonido.
sound_3d_set_sound_distance(snd,mindist,maxdist)
Establece la distancia mínima a la
cual el sonido está en su máxima amplitud, y la distancia máxima a la cual el sonido ya no se debe oir. Entonces, cuando la distancia oscila entre 0 y la máxima distancia, el sonido esta en su máxima amplitud. Cuando se oscile entre la distancia mínima y la máxima, la amplitud decrece lentamente hasta que la distancia máxima es alcanzada, o el sonido ya no es audible. Por predeterminado (default), la distancia mínima es 1 metro y la máxima 1 billón de metros.
sound_3d_set_sound_cone(snd,x,y,z,anglein,angleout,voloutside Normalmente el sonido tiene la misma amplitud, a una distancia dada, en todas las direcciones. Puedes establecer un cono de sonido para cambiar esto y dirigir el sonido. x,y,z especifican la dirección del cono. ANGLEIN especifica el ángulo interior. Si el oyente esta dentro de este ángulo, puede escuchar el sonido en su volumen normal. ANGLEOUT especifica el ángulo exterior. Cuando el oyente esta afuera de este ángulo, el volumen es indicado con VOLOUTSIDE. Para ser precisos, VOLOUTSIDE es un número negativo que indica el número de cientos de decibeles que deben ser sustraídos del volumen interior. Entre el ángulo interior y exterior, el volumen decrece gradualmente.
Música desde el CD Esta funcionalidad esta disponible únicamente en la versión registrada de Game Maker.
Existen también funciones para tratar con la reproducción de música desde un CD:
cd_init()Debe llamarse antes de usar las otras funciones, así como cuando se cambia un CD (o simplemente de tiempo a tiempo).
cd_present()Devuelve si hay un cd en el dispositivo CD predeterminado. cd_number()Devuelve el numero de pistas en el CD. cd_playing()Devuelve si el CD se está reproduciendo. cd_paused().Devuelve si el CD esta pausado o parado. cd_track()Devuelve el número de la pista que se está reproduciendo (1=la primera) cd_length().Devuelve la longitud total del CD en milisegundos. cd_track_length(n). Devuelve la longitud de una pista n del CD en milisegundos cd_position().Devuelve la posición actual en el CD en milisegundos. cd_track_position().Devuelve la posición actual de la pista reproducida, en milisegundos. cd_play(first,last). Le indica al CD desde que pista a que pista debe reproducir. Si deseas que reproduzca todo el CD, indica 1 y 1000 como argumentos.
cd_stop()Deja de reproducir. cd_pause().Pausa la reproducción. cd_resume().Continua la reproducción. cd_set_position(pos). Establece la posición en el CD, en milisegundos. cd_set_track_position(pos). Establece la posición de la pista actual en milisegundos. cd_open_door()Abre la caja del Reproductor de CDs. cd_close_door()Cierra la caja del Reproductor de CDs.
Hay una función general para acceder la funcionalidad multimedia de Windows:
MCI_command(str) Esta función envía el comando al sistema multimedia de Windows usando el Media Control Interface (MCI, Interfaz de Control de Medios). Devuelve la cadena de texto de resultado. Puedes usar esta función
para controlar cualquier tipo de hardware multimedia. Para más información sobre este comando, consulta la ayuda de Windows. Por ejemplo,
MCI_command('play cdaudio from 1') pone a sonar el cd (después de
inicializalo correctamente). Esta función es sólo para uso avanzado.
Ventanas, highscores, y otros pop-ups En esta sección vamos a aprender algunas funciones que pueden ser usadas para mostrar ventanas con videos, imágenes, etc...para mostrar mensajes y preguntas al jugador, y mostrar la tabla de mayores puntajes. Esta sección está dividida en los temas: Ventanas Muchos juegos tienen ventanas, las cuales pueden mostrar un video, una imagen o algún texto. Frecuentemente son usadas al principio del juego (como intro), al principio de un nivel, o al final del juego (p.e. los créditos). En Game Maker, dichas ventanas con texto, imágenes o video pueden mostrarse en cualquier momento del juego, y mientras esto sucede, el juego es temporalmente pausado. Estas son las funciones:
show_text(fname,full,backcol,delay) Muestra una ventana con texto. fname es el nombre del archive de texto (.txt o .rtf). Debes poner este archivo en el directorio del juego tu mismo. También, cuando crees una versión independiente del juego (stand-alone), no debes olvidar incluir este archive ahí. full indica si se debe mostrar en pantalla completa. BACKCOL es el color de fondo, y DELAY es el retardo en segundos antes de volver al juego (aunque el jugador siempre tendrá la posibilidad de volver al juego manualmente, haciendo clic con el Mouse)
show_image(fname,full,delay) Muestra una ventana con una imagen. fname es el nombre del archivo de imagen (únicamente .bmp, .jpg y .wmf), pero debes poner los archivos en el directorio del juego tu mismo. FULL indica si deseas mostrarlo en pantalla completa. DELAY es el retardo en segundos antes de volver al juego.
show_video(fname,full,loop) Muestra una ventana de video. FNAME es el nombre del archivo de video (.avi,.mpg), pero debes poner este archivo en el directorio del juego tu mismo. FULL indica si deseas mostrarlo en pantalla completa. DELAY es el retardo en segundos antes de volver al juego.
show_info() Muestra la información del juego. load_info(fname) Carga la información del juego con el nombre de archive FNAME, el cual debería ser un archivo RTF. Esto hace posible mostrar diferentes archivos de ayuda, en diferentes momentos.
Preguntas y mensajes pop-up Existen otras funciones para mostrar mensajes, preguntas, un menú con opciones, o un cuadro de diálogo en el cual el jugador puede insertar un número, un string, indicar un color o un nombre de archivo:
show_message(str) Muestra un cuadro de dialogo con un string STR como mensaje. show_message_ext(str,but1,but2,but3) Muestra un cuadro de diálogo con el string STR como mensaje, más un máximo de 3 botones. But1,but2 y but3 contienen el texto de cada botón. Un string vacío significa
que el botón no se mostrará. En los textos puedes usar el símbolo “&” para indicar que el siguiente carácter debe ser usado como acceso directo a ese botón (p.e. “&Acepto”, si se presiona “A” se simula la presión del botón). Esta función devuelve el número del botón presionado (0 si el usuario presiona la tecla ESC).
show_question(str) Muestra una pregunta; devuelve true cuando el usuario elige “yes” y de otra manera, false.
get_integer(str,def) Pregunta un número al jugador por medio de un cuadro de diálogo. STR es el mensaje, DEF es el número predeterminado que se mostrará.
get_string(str,def) Pregunta un string al jugador en un cuadro de diálogo. STR es el mensaje, DEF es el valor predeterminado.
message_background(back) Establece la imagen de fondo par a los cuadros de diálogo para cualquiera de las funciones arriba. BACK debe ser uno de los archivos definidos en el juego. Si BACK es parcialmente transparente, también lo será el cuadro de diálogo (solo para Windows 2000 o superior).
message_alpha(alpha) Establece la traslucidez de los cuadros de diálogo para cualquiera de las funciones anteriores. ALPHA debe oscilar entre 0 (completamente traslúcido) y 1 (completamente opaco) (solo para WIN2000 o superior).
message_button(spr) Establece el sprite usado para los botones en los cuadros de diálogo. SPR debe ser un sprite consistiendo de 3 imágenes, la primera indica el botón cuando no esta presionado y el mouse está alejado, el Segundo indica el botón cuando el mouse está sobre el pero no presionado, y el tercero cuando del botón es presionado.
message_text_font(name,size,color,style) Establece la fuente para el texto de los cuadros de diálogo (esta debe ser una fuente normal de Windows, no una de las fuentes de recurso que usas en tu juego!) STYLE indica el estilo de la fuente (0=normal, 1=negrita, 2=cursiva, 3=negrita y cursiva).
message_button_font(name,size,color,style) Establece la fuente para los botones en los cuadros de diálogo. STYLE indica el estilo de fuente. (0=normal, 1=negrita, 2=cursiva, 3=negrita y cursiva).
message_input_font(name,size,color,style) Establece la fuente
para el campo de entrada en
los cuadros de diálogo. STYLE indica el estilo de fuente. (0=normal, 1=negrita, 2=cursiva, 3=negrita y cursiva).
message_mouse_color(col) Establece el color de la fuente para los botones en los cuadros de diálogo cuando el mouse está sobre ellos.
message_input_color(col) Establece el color para el fondo del campo de entrada en los cuadros de diálogo.
message_caption(show,str) Establece el título del cuadro de diálogo. SHOW indica si
se debería mostrar
el borde (1) o no (0) y STR indica el título cuando el borde si es mostrado.
message_position(x,y) Establece la posición para los cuadros de diálogo en la pantalla. message_size(w,h) Arregla el tamaño de los cuadros de diálogo en la pantalla. Si eliges 0 para el ancho(w), el ancho de la imagen es usado. Si eliges 0 para el alto (h), el alto se calcula basándose en el número de líneas del mensaje.
show_menu(str,def) Muestra un menú pop-up. STR indica el texto del menú. Este consiste en diferentes opciones del menú con una barra vertical entre ellas. Por ejemplo, str = ‘menu0|menu1|menu2’. Cuando la primera
opción es seleccionada, se devuelve un 0, para la segunda un 1, etc. Cuando ninguna opción es seleccionada, el valor predeterminado def es devuelto.
show_menu_pos(x,y,str,def) Muestra un menú popup, como en la función anterior, pero en la posición x, y en la pantalla.
get_color(defcol) Le pide al jugador un color. DEFCOL es el color predeterminado. Si el jugador presiona CANCEL el valor -1 es devuelto.
get_open_filename(filter,fname) Le pide al jugador un archive para abrir, con el filtro (FILTER) dado. El filtro tiene la forma ‘nombre1|máscara1|nombre2|máscara2’ . Una máscara contiene diferentes opciones con un ';'(punto y coma) entre ellas. '*' (asterisco) significa cualquier string. Por ejemplo: ‘Mapa de bits|*bmp;*.wmf’.Si el jugador presiona Cancel, un string vacío es devuelto.
get_save_filename(filter,fname) Pide un archivo para guardarlo, con el filtro dado. Si el jugador presiona Cancel, un string vacío es devuelto.
get_directory(dname) Pide un directorio. DNAME es el nombre predeterminado. Si el usuario presiona Cancel, un string vacío es devuelto.
get_directory_alt(capt,root) Una forma alternativa de preguntar por un directorio. CAPT es el titulo a mostrar. ROOT es la raíz del diagrama del directorio a mostrar. Usa un string vacío para mostrar el diagrama completo. Si el usuario presiona Cancel, un string vacío es devuelto.
show_error(str,abort) Muestra un mensaje de error estándar (y/o escribe el error al archivo de registros). ABORT indica si el juego debería cerrarse (true-abortar, false-ignorar).
Tablas de récords
Una ventana “pop-up” especial es la lista de MEJORES PUNTAJES que se mantiene para cada juego. Las siguientes funciones existen para esto:
highscore_show(numb) Muestra la tabla de highscores. NUMB es un nuevo puntaje. Si el puntaje es suficientemente bueno para ser agregado a la lista, el jugador puede introducir un nombre. Usa -1 para únicamente mostrar la lista actual.
highscore_set_background(back) Establece la imagen de fondo. BACK debe ser el índice de uno de los recursos de fondo.
highscore_set_border(show) Establece si la forma de highscore debe mostrar un borde o no. highscore_set_font(name,size,style) Establece la fuente usada para el texto en la tabla. (Esta es una fuente normal de Windows, no una de los recursos de fuente). Debes especificar un nombre y un estilo (0=normal, 1=negrita, 2=cursiva, 3=negrita y cursiva).
highscore_set_colors(back,new,other)Establece los colores usados para el fondo, el de una nueva entrada en la tabla, y el de las demás entradas. entradas.
highscore_set_strings(caption,nobody,escape) Cambia los diferentes strings predeterminados usados al mostrar la tabla de highscores. CAPTION es el título de la forma. NOBODY es el string usado cuando no hay nadie en un lugar en particular. ESCAPE es el string en el inferior indicando que se debe presionar la tecla ESC. Puedes usar esto en particular cuando tu juego usa un idioma diferente.
highscore_show_ext(numb,back,border,col1,col2,name,size) Muestra la tabla de hightscores con diferentes opciones (también puede lograrse usando las funciones anteriores). NUMB es el nuevo puntaje. Si el puntaje es suficientemente bueno para ser agregado a la lista, el jugador puede introducir un nombre. Usa -1 para mostrar simplemente la lista actual. BACK es la imagen de fondo a usar, BORDER indica si se debe mostrar o no el borde. COL1 es el color para la nueva entrada, COL2 para las demás entradas. NAME es el nombre de la fuente a usar, y SIZE es el tamaño de la fuente.
highscore_clear() Limpia la tabla de puntajes. highscore_add(str,numb) Agrega al jugador con el nombre STR y puntaje NUMB a la lista. highscore_add_current() Agrega el puntaje actual a la lista de hightsocres. También se le pide al jugador que proporcione un nombre.
highscore_value(place) Devuelve el puntaje de la persona en el lugar PLACE(1-10). Esto puede ser usado para dibujar tu propia lista de puntajes.
highscore_name(place) Devuelve el nombre de la persona con el lugar PLACE(1-10). draw_highscore(x1,y1,x2,y2) Dibuja la tabla de puntajes en la room, con la caja dada (x1,y1,x2,y2), usando la fuente actual.
Recursos En Game Maker puedes especificar varios tipos de recursos, como sprites, sonidos, fondos, objetos, etc. En este capítulo encontrarás un número de opciones que actúan sobre estos recursos, para modificarlos u obtener información de ellos en tiempo real. Los recursos se dividen en los siguientes tipos: Sprites Las funciones siguientes te dan información sobre un sprite:
sprite_exists(ind) Devuelve si el sprite con el índice (ind) especificado existe. sprite_get_name(ind) Devuelve el nombre del sprite con el ind especificado. sprite_get_number(ind) Devuelve el número de subimágenes del sprite con el índice dado. sprite_get_width(ind) Devuelve el ancho del sprite con el índice especificado. sprite_get_height(ind) Devuelve la altura del sprite con el índice dado. sprite_get_transparent(ind) Devuelve si el sprite con el índice especificado utiliza transparencia. sprite_get_smooth(ind) Devuelve si el sprite tiene los bordes suavizados. sprite_get_preload(ind) Devuelve si el sprite debe ser cargado al principio del juego. sprite_get_xoffset(ind) Devuelve el x-offset (punto de origen en x) del sprite con el índice especificado. sprite_get_yoffset(ind) Devuelve el y-offset (punto de origen en y) del sprite con el índice especificado. sprite_get_bbox_left(ind) Devuelve el valor del límite izquierdo del sprite (bounding box) con el índice especificado.
sprite_get_bbox_right(ind) Devuelve el valor del límite derecho del sprite (bounding box) con el índice especificado.
sprite_get_bbox_top(ind) Devuelve el valor del límite superior del sprite (bounding box) con el índice especificado.
sprite_get_bbox_bottom(ind) Devuelve el valor del límite inferior del sprite (bounding box) con el índice especificado.
sprite_get_bbox_mode(ind) Devuelve el modo usado para la caja de controno del sprite (0=automatic, 1=full image, 2=manual).
sprite_get_precise(ind) Devuelve si el sprite con el índice dado utiliza la colisión precisa (precise collision checking).
Sonidos Las funciones siguientes te dan información sobre los sonidos:
sound_exists(ind) Devuelve si un sonido con el índice dado existe. sound_get_name(ind) Devuelve el nombre del sonido con el índice dado. sound_get_kind(ind) Devuelve el tipo de sonido del sonido especificado (0=normal, 1=background, 2=3d, 3=mmplayer).
sound_get_preload(ind) Devuelve si el sonido especificado debe cargarse al principio del juego.
Los sonidos usan muchos recursos y algunos sistemas sólo pueden guardar y hacer sonar un número limitado de ellos. Si haces un juego muy grande, deberías llevar un control sobre los sonidos que se cargan en la memoria de audio en cada momento. Podrías desactivar la opción Preload para asegurarte de que los sonidos no se carga hasta que van a ser usados (aunque este método puede originar cierto retraso la primera vez que se escuche el sonido). Además, los sonidos no se eliminan de la memoria cuando ya no van a ser utilizados. Para controlar esto puedes usar las siguientes funciones:
sound_discard(index) Elimina el sonido indicado de la memoria de audio. sound_restore(index) Carga el sonido indicado en la memoria de audio para que se pueda utilizar cuando se necesite inmediatamente
Fondos Las siguientes funciones te darán información acerca de un fondo :
background_exists(ind) Devuelve si el background (fondo) con el índice dado existe. background_get_name(ind) Devuelve el nombre del fondo con el índice indicado. background_get_width(ind) Devuelve el ancho del fondo con el índice indicado. background_get_height(ind) Devuelve la altura del fondo con el índice especificado. background_get_transparent(ind) Devuelve si el fondo con el índice indicado es transparente. background_get_smooth(ind) Devuelve si el fondo tiene los bordes suavizados. background_get_preload(ind) Devuelve si el fondo debe ser cargado al principio del juego.
Fuentes Las funciones siguientes te dan información sobre las fuentes:
font_exists(ind) Devuelve si la fuente con el índice especificado existe. font_get_name(ind) Devuelve el nombre de la fuente con el índice especificado. font_get_fontname(ind) Devuelve el nombre de fuente (arial, verdana,...) de la fuente con el índice especificado.
font_get_bold(ind) Devuelve si la fuente con el índice especificado está en negrita. font_get_italic(ind) Devuelve si la fuente con el índice especificado es cursiva. font_get_first(ind) Devuelve el primer carácter de la fuente con el índice especificado. font_get_last(ind) Devuelve el último carácter de la fuente con el índice especificado.
Paths Las funciones siguientes te proporcionarán información sobre un path:
path_exists(ind) Devuelve si el path con el índice dado existe. path_get_name(ind) Devuelve el nombre del path con el índice dado. path_get_length(ind) Devuelve la longitud del path con el índice indicado. path_get_kind(ind) Devuelve el tipo de conexiones del path con el índice especificado (0=recto, 1=curvo). path_get_closed(ind) Devuelve si el path es cerrado o no. path_get_precision(ind) Devuelve la precisión utilizado para paths redondeadas. path_get_number(ind) Devuelve el número de puntos del path. path_get_point_x(ind,n) Devuelve la coordenada x del punto n del path. El primer punto es el 0. path_get_point_y(ind,n) Devuelve la coordenada y del punto n del path. El primer punto es el 0. path_get_point_speed(ind,n) Devuelve la velocidad del punto n del path. El primer punto es el 0. path_get_x(ind,pos) Devuelve la coordenada x en la posición pos del path (pos debe estar comprendida entre 0 y 1).
path_get_y(ind,pos) Devuelve la coordenada y en la posición pos del path (pos debe estar comprendida entre 0 y 1).
path_get_speed(ind,pos) Devuelve la velocidad en la posición pos del path (pos debe estar comprendida entre 0 y 1).
Scripts Las siguientes opciones te darán información acerca de un script:
script_exists(ind) Devuelve si un script con el índice indicado existe. script_get_name(ind) Devuelve el nombre del script con el índice indicado. script_get_text(ind) Devuelve la cadena de texto del script con el índice dado
Time lines Las funciones siguientes te dan información sobre las time lines:
timeline_exists(ind) Devuelve si la time line con el índice especificado existe. timeline_get_name(ind) Devuelve el nombre de la time line con el índice especificado.
Objetos Las siguientes funciones proporcionarán información acerca de un objeto:
object_exists(ind) Devuelve si el objeto con el índice dado existe. object_get_name(ind) Devuelve el nombre del objeto con el índice dado. object_get_sprite(ind) Devuelve el índice del sprite por defecto del objeto con el índice especificado. object_get_solid(ind) Devuelve si el objeto con el índice dado es sólido por defecto. object_get_visible(ind) Devuelve si el objeto con el índice dado es visible por defecto. object_get_depth(ind) Devuelve la profundidad del objeto con el índice dado. object_get_persistent(ind) Devuelve si el objeto con el índice señalado es persistente. object_get_mask(ind) Devuelve el índice de la máscara del objeto con el índice dado (-1 si no tiene máscara especial).
object_get_parent(ind) Devuelve el índice del objeto pariente del objeto ind (-1 si no tiene pariente). object_is_ancestor(ind1,ind2) Devuelve si el objeto ind2 es un parent del objeto ind1.
Rooms Las siguientes funciones darán información acerca de una habitación:
room_exists(ind) Devuelve si el cuarto con el índice señalado existe. room_get_name(ind) Devuelve el nombre de la habitación con el índice dado.
Observa que como las habitaciones cambian durante el juego hay otras rutinas para obtener información de la habitación actual
Modificando los recursos Estas funciones sólo están disponibles en la versión registrada de Game Maker. Es posible crear nuevos recursos durante el juego. También se pueden cambiar recursos existentes. Las posibilidades se muestran en este capítulo. Pero se debe tener cuidado: la modificación de recursos fácilmente puede llevar a ¡¡¡errores en los juegos!!! Al modificar recursos, se deben seguir las siguientes reglas: •
No cambies recursos que están siendo utilizados. ¡Esto provocará errores! Por ejemplo, no cambies un sprite que está siendo utilizado por alguna instancia.
•
Cuando guardas/salvas el juego mientras se está ejecutando, los recursos agregados y modificados NO son guardados con el juego. Por lo que, si más tarde se carga el juego, los recursos modificados podrían no estar disponibles. En general, cuando se manipulan recursos ya no se puede hacer uso del sistema interno de salvado y carga de juegos.
•
Cuando reinicias un juego mientras está en ejecución, los recursos modificados no son reestablecidos a su forma original. En general, cuando se manipulan recursos ya no se puede hacer uso de la acción o de la función para reiniciar el juego.
La manipulación de recursos puede ser muy lenta. Por ejemplo, el cambiar sprites o fondos es relativamente
•
lento. Por lo que no es cambiarlos durante la ejecución del juego. La creación de recursos durante la ejecución del juego (en particular los sprites y fondos) fácilmente
•
consume gran cantidad de memoria. Se debe ser extremadamente cuidadoso con esto. Por ejemplo, si se tiene un un sprite 128x128 con 32 cuadros de animación y decides crear 36 copias rotadas estarás usando 36x32x128x128x4 = ¡36 MB de memoria! Se deben eliminar los recursos que ya no son utilizados. De otra forma pronto se ocuparía toda la memoria
•
del sistema.
En general, no se deben modificar recursos durante el juego. Es mejor crear y cambiar los recursos al inicio del juego o quizá al inicio de una habitación.
Las funciones para modificar recursos se dividen en las siguientes secciones: Sprites Las siguientes funciones permiten modificar las propiedades de los sprites:
sprite_set_offset(ind,xoff,yoff) sprite_set_bbox_mode(ind,mode)
Establece el offset del sprite.
Establece a mode el tipo de caja de colisión del sprite (0 =
automática, 1 = imagen completa, 2 = manual).
sprite_set_bbox(ind,left,top,right,bottom)
Configura la caja de colisión del sprite con
índice ind. Funciona solo cuando es manual el modo de caja de colisión.
sprite_set_precise(ind,mode)
Establece si el sprite con índice ind usa chequeo de colisión precisa
(true o false).
Las siguientes funciones pueden utilizarse para crear nuevos sprites y para eliminarlos.
sprite_duplicate(ind)
Crea una copia del sprite con índice ind. Devuelve el índice del nuevo sprite.
Devuelve -1 si se presenta algún error.
sprite_assign(ind,spr)
Asigna el sprite spr al al sprite con índice ind. Es decir, crea una copia del sprite.
De esta manera fácilmente puedes asignar un sprite existente a p. ej. un nuevo sprite.
sprite_merge(ind1,ind2)
Fusiona las imagines del sprite ind2 en el sprite ind1, agregándolas al final de
este ultimo. Si los tamaños no corresponden los sprites son encogidos. ¡No se elimina el sprite ind2!
sprite_add(fname,imgnumb,precise,transparent,smooth,preload,xorig,yori g)
Agrega a los recursos sprites la imagen del archivo fname. Solo se pueden agregar imágenes bmp, jpg y gif.
Cuando se trata de un bmp o jpg la imagen puede ser una tira que contenga las subimágenes del sprite una junto a la otra. Se usa
imgnumb
para indicar el número de subimágenes (1 para una sola). Este argumento no es empleado
con imágenes gif (animadas); se emplea el número de imágenes del archivo gif. de colisión precisa. los bordes.
transparent
preload
precise
indica si la imagen es parcialmente transparente.
indica si se precargará la imagen en la memoria de texturas.
indica si se usará chequeo
smooth
indica si se alisarán
xorig y yorig
indican la
posición de origen en el sprite. La función devuelve el índice del nuevo sprite. Si ocurre algún error devuelve -1.
sprite_replace(ind,fname,imgnumb,precise,transparent,smooth,preload,xo rig,yorig)
Lo mismo que la anterior pero en este caso se reemplaza al sprite con índice ind. El valor devuelvo
por la función indica si tuvo éxito la operación.
sprite_create_from_screen(x,y,w,h,precise,transparent,smooth,preload,x orig,yorig)
Crea un nuevo sprite copiando de un área indicada de la pantalla. Esto permite crear cualquier
sprite que se desee. Se dibuja la imagen en pantalla empleando las funciones de dibujado y luego se crea un sprite con ella. (Si no se hace dentro del evento drawing aún se puede hacer de manera que no sea visible si no se refresca la pantalla). Los demás parámetros son similares a los indicados en las anteriores funciones. La función devuelve el índice del sprite. Pero se poner algo de cuidado aquí. Aunque se habla de la pantalla, de hecho, lo que importa es el área en donde se dibuja. No importa el hecho de que haya una ventana en pantalla y que la imagen pudiera estar escalada en esta ventana.
sprite_add_from_screen(ind,x,y,w,h)
Agrega un área de la pantalla como la siguiente subimagen
del sprite con índice ind. x, y, w y h indican las dimensiones del área en pantalla (coordenadas x, y, ancho y alto).
sprite_delete(ind)
Elimina el sprite, liberando la memoria utilizada.
Se cuenta con la siguiente rutina para cambiar la apariencia de un sprite.
sprite_set_alpha_from_sprite(ind,spr)
Cambia los valores alfa (transparencia) del sprite con
índice ind usando los valores de luminosidad (hue) del sprite spr. Esta acción no se puede deshacer.
Sonidos Las siguientes rutinas pueden ser utilizadas para crear nuevos sonidos y para eliminarlos.
sound_add(fname,kind,preload)
Agrega un recurso de sonido al juego. Fname es el nombre del
archive de sonido. kind indica el tipo de sonido (0=normal, 1=de fondo, 2=3D, 3=mmplayer), preload indica si el sonido debiera ser almacenado inmediatamente en la memoria de audio (true o false). La función devuelve el índice del nuevo sonido, que puede utilizarse para reproducirlo. (O devolverá -1 si ocurriera algún error, p. ej. que el archivo no existiera).
sound_replace(index,fname,kind,loadonuse)
Lo mismo que la anterior pero esta vez no se
crea un nuevo sonido sino que se sustituye el que tenga índice index, liberando el anterior sonido. El valor devuelto por esta función indica si tuvo éxito la operación.
sound_delete(index)
Elimina el sonido index, liberando toda la memoria asociada con él. Ya no es posible
recuperarlo
Fondos Las siguientes rutinas pueden ser empleadas para crear nuevas imágenes de fondo y para eliminarlas.
background_duplicate(ind)
Crea un duplicado del fondo con el índice ind. Devuelve el índice del nuevo
fondo. Cuando se presenta algún error se devuelve -1.
background_assign(ind,back)
Asigna el fondo back al fondo ind. Esto es, crea una copia del fondo.
background_add(fname,transparent,smooth,preload)
Agrega la imagen almacenada en el
archivo fname al juego de recursos background. Solo se pueden manejar imágenes bmp y jpg. indica si la imagen es parcialmente transparente.
smooth
indica si se alisarán los bordes.
transparent
preload
indica si se
precargará la imagen en la memoria de texturas. La función devuelve el índice del nuevo fondo, el cual se puede usar para dibujarlo o para asignarlo a la variable background_index[0] para hacerlo visible en la habitación actual. Devuelve -1 cuando ocurre algún error.
background_replace(ind,fname,transparent,smooth,preload)
Lo mismo que la
anterior pero en este caso el fondo con índice ind es reemplazado. La función devuelve un valor indicando si tuvo éxito la operación. Cuando el fondo reemplazado es visible en la habitación será reemplazado.
background_create_color(w,h,col,preload)
Crea un nuevo fondo del tamaño dado (w=ancho,
h=alto) con el color col. Devuelve el índice del nuevo fondo, -1 si ocurre algún error.
background_create_gradient(w,h,col1,col2,kind,preload)
Crea un fondo del tamaño
indicado (w=ancho, h=alto) coloreado con un gradiente. col1 y col2 indican los dos colores. kind es un número entre 0 y 5 que indica el tipo de gradiente: 0=horizontal, 1=vertical, 2=rectángulo, 3=elipse, 4=doble horizontal, 5=doble vertical. Esta función devuelve el índice del nuevo fondo, ó -1 si ocurre algún error.
background_create_from_screen(x,y,w,h,transparent,smooth,preload)
Crea un
fondo copiando un área indicada de la pantalla (x, y=coordenadas esquina superior izquierda, w=ancho, h=alto). Esta función permite crear cualquier fondo que se desee. Se dibuja la imagen en pantalla usando las funciones de dibujo y a continuación se crea un nuevo fondo de ella. (Si no se hace esto en el evento drawing incluso se puede lograr que no sea visible en pantalla si no se refresca). Los otros parámetros son similares a los de las anteriores funciones. La función devuelve el índice del nuevo fondo. Se requiere algo de cuidado aquí. Aunque se habla de la pantalla, lo que importa es la región en la que se dibuja. No importa el hecho de que haya una ventana en pantalla y que la imagen en ella pudiera estar escalada.
background_delete(ind)
Elimina el fondo, liberando la memoria utilizada.
La siguiente rutina permite cambiar la apariencia de un fondo.
background_set_alpha_from_background(ind,back)
Cambia los valores alfa (transparencia)
del fondo con índice ind usando los valores hue del fondo back. Esta acción no puede deshacerse.
Fuentes Es posible crear, reemplazar y eliminar fuentes durante el juego usando las siguientes funciones. (No se debe reemplazar una fuente que está configurada como la actual o en su caso se debe reestablecer la fuente después del cambio).
font_add(name,size,bold,italic,first,last)
Agrega una nueva fuente y devuelve su índice.
Se indica el tamaño (size), si es negrita (bold), cursiva (italic) y el primer y ultimo caracteres que deben ser creados (first y last).
font_add_sprite(spr,first,prop,sep)
Agrega una nueva fuente y devuelve su índice. La fuente es
creada de un sprite. El sprite debe contener una subimagen para cada carácter. first indica el índice del primer carácter en el sprite. Por ejemplo, se puede usar ord('0') si el sprite solo contiene los dígitos. prop indica si la fuente es
proporcional. En una fuente proporcional, para cada carácter el ancho de la caja de colisión es utilizado como el ancho del mismo. Finalmente, sep indica la distancia que debe separar a los caracteres horizontalmente. Un valor típico debiera estar entre 2 y 8 dependiendo del tamaño de la fuente.
font_replace(ind,name,size,bold,italic,first,last)
Reemplaza la fuente ind con una
nueva fuente, indicando el nombre (name), tamaño (size) si es negrita (bold) o cursive (italic) y el primer y último carácter que deben crearse.
font_replace_sprite(ind,spr,first,prop,sep)
Reemplaza la fuente ind con una nueva
fuente basada en el sprite spr.
font_delete(ind)
Elimina la fuente con índice ind, liberando la memoria utilizada.
Paths Es posible crear trayectorias y agregar puntos a las mismas. Sin embargo, nunca debe modificarse una trayectoria que está siendo usada por alguna instancia. Se pueden provocar resultados inesperados. Se tienen las siguientes funciones:
path_set_kind(ind,val)
Establece el tipo de conexiones de la trayectoria ind (0=recta, 1=suave)
(0=straight, 1=smooth).
path_set_closed(ind,closed)
Establece si la trayectoria ind debe ser cerrada (true) o abierta (false).
path_set_precision(ind,prec)
Establece la precisión con la que se calcula la suavidad de la trayectoria
(prec debe estar entre 1 y 8).
path_add()
Agrega una nueva trayectoria vacía. Devuelve el índice de la trayectoria.
path_delete(ind)
Elimina la trayectoria con índice ind.
path_duplicate(ind)
Crea un duplicado de la trayectoria ind. Devuelve el índice de la nueva trayectoria.
path_assign(ind,path)
Asigna la trayectoria path a la trayectoria ind. Por tanto, crea una copia de la
trayectoria. De esta manera se puede fácilmente configurar una trayectoria existente a p. Ej. otra trayectoria nueva.
path_add_point(ind,x,y,speed)
Agrega un punto a la trayectoria con índice ind, en la posición (x,y) y
con factor de velocidad speed. Se debe recordar que un factor de 100 corresponde a la velocidad actual. Valores inferiores indican una reducción de la velocidad y valores superiores un incremento de la misma.
path_insert_point(ind,n,x,y,speed)
Inserta un punto en la trayectoria con índice ind antes del
punto n, en la posición (x,y) y con factor de velocidad speed.
path_change_point(ind,n,x,y,speed)
Cambia el punto n de la trayectoria ind a la posición (x,y) y
con factor de velocidad speed.
path_delete_point(ind,n) path_clear_points(ind) path_reverse(ind) path_mirror(ind) path_flip(ind)
Elimina el punto n de la trayectoria con índice ind.
Limpia todos los puntos de la trayectoria ind, volviéndola una trayectoria vacía.
Invierte la trayectoria ind.
Voltea horizontalmente la trayectoria ind (con respecto a su centro).
Voltea verticalmente la trayectoria ind (con respecto a su centro).
path_rotate(ind,angle)
Rota angle grados la trayectoria ind en contra de las manecillas del reloj
(alrededor de su centro).
path_scale(ind,xscale,yscale)
Escala la trayectoria ind con los factores indicados (con respecto a su
centro).
path_shift(ind,xshift,yshift)
Mueve la trayectoria con los valores indicados.
Scripts No se pueden modificar los scripts durante la ejecución del juego. Los scripts son una parte de la lógica del juego. La modificación de los mismos llevaría a una autoreescritura de código que muy fácilmente llevaría a errores. Hay otras formas de hacer algo parecido. Si realmente se necesita ejecutar una pieza de código no conocida en tiempo de diseño (p. ej. Desde un archivo) se pueden hacer uso de las siguientes funciones:
execute_string(str)
Ejecuta el fragmento de código en la cadena str.
execute_file(fname)
Ejecuta el código dentro del archive fname.
En ocasiones se quiere almacenar en una variable el índice de un script para luego ejecutarlo. Para ello se puede utilizar la siguiente función
script_execute(scr,arg0,arg1,...)
Ejecuta el script con índice scr con los argumentos indicados.
Time lines Las siguientes rutinas permiten la creación y modificación de las time lines. ¡No se deben alterar las líneas de tiempo que estén en uso!
timeline_add()
Agrega una nueva línea de tiempo. Devuelve el índice de la misma.
timeline_delete(ind)
Elimina la línea de tiempo con índice ind. Debe asegurarse que ninguna instancia
emplee dicha línea de tiempo en ninguna habitación.
timeline_moment_add(ind,step,codestr)
Agrega una acción de código a la línea de tiempo en el
instante step. codestr contiene el código para las acciones. Si el step no existe se crea. Por lo que pueden agregarse múltiples acciones de código para el mismo instante.
timeline_moment_clear(ind,step)
Se puede utilizar esta función para borrar todas las acciones de
una línea de tiempo (ind) en un instante (step) en particular
Objetos También los objetos pueden ser manipulados y creados en tiempo de ejecución. NUNCA se debe cambiar o eliminar un objeto del cual existan instancias. Esto puede provocar efectos inesperados ya que ciertas propiedades son almacenadas en la instancia y, por tanto, al cambiarlas en el objeto no se tendrá el efecto deseado.
object_set_sprite(ind,spr)
Establece el sprite para el objeto con índice ind. El valor -1 remueve del
objeto el sprite actual.
object_set_solid(ind,solid)
Establece si al crear instancias del objeto ind deberán considerarse
sólidas (true o false).
object_set_visible(ind,vis)
Establece si las instancias creadas del objeto ind deben ser visibles por
defecto (true o false).
object_set_depth(ind,depth)
Establece la profundidad (depth) por defecto de las instancias creadas
del objeto ind.
object_set_persistent(ind,pers)
Establece si las instancias creadas del objeto deben ser
persistentes por defecto (true o false).
object_set_mask(ind,spr)
Establece el sprite para la máscara del objeto con índice ind. Para que la
máscara sea el sprite del objeto se puede usar -1.
object_set_parent(ind,obj)
Establece el padre (obj) para el objeto ind. Si se usa -1 se indica que el
objeto ind no tiene padre. El cambiar el objeto padre cambia el comportamiento de las instancias del objeto.
Las siguientes rutinas son útiles para crear objetos mientras el juego se ejecuta. Como con todas las rutinas de modificación de recursos, se debe ser muy cuidadoso para que no se creen nuevos objetos todo el tiempo.
object_add()
Agrega un nuevo objeto. Devuelve el índice del mismo. Después se puede utilizar este índice en
las rutinas anteriores para configurar ciertas propiedades del objeto y luego se puede utilizar el índice para crear instancias del objeto.
object_delete(ind)
Elimina el objeto con índice ind. Se debe asegurar de que no existan instancias de este
objeto en ninguna de las habitaciones.
object_event_add(ind,evtype,evnumb,codestr)
Para asignarle un comportamiento a un objeto
se deben definir eventos para dicho objeto. Solo se pueden agregar acciones de código. Se debe indicar el objeto (ind), el tipo de evento (evtype), el número de evento (evnumb, se pueden usar las constantes indicadas antes para la función event_perform()). Por ultimo se proporciona la cadena de código que debe ejecutarse (codestr). Se pueden agregar múltiples acciones a cada evento.
object_event_clear(ind,evtype,evnumb)
Se puede emplear esta función para borrar todas las
acciones para un evento en particular.
La creación de objetos es en particular muy útil cuando se están diseñando scripts o bibliotecas de acciones. Por ejemplo, un script de inicialización puede crear un objeto para mostrar un texto y otro script puede agregar un objeto con un texto en particular. De esta forma se tiene un mecanismo simple para desplegar mensajes sin la necesidad de crear objetos usando la interfaz estándar
Rooms La manipulación de habitaciones en tiempo de ejecución es algo muy riesgoso. Se debe tomar en cuenta que las habitaciones cambian todo el tiempo debido a lo que ocurre dentro del juego. Esto normalmente solo ocurre para la habitación en turno y hay muchas rutinas descritas en anteriores secciones para manipular las instancias, los fondos y los tiles en la habitación actual. Pero los cambios en la habitación active se mantendrán si dicha habitación es persistente. Entonces, nunca se debieran manipular elementos de la habitación en turno o de cualquier otra habitación que sea persistente y que ya haya sido visitada antes. Tales cambios en general no serán percibidos pero en ocasiones podrían ocasionar errores inesperados. Debido al hecho de que las habitaciones están enlazadas de manera complicada no hay ninguna rutina que permita eliminar una habitación.
Las siguientes funciones están disponibles
room_set_width(ind,w)
Establece el ancho (w) para la habitación con índice ind.
room_set_height(ind,h)
Establece la altura (h) para la habitación con índice ind.
room_set_caption(ind,str)
Establece el título (caption) para la habitación con índice ind.
room_set_persistent(ind,val) room_set_code(ind,str)
Establece si la habitación con índice ind será persistente o no (val).
Configura el código de inicialización (str) para la habitación con índice ind.
room_set_background_color(ind,col,show)
Configura las propiedades de color para la habitación
con índice ind si no cuenta con una imagen de fondo. col indica el color y show indica si el color debe mostrarse o no.
room_set_background(ind,bind,vis,fore,back,x,y,htiled,vtiled,hspeed,vs peed,alpha)
Establece el fondo con índice bind (0-7) como fondo para la habitación con índice ind. vis indica si el
fondo será visible y fore si se trata de un fondo de primer plano. back es el índice de la imagen de fondo. x, y indican la posición de la imagen y htiled y vtiled indican si la imagen debe dividirse en tiles. hspeed y vspeed indican la velocidad con la que el fondo se mueve y alpha indica un valor de transparencia alfa (1 = sólido y más rápido).
room_set_view(ind,vind,vis,xview,yview,wview,hview,xport,yport,wport,h port,hborder,vborder,hspeed,vspeed,obj)
Establece la vista con índice vind (0-7) para la
habitación con índice ind. vis indica si la vista es visible. xview, yview, wview y hview indican la posición de la vista en la habitación. xport, yport, wport y hport indican la posición en pantalla. Cuando la vista debe seguir a un objeto hborder y vborder indican el borde mínimo visible que debe mantenerse alrededor del objeto. hspeed y vspeed indican la máxima velocidad con la que la vista puede moverse. obj es el índice del objeto o el índice de la instancia.
room_set_view_enabled(ind,val)
Establece si las vistas deben habilitarse para la habitación con
índice ind.
room_add()
Agrega una nueva habitación. Devuelve el índice de la misma. Se debe notar que la habitación no
formará parte del orden de habitaciones. Por lo que la nueva habitación no cuenta con habitaciones previa ni siguiente. Si se desea moverse a una habitación agregada se debe indicar el índice de la misma.
room_duplicate(ind)
Agrega una copia de la habitación con índice ind. Devuelve el índice de la nueva
habitación.
room_assign(ind,room)
Asigna la habitación room al índice ind. Por tanto, esta función crea una copia de la
habitación.
room_instance_add(ind,x,y,obj)
Agrega a la habitación ind una nueva instancia del objeto obj,
colocándola en la posición x, y. Devuelve el índice de la instancia.
room_instance_clear(ind)
Elimina todas las instancias dentro de la habitación ind.
room_tile_add(ind,back,left,top,width,height,x,y,depth) Agrega un nuevo tile a la habitación en la posición indicada. Devuelve el índice del tile. back es el fondo del cual se toma el tile. Left, top, width y height indican la parte del fondo que forma al tile. x, y es la posición del tile en la habitación y depth es la profundidad del tile.
room_tile_add_ext(ind,back,left,top,width,height,x,y,depth,xscale, yscale,alpha)
Lo mismo que la anterior rutina pero también se puede especificar un factor de escalado en las
direcciones x e y, y una transparencia alpha para el tile.
room_tile_clear(ind)
Elimina todos los tiles de la habitación indicada.
Archivos, registro, y ejecución de programas En juegos más avanzados probablemente querrás leer datos de un archivo que has incluido con el juego. Por ejemplo, pudieras crear un archivo que indique en qué momento deben ocurrir ciertas cosas. Probablemente también quieras guardar información para la siguiente vez que se ejecute el juego (por ejemplo, el cuarto actual). En algunas situaciones puede ser que necesites ejecutar programas externos, o crear nuevos archivos con datos del juego.
Puedes encontrar toda la información sobre estos temas en las secciones siguientes: Archivos Es útil utilizar archivos externos en juegos. Por ejemplo, podrías hacer un archivo que describe qué ciertas cosas deben suceder en qué momento. También puedes querer salvar la información para la próxima vez que se ejecute el juego. Las funciones siguientes sirven para leer y escribir datos en archivos de texto:
file_text_open_read(fname) Abre el archivo fname para lectura. La función devuelve la id del archivo que debes utilizar en el resto de funciones. Puedes abrir hasta un máximo de 32 archivos simultáneamente. No olvides cerrar los archivos una vez que no los necesites.
file_text_open_write(fname) Abre el archivo fname para escritura, creándolo si no existe. La función devuelve la id del archivo que debes usar en las demás funciones.
file_text_open_append(fname) Abre el archivo fname para agregar datos al final, creándolo si no existe. La función devuelve la id del archivo que debes usar en las demás funciones.
file_text_close(fileid) Cierra el archivo indicado por fileid (¡No olvides llamarla!). file_text_write_string(fileid,str) Escribe la cadena str al archivo indicado por fileid. file_text_write_real(fileid,x) Escribe el valor real x en el archivo indicado por fileid. file_text_writeln(fileid) Escribe un carácter de nueva línea en el archivo. file_text_read_string(fileid) Lee una cadena del archivo y devuelve esta cadena. Una cadena termina al final de la línea.
file_text_read_real(fileid)
Lee un valor real del archivo y devuelve este valor.
file_text_readln(fileid) Salta el resto de la línea en el archivo e inicia al principio de la siguiente línea. file_text_eof(fileid) Indica si hemos llegado al final del archivo.
Para manipular archivos del sistema puedes utilizar las siguientes funciones:
file_exists(fname) Indica si el archivo con el nombre fname existe (true) o no (false). file_delete(fname) Borra el archivo con el nombre fname. file_rename(oldname,newname) Renombra el archivo con el nombre oldname a newname. file_copy(fname,newname) Copia el archivo fname al nombre newname
directory_exists(dname) Indica si la carpeta dname existe. directory_create(dname) Crea una carpeta con el nombre dname (incluyendo la ruta a esa carpeta) si no existe.
file_find_first(mask,attr) Devuelve el nombre del primer archivo que satisfaga las condiciones de la máscara mask y los atributos attr. Si no existe tal archivo, devuelve una cadena vacía. La máscara puede contener una ruta y comodines (*), por ejemplo ‘C:\temp\*.doc’. Los atributos indican archivos adicionales que quieras ver. (Por lo que los archivos normales son siempre devueltos cuando satisfacen la máscara). Puedes agregar las siguientes constantes para ver el tipo de archivos que desees:
fa_readonly archivos de sólo lectura fa_hidden archivos ocultos fa_sysfile archivos de sistema fa_volumeid archivos volume-id fa_directory carpetas fa_archive archivos archivados file_find_next() Devuelve el nombre del siguiente archivo que satisface la máscara y los atributos indicados previamente. Si no existe tal archivo, devuelve una cadena vacía.
file_find_close() Debe ser llamada después de manipular los archivos para liberar la memoria. file_attributes(fname,attr) Indica si el archivo fname tiene todos los atributos dados por attr. Usa una combinación de las constantes indicadas anteriormente.
Las siguientes funciones sirven para cambiar los nombres de archivos. Observa que estas funciones no afectan a los archivos en sí, si no al nombre:
filename_name(fname) Devuelve el nombre del archivo fname, con la extensión pero sin la ruta. filename_path(fname) Devuelve la ruta al archivo indicado, incluyendo la última barra de separación. filename_dir(fname) Devuelve el directorio del archivo, que normalmente suele ser igual que la ruta pero sin la última barra de separación.
filename_drive(fname) Devuelve la información de la unidad del archivo. filename_ext(fname) Devuelve la extensión del archivo, incluyendo el punto. filename_change_ext(fname,newext) Devuelve el nombre del archivo con la extensión sustituida por newext. Si indicas una cadena de texto vacía para newext puedes eliminar la extensión del archivo.
En algunas situaciones puede que quieras leer datos de archivos binarios. Para ello dispones de las siguientes rutinas de bajo nivel:
file_bin_open(fname,mod) Abre el archivo con el nombre especificado. El parámetro mod indica qué se puede hacer con el archivo: 0=leer, 1=escribir y 2= leer y escribir. La función devuelve la id del archivo que debe utilizarse en las demás funciones. Puedes abrir hasta un máximo de 32 archivos simultáneamente, pero no olvides cerrarlos cuando hayas terminado con ellos.
file_bin_rewrite(fileid) Limpia el archivo indicado por fileid, es decir, borra todo su contenido y se sitúa al principio del archivo para empezar a escribir.
file_bin_close(fileid) Cierra el archivo especificado. No olvides llamarla! file_bin_size(fileid) Devuelve el tamaño en bytes del archivo indicado. file_bin_position(fileid) Devuelve la posición actual en el archivo en bytes (0 es el principio del archivo).
file_bin_seek(fileid,pos) Mueve la posición en el archivo a la posición indicada por pos. Para añadir contenido al final de un archivo, usa el valor file_bin_size(fileid) para el parámetro pos.
file_bin_write_byte(fileid,byte) Escribe un byte de datos al archivo especificado. file_bin_read_byte(fileid) Lee un byte de datos del archivo.
Si el jugador ha seleccionado modo seguro en sus preferencias, para ciertas rutinas, no se permite especificar la ruta y sólo puedes acceder a los archivos en la carpeta de la aplicación p. Ej. para escribir en ellos.
Las siguientes tres variables de sólo lectura pueden ser útiles:
game_id* Identificador único para el juego. Puedes usarlo si necesitas un nombre único de archivo. working_directory* Carpeta de trabajo del juego. (No incluye la diagonal invertida final). temp_directory* Carpeta temporal creada para el juego. Puedes almacenar archivos temporales aquí. Serán eliminados cuando el juego finalice.
En ciertas situaciones podrías dar al jugador la posibilidad de introducir argumentos mediante la línea de comandos al juego que están ejecutando (para por ejemplo activar trucos o modos especiales). Para obtener estos argumentos puedes usar las siguientes dos rutinas:
parameter_count() Devuelve el número de parámetros de la línea de comandos (nota que el nombre del programa es uno de ellos).
parameter_string(n) Devuelve los parámetros n de la línea de comandos. El primer parámetro tiene índice 0 y es el nombre del programa.
Por último, puedes leer el valor de las variables de entorno con la siguiente función:
environment_get_variable(name) Devuelve el valor (una cadena de texto) de la variable de entorno con el nombre especificado
Registro Si deseas almacenar una pequeña cantidad de información entre cada ejecución del juego hay un mecanismo más simple que el emplear un archivo. Puedes usar el registro. El registro es una gran base de datos de Windows para mantener todo tipo de configuraciones para los programas. Una entrada tiene un nombre, y un valor. Puedes usar tanto cadenas como valores reales. Tenemos las siguientes funciones:
registry_write_string(name,str) Crea una entrada en el registro con el nombre name y como valor la cadena str.
registry_write_real(name,x) Crea una entrada en el registro con el nombre name y el valor real x. registry_read_string(name) Devuelve la cadena almacenada con el nombre name. (El nombre debe existir, de otra forma se devuelve una cadena vacía).
registry_read_real(name) Devuelve el valor real almacenado en la entrada name. (El nombre debe existir, de otra forma se devuelve el número 0).
registry_exists(name) Indica si la entrada name existe.
En realidad, los valores en el registro están agrupados en claves. Las rutinas anteriores trabajan con valores dentro de la clave que está creada especialmente para tu juego. Tu programa puede usar esto para obtener cierta información sobre el sistema en el que se está ejecutando el juego. También puedes leer valores en otras claves. Puedes escribir valores en ellas pero ten mucho cuidado. PUEDES FÁCILMENTE DESTRUIR TU SISTEMA de esta forma. (La escritura no se permite en modo seguro). Nota que las claves también están colocadas en grupos. Las siguientes rutinas trabajan por defecto con el grupo HKEY_CURRENT_USER. Pero si quieres puedes cambiar este grupo raíz de trabajo. Por ejemplo, para obtener el directorio temporal actual, usa:
path = registry_read_string_ext('\Environment','TEMP');
Las siguientes funciones existen para moverte por el registro con libertad:
registry_write_string_ext(key,name,str) Crea una entrada en el registro dentro de la clave key con el nombre name y como valor la cadena str.
registry_write_real_ext(key,name,x) Crea una entrada en el registro dentro de la clave key con el nombre name y el valor real x.
registry_read_string_ext(key,name) Devuelve la cadena con el nombre name dentro de la clave key (el nombre debe existir, de otra forma se devuelve una cadena vacía).
registry_read_real_ext(key,name) Devuelve el valor real con el nombre name en la clave key (el nombre debe existir, de otra forma se devuelve el número 0).
registry_exists_ext(key,name) Indica si el nombre name existe dentro de la clave key. registry_set_root(root) Configura la raíz para las otras rutinas. Usa los siguientes valores: 0 = HKEY_CURRENT_USER 1 = HKEY_LOCAL_MACHINE 2 = HKEY_CLASSES_ROOT 3 = HKEY_USERS
Archivos INI Para pasar ciertos parámetros a un programa es corriente utilizar los archivos INI. Los archivos INI contienen secciones y cada sección contiene un número de parejas nombre-valor. Por ejemplo:
[Form] Arriba=100 Izquierda=100 Titulo=El mejor juego de la historia [Game] MaxScore=12324 Este archivo contiene dos secciones: la primera llamada Form y la segunda llamada Game. Primera sección contiene 3 parejas de valores. Las dos primeras tienen un valor real mientras que la tercera es una cadena de texto. Los archivos INI son muy fáciles de crear y editar. En Game Maker puedes utilizar las siguientes funciones para esto:
ini_open(name) Abre el archivo de INI con el nombre dado ¡El archivo INI se debe almacenar en la misma carpeta que el juego!
ini_close()Cierra el archivo INI actualmente abierto. ini_read_string(section,key,default) Lee el valor (una cadena de texto) de la llave indicada con key de la sección indicada como section. Cuando no existe la llave o la sección se devuelve el valor especificado por default.
ini_read_real(section,key,default) Lee el valor real de la llave indicada con key de la sección indicada como section. Cuando no existe la llave o la sección se devuelve el valor especificado por default.
ini_write_string(section,key,value) Escribe el valor value (una cadena de texto) en la llave indicada con key en la sección indicada con section.
ini_write_real(section,key,value) Escribe el valor real value en la llave indicada con key en la sección indicada con section.
ini_key_exists(section,key) Devuelve si la llave indicada existe en la sección indicada. ini_section_exists(section) Devuelve si existe la sección indicada. ini_key_delete(section,key) Elimina la llave indicada de la sección indicada. ini_section_delete(section) Elimina la sección indicada.
Ejecución de programas
Game Maker también tiene la posibilidad de iniciar programas externos. Hay dos funciones disponibles para esto: execute_program y execute_shell. La función execute_program inicia un programa, posiblemente con algunos argumentos. Puede esperar hasta que el programa termine (pausando el juego) o continuar con el juego. La función execute_shell abre un archivo. Puede ser cualquier archivo para cuyo tipo esté definida una asociación, p. Ej. un
archivo html, un archivo de Word, etc. O puede ser un programa. No puede esperar a que se termine la ejecución por lo que el juego continúa.
execute_program(prog,arg,wait) Ejecuta el programa prog con los argumentos arg. wait indica si se debe esperar a que termine la aplicación.
execute_shell(prog,arg) Ejecuta el programa (o archivo) en el entorno.
Ambas funciones no funcionarán si el jugador activa el modo seguro en sus preferencias. Puedes comprobar esto usando la variable de solo lectura:
secure_mode* Indica si el juego está ejecutándose en modo seguro.
Estructuras de datos Esta función sólo está disponible en la versión registrada de Game Maker.
En los juegos normalmente necesitas guardar información. Por ejemplo, necesitas guardar listas de ítems que lleva un personaje o nombres de lugares que necesita visitar. Puedes usar arrays para esto. Pero si necesitas hacer operaciones más complicadas, como ordenar los datos o buscar un ítem determinado, necesitas escribir grandes trozos de código en GML, que pueden ser lentos al ejecutarse.
Para remediar esto, Game Maker dispone de un número de funciones de estructuras de datos. Hasta el momento hay 6 tipos de estructuras de datos diferentes: stacks (pilas), queues (colas), lists (listas), maps (mapas), priority queues (colas de prioridad) y grids (rejillas). Cada uno de estos tipos está preparado para un uso particular.
Todas las estructuras de datos funcionan generalmente de la misma manera. Creas una estructura de datos con una función que devuelve la id de esa estructura y luego usas esa id para hacer diferentes operaciones. Cuando has terminado, puedes destruir la estructura para ahorrar espacio. Puedes usar tantas estructuras a la vez como desees y éstas pueden guardar valores reales o cadenas de texto.
Observa que las estructuras de datos no se guardan cuando guardas el juego usando las funciones o acciones apropiadas. Para guardarlas, deberás crear tu propio sistema.
Al comparar valores, por ejemplo al ordenar una lista, Game Maker debe decidir cuándo dos valores son iguales. Al usar cadenas de texto y valores enteros esto es evidente, pero para valores reales, debido a errores de redondeo, números iguales pueden convertirse en diferentes. Por ejemplo, (5/3)*3 no será igual a 5. Para evitar esto, puedes especificar la precisión que deseas usar. Cuando la diferencia entre dos números es menor que esta precisión, los dos números se consideran iguales. Por defecto se utiliza una precisión de 0.0000001.Puedes cambiar este valor con las funciones siguientes:
ds_set_precision(prec) Especifica la precisión usada en comparaciones.
Esta precisión se utiliza en las estructuras de datos pero no en el resto de comparaciones en GML!
Las estructuras de datos disponibles son las siguientes:
Pilas Una pila es una estructura LIFO (último en entrar, primero en salir). Puedes empujar (push) valores en la pila o quitarlos tirando de ellos (pop). El último valor que se empujó en la lista es el primero al que se accede al tirar de la lista. Es como si vas poniendo un libro encima de otro: cuando quieras coger un libro, el último que pusiste estará encima de todos los demás y será el que cojas. Las pilas se utilizan para gestionar interrupciones o cuando se usan funciones recursivas:
ds_stack_create() Crea una nueva pila. La función devuelve un número entero con la id de la pila para usarla en las diferentes funciones. Puedes crear varias pilas.
ds_stack_destroy(id)
Destruye la pila, liberando la memoria usada. No olvides usar esta función cuando
ya no necesites la pila.
ds_stack_clear(id) Limpia la pila, borrando todos los valores que contiene pero no la destruye. ds_stack_size(id) Devuelve el número de valores en la pila. ds_stack_empty(id) Devuelve true si la pila está vacía. Es lo mismo que chequear si el número de valores en la pila es cero.
ds_stack_push(id,val) Empuja el valor dentro de la pila. ds_stack_pop(id) Devuelve el valor al final de la pila (esto es, el último valor introducido) y lo elimina de la pila.
ds_stack_top(id) Devuelve el valor al principio de la pila (el primero que se introdujo) pero no lo elimina.
Colas Una cola es parecido a una pila, pero funciona como una estructura FIFO (primero en entrar, primero en salir). El primer valor que se mete en la cola es el primero en salir, como una cola en una tienda. El primer cliente en llegar será el primero en ser atendido y los demás tendrán que esperar su turno en orden. Para trabajar con colas existen las funciones siguientes (observa que las 5 primeras son equivalentes a las funciones de las pilas: todas las estructuras de datos poseen estas 5 funciones)
ds_queue_create()Crea una nueva cola. La función devuelve un número entero con la id de la cola para usarla en las diferentes funciones. Puedes crear varias colas.
ds_queue_destroy(id) Destruye la cola, liberando la memoria usada. No olvides usar esta función cuando ya no necesites la cola.
ds_queue_clear(id)Limpia la cola, borrando todos los valores que contiene pero no la destruye. ds_queue_size(id) Devuelve el número de valores en la cola. ds_queue_empty(id) Devuelve true si la cola está vacía. Es lo mismo que chequear si el número de valores en la cola es cero.
ds_queue_enqueue(id,val)
Introduce el valor en la cola.
ds_queue_dequeue(id) Devuelve el último valor de la cola (el primero en introducirse) y lo elimina de la cola.
ds_queue_head(id) Devuelve el valor al principio de la cola, esto es, el primero que se introdujo, pero no lo elimina de la cola.
ds_queue_tail(id) Devuelve el último valor de la cola pero no lo elimina.
Listas Una lista guarda una colección de valore en un orden determinado. Puedes añadir valores a la lista a la posición que desees. Por eso, puedes acceder acceder a los valores usando un índice de su posición en la lista. También puedes ordenar los elementos de forma ascendente o descendente. Las listas se pueden usar para muchas cosas, por ejemplo, para guardar valores que cambian. Las listas se han programado usando arrays, pero al estar definidas en código compilado son mucho más rápidas que los arrays.
ds_list_create()Crea una nueva lista. La función devuelve un número entero con la id de la lista para usarla en las diferentes funciones. Puedes crear varias listas.
ds_list_destroy(id) Destruye la lista, liberando la memoria usada. No olvides usar esta función cuando ya no necesites la lista.
ds_list_clear(id)
Limpia la lista, borrando todos los valores que contiene pero no la destruye.
ds_list_size(id) Devuelve el número de valores en la lista. ds_list_empty(id) Devuelve true si la lista está vacía. Es lo mismo que chequear si el número de valores en la lista es cero.
ds_list_add(id,val) Inserta el valor al final de la lista. ds_list_insert(id,pos,val) Inserta el valor en la posición pos. La primera posición es 0 y la última es igual al tamaño de la lista menos 1.
ds_list_replace(id,pos,val) Reemplaza el valor en la posición pos por val. ds_list_delete(id,pos) Elimina el valor en la posición pos. ds_list_find_index(id,val) Devuelve la posición en la lista del valor val. Si no encuentra el valor en la lista devuelve -1.
ds_list_find_value(id,pos) Devuelve el valor en al posición pos. ds_list_sort(id,ascend) Ordena los valores de la lista. Si ascend es true o 1 los ordena de forma ascendente, en caso contrario los ordena de manera descendente
Mapas En algunas ocasiones necesitas guardar pares de valores consistentes de una llave (key) y un valor. Por ejemplo, un personaje puede llevar varios ítems diferentes y puede tener un número diferente de cada uno. En este caso, el ítem será la llave y la cantidad será el valor. Los mapas manejan estas parejas de valores, ordenándolos por la llave. Puedes añadir valores al mapa y buscar uno concreto usando las llaves. Como las llaves también están ordenadas, puedes
encontrar los valores correspondientes a la llave siguiente o anterior. A veces es útil usar un mapa para guardar llaves sin ningún valor asignado. En este caso puedes asignarles a todas las llaves el valor 0.
ds_map_create()Crea un nuevo mapa. La función devuelve un número entero con la id del mapa para usarla en las diferentes funciones. Puedes crear varios mapas.
ds_map_destroy(id) Destruye el mapa, liberando la memoria usada. No olvides usar esta función cuando ya no necesites el mapa.
ds_map_clear(id)Limpia el mapa, borrando todos las parejas llave-valor que contiene pero no lo destruye. ds_map_size(id) Devuelve el número de parejas llave-valor en el mapa. ds_map_empty(id) Devuelve true si el mapa está vacía. Es lo mismo que chequear si el número de valores en el mapa es cero.
ds_map_add(id,key,val) Añade la pareja llave (key)-valor (val) al mapa. ds_map_replace(id,key,val) Reemplaza el valor correspondiente a la llave con un nuevo valor. ds_map_delete(id,key) Elimina la pareja llave-valor especificada del mapa. Si hay varias parejas con la misma llave sólo 1 es eliminada.
ds_map_exists(id,key) Devuelve true si la llave existe en el mapa. ds_map_find_value(id,key) Devuelve el valor correspondiente a la llave. ds_map_find_previous(id,key) Devuelve la mayor llave que sea menor que la indicada. ds_map_find_next(id,key) Devuelve la menor llave que sea mayor que la indicada. ds_map_find_first(id) Devuelve la menor llave del mapa. ds_map_find_last(id) Devuelve la mayor llave del mapa.
Colas de prioridad En una cola de prioridad a cada valor se le asigna una prioridad. Así puedes buscar los valores con mayor o menor prioridad y controlar ciertas cosas según su prioridad.
ds_ priority _create()Crea una nueva cola. La función devuelve un número entero con la id de la cola para usarla en las diferentes funciones. Puedes crear varias colas.
ds_ priority _destroy(id) Destruye la cola, liberando la memoria usada. No olvides usar esta función cuando ya no necesites la cola.
ds_ priority _clear(id)Limpia la cola, borrando todos los valores que contiene pero no la destruye. ds_ priority _size(id) Devuelve el número de valores en la cola. ds_ priority _empty(id) Devuelve true si la cola está vacía. Es lo mismo que chequear si el número de valores en la cola es cero.
ds_priority_add(id,val,prio) Añade el valor con la prioridad especificada a la cola. ds_priority_change_priority(id,val,prio) Cambia la prioridad del valor especificado al nuevo valor.
ds_priority_find_priority(id,val) Devuelve la prioridad del valor especificado. ds_priority_delete_value(id,val) Elimina el valor (con su prioridad) de la cola de prioridad. ds_priority_delete_min(id)Devuelve el valor con la menor prioridad y lo elimina de la cola.
ds_priority_find_min(id) Devuelve el valor con la menor prioridad pero no lo elimina de la cola. ds_priority_delete_max(id) Devuelve el valor con la mayor prioridad y lo elimina de la cola. ds_priority_find_max(id) Devuelve el valor con la mayor prioridad pero no lo elimina de la cola.
Rejillas Una rejilla es simplemente un vector (o arreglo) bidimensional. Una rejilla tiene un entero con su altura y anchura. Esta estructura te permite acceder a los valores indicando el valor de la celda deseada (el primer valor en las direcciones x e y es 0). Pero también puedes dar valores por regiones, añadirlos y encontrar los valores máximo, mínimo o medio de una región. Esta estructura es útil para representar por ejemplo un campo de juego. Aunque todo lo que hace esta estructura se puede conseguir usando vectores, las operaciones por regiones son mucho más rápidas.
ds_grid_create(w,h) Crea una rejilla con la anchura especificada en w y la altura especificada en h. La función devuelve la id de la rejilla que debe usarse en las demás funciones.
ds_grid_destroy(id) Destruye la rejilla, liberando la memoria usada. No olvides usar esta función cuando ya no necesites la rejilla.
ds_grid_resize(id,w,h) Aumenta el tamaño de la rejilla a la nueva anchura y altura especificadas. Las celdas ya existentes mantienen su valor.
ds_grid_width(id) Devuelve la anchura de la rejilla. ds_grid_height(id) Devuelve la altura de la rejilla. ds_grid_clear(id,val) Hace que todos las celdas de la rejilla tomen el valor val (puede ser un número o una cadena de texto).
ds_grid_set(id,x,y,val)Asigna a la celda x,y el valor val. ds_grid_add(id,x,y,val) Suma el valor a la celda especificada. Para cadenas de texto, la cadena se concatena a la que ya existe en la celda.
ds_grid_multiply(id,x,y,val) Multiplica la celda por el valor. Sólo se puede usar con números. ds_grid_set_region(id,x1,y1,x2,y2,val) Todas las celdas de la región especificada toman el valor especificado.
ds_grid_add_region(id,x1,y1,x2,y2,val)Suma el valor a la región especificada. Para cadenas de texto, concatena la cadena de texto a la existente en cada celda.
ds_grid_multiply_region(id,x1,y1,x2,y2,val) Multiplica el valor por todas las celdas de la región (sólo para valores numéricos).
ds_grid_set_disk(id,xm,ym,r,val) Da el valor especificado a toda las celdas dentro del círculo de centro xm,ym y radio r.
ds_grid_add_disk(id,xm,ym,r,val) Suma el valor a la región especificada por el círculo. Para cadenas de texto, concatena la cadena de texto a la existente en cada celda.
ds_grid_multiply_disk(id,xm,ym,r,val) Multiplica el valor por todas las celdas de la región (sólo para valores numéricos).
ds_grid_get(id,x,y) Devuelve el valor de la celda indicada. ds_grid_get_sum(id,x1,y1,x2,y2) Devuelve la suma de los valores de las celdas de la región
especificada. Sólo funciona con valores numéricos.
ds_grid_get_max(id,x1,y1,x2,y2) Devuelve el máximo valor de las celdas de la región especificada. Sólo funciona con valores numéricos.
ds_grid_get_min(id,x1,y1,x2,y2) Devuelve el mínimo valor de las celdas de la región especificada. Sólo funciona con valores numéricos.
ds_grid_get_mean(id,x1,y1,x2,y2) Devuelve el valor medio de las celdas de la región especificada. Sólo funciona con valores numéricos.
ds_grid_get_disk_sum(id,xm,ym,r) Devuelve la suma de los valores de las celdas de la región circular especificada. Sólo funciona con valores numéricos..
ds_grid_get_disk_min(id,xm,ym,r) Devuelve el mínimo valor de las celdas de la región circular especificada. Sólo funciona con valores numéricos.
ds_grid_get_disk_max(id,xm,ym,r) Devuelve el máximo valor de las celdas de la región circular especificada. Sólo funciona con valores numéricos.
ds_grid_get_disk_mean(id,xm,ym,r) Devuelve el valor medio de las celdas de la región circular especificada. Sólo funciona con valores numéricos.
ds_grid_value_exists(id,x1,y1,x2,y2,val) Devuelve true si el valor especificado existe en la región.
ds_grid_value_x(id,x1,y1,x2,y2,val) Devuelve la coordenada x de la celda de la región en la que aparece el valor especificado.
ds_grid_value_y(id,x1,y1,x2,y2,val) Devuelve la coordenada y de la celda de la región en la que aparece el valor especificado.
ds_grid_value_disk_exists(id,xm,ym,r,val) Devuelve true si el valor especificado existe en la región circular.
ds_grid_value_disk_x(id,xm,ym,r,val) Devuelve la coordenada x de la celda de la región circular en la que aparece el valor especificado.
ds_grid_value_disk_y(id,xm,ym,r,val) Devuelve la coordenada y de la celda de la región circular en la que aparece el valor especificado
Creando partículas Esta función solo está disponible en la versión registrada de Game Maker.
Los sistemas de partículas se emplean para crear efectos especiales. Las partículas son pequeños elementos (ya sean representados por un pequeño sprite, un píxel o una pequeña forma). Tales partículas se mueven de acuerdo a reglas predefinidas y pueden cambiar su color mientras se mueven. Muchas partículas juntas pueden crear p. Ej. Fuegos artificiales, flamas, lluvia, nieve, campos estelares, polvo, etc.
Game Maker contiene un extenso sistema de partículas que puede ser empleado para crear grandes efectos. Aunque no es sencillo de utilizar por lo que mejor lee cuidadosamente esta sección antes de probarlo. Los sistemas de partículas tienen muchos parámetros y no siempre es fácil entender cómo crear los efectos que se desean. Si lo encuentras demasiado difícil, Game Maker también dispone de un sistema de efectos para crear explosiones, humo, lluvia e incluso fuegos artificiales.
Primero que nada hay tipos de partículas. Un tipo de partícula define una clase de partículas en específico. Tales tipos pueden tener muchos parámetros que describen la forma, el tamaño, color y movimiento de las partículas.
Después tenemos los sistemas de partículas. Puede haber diferentes sistemas de partículas en un juego. Un sistema de partículas puede tener partículas de diferentes tipos. Un sistema de partículas cuenta con emisores que crean las partículas, ya sea continuamente o en estallidos. También puede contar con atractores que atraen las partículas. Finalmente, puede también tener destructores que destruirán las partículas. Una vez que se ha creado una partícula en un sistema, ésta será automáticamente dibujada y actualizada por el sistema.
La información sobre partículas se divide en las secciones siguientes:
Efectos simples
La forma más sencilla de crear partículas es usando el mecanismo de efectos. Los efectos se crean usando partículas, pero tú no tienes que preocuparte de eso. Simplemente eliges el tipo de efecto, la posición donde debe crearse, su color y su tamaño y del resto se encarga Game Maker.
Los efectos disponibles son:
•
ef_explosion
•
ef_ring
•
ef_ellipse
•
ef_firework
•
ef_smoke
•
ef_smokeup
•
ef_star
•
ef_spark
•
ef_flare
•
ef_cloud
•
ef_rain
•
ef_snow
Algunos sólo necesitarás crearlos una vez (como la explosión) mientras que otros deberás crearlos en cada step (como el humo o la lluvia). Observa que los efectos de nieve y lluvia siempre se crean en la parte superior de la pantalla así que la posición que especifiques para ellos será irrelevante.
Aunque esto pueda sonar limitado, se pueden conseguir efectos muy espectaculares. Por ejemplo, creando una pequeña nube de humo rojo debajo de una nave espacial en cada step se consigue una cola de fuego. Las dos funciones siguientes sirven para crear efectos:
effect_create_below(kind,x,y,size,color)
Crea un efecto del tipo indicado en kind en la
posición especificada. size es el tamaño del efecto según la convención siguiente: 0 = pequeño, 1 = mediano, 2 = grande. color indica el color del efecto. El efecto se crea por debajo de la instancia con una profundidad (depth) igual a 100000.
effect_create_above(kind,x,y,size,color)
Igual que la función anterior, pero esta vez el
efecto se crea encima de la instancia, con una profundidad igual a-100000.
Para eliminar todos los efectos usa la función:
effect_clear()
Detiene y elimina todos los efectos.
Tipos de partículas
Tipos de partículas Un tipo de particular define la forma, color y movimiento de una clase de partículas. Sólo necesitas definir un tipo de partícula una vez durante el juego, una vez hecho esto puede ser usado por cualquier sistema de partículas. Los tipos de partículas tienen un gran número de características que pueden ser cambiadas para alterar su apariencia o comportamiento. Con la elección adecuada puedes crear cualquier tipo de efecto. Las funciones para crear y destruir tipos de partículas son:
part_type_create()
Crea un Nuevo tipo de particular. Devuelve el índice del tipo. Este índice debe usarse
en todas las llamadas a las funciones siguientes para configurar las propiedades del tipo de partícula, por lo que es aconsejable que lo guardes en una variable global.
part_type_destroy(ind)
Destruye el tipo de particular ind. Llama a esta función cuando ya no se necesite
el tipo de partícula para ahorrar espacio.
part_type_exists(ind) part_type_clear(ind)
Devuelve si el tipo indicado de particular existe.
Devuelve el tipo de particular ind a sus valores por defecto.
La forma de una partícula Las partículas tienen una forma. Esta forma se indica mediante un sprite. Puedes usar cualquier sprite que quieras para tus partículas pero debes saber que además hay 15 tipos de sprites ya definidos. Estos sprites son todos de tamaño 64x64 y poseen valores de transparencia que los hacen fundirse de manera excelente con el fondo. Estos sprites se indican con las constantes:
•
pt_shape_pixel (pixel)
•
pt_shape_disk (disco)
•
pt_shape_square (cuadrado)
•
pt_shape_line (línea)
•
pt_shape_star (estrella)
•
pt_shape_circle (círculo)
•
pt_shape_ring (anillo)
•
pt_shape_sphere (esfera)
•
pt_shape_flare (brillo)
•
pt_shape_spark (chispa)
•
pt_shape_explosion (explosión)
•
pt_shape_cloud (nube)
•
pt_shape_smoke (humo)
•
pt_shape_snow (nieve)
Para indicar la forma debes usar la función:
part_type_shape(ind,shape)
Aplica la forma tipo shape a la partícula ind.
También puedes usar tu propio sprite con la función:
part_type_sprite(ind,sprite,animate,stretch,random)
Asigna el sprite para el tipo de
particular ind. Con la opción animate puedes indicar si el sprite debe ser animado. Con stretch indicas si la animación debe repetirse durante el tiempo de vida de la partícula. Y con random indicas si se escoge una subimagen aleatoria como la imagen inicial.
Una vez que hayas seeccionado el sprite para el tipo de partícula puedes elegir el tamaño de éste. Un tamaño de 1 significa el tamaño normal del sprite. Un tipo de partícula se puede definir para que todas las partículas tengan el mismo tamaño o para que tengan tamaños distintos. También puedes especificar un rango de tamaños, si el tamaño debe variar durante la vida de la partícula y si puede alternarse al crecer o disminuir, creando un efecto de tintineo.
part_type_size(ind,size_min,size_max,size_incr,size_wiggle)
Establece los
parámetros de tamaño para el tipo de partícula. Debes indicar el tamaño inicial, el tamaño final, la cantidad que debe crecer en cada step (usa un valor negativo para que disminuya) y el tintineo (El valor por defecto es 1 y el tamaño no cambia).
Las partículas también tienen una orientación que se indica en grados en sentido antihorario. Al igual que el tamaño, la orientación puede crecer, disminuir y puede ser la misma para todas las partículas o diferente para cada una.
part_type_orientation(ind,ang_min,ang_max,ang_incr,ang_wiggle,ang_rela tive) Aplica la orientación al tipo de partícula. Debes indicar la orientación inicial, final, el incremento o decremento en cada step, si la orientación tintinea y si los valores son relativos al movimiento actual (1) o absolutos (0). Por ejemplo, si ponemos todos los valores a 0 menos ang_relative, que lo ponemos a 1, la orientación de la partícula seguirá el camino que ésta describa.
Color y blending Las partículas tendrán un color. Hay diferentes maneras de especificar un color para una partícula. La más simple es especificar solamente un color. Pero también puedes especificar dos ó tres colores que se interpolarán a lo largo de la vida de la partícula. Por ejemplo, una partícula puede crearse siendo blanca y poco a poco irse convirtiendo en negra. Otra posibilidad es indicar que el color de cada partícula debe ser diferente y elegido de un rango de colores especificado. El color por defecto es blanco. Cuando usas un sprite con sus propios colores esto es lo que normalmente quieres y no necesitas especificar ninguna propiedad de color.
part_type_color1(ind,color1)
Indica un sólo color para la partícula.
part_type_color2(ind,color1,color2)
Especifica dos colores entre los que se interpolará.
part_type_color3(ind,color1,color2,color3)
Ahora los colores representan el color incial,
.
medio y final
part_type_color_mix(ind,color1,color2)
El color será seleccionado de una mezcla aleatoria de
estos dos colores y permanecerá inalterado durante la vida de la partícula.
part_type_color_rgb(ind,rmin,rmax,gmin,gmax,bmin,bmax)
Se puede usar para
especificar que cada partícula tendrá un color fijo pero escogido de un rango de colores que especificas en modo rgb.
part_type_color_hsv(ind,hmin,hmax,smin,smax,vmin,vmax)
Igual que la anterior, pero
esta vez el rango se especifica en modo hsv. Además del color, también puedes especificar un valor de transparencia. Así puedes conseguir que las partículas desaparezcan poco a poco.
part_type_alpha1(ind,alpha1)
Aplica un único valor de transparencia (entre 0 y 1) al tipo de partícula.
part_type_alpha2(ind,alpha1,alpha2)
Igual que la anterior, pero esta vez la transparencia en
cada momento se interpola entre las dos especificadas.
part_type_alpha3(ind,alpha1,alpha2,alpha3)
Igual que a anterior, pero esta vez la
transparencia en cada momento se interpola entre las tres especificadas.
Normalmente las partículas se mezclan con el fondo igual que los sprites. Pero también es posible usar additive blending, controlando así la forma en que se mezclan. Esto resulta un efecto muy bueno para explosiones.
part_type_blend(ind,additive)
Indica si se debe utilizar additive blending (1) or normal blending (0)
para el tipo de partícula.
Vida y muerte Las partículas viven durante un tiempo limitado, después, desaparecen. La vida de las partículas se mide en steps. Puedes indicar su vida, o un rango para cada tipo de partículas. Además, las partículas pueden crear otras partículas en cada step o cuando mueren. Vigila que el número de partículas total no sea demasiado elevado.
part_type_life(ind,life_min,life_max)
Establece los límites del tiempo de vida para el tipo de
partícula (Ambos son 100 por defecto).
part_type_step(ind,step_number,step_type)
Establece el número y tipo de partículas que
deben ser generadas en cada step para el tipo indicado de particula. Si usas un número negativo para step_number, por ejemplo -5, se generará una partícula cada 5 steps.
part_type_death(ind,death_number,death_type)
Establece el número y tipo de partículas que
deben ser generadas cuando una particular del tipo indicado llega al final de su ciclo de vida. Puedes usar valores negativos al igual que en la función anterior. La partícula sólo creará otras partículas al llegar al final de su vida y no cuando sea destruida por un destructor (lee más adelante).
Movimiento de las partículas Las partículas se pueden mover durante su vida. Pueden tener velocidad inicial y dirección y cambiar durante su vida. También pueden verse afectadas por la gravedad.
part_type_speed(ind,speed_min,speed_max,speed_incr,speed_wiggle)
Establece
las propiedades de velocidad para el tipo de partícula. (Todos los valores son 0 por defecto). Especificas un valor mínimo y máximo para la partícula y cuando ésta sea creada se escogerá un valor aleatorio entre ellos. Puedes especificar un valor negativo en speed_incr para hacer que la partícula se frene (aunque nunca llegará a tener velocidad negativa).
part_type_direction(ind,dir_min,dir_max,dir_incr,dir_wiggle)
Establece las
propiedades de dirección para el tipo de partícula, en grados antihorarios (0-360). (Todos los valores son 0 por defecto).
part_type_gravity(ind,grav_amount,grav_dir) tipo de partícula. (Por defecto no hay valor de gravedad).
Establece las propiedades de gravedad para el
Sistemas de partículas
Las partículas tienen vida en sistemas de partículas. Por lo que para tener partículas en tu juego necesitas crear uno o más sistemas de partículas. Puede haber muchos tipos diferentes de sistemas de partículas. Por ejemplo, si tu juego tiene varias pelotas y cada pelota debiera tener una cola de partículas, lo más probable es que cada pelota tenga su sistema de partículas. La manera más fácil para manejar los sistemas de partículas es crear uno y luego crear partículas en él, usando los tipos partículas que especificaste antes. Pero, como veremos a continuación, los sistemas de partículas pueden contener emisores que automáticamente producen partículas, atractores que las atraen y destructores que las eliminan.
Una vez que las partículas se añaden a un sistema son dibujadas y actualizadas en cada step automáticamente. Para definir si las partículas se deben dibujar delante o detrás de las demás instancias, los sistemas de partículas poseen una profundidad.
Los sistemas de partículas no se eliminarán aunque reinicies el juego, para eliminarlos debes usar la función adecuada. Así que asegúrate de hacerlo cuando ya no los necesites.
Las siguientes funciones permiten manejar los sistemas de partículas:
part_system_create()
Crea un nuevo sistema de partículas. Devuelve el índice del sistema creado. Este
índice debe emplearse en las llamadas a las funciones siguientes para configurar las propiedades del sistema de partículas.
part_system_destroy(ind)
Destruye el sistema de partículas ind. Llama a esta función si ya no harás uso
del sistema para ahorrar espacio.
part_system_exists(ind) part_system_clear(ind)
Devuelve si el sistema de partículas ind existe.
Devuelve el sistema ind a sus valores por defecto, removiendo todas las
partículas, emisores y atractores en él.
part_system_draw_order(ind,oldtonew)
Establece el orden en el que el sistema dibuja las
partículas. Cuando oldtonew es true las partículas más viejas son dibujadas primero y las nuevas se dibujan sobre ellas (valor por defecto). De otra forma las partículas más recientes son dibujadas primero. Esto puede resultar en varios efectos diferentes.
part_system_depth(ind,depth)
Especifica la profundidad del sistema de partículas.
part_system_position(ind,x,y)
Especifica la posición en la que se dibuja el sistema de partículas. Si
quieres dibujar el sistema relativo a un objeto puedes usar sus coordenadas.
Como ya hemos visto, los sistemas de partículas se dibujan y actualizan automáticamente. Pero a veces puedes no querer que esto sea así. Las siguientes funciones te permiten controlar estos aspectos:
part_system_automatic_update(ind,automatic)
Indica si el sistema debe actualizare
automáticamente (1) o no (0).
part_system_automatic_draw(ind,automatic)
Indica si el sistema debe dibujarse
automáticamente (1) o no (0).
part_system_update(ind)
Esta función actualiza el sistema y permite a los emisores crear partículas.
Puedes llamar a esta función cuando no uses actualización automática, o para acelerar un poco el sistema al inicio.
part_system_drawit(ind)
Esta función dibuja el sistema de partículas si has desactivado la opción
automática. Debes llamar a esta función desde el evento draw de algún objeto.
Las siguientes funciones se encargan de las partículas de un sistema de partículas:
part_particles_create(ind,x,y,parttype,number)
Esta función crea number partículas del
tipo ind en la posición (x,y) dentro del sistema.
part_particles_create_color(ind,x,y,parttype,color,number)
Esta función crea
number partículas del tipo ind en la posición (x,y) dentro del sistema con el color indicado. Esto es útil cuando el tipo de partícula sólo usa un color o no define ninguno.
part_particles_clear(ind) part_particles_count(ind)
Esta función elimina todas las partículas del sistema.
Esta función devuelve el número de partículas en el sistema.
Emisores
Los emisores crean partículas. Pueden crear un flujo continuo de partículas o pueden lanzar un cierto número de partículas usando la función adecuada. Un sistema de partículas puede tener un número arbitrario de emisores. Un emisor tiene las siguientes propiedades:
•
xmin, xmax, ymin, ymax
•
shape
•
indican la extensión de la región en la cual se generan las partículas.
indica la forma de la región. Puede tener uno de los siguientes valores:
o
ps_shape_rectangle
o
ps_shape_ellipse
o
ps_shape_diamond
o
ps_shape_line
distribution
indica la distribución usada para general las partículas. Puede tener uno de los
siguientes valores:
o
ps_distr_linear
indica una distribución lineal, esto es que en toda la región se tiene la
misma oportunidad de que aparezca una partícula
o
ps_distr_gaussian
indica una distribución Gaussiana en la cual se generan más
partículas en el centro que en las orillas de la región
•
particle type
•
number
indica el tipo de partículas que serán generadas
indica el número de partículas generadas en cada step. Si es menor que 0, en cada step se
generará una particular con una probabilidad de –1/number. Por ejemplo con un valor de –5 se generará una particular en promedio una vez cada 5 steps.
Las siguientes funciones permiten establecer los emisores y permitirles crear partículas. Nota que cada una de ellas necesita el id del sistema de partículas al que pertenecen como primer argumento.
part_emitter_create(ps)
Crea un nuevo emisor en el sistema de partículas ps. Devuelve el índice del
emisor. Este índice debe usarse en todas las llamadas a las funciones siguientes para configurar las propiedades del emisor.
part_emitter_destroy(ps,ind)
Destruye el emisor ind en el sistema de partículas ps. Llama a esta
función si no necesitas más del emisor para ahorrar espacio.
part_emitter_destroy_all(ps) part_emitter_exists(ps,ind) part_emitter_clear(ps,ind)
Destruye todos los emisores del sistema de partículas ps.
Devuelve true si el emisor ind existe en el sistema ps.
Devuelve el emisor ind a sus valores por defecto.
part_emitter_region(ps,ind,xmin,xmax,ymin,ymax,shape,distribution) Establece la región y distribución del emisor.
part_emitter_burst(ps,ind,parttype,number)
Emite number partículas del tipo partype en
forma de estallido (una sola vez) desde el emisor ind.
part_emitter_stream(ps,ind,parttype,number)
Desde este momento se crearán number
partículas de tipo partype desde el emisor ind en cada step. Si indicas un número menor a 0 en cada step una particular sera generada con una probabilidad de –1/number. Por ejemplo con un valor de –5 se creará en promedio una particular cada 5 steps.
Atractores
Además de los emisores, un sistema de partículas también puede contener atractores. Un atractor atrae a las partículas (o las aleja). Un sistema de partículas puede tener múltiples atractores. Aunque se te recomienda solo usar unos pocos porque alentarán el procesamiento de las partículas. Un atractor tiene las siguientes propiedades:
•
x,y
•
force
indican la posición del atractor. indican la fuerza de atracción del atractor. El cómo actúe la fuerza sobre las partículas depende de
los siguientes parámetros.
•
dist
indica la máxima distancia a la cual el atractor tiene efecto. Solo las partículas con una distancia al
atractor menor que dist serán atraídas.
•
kind
indica el tipo de atractor. Se tienen los siguientes valores
o
ps_force_constant
o
ps_force_linear
indica que la fuerza es constante independientemente de la distancia.
indica una fuerza que aumenta linealmente. A la distancia máxima la
fuerza es 0 mientras que en la posición del atractor la fuerza tiene el valor force indicado.
o •
ps_force_quadratic
additive
indica que la fuerza aumenta de manera cuadrática.
indica si la fuerza se suma a la velocidad y dirección en cada step (true) o si solo se aplica a la
posición de la particular (false). Cuando es true la particular acelerará hacia el atractor mientras que cuando sea false se moverá hacia él con velocidad constante.
Las siguientes funciones definen atractores. Nota que todas necesitan como primer argumento el índice del sistema de partículas al cual pertenecen.
part_attractor_create(ps)
Crea un nuevo atractor en el sistema de partículas ps. Devuelve el índice del
atractor. Este índice debe emplearse en la llamada a las funciones siguientes para configurar las propiedades del atractor.
part_attractor_destroy(ps,ind)
Destruye el atractor ind dentro del sistema de partículas ps. Llama
a esta función si ya no necesitas el atractor para ahorrar espacio.
part_attractor_destroy_all(ps)
Destruye todos los atractores que hayan sido creados en el sistema
de partículas ps.
part_attractor_exists(ps,ind)
Devuelve true si el atractor ind existe dentro del sistema de
partículas ps.
part_attractor_clear(ps,ind)
Devuelve los valores del atractor a sus valores por defecto.
part_attractor_position(ps,ind,x,y)
Establece a (x,y) la posición del atractor ind.
part_attractor_force(ps,ind,force,dist,kind,aditive)
Establece las propiedades de
fuerza del atractor ind.
Destructores
Los destructores destruyen las partículas cuando aparecen dentro de su región. Un sistema de partículas puede tener un número arbitrario de destructores. Un destructor cuenta con las siguientes propiedades:
•
xmin, xmax, ymin, ymax
•
shape
indican la extensión de la región en la cual se destruyen las partículas.
indica la forma de la región. Puede tener uno de los valores siguientes:
o
ps_shape_rectangle
o
ps_shape_ellipse
o
ps_shape_diamond
Las funciones siguientes configuran las propiedades de los destructores. Nota que cada una de ellas necesita como primer argumento el índice del sistema de partículas al que pertenece el destructor.
part_destroyer_create(ps)
Crea un nuevo destructor en el sistema de partículas ps. Devuelve el índice
del destructor. Este índice debe usarse en las llamadas a las funciones siguientes para configurar las propiedades del destructor.
part_destroyer_destroy(ps,ind)
Destruye el destructor ind dentro del sistema de partículas ps.
Llama a esta función cuando ya no hagas uso del destructor para ahorrar espacio.
part_destroyer_destroy_all(ps)
Destruye todos los destructores que hayan sido creados en el
sistema de partículas ps.
part_destroyer_exists(ps,ind)
Devuelve true si el destructor ind existe dentro del sistema de
partículas ps.
part_destroyer_clear(ps,ind)
Devuelve el destructor a sus valores por defecto.
part_destroyer_region(ps,ind,xmin,xmax,ymin,ymax,shape)
Establece la región del
destructor.
Deflectores
Los deflectores desvían a las partículas cuando aparecen dentro de su región. Un sistema de partículas puede tener un número arbitrario de deflectores. Un deflector tiene las siguientes propiedades:
•
xmin, xmax, ymin, ymax indican la extensión de la región en la cual las partículas son desviadas.
•
kind o
indica el tipo de deflector. Puede tener uno de los siguientes valores:
ps_deflect_horizontal
refleja las partículas horizontalmente; normalmente usado
para paredes verticales
o
ps_deflect_vertical
refleja las partículas verticalmente; normalmente usado para
paredes horizontales
•
friction
la cantidad de fricción como resultado de un impacto con el deflector. Entre mayor sea este
valor más se alentará la particular al impactarse con el deflector.
Se tienen las siguientes funciones para configurar las propiedades del deflector. Nota que todas necesitan como primer argumento el índice del sistema de partículas al cual pertenece el deflector.
part_deflector_create(ps)
Crea un nuevo deflector en el sistema de partículas ps. Devuelve el índice
del deflector. Este índice debe emplearse en las llamadas a las funciones siguientes para configurar las propiedades del deflector.
part_deflector_destroy(ps,ind)
Destruye el deflector ind del sistema de partículas ps. Llama a esta
función cuando ya no necesites el deflector para ahorrar espacio.
part_deflector_destroy_all(ps)
Destruye todos los deflectores que hayan sido creados en el sistema
de partículas ps.
part_deflector_exists(ps,ind) partículas ps.
Devuelve true si el deflector ind existe dentro del sistema de
part_deflector_clear(ps,ind)
Devuelve el deflector a sus valores por defecto.
part_deflector_region(ps,ind,xmin,xmax,ymin,ymax) Establece la región para el deflector.
part_deflector_kind(ps,ind,kind)
Establece el tipo de deflector.
part_deflector_friction(ps,ind,friction)
Establece la fricción del deflector.
Cambiadores
Los cambiadores cambian ciertas partículas cuando aparecen dentro de su región. Un sistema de partículas puede tener un número arbitrario de cambiadores. Un cambiador tiene las siguientes propiedades:
•
xmin, xmax, ymin, ymax
indican la extensión de la región en la cual las partículas son
cambiadas.
•
shape
indica la forma de la región. Puede tener uno de los valores siguientes:
o
ps_shape_rectangle
o
ps_shape_ellipse
o
ps_shape_diamond
•
parttype1
indica el tipo de partícula a ser cambiado.
•
parttype2
indica el tipo de partícula al que se cambiará.
•
kind o
indica el tipo del cambiador. Puede tener uno de los siguientes valores:
ps_change_motion
sólo cambia los parámetros de movimiento de la partícula, no el color o
forma ni las propiedades de tiempo de vida
o
ps_change_shape
sólo cambia los parámetros de forma de la partícula como el tamaño, el
color y forma
o
ps_change_all
cambia todos los parámetros, básicamente significa que la partícula es
destruida y se crea una nueva del nuevo tipo.
Las siguientes funciones permiten establecer las propiedades del cambiador. Nota que cada una de ellas necesita como primer argumento el índice del sistema de partículas al que pertenece el cambiador.
part_changer_create(ps)
Crea un nuevo cambiador en el sistema de partículas ps. Devuelve el índice del
cambiador. Este índice debe usarse en las llamadas a las funciones siguientes para configurar las propiedades del cambiador.
part_changer_destroy(ps,ind)
Destruye el cambiador ind en el sistema de partículas ps. Llama a esta
función si ya no necesitas el cambiador para ahorrar espacio.
part_changer_destroy_all(ps)
Destruye todos los cambiadores que hayan sido creados en el sistema
de partículas ps.
part_changer_exists(ps,ind) part_changer_clear(ps,ind)
Devuelve true si el cambiador ind existe en el sistema de partículas ps.
Devuelve las propiedades del cambiador a sus valores por defecto.
part_changer_region(ps,ind,xmin,xmax,ymin,ymax,shape)
Establece la región para el
cambiador.
part_changer_types(ps,ind,parttype1,parttype2)
Establece el tipo de partícula que debe
ser cambiado y el tipo al que debe ser cambiado.
part_changer_kind(ps,ind,kind)
Establece el tipo de cambiador.
Ejemplo de fuegos artificiales
Aquí tienes un ejemplo de un sistema de partículas que crea unos fuegos artificiales. Hace uso de dos tipos de partículas: uno que forma el cohete y otro que forma los fuegos artificiales en si. El cohete genera las partículas de los fuegos artificiales cuando se destruye. También tenemos un emisor en el sistema de partículas que emite regularmente partículas del cohete desde la parte baja de la pantalla. Para hacer que esto funcione necesitas un objeto. En su evento de creación colocamos el siguiente código que crea los tipos de partículas, un sistema de partículas y el emisor:
{ // Crear el sistema de partículas ps = part_system_create();
// the firework particles pt1 = part_type_create(); part_type_shape(pt1,pt_shape_flare); part_type_size(pt1,0.1,0.2,0,0); part_type_speed(pt1,0.5,4,0,0); part_type_direction(pt1,0,360,0,0); part_type_color1(pt1,c_red); part_type_alpha2(pt1,1,0.4); part_type_life(pt1,20,30); part_type_gravity(pt1,0.2,270);
// El cohete pt2 = part_type_create(); part_type_shape(pt2,pt_shape_sphere); part_type_size(pt2,0.2,0.2,0,0); part_type_speed(pt2,10,14,0,0); part_type_direction(pt2,80,100,0,0); part_type_color2(pt2,c_white,c_gray); part_type_life(pt2,30,60);
part_type_gravity(pt2,0.2,270); part_type_death(pt2,150,pt1);
/* Crear el fuego artificial al
morir*/
// Crear el emisor em = part_emitter_create(ps);
part_emitter_region(ps,em,100,540,480,490,ps_shape_rectangle,ps_distr_ linear); part_emitter_stream(ps,em,pt2,-4);
// Crear 1 cada 4 steps
} Listo! Asegúrate de que el sistema de partículas (y tal vez los tipos de partículas) se destruyen al cambiar de room o el efecto durará para siempre.
Juegos multijugador Esta funcionalidad sólo está disponible en la versión registrada de Game Maker. Jugar contra la computadora es divertido. Pero jugar contra oponentes humanos puede serlo mucho más. Es relativamente fácil crear este tipo de juegos porque no tienes que implementar IA compleja para un oponente manejado por la computadora. Por supuesto que puedes sentar a dos jugadores tras el mismo monitor y emplear diferentes teclas o algún otro dispositivo de entrada, pero es mucho más interesante cuando cada jugador está detrás de su propia computadora. O aún mejor, un jugador se encuentra del otro lado del océano. El Game Maker tiene soporte multijugador. Por favor toma en cuenta que el crear juegos multijugador efectivos que se sincronicen bien y no tengan latencia es una tarea difícil. Este capítulo da una breve descripción de las posibilidades. En el sitio web hay un tutorial con más información.
La ayuda sobre este tema se encuentra en las siguientes secciones: Configurando la conexión Para que dos computadoras se comuniquen necesitarán cierto protocolo de conexión. Como la mayoría de los juegos, el Game Maker ofrece cuatro diferentes tipos de conexión: IPX, TCP/IP, Módem y Serial. La conexión IPX (para ser más precisos, se trata de un protocolo) funciona casi completamente transparente. Puede emplearse para jugar juegos con otras personas en la misma red local. Se necesita que esté instalada en tu computadora para que pueda emplearse. (Si no funciona, consulta la documentación de Windows. O ve a la opción Red dentro del Panel de Control y agrega el protocolo IPX). TCP/IP es el protocolo de internet. Puede usarse para jugar con otros jugadores en cualquier lugar mediante internet, suponiendo que conozcas su dirección IP. Una conexión modem se realiza a través del modem.
Tienes que introducir cierta información del modem (una cadena de inicialización y un número telefónico) para usarla. Finalmente, al usar la línea serial (una conexión directa entre las computadoras) necesitas introducir varias opciones de puertos. Hay cuatro funciones dentro del GML que pueden emplearse para inicializar estas conexiones:
mplay_init_ipx()inicializa una conexión IPX. mplay_init_tcpip(addr)inicializa una conexión TCP/IP. addr es una cadena que indica la dirección web o IP, p. Ej. 'www.gameplay.com' o '123.123.123.12', posiblemente seguida por un número de puerto (p. Ej. ':12'). Sólo se necesita la dirección para unirse a una sesión (ve a continuación). En una red local no se necesitan direcciones.
mplay_init_modem(initstr,phonenr)inicializa una conexión vía módem. initstr es la cadena de inicialización para el módem (puede estar vacía). phonenr es una cadena que contiene el número telefónico a marcar (p. Ej. '0201234567'). Sólo se necesita el número telefónico al unirse a una sesión (ve a continuación).
mplay_init_serial(portno,baudrate,stopbits,parity,flow) inicializa una conexión en serie. portno es el número de puerto (1-4). baudrate es la velocidad a emplear en baudios (100-256K). stopbits indica el número de bits de parada (0 = 1 bit, 1 = 1.5 bit, 2 = 2 bits). parity indica la paridad (0=ninguna, 1=impar, 2=par, 3=mark). Y flow indica el tipo de control de flujo (0=ninguna, 1=xon/xoff, 2=rts, 3=dtr, 4=rts and dtr). La función devuelve si la conexión se efectuó con éxito. Una llamada típica a esta función es mplay_init_serial(1,57600,0,0,4). Si especificas 0 al primer argumento se abre un diálogo para que el usuario pueda cambiar la configuración manualmente. Tu juego debe llamar una de estas funciones una sola vez. Todas las funciones indican si han tenido éxito (si se logra la conexión). No tiene éxito si el protocolo en particular no está instalado o soportado por tu máquina. Para checar si hay una conexión exitosa disponible puedes emplear la siguiente función
mplay_connect_status()devuelve el estado de la conexión actual. 0=no hay conexión, 1=conexión IPX, 2=conexión TCP/IP, 3=conexión vía módem, y 4=conexión serial.
Para finalizar la conexión llama a la función
mplay_end() finaliza la conexión actual.
Cuando empleas una conexión TCP/IP tal vez quieras decirle a la persona con la que deseas jugar cuál es la dirección ip de tu computadora. La siguiente función te será de ayuda:
mplay_ipaddress()devuelve la dirección IP de tu máquina (p. Ej. '123.123.123.12') como cadena. Puedes p. Ej. mostrarla en pantalla. Nota que esta rutina es lenta por lo que mejor no la llames a menudo.
Creando y uniéndose a sesiones Cuando te conectas a una red, puede haber múltiples juegos ejecutándose en la misma red. Esos juegos reciben el nombre de sesiones. Estas diferentes sesiones pueden corresponder a juegos diferentes o al mismo. Un juego debe identificarse en la red. Afortunadamente, Game Maker hace esto por ti. Lo único que debes saber es que cuando
cambias el id del juego en la ventana de opciones esta identificación cambia. De esta forma puedes evitar que personas con versiones anteriores de tu juego jueguen contra personas que cuentan con versiones más recientes.
Si quieres iniciar un nuevo juego multijugador necesitas crear una nueva sesión. Para esto puedes emplear la siguiente rutina:
mplay_session_create(sesname,playnumb,playername) Crea una nueva nueva sesión en la conexión actual. sesname es una cadena que indica el nombre de la sesión. playnumb indica el número máximo de jugadores permitidos para este juego (usa 0 para un número arbitrario de jugadores). playname es tu nombre como jugador. Indica si ha tenido éxito.
Una instancia del juego debe crear la sesión. La(s) otra(s) instancia(s) del juego deben unirse a esta sesión. Esto es un poco más complicado. Primero debes ver las sesiones disponibles y luego elegir a la que te unirás. Hay tres rutinas importantes para esto:
mplay_session_find()
Busca todas las sesiones que aún aceptan jugadores y devuelve el número de
sesiones encontradas.
mplay_session_name(numb) Devuelve el nombre de la sesión número numb (0 es la primer sesión). Esta rutina puede ser llamada sólo después de haber llamado a la anterior.
mplay_session_join(numb,playername)
Con esta rutina te unes a la sesión número numb (0 es la
primer sesión). playername es tu nombre como jugador. Indica si ha tenido éxito.
Hay una rutina más que puede cambiar el modo de la sesión. Debe llamarse antes de crear una sesión:
mplay_session_mode(move) Indica si se mueve la sesión de host a otra computadora cuando el host actual cierre. move debe ser true o false (valor por defecto).
Para checar el estado de la sesión actual puedes usar la siguiente función
mplay_session_status() Devuelve el estado de la sesión actual. 0 = no hay sesión, 1 = sesión creada, 2 = se unió a la sesión.
Un jugador puede detener una sesión empleando la siguiente rutina:
mplay_session_end() Finaliza la sesión para este jugador.
Jugadores Cada instancia del juego que se una a una sesión es un jugador. Como se indicó antes, los jugadores tienen nombres. Hay tres rutinas referentes a los jugadores:
mplay_player_find()
Busca todos los jugadores en la sesión actual y devuelve el número de jugadores
encontrados.
mplay_player_name(numb)
Devuelve el nombre del jugador número numb (0 es el primer jugador, el cual
siempre eres tú). Esta rutina puede sólo ser llamada después de haber llamado a la anterior.
mplay_player_id(numb)
Devuelve el id único del jugador número numb (0 es el primer jugador, el cual
siempre eres tú). Esta rutina puede llamarse sólo después de haber llamado la primera. Este id es usado al enviar y recibir mensajes de otros jugadores.
Datos compartidos La comunicación mediante datos compartidos es probablemente la mejor forma de sincronizar el juego. Todo el proceso de comunicación no es visible para ti. Hay un juego de 10000 valores que son comunes a todas las entidades del juego. Cada entidad puede establecer y leer valores. Game Maker se asegura de que cada entidad vea los mismos valores. Un valor puede ser un número real o una cadena. Sólo hay dos rutinas:
mplay_data_write(ind,val)
Escribe el valor val (cadena o real) en la ubicación ind (ind entre 0 y
10000).
mplay_data_read(ind)
Devuelve el valor en la ubicación ind (ind entre 0 y 10000). Inicialmente todos los
valores son 0.
Para sincronizar la información en las diferentes máquinas puedes ya sea usar un modo garantizado (guaranteed mode) que asegura que el cambio llegue a la otra máquina (pero el cual es lento) o un modo no garantizado (non-guaranteed mode). Para cambiar esto usa la siguiente rutina:
mplay_data_mode(guar) Indica si se usa o no transmisión garantizada para los datos compartidos. guar debe ser true (valor por defecto) o false.
Mensajes El segundo mecanismo de comunicación que el Game Maker soporta es el envío y la recepción de mensajes. Un jugador puede enviar mensajes a un jugador o a todos los jugadores. Los jugadores pueden ver si han llegado mensajes y llevar a cabo las acciones adecuadas. Los mensajes pueden enviarse en modo garantizado en el que estás seguro de que llegarán (pero puede ser lento) o en modo no garantizado, el cual es más rápido.
Existen las siguientes rutinas de mensajes:
mplay_message_send(player,id,val) Envía un mensaje al jugador player (ya sea un identificador o un nombre; usa 0 para enviar el mensaje a todos los jugadores). id es un entero que funciona como identificador del mensaje y val es el valor (ya sea un número real o una cadena). El mensaje es enviado en modo no garantizado. Si val contiene un mensaje de texto la longitud máxima del mensaje es de 30000 caracteres.
mplay_message_send_guaranteed(player,id,val) Envía un mensaje al jugador player (ya sea un identificador o un nombre; usa 0 para enviar el mensaje a todos los jugadores). id es un entero que funciona como identificador del mensaje y val es el valor (ya sea un número real o una cadena). Este es un envío garantizado. Si val contiene un mensaje de texto la longitud máxima del mensaje es de 30000 caracteres.
mplay_message_receive(player) Recibe el siguiente mensaje de la cola de mensajes que llegó del jugador player (identificador o nombre). Usa 0 para indicar mensajes de cualquier jugador. La rutina indica si de hecho hubo un nuevo mensaje. Si es así, puedes emplear las siguientes rutinas para obtener su contenido:
mplay_message_id() Devuelve el identificador del último mensaje recibido. mplay_message_value() Devuelve el valor del último mensaje recibido. mplay_message_player() Devuelve el jugador que envió el último mensaje recibido. mplay_message_name() Devuelve el nombre del jugador que envió el último mensaje recibido. mplay_message_count(player) Devuelve el número de mensajes restantes en la cola de espera del jugador player (usa 0 para contar todos los mensajes).
mplay_message_clear(player) Elimina todos los mensajes en la cola de espera del jugador player (usa 0 para eliminar todos los mensajes). Debemos hacer algunas observaciones. Primero, si quieres enviar un mensaje sólo a un usuario en particular, necesitarás conocer el id del jugador. Como se indicó antes puedes obtener este id con la función mplay_player_id(). Este identificador del jugador también es empleado al recibir mensajes de un jugador en particular. Alternativamente, puedes dar el nombre del jugador como cadena. Si varios jugadores tienen el mismo nombre, el mensaje sólo llegará al primero.
En segundo lugar, podrías preguntarte porqué cada mensaje tiene un entero identificador. La razón es que esto ayuda a tu aplicación a enviar diferentes tipos de mensajes. El receptor puede checar el tipo de mensaje usando el id y llevar a cabo las acciones apropiadas. (Como no está garantizado que los mensajes lleguen, el enviar el id y el valor en mensajes diferentes causaría serios problemas).
Usando DLLs
Esta función solo está disponible en la versión registrada de Game Maker.
En aquellos casos en los que la funcionalidad del GML no cubra tus objetivos, se pueden ampliar las posibilidades usando plug-ins (agregados o complementos). Un plug-in consiste en un archivo DLL (Dynamic Link Library – Biblioteca de enlazado dinámico). En este archivo DLL se pueden definir funciones. Tales funciones pueden ser programadas en cualquier lenguaje que soporte la creación de DLLs (p. Ej. Delphi, C, C++, etc.). Aunque se necesita contar con ciertas habilidades de programación para poder hacerlo. Las funciones plug-in deben tener un formato específico. Pueden tener entre 0 y 11 argumentos, cada uno de los cuales puede ser un número real (double en C) o una cadena terminada en un carácter nulo. (Para más de 4 argumentos, sólo se soporta argumentos reales). Estas funciones deben devolver ya sea un valor real o una cadena terminada en carácter nulo.
En Delphi se puede crear una DLL seleccionando New en el menú File y luego eligiendo DLL. A continuación tienes un ejemplo de una DLL escrita en Delphi que puede ser empleada con Game Maker. (Esto es código de Delphi ¡no GML!)
library MyDLL;
uses SysUtils, Classes;
function MyMin(x,y:double):double;
cdecl;
begin if x
var res : array[0..1024] of char;
function DoubleString(str:PChar):PChar; cdecl; begin StrCopy(res,str); StrCat(res,str); Result := res; end;
exports MyMin, DoubleString;
begin end.
Esta DLL define dos funciones: MyMin toma dos argumentos reales y devuelve el de menor valor, y DoubleString que duplica una cadena. Cabe recordar que se debe ser cuidadoso con el manejo de la memoria. Esta es la razón por la que declaré la cadena resultante como global. También nota el uso de la convención de llamada cdecl. Puedes emplear las convenciones de llamada cdecl o stdcall. Una vez que crees la DLL en Delphi tendrás el archivo MyDLL.DLL. Este archivo debe ser colocado en la carpeta de tu juego. (O en cualquier otro lugar donde Windows pueda encontrarlo).
Para hacer uso de esta DLL en Game Maker primero necesitas especificar las funciones externas que deseas emplear y qué tipo de argumentos requieren. Para ello tenemos la siguiente función en el GML:
external_define(dll,name,calltype,restype,argnumb,arg1type,arg2type, …)
Define una función externa. dll es el nombre del archivo dll. name es el nombre de las funciones. calltype es la
convención de llamada empleada. Usa dll_cdecl o dll_stdcall. restype es el tipo del resultado. Usa ty_real o ty_string. argnumb es el número de argumentos (0-11). Después, para cada argumento debes especificar su tipo. Para ello usa nuevamente ty_real o ty_string. Cuando hay más de 4 argumentos todos ellos deben ser de tipo ty_real.
Esta función devuelve el id de la función externa que debe emplearse para llamarla. En el ejemplo de arriba, al inicio del juego usarías el siguiente código GML:
{ global.mmm = external_define('MYOWN.DLL','MyMin',dll_cdecl, ty_real,2,ty_real,ty_real); global.ddd = external_define('MYOWN.DLL','DoubleString',dll_cdecl, ty_string,1,ty_string); }
Ahora en el momento que necesites llamar a las funciones, usas la siguiente función:
external_call(id,arg1,arg2,…)
Llama a la función externa con el id y los argumentos dados.
Necesitas proporcionar el número correcto de argumentos del tipo correcto (real o string). La función devuelve el resultado de la función externa.
Así, por ejemplo, escribirías:
{ aaa = external_call(global.mmm,x,y); sss = external_call(global.ddd,'Hello'); }
Si ya no utilizarás la DLL, lo mejor sería liberarla.
external_free(dll)
Libera la DLL con el nombre indicado. Esto es particularmente necesario si el juego
debiera remover la DLL. Mientras la DLL no haya sido liberada, no puede ser removida. Lo mejor es realizar esto p. Ej. en el evento game end.
Ahora te preguntarás cómo hacer una función en una DLL que haga algo en el juego. Por ejemplo, podrías querer una DLL que agregue instancias de objetos a tu juego. La forma más fácil es dejar que la función DLL devuelva una cadena que contenga una pieza de código GML. Esta cadena que contiene una pieza de GML puede ser ejecutada usando la siguiente función del GML
execute_string(str)
Ejecuta la pieza de código en la cadena str.
Alternativamente puedes dejar que la DLL cree un archivo con un script que puede ser ejecutado (esta función también puede ser empleada para luego modificar el comportamiento del juego).
execute_file(fname)
Ejecuta el código en el archivo fname.
Ahora puedes llamar una función externa y ejecutar la cadena resultante, por ejemplo:
{ ccc = external_call(global.ddd,x,y); execute_string(ccc); } En algunos casos especiales pudieras necesitar el handle de ventana (identificador de Windows) de la ventana principal del juego. Este puede obtenerse con la siguiente función y luego ser pasado a la DLL:
window_handle()
Devuelve el handle de la ventana principal.
Nota: las DLLs no pueden ser empleadas en modo seguro. El empleo de DLLs externas es una función extremadamente poderosa. Pero por favor haz uso de ellas sólo si sabes lo que estás haciendo
Gráficos 3D
Esta función sólo está disponible en la versión registrada de Game Maker.
Game Maker es un programa destinado para crear juegos en 2 dimensiones e isométricos. Sin embargo existen algunas funciones para crear gráficos tridimensionales. Pero antes que inicies con esto hay un par de cosas que debes entender.
•
La funcionalidad 3D en Game Maker está limitada a la parte gráfica. No hay más soporte para otras funciones 3D. Una vez que comiences a usar gráficos 3D posiblemente tendrás problemas con otros aspectos de Game Maker, como las vistas, orden de profundidad, etc. La funcionalidad es limitada y tiene baja prioridad de ser extendida. Así que no esperes soporte para objetos de modelos 3D, etc.
•
Cuando usas la funcionalidad 3D, son varias cosas las que ya no pueden ser usadas. o
Ya no podrás usar backgrounds ni foregrounds en tus rooms (La razón es que son usados como mosaicos para llenar la imagen pero con proyecciones de perspectiva esto ya no funciona correctamente).
o
Ya no podrás usar la posición del ratón. El ratón no será transformado a las coordenadas 3D. Aún puedes obtener la posición del ratón en la pantalla (en la vista) pero tendrás que hacer cálculos con esto por ti mismo (o no usar el ratón del todo).
o
Ya no podrás usar tiles. Lo más posible es que los tiles ya no encajen correctamente.
o
La comprobación de colisiones todavía usa las posiciones 2-d de las instancias en el cuarto. Así que no hay detección de colisiones en 3D. En ocasiones aún podrás usar esto (si utilizas el cuarto como una representación de un mundo plano (peje. para carreras o juegos FPS) pero en otras circunstancias tendrás que hacer las cosas por ti mismo.
•
Todas las funciones 3D son a través de código. Debes estar algo familiarizado con el lenguaje GML. Además debes entender bastante acerca de como funciona Game Maker de lo contrario te meterás en dificultades.
•
Necesitas tener conocimiento básico acerca de gráficos 3D. En particular usaré términos como proyecciones de perspectiva, hidden surface removal, iluminación, y niebla, sin mucha explicación.
•
No hay modelado 3D en Game Maker. Además que no planeo añadir soporte para cargar modelos 3D.
•
Es preciso que trabajes con cuidado para mantener una velocidad razonable. Además, esto no está realmente optimizado en velocidad.
Si todo esto no te desanimó, continúa leyendo.
Esta sección se divide en los siguientes temas: Rumbo al modo 3D
Rumbo al modo 3D Si deseas usar el modo 3D antes necesitas poner Game Maker en modo 3D. Después puedes regresar al modo 2D si lo deseas. Las siguientes dos funciones existen para ello.
d3d_start ( ) d3d_end ( )
Inicia el modo 3D.
Detiene el modo 3D.
Nota que todas las funciones relacionadas con 3D inician con d3d_.
Iniciar el modo 3D dará resultado a los siguientes cambios. Primero que nada hidden surface removal es cambiado a encendido (usando un z-buffer de 16 bits). Esto significa que por cada píxel en la pantalla sólo el dibujado con el valor z menor (= al valor depth, profundidad) es mostrado. Si las instancias tienen la misma profundidad es confuso lo que pasará y puedes obtener efectos feos. ¡Asegúrate que las instancias que pueden sobreponerse no tengan la misma profundidad!
En segundo lugar, la proyección ortográfica es remplazada por una perspectiva. Esto significa lo siguiente. Normalmente el tamaño de las instancias en la pantalla es independiente de su profundidad. Con una proyección en perspectiva las instancias que poseen mayor profundidad parecerán más pequeñas. Cuando la profundidad es 0 al viejo tamaño (a menos que cambies la proyección; velo mas adelante). El punto de vista para la cámara es puesta a una distancia sobre el cuarto. (Esta distancia es igual al ancho del cuarto; la cual da una proyección por defecto razonable.) Sólo las instancias que estén frente la cámara son dibujadas. Así que no uses instancias con una profundidad menor a 0 (o al menos no menor que –w donde w (width) es el ancho del cuarto o la vista).
En tercer lugar, la coordenada-y vertical es invertida. Mientras que la posición (0,0) está en la esquina superiorizquierda de la vista, en el modo 3D la posición (0,0) está en la posición inferior-izquierda, lo cual es normal para las vistas 3-dimensionales.
En realidad puedes cambiar hidden surface removal (eliminar las superficies ocultas) y la proyección de perspectiva encendido o apagado usando las siguientes funciones.
d3d_set_hidden (habilita)
Habilita eliminar superficie oculta (true) o la deshabilita
(false).
d3d_set_perspective(habilita)
Habilita el uso de una proyección en perspectiva
(true) o la deshabilita (false).
Dibujo fácil Una vez que haz cambiado al modo 3D puedes usar Game Maker como sueles hacerlo (excepto por las remarcas hechas al principio). Sólo los objetos aparecerán en diferentes tamaños basados en profundidad. Incluso puedes usar vistas. Una función adicional puede ser usada. Si dibujas cierto número de cosas en una pieza de código quizá quieras cambiar el valor de profundidad entre las primitivas que dibujes. Para esto usa:
d3d_set_depth(profundidad)
Establece la profundidad usada para dibujar.
Nota que al momento en que una nueva instancia es dibujada la profundidad es devuelta al valor original de esa instancia.
Dibujando polígonos en 3D El problema de dibujar de la vieja manera es que un sprite o polígono siempre se encuentra en un plano-xy, esto es, todas las esquinas tienen la misma profundidad. Para 3D verdadero debes ser capaz de tener vértices a diferentes profundidades. A partir de este momento hablaremos de la coordenada-z en vez de la profundidad (depth). Así que debemos especificar coordenadas como (x,y,z). Para esto existe una versión especial de funciones de dibujado avanzado:
d3d_primitive_begin(tipo)
Inicia una primitiva 3D de un tipo indicado:
pr_pointlist,
pr_linelist,pr_linestrip,pr_trianglelist,pr_trianglestrip o pr_trianglefan. d3d_vertex(x,y,z)
Añade el vértice (x,y,z) a la primitiva, usando color y valor alfa
especificados con anterioridad.
d3d_vertex_color(x,y,z,col,alpha)
Añade el vértice (x,y,z) a la primitiva, con su
propio color y valor alpha. Esto te permite crear primitivas con un suave cambio de color y valores alfa.
d3d_primitive_end( )
Finaliza la descripción de la primitiva. Esta función realmente la
dibuja.
Por ejemplo, para dibujar un tetraedro (pirámide de tres lados) situada en el plano z=0 con su punta en z = 200, puedes usar el siguiente código:
{ d3d_primitive_begin(pr_trianglelist); d3d_vertex(100,100,0); d3d_vertex(100,200,0); d3d_vertex(150,150,200); d3d_vertex(100,200,0); d3d_vertex(200,200,0); d3d_vertex(150,150,200); d3d_vertex(200,200,0); d3d_vertex(100,100,0); d3d_vertex(150,150,200); d3d_vertex(100,100,0); d3d_vertex(100,200,0); d3d_vertex(200,200,0); d3d_primitive_end(); }
Ahora que si quieres usar esto, lo más posible es que sólo verás un triangulo en la pantalla porque la punta del tetraedro estaré en frente del punto de vista. Aparte, usando sólo un color, sería difícil de ver las diferentes caras. Adelante veremos maneras de cambiar el punto de vista. Asignando colores lo cual puede hacerse como antes añadiendo la función draw_set_color(col) entre las vértices.
También puedes usar polígonos texturizados en 3D. Funciona de la misma manera como está descrito en las funciones de dibujado avanzado en la documentación. Pero en esta ocasión necesitas variantes 3D de las funciones básicas. De una cosa te debes dar cuenta. En una textura la posición (0,0) es la esquina superiorizquierda. Pero en ocasiones, cuando se usan proyecciones (como se indica adelante), la esquina inferiorizquierda es (0,0). En ese caso necesitas girar la textura verticalmente.
d3d_primitive_begin_texture(tipo,texid)
Inicia una primitiva 3D del tipo
indicado con la textura indicada.
d3d_vertex_texture(x,y,z,xtex,ytex)
Añade el vértice (x,y,z) a la primitiva con
posición (xtext,ytext) en la textura, mezclando con el color y valor alpha especificados anteriormente.
d3d_vertex_texture_color(x,y,z,xtex,ytex,col,alpha)
Añade el
vértice (x,y,z) a la primitiva con posición (xtex,ytex) en la textura, mezclando con su propio color y valor alpha.
d3d_primitive_end() dibuja.
Finaliza la descripción de la primitiva. Esta función en realidad la
Así que, por ejemplo puedes usar el siguiente código para dibujar una imagen de fondo que desaparece a la distancia
{ var ttt; ttt = background_get_texture(back); d3d_primitive_begin_texture(pr_trianglefan,ttt); d3d_vertex_texture(0,480,0,0,0); d3d_vertex_texture(640,480,0,1,0); d3d_vertex_texture(640,480,1000,1,1); d3d_vertex_texture(0,480,1000,0,1); d3d_primitive_end(); }
Un triángulo tiene una cara frontal y una trasera. La cara frontal está definida para ser el lado donde los vértices son definidos en orden contrario al reloj. Normalmente ambos lados son dibujados. Pero si haces una forma cerrada esto es inútil porque el lado trasero del triángulo nunca puede ser visto. En este caso puedes encender backface culling. Esto salva cerca de la mitad la cantidad del tiempo dibujando pero te deja con la tarea de definir tus polígonos de la manera correcta. Existe la siguiente función:
d3d_set_culling(separar)
Indica iniciar backface culling (true) o detener backface
culling (false).
Dibujando formas básicas Existen varias funciones para dibujar formas básicas, como bloques y paredes. Nota que estas formas también funcionan correctamente con backface culling.
d3d_draw_block(x1,y1,z1,x2,y2,z2,texid,hrepeat,vrepeat)
Dibuja
un bloque del color actual con las esquinas opuestas indicadas usando la textura indicada. Usa -1 para no usar una textura.
hrepeat indica que tan seguido la textura debe ser repetida a través
del borde horizontal de cada cara.
vrepeat hace lo mismo con el borde vertical.
d3d_draw_cylinder(x1,y1,z1,x2,y2,z2,texid,hrepeat,vrepeat,ce rrado,steps)
Dibuja un cilindro vertical usando el color actual en la caja de colisión indicada
usando la textura indicada. Usa -1 para no usar una textura.
hrepeat
textura debe ser repetida a través del borde horizontal de cada cara. el borde vertical.
closed
indica que tan seguido la
vrepeat
hace lo mismo con
indica si se debe cerrar la tapa y fondo del cilindro. steps indica cuantos
pasos rotacionales deben ser tomados. Un valor típico es 24.
d3d_draw_wall(x1,y1,z1,x2,y2,z2,texid,hrepeat,vrepeat)
Dibuja
una pared vertical en el color actual con las puntas dadas usando la textura indicada. Usa -1 para no usar una textura.
hrepeat indica que tan seguido la textura debe ser repetida a través del borde
horizontal de cada cara.
vrepeat
hace lo mismo con el borde vertical.
d3d_draw_floor(x1,y1,z1,x2,y2,z2,texid,hrepeat,vrepeat) un piso (inclinado) en el color actual con las esquinas dadas usando la textura indicada.
Dibuja
hrepeat
indica que tan seguido la textura debe ser repetida a través del borde horizontal de cada cara.
vrepeat
hace lo mismo con el borde vertical.
La siguiente pieza de código dibuja dos bloques:
{ var ttt; ttt = background_get_texture(back); d3d_draw_block(20,20,20,80,40,200,ttt,1,1); d3d_draw_block(200,300,-10,240,340,100,ttt,1,1); }
Viendo el mundo Por defecto tu vista es a través del eje-z negativo hacia el medio del cuarto. En ocasiones en juegos 3D deseas poder cambiar la manera en que miras al mundo. Por ejemplo, en un juego de disparos en primera persona probablemente deseas tener la cámara mirando desde una posición un poco sobre el plano-xy a lo largo del plano-xy. En términos gráficos estás estableciendo la proyección correcta. Para cambiar la manera en que miras, las siguientes funciones existen.
d3d_set_projection(xfrom,yfrom,zfrom,xto,yto,zto,xup,yup,zup )
Defines como mirar en el mundo. Especificas el punto desde dónde mirar, el punto a mirar y el
rumbo ascendente.
Esta función requiere alguna explicación. Para establecer la proyección primero necesitas la posición desde la cual miras. Esto está indicado por los parámetros (xfrom,yfrom,zfrom). En seguida debes especificar la dirección que miras. Esto se hace dando un segundo punto al cual mirar. Este es el punto (xto,yto,zto). Finalmente, incluso puedes rotar la cámara alrededor de la línea del punto de vista al punto observado. Para especificar esto debemos tener un rumbo ascendente, esto es, la dirección ascendente en la cámara. Esto es dado por los últimos tres parámetros (xup,yup,zup). Permítanme dar un ejemplo. Para mirar a través del planoxy como en un primera persona puedes usar
{
d3d_set_projection(100,100,10,200,100,10,0,0,1); }
Así miras desde el punto (100,100) y 10 sobre el plano en dirección de (200,100). Los puntos del vector up es la dirección-z que requiere. Para hacer esto ligeramente más complicado, asumamos que tienes una instancia en tu cuarto que especifica la posición de la cámara. Esta tendrá una posición (x,y) actual y una dirección (y quizá incluso una velocidad). Ahora puedes especificar esto como tu cámara usando el siguiente código:
{ with (obj_camera) d3d_set_projection(x,y,10, x+cos(direction*pi/180),y-sin(direction*pi/180),10, 0,0,1); }
Esto puede lucir algo complicado. Miramos desde la posición de la cámara (x,y), 10 sobre el piso. Para determinar un punto en la dirección correcta necesitamos hacer algo de aritmética. Este punto está indicado por los tres siguientes parámetros. Finalmente usamos el vector up como antes.
¡Una observación importante! Cuando Game Maker comienza a dibujar un cuarto este pondrá el punto de vista de regreso a la posición por defecto. Así que la primera cosa que necesitas hacer cuando dibuja la escena es poner la proyección que desees. ¡Esto debe hacerse en un evento Draw!
Existe también una versión extendida de la función anterior:
d3d_set_projection_ext(xfrom,yfrom,zfrom,xto,yto,zto,xup,yup ,zup,angle,aspect,znear,zfar)
Una versión extendida de esta función en la cual
además especificas el ángulo definiendo el campo de vista, el radio de aspecto entre el tamaño vertical y horizontal de la vista, y los planos cercanos y lejanos de recorte.
Los parámetros adicionales trabajan como sigue. Si especificaste la posición de la cámara, el punto a mirar, y el vector up, puedes todavía cambiar qué tan unidos están los lentes de la cámara. Esto es conocido como el campo de vista. Un valor razonable es 45 grados y esto es el valor por defecto tomado. Pero puedes cambiar esto si gustas. Después puedes especificar la relación de aspecto entre la proyección horizontal y vertical. Normalmente quieres usar el mismo como la radio de aspecto del cuarto o la vista, p.ej. 640/480. Finalmente puedes indicar los planos de recorte. Los objetos que son más cercanos que
znear
a la cámara no son
dibujados. Similar para objetos más lejos que zfar. Puede ser importante poner estos parámetros a valores
razonables porque ellos también influyen en la precisión de las comparaciones de z. Si usted hace el rango demasiado grande la precisión empeora. Por defecto usamos 1 y 32000. ¡znear debe ser más grande que 0!
A veces necesitas temporalmente una proyección normal ortográfica como la usada cuando no hay 3D. Por ejemplo, cuando quieres dibujar alguna capa. Para esto puedes usar la función siguiente:
d3d_set_projection_ortho(x,y,w,h,ángulo)
Pone una proyección normal ortográfica
del área indicada en el cuarto, girando sobre el ángulo indicado.
Un uso estándar para esto es dibujar una capa para p.ej. mostrar el puntaje u otros aspectos. Para hacer esto ponemos una proyección ortográfica. También debemos desactivar hidden surface removal temporalmente porque queremos que la información sea dibujada independientemente del valor de profundidad actual. El ejemplo siguiente muestra como crear una capa con el puntaje.
{ draw_set_color(c_black); d3d_set_projection_ortho(0,0,room_width,room_height,0); d3d_set_hidden(false); draw_text(10,10,'Score: ' + string(score)); d3d_set_hidden(true); }
Transformaciones Las transformaciones te permiten cambiar el lugar donde las cosas son dibujadas en el mundo. Por ejemplo, la función para dibujar bloques sólo puede dibujar bloques paralelos de eje. Por primera opción una transformación de rotación puedes crear bloques girados. También los sprites siempre son dibujados paralelos al plano-xy. Estableciendo una transformación puedes cambiar esto. Hay dos tipos de funciones: las funciones que ponen la transformación y las funciones que añaden transformaciones.
d3d_transform_set_identity ()
Pone la transformación a la identidad (ninguna
transformación).
d3d_transform_set_translation(xt,yt,zt)
Pone la transformación a una
conversión sobre el vector indicado.
d3d_transform_set_scaling(xs,ys,zs)
Pone la transformación a un
escalamiento con las cantidades indicadas.
d3d_transform_set_rotation_x(ángulo)
Pone la transformación a una rotación
alrededor del eje-x con la cantidad indicada.
d3d_transform_set_rotation_y(ángulo) alrededor del eje-y con la cantidad indicada.
Pone la transformación a una rotación
d3d_transform_set_rotation_z(ángulo)
Pone la transformación a una rotación
alrededor del eje-z con la cantidad indicada.
d3d_transform_set_rotation_axis(xa,ya,za,ángulo)
Pone la
transformación a una rotación alrededor del eje indicado por el vector con la cantidad indicada.
d3d_transform_add_translation(xt,yt,zt)
Añade una transformación sobre
vector indicado.
d3d_transform_add_scaling(xs,ys,zs)
Añade un escalamiento con las
cantidades indicadas.
d3d_transform_add_rotation_x(ángulo)
Añade una rotación alrededor del eje-x
con la cantidad indicada.
d3d_transform_add_rotation_y(ángulo)
Añade una rotación alrededor del eje-y
con la cantidad indicada.
d3d_transform_add_rotation_z(ángulo)
Añade una rotación alrededor del eje-z
con la cantidad indicada.
d3d_transform_add_rotation_axis(xa,ya,za,ángulo)
Añade una
rotación alrededor del eje indicado por el vector con la cantidad indicada. Comprende que la rotación y el escalamiento son con respecto al origen del mundo, no al respecto al objeto que debe ser dibujado. Si el objeto no está en el origen este también se moverá a un lugar diferente, que no es lo que queremos. Así para p.ej. hacer girar un objeto sobre su propio eje-x, primero debemos trasladar al origen, después hacerlo girar, y finalmente trasladarlo de regreso a su posición. Esto es para qué son las funciones de añadir transformaciones. Los ejemplos siguientes podrían explicar esto mejor. Asumiendo que tenemos un sprite spr que queremos dibujar en la posición (100,100,10). Podemos usar el código siguiente para hacer esto
{ d3d_transform_set_translation(100,100,10); draw_sprite(spr,0,0,0); d3d_transform_set_identity(); }
Nota que porque usamos una transformación ahora deberíamos dibujar el sprite en la posición (0,0). (¡Esto asume que la instancia actual tiene una profundidad de 0! Si no estas seguro, primero pon la profundidad.) Si usáramos esto en nuestro tirador en primera persona no veríamos el sprite. La razón es que es todavía paralelo al plano-xy. Queremos hacerlo girar más de 90 grados a lo largo del eje-x (o el eje-y). Entonces tenemos que añadir una rotación. Recuerda el orden: primero debemos hacer girar el sprite y luego transformarlo. Entonces podemos usar el código siguiente.
{
d3d_transform_set_identity(); d3d_transform_add_rotation_x(90); d3d_transform_add_translation(100,100,10); draw_sprite(spr,0,0,0); d3d_transform_set_identity(); }
En ocasiones quieres guardar temporalmente la transformación actual, por ejemplo para añadir una transformación adicional y luego restaurar a la anterior (esto a menudo pasa dibujando modelos jerárquicos). Para este fin puedes empujar la transformación actual sobre un montón y luego sacarlo del montón para hacerlo la transformación actual de nuevo. Las funciones siguientes existen para esto:
d3d_transform_stack_clear()
Limpia el montón de transformaciones.
d3d_transform_stack_empty()
Devuelve si el montón de transformación está vacío.
d3d_transform_stack_push()
Empuja la transformación actual sobre el montón.
Devuelve si hay espacio sobre el montón para empujarlo allí (si olvidas sacar la transformación en algún momento te quedarás sin espacio en el montón).
d3d_transform_stack_pop()
Saca la transformación superior del montón y la hace la
actual. Devuelve si había una transformación sobre el montón.
d3d_transform_stack_top()
Hace a la transformación superior la actual, pero no lo
quita del montón. Devuelve si había tras una transformación sobre el montón.
d3d_transform_stack_discard()
Quita la transformación superior del montón, pero
no lo hace la actual. Vueltas si había tras una transformación en el montón.
La utilización de la transformación es un mecanismo poderoso. Pero sé cuidadoso y siempre restaura la transformación a la identidad una vez que terminaste.
Niebla La niebla puede ser usada en juegos de 3D para hacer que los objetos a la distancia luzcan borrosos o incluso desaparecer. Esto ayuda en la creación de la atmósfera y hace posible el no dibujar los objetos que están a lo lejos. Para habilitar o deshabilitar la niebla usa la función siguiente:
d3d_set_fog(habilitar,color,principio,fin)
Habilita o deshabilita el
empleo de niebla. color indica el color de la niebla. principio indica la distancia a la cual la niebla debe comenzar. fin indica la distancia a la cual la niebla es máxima y nada puede ser visto más allá.
Iluminación
Las escenas que dibujas con las funciones anteriores parecen bastante planas porque no hay luz alguna. El color si las caras son iguales, independiente de su orientación. Para crear escenas que luzcan más realistas debes permitir la iluminación y poner las luces en los sitios correctos. La creación de escenas correctamente alumbradas no es fácil pero el efecto es muy bueno.
Para permitir la iluminación puedes usar la función siguiente;
d3d_set_lighting(habilitar)
Habilita o inhabilita el uso de iluminación.
Usando la iluminación, para cada vértice de un polígono el color es determinado. Después, el color de los píxeles internos está basado en el color de estos vértices. Hay dos modos que puede ser hecho: El polígono entero consigue el mismo color, o el color suavemente es interpolado sobre el polígono. Por defecto smooth shading es usado. Esto puede ser cambiado usando la función siguiente:
d3d_set_shading(smooth)
Establece si usar smooth shading o no.
Para usar la iluminación obviamente tienes que definir luces. Dos diferentes luces existen: luces direccionales (como el sol), y luces posiciónales. La luz tiene un color. (Sólo soportamos la luz difusa, no la reflexión especular.) Las funciones siguientes existen para definir y usar luces:
d3d_light_define_direction(ind,dx,dy,dz,col)
Define una luz dirigida.
ind es el índice de la luz (usa un pequeño número positivo). (dx,dy,dz) es la dirección de la luz. col es el color de la luz (a menudo quieres usar c_white. Esta función no enciende la luz.
d3d_light_define_point(ind,x,y,z,rango,col)
Define una luz de punto. ind
es el índice de la luz (usa un pequeño número positivo). (x,y,z) es la posición de la luz. rango indica qué lejos brilla la luz. La intensidad de la luz se disminuirá sobre este rango. col es el color de la luz. Esta función no enciende la luz.
d3d_light_enable(ind,permitir)
Habilita (true) o deshabilita (false) la luz número
ind.
La manera en que un objeto refleja la luz depende del ángulo entre la dirección de la luz y el normal de la superficie, es decir el vector que señala lejos de la superficie. De ahí, para crear objetos iluminados no sólo tienes que proporcionar la posición de los vértices, sino también sus normales. Para estas cuatro funciones adicionales están disponibles para definir los vértices de primitivas:
d3d_vertex_normal(x,y,z,nx,ny,nz) vector normal (nx,ny,nz).
Añade el vértice (x,y,z) a la primitiva, con
d3d_vertex_normal_color(x,y,z,nx,ny,nz,col,alpha)
Añade el vértice
(x,y,z) a la primitiva, con vector normal (nx,ny,nz), y con su propio color y valor alpha.
d3d_vertex_normal_texture(x,y,z,nx,ny,nz,xtex,ytex)
Añade el
vértice (x,y) a la primitiva, con vector normal (nx,ny,nz), y con la posición (xtex,ytex) en la textura, mezclándose con el color y valor alpha establecidos antes.
d3d_vertex_normal_texture_color(x,y,z,nx,ny,nz,xtex,ytex,col ,alpha)
Añade el vértice (x,y) a la primitiva, con vector normal (nx,ny,nz), y con la posición
(xtex,ytex) en la textura, mezclándose con su propio color y valor alpha.
Note que para las formas básicas que puedes dibujar las normales automáticamente son puestas correctamente.
Creando modelos Cuando necesitas dibujar modelos complejos es muy costoso llamar a todas las funciones diferentes una y otra vez en cada step. Para evitarte esto, puedes crear modelos. Un modelo consiste de un número de primitivas y formas de dibujo. Una vez que creas un modelo puedes dibujarlo en diferentes lugares y en diferenes posiciones con una simple llamada a ese modelo.
Antes de listar las funciones de modelos es importante resaltar un aspecto: el manejo de texturas. Como ya se ha explicado antes, las texturas se toman de sprites y backgrounds. Los índices de las texturas pueden ser diferentes en cada momento, por lo que los modelos no contienen información sobre ellas. Sólo cuando dibujas un modelo debes especificar información sobre la textura. De esta forma, sólo puedes usar una textura en cada modelo. Si necesitas usar más texturas puedes combinar varias (y tener mucho cuidado con las coordenadas de cada una) o usar múltiples modelos. La ventaja de esto es que puedes dibujar sencillamente el mismo modelo con diferentes texturas.
Para crear, cargar, guardar y dibujar modelos se usan las siguientes funciones:
d3d_model_create()
Crea un nuevo modelo y devuelve su índice, que debe ser usado en las demás
funciones.
d3d_model_destroy(ind) d3d_model_clear(ind)
Destruye el modelo especificado, liberando memoria.
Limpia el modelo especificado, eliminando todas sus primitivas.
d3d_model_save(ind,fname)
Salva el modelo ind al archivo especificado en fname.
d3d_model_load(ind,fname)
Carga el modelo desde el archivo especificado.
d3d_model_draw(ind,x,y,z,texid)
Dibuja el modelo en la posición (x,y,z). texid es la textura que se
aplicará al modelo. Usa -1 para no usar texturas. Si quieres rotar o escalar el modelo usa las rutinas de transformación descritas en el apartado de Transformaciones.
Para cada primitiva existe una función equivalente que la añade a un modelo. Estas funciones se usan igual que las que ya hemos visto en los capítulos anteriores, sólo que en lugar de especificar una textura ahora daremos el índice del modelo.
d3d_model_primitive_begin(ind,kind) indicado en kind:
Añade al modelo una primitiva 3D del tipo siguiente
pr_pointlist, pr_linelist, pr_linestrip, pr_trianglelist,
pr_trianglestrip o pr_trianglefan. d3d_model_vertex(ind,x,y,z)
Añade el vértice (x,y,z) al modelo.
d3d_model_vertex_color(ind,x,y,z,col,alpha)
Añade el vértice (x,y,z) al modelo con su
propio color y valor de transparencia.
d3d_model_vertex_texture(ind,x,y,z,xtex,ytex)
Añade el vértice (x,y,z) al modelo con la
posición (xtex,ytex) en la textura.
d3d_model_vertex_texture_color(ind,x,y,z,xtex,ytex,col,alpha)
Añade el
vértice (x,y,z) al modelo con valores de textura y color.
d3d_model_vertex_normal(ind,x,y,z,nx,ny,nz)
Añade el vértice (x,y,z) al modelo con el
vector normal (nx,ny,nz).
d3d_model_vertex_normal_color(ind,x,y,z,nx,ny,nz,col,alpha)
Añade el vértice
(x,y,z) al modelo con el vector normal (nx,ny,nz) y sus propios valores de color y transparencia.
d3d_model_vertex_normal_texture(ind,x,y,z,nx,ny,nz,xtex,ytex)
Añade el
vértice (x,y,z) al modelo con el vector normal (nx,ny,nz) y coordenadas de textura (xtex, ytex).
d3d_model_vertex_normal_texture_color(ind,x,y,z,nx,ny,nz,xtex,ytex,col ,alpha)
Añade el vértice (x,y,z) al modelo con el vector normal (nx,ny,nz) con valores de color y textura.
d3d_model_primitive_end(ind)
Finaliza la descripción de la primitiva en el modelo.
Además de primitivas también puedes añadir formas al modelo. Las funciones son análogas a las ya vistas usando el índice del modelo en lugar del de la textura:
d3d_model_block(ind,x1,y1,z1,x2,y2,z2,hrepeat,vrepeat)
Añade un bloque al
modelo.
d3d_model_cylinder(ind,x1,y1,z1,x2,y2,z2,hrepeat,vrepeat,closed,steps) Añade un cilindro al modelo.
d3d_model_cone(ind,x1,y1,z1,x2,y2,z2,hrepeat,vrepeat,closed,steps)
Añade
un cono al modelo.
d3d_model_ellipsoid(ind,x1,y1,z1,x2,y2,z2,hrepeat,vrepeat,steps)
Añade una
elipsoide al modelo.
d3d_model_wall(ind,x1,y1,z1,x2,y2,z2,hrepeat,vrepeat)
Añade una pared al modelo.
d3d_model_floor(ind,x1,y1,z1,x2,y2,z2,hrepeat,vrepeat)
Añade un suelo al modelo.
El uso de modelos puede aumentar considerablemente la velocidad de los gráficos en los juegos 3D, así que deberías usarlo siempre que sea posible
Crear script. Lo primero antes de crear un script hay que entender que es un script. Un script es un trozo de codigo que nos sirve por ejemplo para acortar una accion que usaremos en varios objetos para no tener qu ir todo el rato copiando y pegando el codigo y resumirlo en una sola linea. Por ejemplo en tu script que se llama draw pones: draw_sprite(pers,1,x,y) y cuando quieras aplicarlo en vez de poner draw_sprite pones draw. En este ejemplo no nos sirve de mucho pero mas adelante vereis como si influye. Lo segundo que debemos tener en cuenta es la finalidad de nuestro script antes de empezar es decir debemos saber para que haremos nuestro script. No es lo mismo hacer un script para calcular la hipotenusa de un triangulo (xd quizas me pase un poco) o crear un script para dibujar un rectangulo (demasiado facil). Vamos que si es la primera vez que creas un script no intentes hacer algo dificil. Lo tercero Hay que tener unos conocimientos minimos del gml no se puede usar lo de arrastrar y soltar por que no funciona aqui solo codigo. Los conocimientos de gml van en proporcion a la dificultad de lo que quieras hacer. Volviendo al punto 2 si no tienes muchos conocimientos y quieres hacer algo muy dificil vas a tener que estar preguntando. Lo cuarto debes saber como se aplica un script. Si no sabes a continuacion te lo explico: Puedes usarlo usando arrastrar y soltar es un icono en la pestaña code o poniendo en el codigo (usamos el script que he puesto arriba) draw() lo de los parentesis es para poner los arguments pero eso ya lo explicare. Y la quinta cosa que debes tener en cuenta es en que evento aplicaras el script si lo pondras en step o en create. Esto importa mucho ya que si lo pones en create se hara nada mas se cre el personaje si lo pones en step lo estara haciendo continuamente. Otro ejemplo si lo pones en animation end lo ejecutara cuando se acabe la animacion.
Arrays. Imaginemos que el usuario debe entrar 5 números para hacer un promedio. Podríamos usar las variables num1, num2, num3, num4 y num5, por ejemplo. Pero si el usuario tuviera que introducir 100 valores a lo largo de la partida, la cosa ya sería más difícil. Para ello tenemos las arrays. Una array es una estructura de valores ordenada de forma secuancia. una forma gráfica de expresar un array sería esta:
Como una tabla de una línea que espera valores.Esta puede contener los valores que queramos solo que aquí la he hecho de 5 celdas para ahorrar espacio. A los valores de la array podemos acceder de esta manera: Array[1]=7 Array[2]=672 Array[5]=1 7 672
1
Y podemos modificarlos a nuestro antojo Array[1]-=3 Array[3]=-4 4
-4
1
Vamos a hacer un programa que nos va a pedir 10 valores y seguidamente hará el promedio. Añade un objeto y en su evento de creación añade el código necesario para que nos pida los valores y haga la mediana: numero[1]=get_integer("Introduce numero[2]=get_integer("Introduce numero[3]=get_integer("Introduce numero[4]=get_integer("Introduce numero[5]=get_integer("Introduce
el el el el el
primer numero","") segundo numero","") tercer numero","") quarto numero","") quinto numero","")
numero[6]=get_integer("Introduce el sexto numero","") numero[7]=get_integer("Introduce el septimo numero","") numero[8]=get_integer("Introduce el octavo numero","") numero[9]=get_integer("Introduce el noveno numero","") numero[10]=get_integer("Introduce el decimo numero","") mediana=mean(numero[1],numero[2],numero[3],numero[4],numero [5],numero[6],numero[7],numero[8],numero[9],numero[10]) Ahora ne su evento de dibujo añade el código necesario para que se dibuje la variable de la mediana: draw_text(50,50,string(mediana)) Y "voilá". Aunque viendo este ejemplo no pueda parecer de utilidad, las arrays nos pueden evitar de usar muchas variables y hacernos un lío.
Lectura / Escritura de Archivos INI. Los archivos *.ini son archivos especialmente diseñados para trabajar con los datos organizados en registros y variables. Para empezar, los archivos *.ini están divididos en registros que a su vez estan divididos en variables. Un ini común: [HP] hp = 375 maxhp = 500 [nivel] exp = 5075 nivel = 4 Este ini tiene dos registros, HP y nivel. Cada uno de ellos tiene dos variables. HP tiene las variables hp y maxhp, con los valores 375 y 500, respectivamente. nivel tiene las variables exp y nivel, con los valores 5075 y 4.
Game Maker proporciona funciones para leer y escribir datos fácilmente en los archivos *.ini. Para empezar, hay que abrirlo con: ini_open(fname); Donde fname es un string conteniendo la dirección relativa o absoluta del archivo. Una vez abierto, podemos realizar dos tipos de operaciones con él: Operaciones de escritura: Mediante las funciones de escritura podemos escribir reales o strings. Las funciones son: ini_write_real(key, var, value); ini_write_string(key, var, value); Estas funciones escriben el valor value en la variable var del registro key. Para escribir valores reales recomiendo usar la función de escritura de strings mediante un cast del valor a string mediante la función string(x). Operaciones de lectura: Mediante las funciones de lectura, podemos leer valores del ini. Son: ini_read_real(key, var, def); ini_read_string(key, var, def); Estas funciones devuelven el valor de la variable var del registro key. Si no se puede ejecutar la lectura, devuelven el valor def. Una vez se ha acabado de ejecutar operaciones con el ini, hay que cerrarlo mediante la función: ini_close();
Bucles
Hola a todos, hoy explicaré los bucles. Los bucles son unas sentencias que permiten repetir un determinado conjunto de órdenes tantas veces como queramos, ya sean 1, 3, 7, 10, 57, 94 o infinitas. Hay 3 tipos de bucles: Bucle básico, también conocido como while. Llamado así por la sentencia que lo ejecuta. El bucle while permite ejecutar un conjunto de instrucciones mientras una instrucción se cumple. Se ejecuta así: while (/*cond*/) { //acciones } Mientras la sentencia /*cond*/ devuelva diferente de 0 (true), se ejecutará la(s) sentencia(s) //acciones. Bucle do while. Básicamente es lo mismo que el bucle básico, pero ejecuta las instrucciones antes de comprovar la condición. Para ejecutarlo el código es: do { //acciones }while(/*cond*/); Básicamente la explicación es la misma que el while, pero se ejecuta //acciones antes de comprobar /*cond*/. Bucle for. Es un bucle bastante más avanzado. Incluye una incialización, una condición y una actualización. Se ejecuta mediante la sentencia: for (/*init*/; /*cond*/; /*act*/) { //acciones }
Esto equivaldría a: /*init*/ while (/*cond*/) { //acciones /*act*/ } Este bucle permite ejecutar una serie de acciones (//acciones) mientras se cumpla una condición /*cond*/, que viene inicializada por /*init*/ y se actualiza con /*act*/. Sentencia repeat. Es una sentencia exclusiva del GML que es un "pseudobucle". Se usa así: repeat (/*num*/) { //acciones } Repite /*num*/ veces la(s) accion(es) //acciones. Es considerado un pseudobucle porque no utiliza una condición para regular su uso.
FSM IA (inteligencia Artificial) sobre Inteligencia Artificial. Lo primero que veremos es la arquitectura de máquina de estados finitos. Esta arquitectura es la más usada para programar la IA en los juegos profesionales y muchos otros ámbitos como la rob?tica, los automatismos o la simulación. ¿Qué es una máquina de estados finitos? Una máquina de estados finitos (Finite State Machine, FSM)se compone de las siguientes partes: • Estados • Transiciones • Acciones Así, puede decirse que una FSM es un sistema que tiene varios estados, pasa de uno a otro por unas transiciones y realiza una serie de acciones. Por ejemplo, una bombilla puede tener dos estados:
encendida y apagada. Para pasar de un estado a otro (transición), hay que accionar un interruptor. Por último, la bombilla puede realizar acciones cuando se encuentre en un estado concreto. Por ejemplo, si está encendida realizará la acción de iluminar una habitación. Estados Los estados son el núcleo de las FSMs. Una FSM puede tener tantos estados como definamos, pero solamente podrá estar en uno de ellos a la vez. En el ejemplo anterior de la bombilla, ésta puede estar encendida o apagada, pero no puede estar encendida y apagada a la vez. Es decir, los estados de la bombilla serán encendida y apagada. Transiciones Las transiciones llevan a la FSM de un estado a otro. Para que ocurra una transición, deben cumplirse unas condiciones. Ejemplo: En nuestro RPG hay un minero que tiene 2 estados: buscar oro en la mina y descansar. Cuando está buscando oro el minero se va cansando. Al llegar a un nivel de fatiga, el minero pasará al estado de descanso hasta que recupere las fuerzas. Así, tenemos 2 condiciones: • Si estás cansado->Descansa • Si te has recuperado->Vuelve a buscar oro La primera condición Si estás cansado indica que si estamos en el estado de buscar oro pasemos al estado de descanso. La segunda condición Si te has recuperado indica que si estamos en el estado de descanso volvamos al estado de buscar oro. Observad que cada condición sólo tiene sentido si se aplica cuando el minero está en el estado correcto. Es decir, si el minero está en el estado de descanso no tiene sentido comprobar si está cansado, ya que aunque fuera cierto, no haríamos más que decir al minero que se pusiera en estado de descanso..¡pero el minero ya está en ese estado! Cuando tenemos varios estados, puede que desde un estado podamos pasar a varios estados distintos dependiendo de qué condiciones se cumplan. Para llevar un control sobre esto y no perdernos cuando tengamos más estados y varias transiciones posibles, se suele crear una tabla de transiciones:
Condición\Estado actual A B C 1
C C X
2
X A X
3
X X B
En esta tabla vemos lo siguiente: • Si estamos en el estado A o B y se cumple la condición 1 pasamos al estado C • Si estamos en el estado B y se cumple la condición 2 pasamos al estado A • Si estamos en el estado C y se cumple la condición 3 pasamos al estado B Ejemplo: El minero del ejemplo anterior tiene ahora 3 estados: • Buscar oro • Descansar • Comer El paso de un estado a otro se define con esta tabla de transiciones: Condición\Estado actual Buscar oro Descansar Comer Cansado
Descansar
X
X
Recuperado
X
Buscar oro X
Hambriento
Comer
Comer
X
Saciado
X
X
Buscar oro
Esta tabla nos dice lo siguiente: • Si el minero está en el estado de Buscar oro y está cansado, pasa al estado Descansar. • Si el minero está en el estado de Descansar y se ha recuperado, pasa al estado Buscar oro. • Si el minero está en el estado de Buscar oro o en el de Descansar y está hambriento, pasa al estado Comer. • Si el minero está en el estado de Comer y ya se ha saciado, pasa al estado Buscar oro. Las condiciones que lanzan transiciones de un estado a otro pueden ser tan complejas como queramos. Por ejemplo, podemos decir que un jugador de fútbol sólo tire a portería si tiene el balón, está dentro del área, el portero está en el centro de la portería y no tiene a ningún compañero para pasarle el balón.
Acciones Una acción es la descripción de una actividad que la FSM puede ejecutar en un momento concreto. Lógicamente, las acciones dependerán del estado en el que nos encontremos. Ejemplo: El minero puede realizar la acción encender linterna, si se encuentra buscando oro en la mina y todo está muy oscuro. Es decir, si se encuentra en el estado Buscar oro. Sin embargo esta acción no tendrá sentido si se encuentra en los estados Comer o Descansar. Las acciones se dividen en 4 tipos: • Entrada: son las acciones que se ejecutan al entrar a un estado concreto. • Salida: son las acciones que se ejecutan al salir de un estado concreto. • Estado: son las acciones que se ejecutan regularmente por estar en un estado. • Transción: son las acciones que se ejecutan al pasar de a un estado concreto a otro. Ejemplo: Si el minero está en el estado Buscar oro realizará la acción Cavar para cavar y buscar oro. Esta acción se realizará regularmente, o sea que es una acción de estado. Cuando el minero va a descansar, antes de tumbarse a dormir se quita las botas para estar más cómodo (acción Descalzarse). Cuando está durmiendo ronca (acción Roncar) y cuando se despierta se vuelve a poner las botas sólo si va a volver a buscar más oro (acción Calzarse). Al despertarse, el minero siempre soltará un gran bostezo (acción Bostezar). Así: • La acción Descalzarse es una acción de entrada, ya que la ejecutaremos siempre una vez al entrar en el estado Descansar. • La acción Roncares una acción de estado, ya que la ejecutaremos repetidamente mientras estemos en el estado Descansar. • La acción Calzarse es una acción de transición, ya que sólo la ejecutaremos una vez si pasamos del estado Descansar al estado Buscar oro.
• La acción Bostezares una acción de salida, ya que la ejecutaremos siempre una vez al salir del estado Descansar.
Estados globales Nuestra FSM puede tener unos estados que sean capaces de interrumpir los demás y ejecutarse inmediatamente. Normalmente son estados que realizan una acción y luego vuelven al estado que se estaba ejecutando. Ejemplo: Nuestro minero acaba de ganar un nuevo estado global: Ir al baño. Este estado tiene prioridad absoluta, cuando el minero tenga ganas de ir al baño (es decir, cuando se cumpla la condición que lanza la transición a este estado, por ejemplo vejiga llena) dejará lo que está haciendo e irá al baño de inmediato. Después, volverá a lo que estaba haciendo, como si nada hubiera pasado. Para hacer esto posible, necesitaremos que el sistema recuerde el estado en el que se encontraba antes de entrar al estado global, para así volver a ese estado una vez que finalize la ejecución del estado global. FSMs dentro de FSMs Las FSMs son herramientas muy potentes, pero conforme crezca la complejidad del sistema será más difícil entender cómo funciona el sistema. Para simplificar esto, podemos tener FSMs dentro de otras FSMs. Por ejemplo, en el juego Unreal 2 hay una FSM general para controlar a cada enemigo. Pero además dentro de cada estado existen otras FSMs para realizar tareas específicas, como defender una zona de ataques enemigos o explorar el nivel. Consideraciones finales Las máquinas de estados finitos son muy utilizadas pese a ser un sistema algo viejo. Las razones principales son las siguientes: • Son rápidas y fáciles de implementar. • Son fáciles de depurar para buscar errores • Usan poco tiempo de procesamiento, debido a su sencillez • Son intuitivas y faciles de comprender por la mente humana • Son flexibles, se pueden expandir facilmente y se acoplan muy bien a otros métodos (redes neuronales, algoritmos genéticos...) para crear sistemas híbridos Los juegos profesionales utlizan FSMs en todo momento,
combinándolas además con otros métodos para tareas que requieran un nivel de inteligencia más desarrollado. Las FSMs se basan en reglas simples (condiciones if), por ello se suele decir que el sistema realmente no "piensa". Sin embargo, una FSM correctamente desarrollada y detallada puede dar esa impresión. Su gran rapidez hace que sean perfectas para tareas sencillas y repetitivas en las que no se necesite un alto nivel de inteligencia, como por ejemplo buscar caminos, realizar tareas repetitivas...Además, para las tareas que requieran inteligencia más avanzada se pueden integrar perfectamente con otros métodos. Un ejemplo clarísimo es el juego Los Sims, en el que prácticamente todo el comportamiento de los personajes se ha programado con FSMs. Hasta aquí la introducción a las máquinas de estados finitos. En la pr?xima entrega veremos cómo implementar una FSM en GML. Para finalizar veremos cómo hacer un ejemplo completo de un topdown shooter (juego de disparos visto desde arriba). Con el fín de hacer el ejemplo lo más sencillo posible usaremos GM6 registrado para poder usar las funciones para calcular paths en laberintos. De todas formas, a parte de esto el ejemplo será igual para GM6 sin registrar o GM5. Descripción general Vamos a hacer un juego de disparos visto desde arriba en el que el jugador puede moverse por un mapa enorme buscando la salida mientras se enfrenta a multitud de enemigos. Será una especie de Quake pero visto desde arriba. Antes de nada, vamos a crear el escenario. Paredes Creamos un objeto y lo llamamos objPared. Creamos un sprite que será un cuadrado de 32x32 de color negro y no transparente y se lo asignamos a este objeto. Bien, ya estamos listos para definir al jugador. Jugador El jugador será una máquina de estados finitos. Hasta ahora habíamos visto cómo usar la arquitectura de las FSM en personajes controlados por el ordenador. Sin embargo ahora vamos a usar una
FSM para un personaje controlado por el jugador. Recordad que las FSM se rigen por condiciones que hacen que el personaje pase de un estado a otro. Primero, debemos definir sus características. El jugador tendrá 2 estados: • Vivo: el estado normal del jugador, en el que podrá moverse y disparar. • Muerto: el jugador se quedará muerto en el campo de batalla y lógicamente no podrá disparar ni moverse. Pasado un tiempo, volverá al estado Vivo perdiendo una vida. Necesitaremos 2 características para controlar estos estados: • Salud: la salud que tiene el jugador. • Vidas: número de vidas que tiene el jugador. • Tiempo de muerte: tiempo que el jugador permanece en el estado Muerto antes de volver al estado Vivo. La tabla de transiciones del jugador será bastante sencilla: Condición\Estado actual
Vivo
Salud<=0
Muerto X
Vidas>0 & Tiempo de muerte=0 X
Muerto Vivo
Viendo esta tabla sabemos que: • Si el jugador está en el estado Vivo y su salud se agota (Salud<=0) pasa al estado Muerto. • Si el jugador está en el estado Muerto y ya ha cumplido su tiempo de muerte (Tiempo de muerte=0) y todavía le quedan vidas (Vidas>0) pasa al estado Vivo. Bueno, ya podemos empezar a programar el jugador. Creamos un objeto y lo llamamos objPlayer. Creamos un sprite y lo llamamos sprPlayerVivo. Este sprite será un círculo azul de tamaño 32x32 y centro en (16,16). Copiamos el sprite, le cambiamos el color a negro y lo llamamos sprPlayerMuerto. Ahora volvemos al sprite sprPlayerVivo y lo editamos, dibujando una línea amarilla horizontal desde el punto (16,16) hasta el punto (31,16). Esto nos servirá para poder ver la dirección en la que mira el jugador en todo momento. En el Evento Create del objeto especificamos las características de la FSM:
/*CARACTERISTICAS DE LA FSM*/ estado = "Vivo"; execute_string(estado+"(0)");
Y a continuación declaramos las características del jugador:
/*CARACTERISTICAS DEL JUGADOR*/ vidas = 5; saludMax = 100; salud = saludMax; tiempoMuerteMax = 150; tiempoMuerte = tiempoMuerteMax;
En el Evento Step como siempre, definimos el funcionamiento de la FSM:
execute_string(estado+"(1)"); image_angle = direction;//rotar el sprite (s?lo GM6)
Ahora vamos a definir los estados. Empezamos por el estado Muerto:
switch argument0{ case 0://Entrar //Aquí podemos hacer que el personaje grite, o crear una explosión... tiempoMuerte = tiempoMuerteMax; sprite_index = sprPlayerMuerto; break; case 1://Ejecutar tiempoMuerte -= 1; if (tiempoMuerte = 0){ if (vidas > 0){ vidas -= 1; salud = saludMax;
estado = "Vivo"; execute_string(estado+"(0)"); }else game_end(); } break; case 2://Salir break; case 3://Transición break; }
Ahora creamos el estado Vivo:
switch argument0{ case 0://Entrar sprite_index = sprPlayerVivo; break; case 1://Ejecutar if (salud <= 0){ estado = "Muerto"; execute_string(estado+"(0)"); exit; } scrAndar(keyboard_check(vk_up), keyboard_check(vk_down), keyboard_check(vk_left), keyboard_check(vk_right), velocidad); scrDisparar(keyboard_check(ord('C'))); break; case 2://Salir break; case 3://Transición break; }
En este estado, el jugador realizará 2 acciones, Disparar y Andar. Creamos un script y lo llamamos scrAndar:
//scrAndar(avanzar, retroceder, izquierda, derecha, velocidad) var avanzar, retroceder, izquierda, derecha, vel; avanzar = argument0; retroceder = argument1; izquierda = argument2; derecha = argument3; vel = argument4; if (izquierda) direction += 5; else if (derecha) direction -= 5; if (avanzar) retroceder = false; if ((avanzar)||(retroceder)){ var i; for (i=0;i<=vel;i+=1){ if (place_meeting(x+(i+1)*cos(degtorad(direction+retroceder*180)), y-(i+1)*sin(degtorad(direction+retroceder*180)), objPared)){ x += i*cos(degtorad(direction+retroceder*180)); y -= i*sin(degtorad(direction+retroceder*180)); break; }else if (i = vel){ x += vel*cos(degtorad(direction+retroceder*180)); y -= vel*sin(degtorad(direction+retroceder*180)); } } }
Este script es bastante sencillo: se chequean las teclas de entrada y en función de ellas se calcula la dirección y se mueve al personaje si es que no choca contra una pared. Observad que las teclas se pasan como argumento al script (scrAndar(keyboard_check(vk_up), keyboard_check(vk_down), keyboard_check(vk_left), keyboard_check(vk_right), velocidad);) con lo que este script servirá para mover al personaje con cualquier combinación de teclas que queréis. Podéis incluso usarlo para un segundo jugador que utilice las teclas WASD: scrAndar(keyboard_check(ord('W')),
keyboard_check(ord('S')),keyboard_check(ord('A')),keyboard_check( ord('D')), velocidad); Sólo tenéis que fijaros en el orden en el que se ponen las teclas en el script (avanzar-retroceder-izquierda-derecha). Si os habéis fijado, el script toma comom argumento una variable llamada velocidad. Podríamos hacer que esta variable fuera local del script (var) y definirla ahí mismo, pero resulta mejor hacer que la velocidad sea una característica del jugador, así podremos hacer que jugadores con distintas velocidades puedan usar este mismo script. Para ello, volvemos al Evento Create y ponemos: velocidad = 4; El siguiente paso es crear el script scrDisparar: //scrDisparar(disparar) if (tiempoDisparo = 0){ if (argument0){ with (instance_create(x, y ,objBala)){ speed = 8; direction = direction; parent = id; } tiempoDisparo = tiempoDisparoMax; } }else tiempoDisparo -=1;
Ahora debemos definir la variable tiempDisparo que dirá el tiempo entre dos disparos sucesivos. Vamos de nuevo al Evento Create de objPlayer y ponemos: tiempoDisparoMax = 15; tiempoDisparo = 0;
Listo, este script crea una bala que se mueve a una velocidad de 8 píxeles por step en la dirección en la que mire la instancia que llame al script. Además, le asignamos la id de la instancia a la variable
parent de la bala recién creada. Esto nos servirá para poder sabe luego quién ha disparado cada bala. Ya hemos terminado con el jugador, ha sido sencillo, no? ;) Balas Lógicamente, para poder disparar necesitamos crear un objeto y llamarlo objBala. Además, creamos un sprite que será un círculo rojo de 5x5 y con el centro en (2,2). En este nuevo objeto, vamos al Evento de Colisión con el objeto objPared y ponemos el siguiente código: instance_destroy();
También añadimos el Evento de Colisión con el objeto objPlayer y añadimos lo siguiente: if ((parent != other.id)&&(other.estado != "Muerto")){ other.salud -=1; instance_destroy(); }
Lo que hacemos es ver si la bala está chocando con el jugador y el juagdor no ha sido quien la ha disparado ni está muerto. Entonces, disminuimos la salud del jugador. Este mismo código nos servirá más adelante. Listo! Ya podemos crear un room, poner una instancia de objPlayer, unas cuantas paredes y darnos una vuelta disparando :D Enemigos (I) Por fín hemos llegado al meollo del asunto: los enemigos. Como siempre, empezamos definiendo sus estados: • Atacar: en este estado el enemigo se acercará al jugador hasta una distancia en la que empezará a disparar. Si su salud es menor que la mitad de la salud del personaje mandará un mensaje por radio a la instancia más cercana para que venga a ayudarle. • Huir: Si su salud es menor que la cuarta parte de la salud del jugador intentará huir a un lugar seguro siempre que no está solo. • Patrullar: el enemigo simplemente irá caminando por el laberinto.
• Ayudar: cuando reciba una petici?n de ayuda irá corriendo a socorrer a su compañero en apuros. • EnemigoMuerto: en esta ocasión será un estado global que podrá interrumpir todos los demás. Para controlar estos estados necesitaremos las siguientes características: • Salud. • Jugador visible: si puede ver al jugador. • Solo: indica si el enemigo se encuentra solo o va acompañado por más enemigos. Ya podemos crear la tabla de transiciones: Condición\Est ado actual
Ataca Huir r
Patrull Ayuda **EnemigoMuert ar r o**
Salud < salud del jugador/4 & Solo
Huir
X
X
X
X
Atacar
Atacar X
X
X
X
Jugador visible X
X
Jugador no visible
Patrull X ar
Compañero cerca
X
Patrull X ar
X
X
Mensaje "Ayuda"
X
X
Ayudar
X
X
Recibir balazo
X
X
Atacar
X
X
Salud <= 0
EMuert EMuert EMuert EMuerto X o o o
Bueno, ya podemos empezar a definir el enemigo. Creamos el objeto objEnemy y definimos sus características: /*CARACTERISTICAS DE LA FSM*/ estado = "Patrullar"; estado_global = ""; mensaje = -1; /*CARACTERISTICAS DEL ENEMIGO*/ saludMax = 100; salud = saludMax; recibirBalazo = false;
/*PROPIEDADES*/ velocidadNormal = 4; velocidadRapida = 6; tiempoDisparoMax = 15; tiempoDisparo = 0;
Observad que no hemos definido las características solo ni jugadorVisible. Esto es debido a que estas características necesitan de varias operaciones para chequear si son verdaderas o falsas, por ello es mejor realizar unos scripts que devuelvan true o false y así podremos usarlos como si se trataran de variables. Dicho, esto, comenzamos por definir el script jugadorVisible. Este script verá si el enemigo puede ver al jugador, lanzando una línea recta y viendo si intersecta con alguna pared. Si esta línea no se cruza con nada, significará que podemos ver al jugador. Además, tendremos en cuenta hacia dónde está mirando el enemigo. Es decir, el enemigo no podr? ver al jugador si está de espaldas a éste: //scrJugadorVisible() if (instance_exists(objPlayer)){ var target; target = instance_nearest(x, y, objPlayer); if ((collision_line(x, y, target.x, target.y, objPared, false, true) < 0)&& (abs(point_direction(x, y, target.x, target.y)-direction) <= 60)) return true; else return false; }else return false;
Ahora creamos el script scrSolo que servirá para ver si el enemigo está sólo o tiene algún compañero cerca en estado de ataque o de ayuda:
//scrSolo() var i, enemigo; for (i=0;i
Ya podemos definir el funcionamiento de la FSM. En el Evento step
ponemos lo de siempre (atención que esta vez sí tenemos un estado global, el estado EnemigoMuerto): if (estado_global != "") execute_string(estado_global+"(1)"); else{ if (salud <= 0){ estado_global = "EnemigoMuerto"; execute_string(estado_global+"(0)"); }else execute_string(estado+"(1)"); } image_angle = direction;
Ya podemos empezar a definir los estados. Empezamos por el estado Patrullar:
switch argument0{ case 0://Entrar break; case 1://Ejecutar if ((scrJugadorVisible())||(recibirBalazo)){ recibirBalazo = false; direction = point_direction(x, y, objPlayer.x, objPlayer.y); estado = "Atacar"; }else if (mensaje = 0){ estado = "Ayudar"; execute_string(estado+"(0)"); }else scrVagar(); break; case 2://Salir break; case 3://Transición break;
}
En ausencia de condiciones el enemigo se dedicará a vagar por el escenario. Definimos esta acción en el script scrVagar: //scrVagar() if (place_meeting(x+2*velocidadNormal*cos(degtorad(direction)), y2*velocidadNormal*sin(degtorad(direction)), objPared)){ var i; for (i=-1;i<=1;i+=2){ if (!place_meeting(x+2*velocidadNormal*cos(degtorad(direction+i*90) ), y-2*velocidadNormal*sin(degtorad(direction+i*90)), objPared)){ direction += i*90; break; }else if (i = 1) direction += 180; } } x += velocidadNormal*cos(degtorad(direction)); y -= velocidadNormal*sin(degtorad(direction));
Este script hace que el enemigo se mueva en la dirección en la que va. Si ve que siguiendo en esa dirección chocará con una pared, intenta moverse en una dirección perpendicular. Si aún así choca con otra pared, se mueve en la otra dirección perpendicular. Si vuelve a chocar, se da la vuelta y vuelve por donde había venido. Observad que para realizar los chequeos usamos 2*velocidadNormal. De esta forma podemos hacer que el enemigo "vea el futuro" y podrá moverse de una manera más realista, ya que si usáramos el valor velocidadNormal tendría menos tiempo de reacción y siempre iría pegado a las paredes. Para saber si hemos recibido un balazo nos serviremos del objeto objBala. En él, creamos el Evento de colisión con el objeto objEnemy y ponemos lo siguiente:
if ((parent.object_index != other.object_index)&&(other.estado_global = "")){ other.salud -=1; other.recibirBalazo = true; instance_destroy(); }
Como véis, es casi igual que el del jugador. Ahora definimos el estado Atacar:
switch argument0{ case 0://Entrar break; case 1://Ejecutar if (!scrJugadorVisible()) estado = "Patrullar"; else if ((scrDebil(4))&&(scrSolo())){ estado = "Huir"; execute_string(estado+"(0)"); }else{ if (!scrAcercarse(objPlayer)) scrPelear(); if ((scrDebil(2))&&(scrSolo())) scrEnviarMensaje(scrNearestFriend()); } break; case 2://Salir break; case 3://Transición break; }
El script scrDebil se encargará de chequear si el enemigo tiene
menos de la energía indicada del jugador: //scrDebil(energia) if (instance_exists(objPlayer)){ if (salud < objPlayer.salud/argument0) return true; } return false;
El script scrNearestFriend devuelve la id del compañero más cercano que no está muerto: var i, dist_min, nearest; dist_min = room_width; nearest = 0; for (i=0; i var friend, dist; friend = instance_id[i]; if ((friend.object_index = object_index)&&(friend.id != id)){ if (friend.estado_global = ""){ dist = distance_to_object(friend); if (dist nearest = friend; } } } } return nearest;
Por último, el script scrAcercarse se encarga de acercarse al personaje hasta la distancia de ataque. Esto puede resultar bastante complejo ya que muchas veces el enemigo no podrá ir directamente hasta donde se encuentre el jugador, ya que puede que haya paredes entre ellos. Para solucionar este problema vamos a usar las potentes funciones de planificaci?n de movimientos que incorpora GM. Con el fín de reducir el uso de memoria y aumentar la velocidad del juego, vamos a crear un objeto controlador para esto. Navegación Creamos un objeto y lo llamamos objControlador. Este objeto se encargará de calcular los caminos libres que seguirán los enemigos. Su funcionamiento será el siguiente: en un momento determinado un enemigo le pedirá que calcule un camino para ir de un lugar a otro.
Este objeto calculará el camino y lo devolverá como una path asociada a la instancia que le hizo la petición. Ahora, la instancia sólo tiene que seguir ese path y llegar a su destino de forma segura. Para realizar todo esto vamos a usar la versión de GM del famoso algoritmo A*. En el evento de creación del objeto ponemos: var width, height; width = sprite_get_width(sprEnemyVivo); height = sprite_get_height(sprEnemyVivo); rejilla = mp_grid_create(0, 0, floor(room_width/width),floor(room_height/height), width, height); mp_grid_add_instances(rejilla, objPared, false); Con esto creamos una rejilla que divide el room en celdas del tamaño de los enemigos y marcamos las celdas ocupadas por una pared como prohibidas. Si ponéis este código en el Evento Draw de este objeto podréis ver la rejilla con las celdas marcadas, que coincidirán con donde habéis puesto las paredes:
mp_grid_draw(rejilla);
Ahora, cuando desde un enemigo queramos pedirle que calcule un camino desde la posición actual hasta la posición del jugador tendremos que hacer: with (objControlador) mp_grid_path(rejilla, other.path, other.x, other.y, objPlayer.x, objPlayer.y, true);
Esta función crea un path y lo guarda en other.path, e decir, en la variable path de la instancia de objEnemy que la llamó. Por ello, tenemos que crear esta variable en objEnemy:
path = path_add();//creamos un path vac?o
Ahora ya podemos crear el script scrAcercarse: //scrAcercarse(objetivo) if (instance_exists(argument0)){ if (distance_to_object(argument0) > 100 ){ if (path_index != path){ with (objControlador) mp_grid_path(rejilla, other.path, other.x, other.y, argument0.x, argument0.y, true); path_start(path, velocidadRapida, 0, true); }else if (path_position = 1) path_end(); return true; }else path_end(); } return false;
El script devuelve false si el enemigo está suficientemente cerca como para atacar y true si todavía debe acercarse (además calcula un path y se lo asigna al enemigo). El siguiente script que necesitamos es scrPelear:
direction = point_direction(x, y, objPlayer.x, objPlayer.y ); var i; if (esquiva = 0) { giro *= -1; esquiva = esquivaMax; }else esquiva -= 1; if (place_meeting(x+2*velocidadNormal*cos(degtorad(direction+giro)), y-2*velocidadNormal*sin(degtorad(direction+giro)),objPared)) esquiva = 0; else{
x += velocidadNormal*cos(degtorad(direction+giro)); y -= velocidadNormal*sin(degtorad(direction+giro)); } scrDisparar(true);
El enemigo apunta al jugador y comienza a disparar mientras se va moviendo alrededor de él y cambiando de dirección. Para poder manejar este script necesitamos declarar las variables siguientes:
giro = 90; tiempoGiroMax = 150; tiempoGiro = tiempoGiroMax;
Enemigos (II) El siguiente estado es Huir. En este estado el enemigo intentará huir hasta la posición del compañero más cercano:
switch argument0{ case 0://Entrar break; case 1://Ejecutar if (!scrAcercarse(scrNearestFriend())) estado = "Patrullar"; break; case 2://Salir break; case 3://Transici?n break; }
Listo!
Ahora creamos el estado global EnemigoMuerto: switch argument0 { case 0://Entrar sprite_index = sprEnemyMuerto; break; case 1://Ejecutar break; case 2://Salir break; case 3://Transición break; }
Podemos hacer una copia del sprite sprEnemyVivo, oscurecerlo un poco y guardarlo como sprEnemyMuerto. Ya hemos llegado al último estado, Ayudar. En este estado el enemigo se dirige hasta el compañero que le pidió ayuda para socorrerle. Pero antes de crearlo, vamos a hacer primero el sistema de mensajes. Sistema de mensajes El sistema de mensajes estará controlado por el objeto objControlador que ya habíamos creado. En su Evento Create definimos el sistema:
mensajes = ds_queue_create(); mensaje_id=0 mensaje_remitente=ds_map_create(); mensaje_destino=ds_map_create();
En esta ocasión sólo tenemos un tipo de mensaje. Adem?s, no vamos a mostrar el texto así que no necesitamos inclu?rlo. Y como todos los
mensajes van a tener la misma prioridad podemos usar una cola normal y corriente. Creamos ahora nuestro script para enviar mensajes, scrEnviarMensaje
//scrEnviarMensaje(Destino) var Id, Remitente, Destino; Id = objControlador.mensaje_id; Remitente = id; Destino = argument0; ds_queue_enqueue(objControlador.mensajes, Id); //Añadir las características asociadas al mensaje a los mapas de memoria ds_map_add(objControlador.mensaje_remitente, Id, Remitente); ds_map_add(objControlador.mensaje_destino, Id, Destino); //aumentar el identificador del mensaje objControlador.mensaje_id +=1;
Igual que hicimos en el tutorial anterior, el objeto controlador se encargará de leer los mensajes con el script scrRecibirMensaje:
if !ds_queue_empty(mensajes){ var Id, Remitente, Destino; Id = ds_queue_dequeue(mensajes); Remitente = ds_map_find_value(mensaje_remitente,Id); ds_map_delete(mensaje_remitente,Id); Destino = ds_map_find_value(mensaje_destino,Id); ds_map_delete(mensaje_destino,Id); Destino.mensaje = 0; Destino.remitente = Remitente; }
En este caso, guardamos el remitente en una variable de la instancia a la que mandamos el mensaje (Destino.remitente = Remitente;). Por ello, debemos definir esta variable en objEnemy:
remitente = 0;
En el Event Step de objControlador indicamos que lea los mensajes:
scrRecibirMensaje();
Ahora ya podemos crear el último estado, Ayudar:
switch argument0{ case 0://Entrar mensaje = -1; break; case 1://Ejecutar if (!scrAcercarse(remitente)){ remitente = 0; estado = "Patrullar"; } break; case 2://Salir break; case 3://Transición break; }
Ya hemos terminado con la IA! :D Creando un nivel Lo único que nos falta es crear un nivel y ponernos a jugar. Creamos una room y en Creation code inicializamos el room, creando el objeto controlador, el objeto del jugador, inicializando las propiedades de la vista y rodeando el nivel de paredes:
var w, h; w = sprite_get_width(sprPared); h = sprite_get_height(sprPared); instance_create(0, 0, objControlador); var i; for (i=0;i instance_create(i, 0, objPared); instance_create(i, room_height-h, objPared); } for (i=0;i instance_create(0, i, objPared); instance_create(room_width-w, i, objPared); } instance_create(2*w, room_height-2*h, objPlayer); view_hborder[0] = ((view_wview[0]sprite_get_width(sprPlayerVivo))/2) -1; view_vborder[0] = ((view_hview[0]sprite_get_height(sprPlayerVivo))/2) -1;
En la pestaña de las views tendremos que activar la View 0 y hacerla visible al comienzo del room. Ahora desde el editor de rooms añadimos unas cuantas paredes para crear un nivel laberíntico con algunas zonas despejadas para pelear y ponemos unos cuantos enemigos. Ejecutamos el juego e intentamos sobrevivir :D Conclusiones Hemos creado un Quake sencillo en 2D basándonos en la arquitectura de FSM. Hemos usado un montón de scripts, de forma que los estados de las entidades que tenían más o menos esta forma:
switch estado{ case 0: break; case 1:
if (condicion1){ estado = "NuevoEstado1"; }else if (condicion2){ estado = "NuevoEstado2"; }else{ //acciones } break; }
De esta forma la IA se puede programar muy sencillamanete ya que básicamente se reducen a una serie de if(){}else if(){}else if (){}.... Esto además hace que sea tremendamente sencillo añadir nuevos estados o modificar la lógica de los ya presentes. En juegos como Unreal o Quake se usa este mismo sistema para programar la IA de los bots (los enemigos). Pero además, utilizan otros métodos para hacer que los bots "aprendan" e intenten sorprender al jugador en cada partida. Para ello, se modifica la lógica de los estados usando métodos como los algoritmos genéticos, las redes neuronales o las secuencias de reglas SOAX. En próximos tutoriales veremos cada una de estas técnicas y aprenderemos a implementarlas para crear personajes verdaderamente inteligentes. correojon, 27 de Mayo de 2006
OCT
22
2007
IA: FSM III
by Fenris78 | 159 Views | 0 Comments | Rating: (0 rates)
En esta nueva entrega del curso de FSMs continuaremos con el ejemplo anterior y lo haremos un poco más complejo introduciendo la capacidad de enviar y recibir mensajes. Para empezar, añadiremos la Central de Policía, capaz de comunicarse con el policía. Descripción La Central de Policía podrá enviar diferentes tipos de mensajes:
• "Atraco en el banco": un aviso que hará que el policía deje lo que está haciendo y vaya al banco a parar el atraco. • "Información sobre un criminal": con este mensaje el policía recibirá un aviso para ir a la comisaría y recibir información sobre un criminal, lo que le facilitará su captura. El Policía sólo recibirá este mensaje si se encuentra patrullando. Además, el policía podrá enviar mensajes a la Central: • "Mandar una unidad": si el policía está ocupado y presencia un delito, enviará un mensaje a la Central para que manden una unidad a hacerse cargo. El policía tendrá ahora 2 nuevos estados, IrAlBancoYDetenerAtraco e IrALaCentralYRecibirInfo. Su tabla de transiciones será ahora:
Condición\Es BuscarY Entregar IrAlBAr IrAlBan IrALaCe *IrACa tado Atrapar Criminal YBeber co ntral sa* Agresividad>= Entregar X Agresividad Criminal criminal
X
X
X
X
Detenciones= Detenciones X requeridas
IrAlBarY X Beber
X
X
X
Detenciones
X
BuscarY X Atrapar
X
X
X
Mensaje "Atraco!"
IrAlBanc IrAlBanc IrAlBan X o o co
IrAlBan X co
Mensaje "Informaci?n"
IrALaCe X ntral
X
X
X
X
X
BuscarY X Atrapar
Agresividad>= X 7
X
X
Cansancio>= Cansancio_ma IrACasa x
IrACasa
IrACas IrACasa IrACasa X a
Cansancio=0 & Detenciones> X =Detenciones requeridas
X
X
X
Atraco
X
X
IrAlBarY X
X
X
BuscarY Atrapar X
detenido Atraco no detenido
Beber X
X
X
BuscarY X Atrapar
X
Recordad que el estado IrACasaYDormir es un estado global y que al salir de él se puede volver al estado anterior que se estaba ejecutando. He tenido que acortar los nombres de los estados para que la tabla no saliera demasiado grande. Recordad que los estados del policía eran: • BuscarYAtraparCriminales • EntregarCriminal • IrAlBaryBeber • **IrACasaYDormir** • IrAlBancoYDetenerAtraco • IrALaCentralYRecibirInfo Para que el policía responda cuando recibe un mensaje, añadimos una nueva característica en su Evento Create junto con las que ya habíamos definido en el tutorial anterior: [code] mensaje = -1; [/code] Esta variable tomará distintos valores dependiendo del mensaje que reciba el policía, así podremos chequear qué valor tiene y actuar en consecuencia. Bueno, es hora de ver cómo haremos el sistema de mensajes. Sistema de mensajes Antes de definir el sistema de mensajería, hay que describir las características que tendrán los mensajes en sí: • Id: identificador del mensaje. • Remitente: quien lo envía (id de la instancia que envía el mensaje). • Destino: a quién va dirigido (id de la instancia que recibirá el mensaje). • Tipo: tipo de mensaje (0=Aviso de atraco, 1=Recibir información, 2=Avisar de un delito"). Los campos Remitente y Destino son muy importantes. Usando las
ids de las instancias podremos decir exactamente a qui?n va dirigido el mensaje. Además, si en nuestro juego existe más de un policía o más de una central será imperativo determinar a quién queremos enviarle el mensaje. Usando la variable id de GM podemos hacerlo sin mayores complicaciones. Para el sistema de mensajes vamos a utilizar una cola de prioridad. Una cola de prioridad es una estructura de datos de GM que permite introducir valores asignándoles una prioridad. Así, podemos poner una prioridad alta a los mensajes importantes para que sean atendidos antes que los mensajes menos importantes. Además, para manejar todo el sistema de mensajes usaremos un objeto controlador. Creamos un nuevo objeto y lo llamamos obj_ControlMensajes. En su Evento Create ponemos el siguiente código: [code] mensajes = ds_priority_create(); mensaje_id=0; [/code] Con esto creamos nuestra lista de prioridad e inicializamos la variable mensaje_id que nos servirá para ir asignando a cada mensaje un identificador único. Así podremos distinguir los mensajes entre sí. Para guardar las características de los mensajes usaremos mapas de memoria, que son otra estructura de datos de GM. Los mapas nos permiten añadir un valor y asociarlo a una clave. Así, podremos guardar las características de cada mensaje y asociarlas al identificador del mensaje para poder acceder a ellas cuando las necesitemos. Añadimos lo siguiente: [code] mensaje_remitente=ds_map_create(); mensaje_destino=ds_map_create(); mensaje_tipo=ds_map_create(); [/code] Por último creamos un vector que guardará el texto del mensaje. Los mensajes podran ser de 3 tipos (0, 1 ? 2), así: [code] mensaje_texto[0] = "Se esta produciendo un atraco en el banco!"; mensaje_texto[1] = "Dirigete a la central para recibir informacion"; mensaje_texto[2] = "Se esta produciendo un delito, enviad una unidad!"; [/code]
Puede que os parezca que utilizar un objeto para esto es un gasto inútil, ya que podría hacerse con variables globales y que cada instancia se encargara de sus mensajes. Sin embargo si lo hacemos de esa forma tendremos muchos problemas cuando tengamos varias instancias, ya que todas ellas estarán accediendo a la vez a los mismos recursos (la lista de prioridad y los mapas de memoria), con lo que facilmente podremos provocar errores. De esta otra forma, con un objeto controlador, dejamos todo el sistema de mensajes en sus manos con lo que será muy difícil que algo vaya mal. Además así será mucho más facil incluir nuevos objetos que puedan enviar y recibir mensajes (veréis esto de froma mucho más evidente segun avance el tutorial). Enviar mensajes Con todo esto, ya podemos crear el script enviarMensaje que servirá para (quién lo diría? :P) enviar mensajes. Este script será ejecutado directamente por las instancias del policía y la central: [code] //enviarMensaje(Destino, Tipo) var Id, Remitente, Destino, Tipo; Id = obj_ControlMensajes.mensaje_id; Remitente = id; Destino = argument0; Tipo = argument1; var prioridad; switch Tipo{//Calcular la prioridad del mensaje dependiendo del tipo case 0: prioridad = 3; break; case 1: prioridad = 1; break; case 2: prioridad = 2; break; } /Añadir el mensaje a la cola de prioridad ds_priority_add(obj_ControlMensajes.mensajes, Id, prioridad); //Añadir las caracter?sticas asociadas al mensaje a los mapas de
memoria ds_map_add(obj_ControlMensajes.mensaje_remitente, Id, Remitente); ds_map_add(obj_ControlMensajes.mensaje_destino, Id, Destino); ds_map_add(obj_ControlMensajes.mensaje_tipo, Id, Tipo); //aumentar el identificador del mensaje obj_ControlMensajes.mensaje_id +=1; //Mostrar el mensaje show_message("Mensaje enviado:#"+obj_ControlMensajes.mensaje_texto[Tipo]); [/code] Observad que para enviar un mensaje sólo necesitamos pasarle 2 argumentos al script: Destino y Tipo. Esto es debido a que el valor de la Id del mensaje se coge de la variable obj_ControlMensajes.mensaje_id y por tanto no hace falta pasársela como argumento al script ya que éste podrá acceder a ella directamente. Como sólo hará 1 única instancia del objeto obj_ControlMensajes no habrá lugar a errores. A su vez, el Remitente será la id de la instancia que envía el mensaje, es decir, la id de la instancia que ejecute el script enviar_mensaje(). Por lo tanto, esta variable también será accesible directamente desde el script. El siguiente paso será (cómo no? :P) crear el sistema para recibir y leer los mensajes. Recibir y leer mensajes El objeto obj_ControlMensajes se encargará de chequear la cola de prioridad de mensajes para ver si hay mensajes nuevos. Si es así, cogerá el mensaje con mayor prioridad y lo enviará al destino adecuado. Creamos un script llamado recibirMensaje y ponemos lo siguiente: [code] if !ds_priority_empty(mensajes){//Si hay mensajes en la cola var Id, Remitente, Destino, Tipo; //encontrar la id del mensaje con más prioridad y borrarlo de la cola Id = ds_priority_delete_max(mensajes); //obtener las caracter?sticas del mensaje y borrarlas de los mapas
Remitente = ds_map_find_value(mensaje_remitente,Id); ds_map_delete(mensaje_remitente,Id); Destino = ds_map_find_value(mensaje_destino,Id); ds_map_delete(mensaje_destino,Id); Tipo = ds_map_find_value(mensaje_tipo,Id); ds_map_delete(mensaje_tipo,Id); //Notificar al remitente de que tiene un mensaje Destino.mensaje = Tipo; //Mostrar el mensaje show_message("Mensaje recibido:#"+mensaje_texto[Tipo]); } [/code] Es muy importante llevar un control estricto de los mensajes, eliminándolos cuando los leemos y borrando sus valores de todos los mapas de memoria para ahorrar memoria y evitar errores. Observad que en este caso concreto no hemos usado para nada el valor del Remitente. Sin embargo me ha parecido interesante incluirlo para que veáis cómo se pueden manejar los datos a través del mensaje. Además, como el valor de Remitente es la id de la instancia que envió el mensaje, podéis usarlo para acceder a cualquier característica de esta instancia. Por ejemplo, podrás modificar el código para mostrar el mensaje y que incluyera el nombre de la instancia que lo envía. Por ejemplo, si la Central tiene una característica que es nombre e indica el nombre de la comisaría, podríais mostrarlo haciendo: [code] show_message("Mensaje recibido de "+Remitente.nombre+":#"+mensaje_texto[Tipo]); [/code] Y así el mensaje que se mostraría en pantalla podría ser algo como: [quote="Mensaje recibido"] Mensaje recibido de Comisaría del Norte: Se esta produciendo un atraco en el banco!! [/quote] También podéis usar este valor para actuar sobre las características de la otra instancia. Si el policía encuentra a un criminal y no tiene agresividad suficiente para detenerlo puede enviar un mensaje pidiendo refuerzos. La central recibiría el mensaje y con la id del Remitente podría aumentar al agresividad de éste: [code]Remitente.agresividad += 1;[/code]
Lo único que nos queda es ir al [b]Evento Step[/b] del [b]obj_ControlMensajes[/b] y decirle que realize el chequeo de mensajes: [code] recibirMensaje(); [/code] Listo! Bueno, con esto ya hemos terminado el sistema de mensajes! Lo siguiente que haremos será modificar la IA del policía para que reaccione a los mensajes que le lleguen y para que él mismo envíe sus propios mensajes. Actualización de la IA del policía Para que tengáis más a mano la tabla de transiciones la vuelvo a poner aquí, estudiarla atentamente para poder entender el comportamiento del policía:
Condición\Es BuscarY Entregar IrAlBAr IrAlBan IrALaCe *IrACa tado Atrapar Criminal YBeber co ntral sa* Agresividad>= Entregar X Agresividad Criminal criminal
X
X
X
X
Detenciones= Detenciones X requeridas
IrAlBarY X Beber
X
X
X
Detenciones
X
BuscarY X Atrapar
X
X
X
Mensaje "Atraco!"
IrAlBanc IrAlBanc IrAlBan X o o co
IrAlBan X co
Mensaje IrALaCe X "Información" ntral
X
X
X
Agresividad>= X 7
X
X
X
BuscarY X Atrapar
Cansancio>= Cansancio_ma IrACasa x
IrACasa
IrACas IrACasa IrACasa X a
Cansancio=0 X & Detenciones>
X
X
X
X
X
BuscarY Atrapar
=Detenciones requeridas Atraco detenido
X
X
X
IrAlBarY X Beber
X
Atraco no detenido
X
X
X
BuscarY X Atrapar
X
Como hemos dicho al principio, el policía tendrá ahora 2 nuevos estados. El primero de ellos será IrAlBancoYDetenerAtraco. En este estado, el agente irá al banco e intentará detener el atraco con una probabilidad de éxito del 50% ([b]floor(random(2))[/b]). Si lo consigue se irá al bar a celebrarlo, si no, volverá a patrullar para buscar a los culpables: [code] switch argument0{ case 0://Entrar show_message("Voy corriendo!"); break; case 1://Ejecutar if (floor(random(2))){ show_message("Alto! Nadie va a robar este banco en mi turno"); execute_string(estado+"(2)");//ejecutar acciones de salida estado="IrAlBarYBeber"; }else{ show_message("He llegado demasiado tarde...voy a buscar a los culpables de esto!"); estado="BuscarYAtraparCriminales"; } break; case 2://Salir show_message("Conseguido! Esto hay que celebrarlo"); break; case 3://Transición break; } [/code] El otro estado del policía es IrALaCentralYRecibirInfo. En este estado el policía acudirá a la central para recibir información sobre los malhechores. Su agresividad irá aumentando, de forma que tendrá
más éxito al detener criminales. Cuando su agresividad crezca lo suficiente, volverá a patrullar las calles: [code] switch argument0{ case 0://Entrar show_message("Voy corriendo a la central"); break; case 1://Ejecutar if (agresividad>=7){ show_message("Esta informaci?n me va a resultar muy util para detener criminales");? estado="BuscarYAtraparCriminales"; }else{ show_message("Necesito mas informacion..."); agresividad+=1; } break; case 2://Salir break; case 3://Transición break; } [/code] Lo último que nos falta es modificar el código del [b]Evento Step[/b] del policía para que pueda recibir mensajes: [code] if (keyboard_check(vk_escape)){ game_end(); } if (estado_global != ""){ execute_string(estado_global+"(1)"); }else{ cansancio += 1; if (cansancio >= cansancio_max){? estado_global = "IrACasaYDormir";? execute_string(estado_global+"(0)");? }else{ if (mensaje=0){ estado = "IrAlBancoYDetenerAtraco";
execute_string(estado+"(0)"); mensaje = -1; } execute_string(estado+"(1)"); } } [/code] El policía podía recibir 2 tipos de mensajes: • 0: Atraco en el banco 1: Información sobre un criminal Si el mensaje es de tipo=0 (Atraco en el banco) el policía dejará lo que está haciendo y se dirigirá al banco. En cierta forma, es como si el estado IrAlBancoYDetenerAtraco fuera un estado global, ya que puede interrumpir a todos los demás excepto al estado global IrACasaYdormir. Por eso, resulta más sencillo implementarlo en el código general de la FSM (el policía) en el evento step, en lugar de ir al código de cada estado y añadir la misma condición una y otra vez. Observad que volvemos a poner [b]mensaje = -1[/b] porque el mensaje ya se ha recibido y se ha actuado, con lo que ya nos podemos librar de él. Sin embargo el otro mensaje (tipo=1, Información sobre un criminal) sólo tendrá sentido en el estado BuscarYAtraparCriminales. Así que vamos al código de este estado y lo modificamos: [code]switch argument0{ case 0://Entrar show_message("Me faltan "+string(detenciones_maxdetenciones)+" detenciones"); break; case 1://Ejecutar if (mensaje=2){ estado = "IrALaCentralYRecibirInfo"; execute_string(estado+"(0)"); }else{ agresividad_criminal = floor((random(10))); if (agresividad >= agresividad_criminal){ show_message("He detenido a un criminal"); detenciones += 1; //CAMBIAMOS DE ESTADO? estado = "EntregarCriminal";
execute_string(estado+"(0)");//Ejecutar las acciones de entrada al nuevo estado }else{ show_message("Se me ha escapado un criminal. El tenia agresividad "+string(agresividad_criminal)+" y yo solo "+string(agresividad)); agresividad += 1; } } break; case 2://Salir break; case 3://Transición break; } [/code] Con esto hemos terminado con el sistema de recepción de mensajes pero también queremos que el policía los envíe. El policía enviará un mensaje de tipo 2 (informar de un delito) cuando se encuentre en el estado EntregarCriminal y vea un delito. Así que vamos al código del estado EntregarCriminal: [code] switch argument0{ case 0://Entrar show_message("Voy a la comisaria a entregar al criminal"); if (!floor(random(3))){ enviarMensaje(obj_central,2); } break; case 1://Ejecutar if (detenciones >= detenciones_max){ show_message("Ya he cumplido con mi labor, creo que ire a celebrarlo al bar."); estado = "IrAlBarYBeber"; }else{ show_message("Ya llevo "+string(detenciones)+" detenciones"); estado = "BuscarYAtraparCriminales"; } execute_string(estado+"(0)");
break; case 2://Salir break; case 3://Transición break; } [/code] El policía enviará un mensaje con una probabilidad del 33%. Observad que en este caso sólo existe una instancia del objeto obj_central, con lo que podemos enviar el mensaje usando el nombre del objeto (obj_central) como destino del mensaje (enviarMensaje(obj_central,2);). Cuando haya más de una instancia del mismo objeto tendremos que usar la id para mandar el mensaje a la instancia correcta. Si usáramos el nombre del objeto, el mensaje se enviaría a todas las instancias del objeto (lo que tambié puede ser algo útil, por ejemplo para avisar a todos los policías a la vez para que vayan al banco a detener el atraco). El policía ahora es capaz de recibir mensajes, actuar según ellos y enviar los suyos propios. Como habéis visto, para poder usar el sistema de mensajes casi no hemos hecho cambios. Estos es gracias a que hemos puesto toda la funcionalidad de los mensajes en el objeto [b]obj_ControlMensajes[/b]. Así podremos poner cualquier otro personaje en el juego y darle facilmente la capacidad de enviar y recibir mensajes. Y como prueba de esto último, vamos a añadir por fín la Central de policía. La Central de policía La Central de policía será algo muy sencillo: básicamente mandará mensajes al policía aleatoriamente y enviará unidades cuando el policía se lo pida. Como siempre, pimero definimos sus estados: • Vigilar: en este estado la central mandará mensajes al policía de forma aleatoria para que vaya al banco o acuda a recibir información. • EnviarUnidad: la central enviará una unidad de policía a investigar los avisos del policía. Para controlar estos estados, necesitaremos unas características:
• Tiempo de envío: un contador de tiempo para enviar mensajes. Así, la tabla de transiciones de la central será: Condición\Estado actual
Vigilar
EnviarUnidad
Mensaje "Mandar una unidad" EnviarUnidad X Como veis, es muy sencilla. Al recibir un mensaje enviará una unidad. Después, volverá al estado Vigilar. Bien, creamos un objeto llamado obj_central y su Create Event definimos las características de la FSM como hicimos con el policía: [code] /*CARACTERISTICAS DE LA FSM*/? estado = "Vigilar";? mensaje = -1; [/code] En este caso no necesitamos definir ningún estado global. A continuación definimos las características de la central: [code] tiempo_envio = 15+floor(random(15)); [/code] En el Evento Step definimos el funcionamiento de la FSM: [code] execute_string(estado+"(1)"); [/code] Y ahora definimos los estados. Creamos el script EnviarUnidad: [code] switch argument0{ case 0://Entrar break; case 1://Ejecutar show_message("Enviamos una unidad a investigar"); estado = "Vigilar"; break; case 2://Salir break; case 3://Transición break; }
[/code] Y el script Vigilar: [code] switch argument0{ case 0://Entrar break; case 1://Ejecutar if (mensaje = 2){ estado = "EnviarUnidad"; mensaje = -1; }else{ if (tiempo_envio>1){ tiempo_envio -= 1; }else{ enviarMensaje(obj_policia,floor(random(2))); tiempo_envio = 15+floor(random(15)); } } break; case 2://Salir break; case 3://Transición break; } [/code] Listo! Ya hemos terminado con nuestro sistema de FSM con mensajería! :D Mejoras y ampliaciones • En el ejemplo que hemos creado los mensajes se envían y reciben instant?neamente. Podéis añadir mensajes con retardo, de forma que el destinatario no los reciba inmediatamente sino que se guarden en la cola durante un tiempo (sería una nueva característica de los mensajes y requeriría de otro mapa de memoria). Así podríais ver cómo funciona el sistema cuando hay varios mensajes circulando a la vez. • Crear varias instancias de los policías y hacer que se envíen mensajes entre ellos. Por ejemplo, hacer que pidan ayuda al policía con mayor agresividad.
• Ahora que ya tenemos un objeto controlador (obj_ControlMensajes) del sólo existirá un instancia podemos quitar el código para salir del juego pulsando Escape del evento step del policía y ponerlo en el evento step de este objeto. Consideraciones finales Los sistemas de mensajes son una caracter?sica muy importante en los juegos. Seguramente no habíais oído hablar antes de ellos, pero seguro que os suenan si los llamo por su otro nombre: Eventos. Así es, los juegos profesionales utilizan este sistema para crear y controlar los eventos del juego, como en GM. Cualquier objeto que use el sistema de mensajes podrá comunicarse con los demás. Con unos ejemplos ver?is muy clara la importancia de esto: • Cuando un personaje intenta abrir una puerta, le manda un mensaje a ésta diciéndole "Abrete!". La puerta recibe el mensaje y puede abrirse o responder con otro mensaje diciendo que está cerrada y que para abrirla necesita una llave. • Un jugador empuja una caja. La caja recibe el mensaje de que está siendo empujada y debe moverse. • Una bala golpea a un enemigo. La bala manda un mensaje al enemigo diciendo que debe morir y éste cambia su animación, emite un grito... • Un jugador de un equipo de fútbol avanza con el balón. Puede mandar un mensaje a los delanteros para que se desmarquen, otro a los defensas para que cubran su posición y otro a un compañero para avisarle de que le va a pasar el balón. Resumiendo, para poder crear vuestra propia IA es indispensable saber manejar los eventos. GM nos proporciona muchas ventajas por su sistema de eventos, pero con este método podráis crear vuestros propios eventos específicos para cada juego consiguiendo unas IA más complejas pero más sencillas de programar que con el sistema de GM. correojon, 25 de Mayo de 2006 22 2007 IA: FSM III
OCT
by Fenris78 | 666 Views | 0 Comments | Rating: (0 rates)
Seguimos con la serie de artìculos sobre Inteligencia Artificial. En esta ocasión, veremos cómo implementar un máquina de estados finita en GML. Para este ejemplo haremos un ejemplo sencillo, sin gráficos ni nada, en el que la salida serán mensajes de texto.
Descripción general En este ejemplo crearemos un policía que patrulla las calles buscando criminales. Si encuentra un criminal intentará atraparlo. Si lo consigue, lo llevará a comisaría y lo meterá a la cárcel. Si no lo consigue, el policía se volverá más agresivo. Cuando consiga 5 detenciones el policía habrá cubierto su cupo de detenciones diarias y podrá irse al bar a celebrar el cumplimiento de su trabajo. Además de todo esto, el policía se irá cansando con lo que llegado el momento, dejará lo que está haciendo y volverá a su casa a dormir. Planificación Antes de nada, hay que definir primero los estados de nuestro policía: • BuscarYAtraparCriminales: en este estado el policía buscará criminales. Si encuentra uno, intentará atraparlo. • EntregarCriminal: cuando detenga a un criminal, el policía lo llevará a la comisaría. • IrACasaYDormir: ir a casa cuando está cansado a echar un sueñecillo. Este será un estado global, capaz de interrumpir cualquier otro estado para ejecutarse. • IrAlBarYBeber: ir al bar a tomar unas merecidas copas. Para poder pasar de un estado a otro deben cumplirse unas condiciones. Para ello, necesitaremos conocer las características del policía: • Cansancio: nivel de cansancio actual del policía. • Cansancio máximo: nivel máximo de cansancio que puede soportar el policía. • Detenciones: número de detenciones que ha realizado. • Detenciones requeridas: número de detenciones que necesita realizar para terminar su jornada laboral. • Agresividad: agresividad del policía. Necesaria para atrapar criminales. • Agresividad criminal: agresividad del criminal. Si es mayor que la del policía se escapará. Una vez que hemos definido todo esto, ya podemos escribir la tabla de transiciones del policía: Condición\Estad BuscarYAtraparCri EntregarCriminal o actual minales
IrAlBArYB eber
Agresividad>=Agre EntregarCriminal sividad criminal
X
X
Detenciones=Dete X nciones requeridas
IrAlBarYBeber
X
Detenciones<>
BuscarYAtraparCri X minales
X
Como veis, en la tabla no añadimos la condición para entrar al estado global IrACasaYdormir. Podría ponerse, pero sería algo tonto ya que sería igual para todos los estados: si Cansancio>=Cansancio máximo->IrACasaYdormir. Bueno, una vez que tenemos todo esto ya podemos empezar a programar. Características del policía Creamos el objeto obj_policia y en su Create Event inicializamos las caracter?sticas de la máquina de estados finitos: [code] /*CARACTERISTICAS DE LA FSM*/ estado = "BuscarYAtraparCriminales"; estado_global = "IrACasaYDormir"; [/code] Al comienzo del juego el policía se encontrará durmiendo en su casa. Cuando se ejecute el estado global interrumpirá cualquier estado que estuviéramos llevando a cabo. Una vez que finalize este estado, volveremos al estado anterior. Por último, decimos que el estado global en este momento es IrACasaYDormir. De esta forma, este estado interrumpe al estado actual que definimos en la primera línea (estado = "BuscarYAtraparCriminales") y así conseguiremos que inicialmente el policía se encuentre en casa durmiendo. A continuación inicializamos las características del policía: [code] /*CARACTERISTICAS DEL POLICIA*/ cansancio = 0; cansancio_max = 10; detenciones = 0; detenciones_max = 5; agresividad = floor(random(10)); agresividad_criminal = 0; [/code]
Inicialmente el policía no está cansado (cansancio = 0). Además, no ha realizado ninguna detención (detenciones = 0). Su agresividad es aleatoria (agresividad = floor(random(10))). Así cada vez que ejecutemos el juego el policía se comportará de manera diferente. Muy bien, ahora sólo nos falta hacer que cada estado se ejecute. Para ello, en el Evento Step ponemos lo siguiente: [code] if (estado_global != ""){ ?execute_string(estado_global+"(1)"); }else{ ?execute_string(estado+"(1)"); } [/code] Veremos este código con más detalle cuando creemos la lógica de los estados, pero de modo genreal podemos decir que hace lo siguiente: Si hay un estado global (if (estado_global != "")) se ejecuta. Si no, ejecutamos el estado actual. Para ello, tenemos que crear unos scripts con los nombres de los estados. Dentro de estos scripts definiremos las acciones que realizará cada uno. Acciones Como ya vimos en el tutorial básico sobre FSMs, las acciones dependen del estado actual. También vimos que había varios tipos de acciones (de entrada, de salida, de transición y de ejecución). Por ello, cada estado se comportará de forma diferente al entrar en él, salir de él, pasar a otro estado o ejecutarse normalmente. Creamos un script llamado BuscarYAtraparCriminales y ponemos lo siguiente: [code] switch argument0{ case 0://Entrar break; case 1://Ejecutar break; case 2://Salir break; case 3://Transición break; }
[/code] Este script tendrá 1 argumento, que controlará las acciones que se ejecuten. Así: • Si argument0 = 0 se ejecutarán las acciones de entrada. • Si argument0 = 1 se ejecutarán las acciones de ejecución. • Si argument0 = 3 se ejecutarán las acciones de transición. En este estado no habrá acciones de salida. Describimos las acciones que se realizarán en el estado BuscarYAtraparCriminales: • Al entrar en el estado el policía nos dirá cuantas detenciones le faltan por hacer para cubrir su cupo. • Al ejecutar el estado el policía intentará detener a los criminales. Si lo consigue, los llevará a la comisaría, si no, aumentará su agresividad. • Al salir del estado el policía no hará nada, es decir, no habrá acciones de salida. • En este estado no habrá acciones de transición. Ahora que ya hemos definido las acciones, las metemos en el script: [code] switch argument0{ case 0://Entrar show_message("Me faltan "+string(detenciones_maxdetenciones)+" detenciones"); break; case 1://Ejecutar agresividad_criminal = floor((random(10))); if (agresividad >= agresividad_criminal){ show_message("He detenido a un criminal"); detenciones += 1; //CAMBIAMOS DE ESTADO estado = "EntregarCriminal"; execute_string(estado+"(0)");//Ejecutar las acciones de entrada al nuevo estado }else{ show_message("Se me ha escapado un criminal. El tenia agresividad "+string(agresividad_criminal)+" y yo solo "+string(agresividad)); agresividad += 1; } break; case 2://Salir
break; case 3://Transición break; } [/code] Ahora vamos a programar el estado EntregarCriminal. En este estado el policía simplemente llevará al criminal a comisaría, verá cuántas detenciones ha hecho y entonces decidirá si captura más criminales o se va al bar a tomarse unas copas: [code] switch argument0{ case 0://Entrar show_message("Voy a la comisaria a entregar al criminal"); break; case 1://Ejecutar if (detenciones >= detenciones_max){ show_message("Ya he cumplido con mi labor, creo que ire a celebrarlo al bar."); estado = "IrAlBarYBeber"; }else{ show_message("Ya llevo "+string(detenciones)+" detenciones"); estado = "BuscarYAtraparCriminales"; } execute_string(estado+"(0)"); break; case 2://Salir break; case 3://Transici?n break; } [/code] Bien, el siguiente estado será IrAlBarYBeber. En este estado el policía simplemente se tomará unas copas hasta que está cansado y se quiera ir a casa. [code] switch argument0{ case 0://Entrar show_message("Jeje, ya estoy en el bar, se acabo el trabajo
por hoy!"); detenciones = 0; break; case 1://Ejecutar show_message("Hmmm...este whisky esta delicioso"); break; case 2://Salir break; case 3://Transición break; } [/code] Como veis, este estado es muy sencillo y no necesita comentarios :) Ahora ya sólo nos falta el estado global "IrACasaYDormir": [code] switch argument0{ case 0://Entrar show_message("Uff, que cansado estoy! Voy a dormir un rato"); break; case 1://Ejecutar if (cansancio > 0){ show_message("ZZzzzzzz...."); cansancio -= 1;//recuperarse }else{ show_message("Que bien me ha venido este descanso"); execute_string(estado_global+"(2)");//ejecutar acciones de salida if (detenciones = 0){ estado = "BuscarYAtraparCriminales"; } execute_string(estado+"(0)");//volver al estado anterior } break; case 2://Salir if (detenciones >= detenciones_max){ show_message("Listo para un nuevo dia de trabajo!"); detenciones = 0; }else{ show_message("Ya solo me quedan
"+string(detenciones_max-detenciones)); } estado_global = ""; break; case 3://Transición break; } [/code] Casi hemos terminado, sólo nos queda hacer que el policía se vaya cansando y que lance el estado global IrACasaYDormir cuando está demasiado cansado. Volvemos al Evento Step y modificamos el código que teníamos: [code] /*SI MANTENEMOS PULSADO ESCAPE SALIMOS DEL JUEGO*/ if (keyboard_check(vk_escape)){ game_end(); } if (estado_global != ""){ execute_string(estado_global+"(1)"); }else{ cansancio += 1; if (cansancio >= cansancio_max){ estado_global = "IrACasaYDormir"; execute_string(estado_global+"(0)"); }else{ execute_string(estado+"(1)"); } } [/code] Listo! Ya hemos creado nuestra primera FSM en GML! :D Ampliaciones Podéis practicar con este ejemplo, a?adiendo cosas como: • Una nueva característica llamada lugar que indique dónde se encuentra el policía en cada momento. Así, esta variable podrá tener los valores "casa", "comisaria", "calle" y "bar". Hacer que el policía diga cuándo va de un sitio a otro (tendráis que usar las acciones de entrada y salida de cada estado). Por ejemplo, en la
acción de salida del estado BuscarYAtraparCriminales el policía dirá "Me voy a la comisaria" (si es que va a la comisaría ;) ) y en la acción de entrada del estado EntregarCriminal dirá algo como "Ya estoy en la comisaria". • Nuevos estados, como Comer, Investigar (para encontrar información sobre dónde se encuentran los criminales)...Tendráis que definir nuevas características para controlar estos estados, por ejemplo hambre para controlar el estado Comer. • Comportamiento avanzado: por ejemplo, añadiendo el estado Investigar podéis poner una nueva característica llamada inteligencia. Si el policía tiene más inteligencia que agresividad tal vez decida investigar un poco antes de intentar detener criminales. Si fracasa al intentar detener un criminal podéis hacer que su inteligencia se reduzca. • Hacer que la agresividad se reduzca después de descansar y que la inteligencia aumente. También podéis hacer que el policía descanse un tiempo aleatorio, así no será capaz de recuperarse del todo, la agresividad no se reducirá siempre lo mismo ni la inteligencia crecerá lo mismo...de esta forma las características del policía cambiarán constantemente y cada vez se comportará de una manera diferente! Os animo a que pongáis aquí vuestros experimentos! Consideraciones finales De una manera sencilla hemos creado una IA, bastante simple pero muy robusta. Además, se puede ampliar muy facilmente añadiendo nuevos estados y acciones. En la próxima entrega introduciremos la Central de Policía, capaz de enviar mensajes al policía dándole información sobre los criminales...por ejemplo, podremos despertar al policía con un aviso urgente para que vaya a detener el robo de un banco!
Listas. veremos qué son las listas, cómo se usan y porqué resultan una herramienta tan poderosa. Definición Extraído del Manual Online de CGM: Una lista guarda una colección de valores en un orden determinado. Puedes añadir valores a la lista en la posición que desees. Por eso,
puedes acceder acceder a los valores usando un índice de su posición en la lista. También puedes ordenar los elementos de forma ascendente o descendente. Las listas se pueden usar para muchas cosas, por ejemplo, para guardar valores que cambian. Las listas se han programado usando arrays, pero al estar definidas en código compilado son mucho más rápidas que los arrays. A priori se puede decir que una lista es como un array: una colección de valores que se guardan en orden. Sin embargo las listas poseen unas funciones que hacen que sean una de las estructuras más utilizadas para el manejo de grandes cantidades de datos de manera eficiente. Para poder usar una lista, primero debemos crearla con la función ds_list_create(). Esta función nos devuelve la id de la nueva lista creada, que tendremos que usar para llamar a las demás funciones de la lista. Para conocer las propiedades de la lista tenemos 2 funciones: ds_list_size(idLista) Devuelve el número de valores en la lista. ds_list_empty(idLista) Devuelve true si la lista está vacía. Para saber si la lista está vacía tenemos 2 opciones: llamar a la función ds_list_empty(idLista) o chequear si su tamaño es igual a 0: //Este código if (ds_list_empty(idLista){...} //es lo mismo que este otro if (ds_list_size(idLista) = 0){...} Para inicializar una lista, tenemos la función: ds_list_clear(idLista) Limpia la lista, borrando todos los valores que contiene pero no la destruye. Por último, cuando ya no necesitemos la lista tenemos que eliminarla, ya que si la dejamos en la memoria nos estaría ocupando espacio inútilmente. Para ellos usamos la función: ds_list_destroy(idLista) Destruye la lista, liberando la memoria usada. Con estas funciones ya podemos crear una lista, ver su tamaño, ver si está vacía y eliminarla. Ahora veremos las funciones para usar realmente las listas. Funciones ds_list_add(idLista,val) Inserta el valor al final de la lista. ds_list_insert(idLista,pos,val) Inserta el valor en la posición pos. La primera posición es 0 y la última es igual al tamaño de la lista menos 1. ds_list_replace(idLista,pos,val) Reemplaza el valor en la posición pos por val.
ds_list_delete(idLista,pos) Elimina el valor en la posición pos. ds_list_find_index(idLista,val) Devuelve la posición en la lista del valor val. Si no encuentra el valor en la lista devuelve -1. ds_list_find_value(idLista,pos) Devuelve el valor en la posición pos. ds_list_sort(idLista,ascend) Ordena los valores de la lista. Si ascend es true o 1 los ordena de forma ascendente (de menor a mayor), en caso contrario los ordena de manera descendente (de mayor a menor). Con estas funciones podemos hacer muchas cosas de manera mucho más sencilla y efectiva que usando arrays. Por ejemplo, para ordenar los valores de la lista de mayor a menor basta con poner esto: ds_list_sort(idLista, false); Recorriendo una lista Para recorrer una lista, lo hacemos igual que como lo haríamos con un array: var i; for (i=0; i[ds_list_size(idLista); i+=1){ miValor = ds_list_find_value(idLista, i); draw_text(x, y+8*i, miValor);//Imprimimos los valores uno debajo de otro } Pero esta vez tenemos una gran ventaja y es que no necesitamos conocer el tamaño de la lista. Con un array tendríamos que saber el tamaño exacto que tiene para poder coger todos los valores, pero con una lista la función ds_list_find_size(idLista) ya nos da esa información. Ejemplo: Programa sencillo de dibujo Para entender bien el funcionamiento de las listas, vamos a ver un ejemplo sencillo. Vamos a crear un panel en el que usuario podría dibujar formas sencillas. Cada vez que haga click con el botón izquierdo se añadirá un punto y cada vez que haga click con el botón derecho se borrará el último punto añadido. Evento create //Creamos las listas que guardarán las coordenadas de los puntos listaPuntosX = ds_list_create(); listaPuntosY = ds_list_create(); Al hacer click con el botón izquierdo añadimos un punto: Evento Mouse Global Left Pressed //Añadimos el punto al final de la lista ds_list_add(listaPuntosX, mouse_x); ds_list_add(listaPuntosY, mouse_y); Y al hacer click con el botón derecho borramos el último punto
añadido Evento Mouse Global Right Pressed //Quitamos el último punto de la lista ds_list_delete(listaPuntosX, ds_list_size(listaPuntosX)-1); ds_list_delete(listaPuntosY, ds_list_size(listaPuntosY)-1); Como véis, no hace falta llevar una cuenta de cuántos puntos hemos metido o quitado. Por último, unimos todos los puntos con una línea y los dibujamos: Evento Draw if (ds_list_size(listaPuntosX) ] 1){//si hay dos ó más puntos en la lista draw_set_color(c_black); draw_primitive_begin(pr_linestrip); var i; for (i=0; i[ds_list_size(listaPuntosX); i+=1){ draw_vertex(ds_list_find_value(listaPuntosX, i),ds_list_find_value(listaPuntosY,i)); } draw_primitive_end(); } draw_set_color(c_red); var i; for (i=0;i[ds_list_size(listaPuntosX); i+=1){//dibujar los puntos draw_circle(ds_list_find_value(listaPuntosX, i),ds_list_find_value(listaPuntosY,i), 2, false); } Ya está, poned este objeto en una room vacía y cread arte XD. La principal ventaja que vemos en este ejemplo de las listas sobre los arrays es que no hace falta que llevemos la cuenta de los puntos que metemos en la lista. Así mismo, tampoco necesitamos chequear nada cuando quitamos puntos. Si lo hubiéramos hecho con arrays, tendríamos que haber chequeado antes de borrar un punto de la lista que el array tenía algún elemento o nos huberia salido un error de índice negativo en el array. A parte de esto, otra clara ventaja de las listas es la de poder ordenar todos los valores con una simple llamada a la función ds_list_sort(idLista, ascend). Con arrays tendríamos que programar un algoritmo de ordenación que, además, sería mucho menos eficiente. También podemos saber en qué lugar de la lista se encuentra un valor con la función ds_list_find_index(idLista, pos), algo que con arrays sería tremendamente complicado de hacer. También podemos insertar valores en cualquier posición de la lista,
desplazando los demás con ds_list_insert(idLista, pos, val). De nuevo, esto sería bastante más trabajoso de hacer con arrays. Colas. veremos otra estructura de memoria que podemos usar con GM: las colas. Es recomendable aunque no necesario haber leído antes el artículo sobre listas. Definición Extraída del Manual Online de CGM: Una cola es parecido a una pila, pero funciona como una estructura FIFO (primero en entrar, primero en salir). El primer valor que se mete en la cola es el primero en salir, como una cola en una tienda. El primer cliente en llegar sería el primero en ser atendido y los demás tendrán que esperar su turno en orden. Los últimos en llegar se situarán al final de la cola y tendrán que esperar a que sean atendidos todos los que tengan delante. Las primeras 5 funciones de las colas para crearlas, eliminarlas, vaciarlas o ver su tamaño son iguales que las que vimos en las listas. ds_queue_create() Crea una nueva cola. La función devuelve un número entero con la id de la cola para usarla en las diferentes funciones. Puedes crear varias colas. ds_queue_destroy(id) Destruye la cola, liberando la memoria usada. No olvides usar esta función cuando ya no necesites la cola. ds_queue_clear(id) Limpia la cola, borrando todos los valores que contiene pero no la destruye. ds_queue_size(id) Devuelve el número de valores en la cola. ds_queue_empty(id) Devuelve true si la cola está vacía. Es lo mismo que chequear si el número de valores en la cola es cero. Funciones ds_queue_enqueue(id,val) Introduce el valor en la cola. ds_queue_dequeue(id) Devuelve el último valor de la cola (el último en introducirse) y lo elimina de la cola. ds_queue_head(id) Devuelve el valor al principio de la cola, esto es, el primero que se introdujo, pero no lo elimina de la cola. ds_queue_tail(id) Devuelve el último valor de la cola pero no lo elimina. Las funciones de las colas nos permiten hacer muchísimas cosas de manera muy sencilla, como veremos a continuación. Recorriendo una cola Para recorrer una cola lo podemos hacer de manera muy sencilla
mientras vamos eliminando los valores: var d; d = 0; while (!ds_queue_empty(idCola)){ draw_text(x, y+8*d, ds_queue_dequeue(idCola));//Imprimimos los valores uno debajo de otro d += 1; } Observad que la variable temporal d sólo la usamos para poder dibujar los valores de la cola uno debajo de otro. Para cualquier otra cosa no la necesitaríamos! Es decir, que ni siquiera necesitamos un bucle for para recorrer una cola! También hay que darse cuenta de que al final del bucle la cola estaría vacía, ya que al usar la función ds_queue_dequeue(idCola) vamos borrando los valores de la cola. ¿Entonces para qué sirve esto? Las colas se utilizan cuando hay que calcular algo en cada step y luego utilizar los resultados 1 sola vez. Ejemplo: Elegir a un enemigo sin repetir En este ejemplo vamos a hacer que el jugador, pulsando una tecla, apunte automáticamente a un enemigo. Esto es muy sencillo de hacer pero, ¿y si tenemos varios enemigos a la vez en pantalla? ¿Y si además queremos que al volver a pulsar la tecla, el jugador apunte a otro enemigo distinto? Vamos a ver cómo se hace esto usando colas: Creamos un objeto y lo llamamos objEnemigo: Evento Draw: -Dibujar un círculo rojo draw_set_color(c_red); draw_circle(x, y, 16, false); Muy bien, ahora creamos otro objeto y lo llamamos objJugador: Evento Create: -Crear la cola -Inicializar variables enemigos = ds_queue_create(); objetivo = 0; Evento Key Press Control: -Si la cola está vacía metemos los enemigos en la cola -Elegimos un enemigo y lo borramos de la cola if (ds_queue_empty(enemigos)){ with (objEnemigo){ ds_queue_enqueue(other.enemigos, id); } }
objetivo = ds_queue_dequeue(enemigos); Evento Draw: -Dibujar un triángulo azul apuntando al objetivo direction = point _direction(x, y, objetivo.x, objetivo.y); var d, dir; d = 16; dir = degtorad(direction); draw_set_color(c_blue); draw_primitive_begin(pr_trianglestrip); draw_vertex(x+(2*d)*cos(dir)+0*sin(dir), y+0*cos(dir)(2*d)*sin(dir)); draw_vertex(x+(-d/2)*cos(dir)+(-d)*sin(dir), y+(-d)*cos(dir)-(d/2)*sin(dir)); draw_vertex(x+(-d/2)*cos(dir)+d*sin(dir), y+d*cos(dir)-(d/2)*sin(dir)); draw_primitive_end(); Listo! Crear un room, poner uno o varios objetos jugadores, varios enemigos y probarlo. Cada vez que pulsáis Control los jugadores irán apuntando a un enemigo diferente sin repetir! Las colas son muy rápidas y usan muy poca memoria si se utilizan correctamente, ya que tienen la capacidad de acceder y eliminar los valores que guardan al momento. Y para recorrerlas ni siquiera necesitamos acceder al índice de cada elemento! Colas de prioridad. Las colas de prioridad son otra estructura de memoria, muy similares a las colas pero con algunas diferencias. Es recomendable aunque no necesario haber leído antes el artículo sobre colas. Definición Extraído del Manual Online de CGM: En una cola de prioridad a cada valor se le asigna una prioridad. Así puedes buscar los valores con mayor o menor prioridad y controlar ciertas cosas según su prioridad. Es decir, las colas de prioridad funcionan igual que las colas normales pero además poseen una nueva característica llamada prioridad que nos permite acceder a los valores de la cola de otra forma. Las primeras 5 funciones de las colas de prioridad para crearlas, eliminarlas, vaciarlas o ver su tamaño son iguales que las que vimos en las listas y en las colas: ds_priority_create() Crea una nueva cola. La función devuelve un
número entero con la id de la cola para usarla en las diferentes funciones. Puedes crear varias colas. ds_priority_destroy(id) Destruye la cola, liberando la memoria usada. No olvides usar esta función cuando ya no necesites la cola. ds_priority_clear(id) Limpia la cola, borrando todos los valores que contiene pero no la destruye. ds_priority_size(id) Devuelve el número de valores en la cola. ds_priority_empty(id) Devuelve true si la cola está vacía. Es lo mismo que chequear si el número de valores en la cola es cero. Funciones ds_priority_add(id,val,prio) Añade el valor con la prioridad especificada a la cola. ds_priority_change_priority(id,val,prio) Cambia la prioridad del valor especificado al nuevo valor. ds_priority_find_priority(id,val) Devuelve la prioridad del valor especificado. ds_priority_delete_value(id,val) Elimina el valor (con su prioridad) de la cola de prioridad. ds_priority_delete_min(id) Devuelve el valor con la menor prioridad y lo elimina de la cola. ds_priority_find_min(id) Devuelve el valor con la menor prioridad pero no lo elimina de la cola. ds_priority_delete_max(id) Devuelve el valor con la mayor prioridad y lo elimina de la cola. ds_priority_find_max(id) Devuelve el valor con la mayor prioridad pero no lo elimina de la cola. Como véis, ahora podemos acceder a los valors según su prioridad. En el caso de que varios valores tengan la misma prioirdad la cola funionará como una cola normal, devolviendo el primero que introdujimos en la cola. Recorriendo una cola de prioridad Para recorrer una cola de prioridad lo más sencillo es hacerlo por orden de prioridad. Así, podemos elegir ir sacando primero los valores con menos prioridad: var d; d = 0; while (!ds_priority_empty(idCola)){ draw_text(x, y+8*d, ds_priority_delete_min(idCola));//Imprimimos los valores uno debajo de otro d += 1; } o sacando primero los valores con mayor prioridad: var d;
d = 0; while (!ds_priority_empty(idCola)){ draw_text(x, y+8*d, ds_priority_delete_max(idCola));//Imprimimos los valores uno debajo de otro d += 1; } Al igual que ocurría con las colas normales la variable temporal d sólo la usamos para poder dibujar los valores de la cola uno debajo de otro. Para cualquier otra cosa no la necesitaríamos! Es decir, que ni siquiera necesitamos un bucle for para recorrer una cola! También hay que darse cuenta de que al final del bucle la cola estará vacía, ya que al obtener los valores los vamos borrando de la cola. Ejemplo: Elegir a un enemigo según la distancia Para ver la utilidad de las colas de prioridad vamos a modificar el ejemplo que hicimos para las colas: en esta ocasión haremos que el personaje vaya apuntando en orden a los enemigos según estén más cerca o más lejos. Es decir, primero apuntaremos al enemigo más cercano, luego al segundo más cercano, luego al tercero...y así hasta que hayamos apuntado a todos, momento en el que volveremos a apuntar al primero. Para ello sólo necesitaremos editar un poco el código del objPersonaje: Evento Create: -Crear la cola de prioridad -Inicializar variables enemigos = ds_priority_create(); objetivo = 0; Evento Key Press Control: -Si la cola está vacía metemos los enemigos en la cola, asignándoles como prioridad su distancia a objPersonaje -Elegimos un enemigo por oden de cercanía y lo borramos de la cola if (ds_priority_empty(enemigos)){ with (objEnemigo){ ds_priority_add(other.enemigos, id, distance_to_object(other)); } } objetivo = ds_priority_delete_min(enemigos); Con esta pequeña modificación hemos conseguido que ahora el personaje elija a los enemigos según su prioridad, que será igual a la la distancia del enemigo al personaje. Así, eligiendo a los enemigos en orden de prioridad mínima (objetivo = ds_priority_delete_min(enemigos)) vamos elegiendo primero a los más cercanos.
Las colas de prioridad tienen un montón de usos. Por ejemplo, si añadimos varios valores a la cola y en el momento de añadirlos les damos una prioridad aleatoria sería como si los estuviéramos desordenando, ya que no sabemos qué prioridad tendrá cada valor. Así, al acceder a los valores por prioridad podemos hacer una especie de sorteo: Añadiendo un valor con prioridad aleatoria: ds_priority_add(idCola, valor, random(10)); Eligiendo un valor por sorteo: resultado = ds_priority_find_min(idCola); Así podemos el resolver el típico problema de "tengo varios objetos y quiero coger algunos al azar, pero una vez que elija uno éste no debe volver a salir". Mapas de Memorias. Los mapas de memoria son una estructura que nos puede facilitar muchísimo la escritura de códigos complicados. Definición Extraído del Manual Online de CGM: En algunas ocasiones necesitas guardar pares de valores consistentes de una llave (key) y un valor. Por ejemplo, un personaje puede llevar varios ítems diferentes y puede tener un número diferente de cada uno. En este caso, el ítem será la llave y la cantidad será el valor. Los mapas manejan estas parejas de valores, ordenándolos por la llave. Puedes añadir valores al mapa y buscar uno concreto usando las llaves. Como las llaves también están ordenadas, puedes encontrar los valores correspondientes a la llave siguiente o anterior. A veces es útil usar un mapa para guardar llaves sin ningún valor asignado. En este caso puedes asignarles a todas las llaves el valor 0. Como siempre, las primeras 5 funciones de los mapas sirven para crearlos, eliminarlos, vaciarlos o ver su tamaño: ds_map_create() Crea un nuevo mapa. La función devuelve un n?mero entero con la id del mapa para usarla en las diferentes funciones. Puedes crear varios mapas. ds_map_destroy(id) Destruye el mapa, liberando la memoria usada. No olvides usar esta función cuando ya no necesites el mapa. ds_map_clear(id) Limpia el mapa, borrando todos las parejas llavevalor que contiene pero no lo destruye. ds_map_size(id) Devuelve el número de parejas llave-valor en el mapa.
ds_map_empty(id) Devuelve true si el mapa está vacía. Es lo mismo que chequear si el número de valores en el mapa es cero. Funciones ds_map_add(id,key,val) Añade la pareja llave (key)-valor (val) al mapa. ds_map_replace(id,key,val) Reemplaza el valor correspondiente a la llave con un nuevo valor. ds_map_delete(id,key) Elimina la pareja llave-valor especificada del mapa. Si hay varias parejas con la misma llave sólo 1 es eliminada. ds_map_exists(id,key) Devuelve true si la llave existe en el mapa. ds_map_find_value(id,key) Devuelve el valor correspondiente a la llave. ds_map_find_previous(id,key) Devuelve la mayor llave que sea menor que la indicada. ds_map_find_next(id,key) Devuelve la menor llave que sea mayor que la indicada. ds_map_find_first(id) Devuelve la menor llave del mapa. ds_map_find_last(id) Devuelve la mayor llave del mapa. Con los mapas de memoria el sistema cambia mucho respecto a lo que habíamos visto en las listas y colas. Los mapas se usan para guardar muchos valores y tenerlos de forma ordenada de manera que sea fácil acceder exactamente al valor que queremos. Ejemplo: Creando varios tipos de disparos Vamos a crear un ejemplo en el que el personaje pueda disparar 3 tipos de disparos diferentes. Pulsando + y - cambiaremos el tipo de disparo y con Espacio dispararemos. Creamos el objeto objJugador Evento Create: -Crear la variable que guarda el tipo de disparo usado -Crear el mapa de memoria con los tipos disparo = 0; disparos = ds_map_create(); ds_map_add(disparos, 0, "normal"); ds_map_add(disparos, 1, "doble"); ds_map_add(disparos, 2, "triple"); Evento Press Keypad +: -Usar el siguiente tipo de disparo disparo = ds_map_find_next(disparos, disparo); Evento Press Keypad -: -Usar el tipo de disparo anterior disparo = ds_map_find_previous(disparos, disparo);
Evento Press Space: -Crear las balas según el tipo de disparo tipo = ds_map_find_value(disparos, disparo); with (instance_create(x, y, objBala)){//crear la bala ?tipo = other.tipo;//definir el tipo de la bala ?event_perform(ev_other, ev_user0);//Inicializar características de la bala } Evento Draw: -Dibujar al jugador draw_set_color(c_blue); draw_circle(x, y, 16, false); Ahora creamos el objeto objBala: Evento Create: -Definir los mapas con las características de cada tipo de disparo /*COLORES*/ colores = ds_map_create(); ds_map_add(colores, "normal", c_black); ds_map_add(colores, "doble", c_red); ds_map_add(colores, "triple", c_white); /*VELOCIDADES*/ velocidades = ds_map_create(); ds_map_add(velocidades, "normal", 4); ds_map_add(velocidades, "doble", 8); ds_map_add(velocidades, "triple", 16); Evento User Defined 0: -Inicializar las características de la bala speed = ds_map_find_value(velocidades, tipo); Evento Draw: -Dibujar la bala según su tipo draw_set_color(ds_map_find_value(colores, tipo)); draw_circle(x, y, 4, false); Las ventajas de este método son muy importantes: -Podemos identificar facilmente en el código la parte de cada bala, ya que las variables se llaman "normal", "doble", "triple" o como queramos. -El sistema es facilmente ampliable: sólo hay que definir las características de los nuevos tipos de balas que queramos añadir el indicarlo en el evento Create del objeto jugador. El resto (aplicar velocidad a la bala, color, cmabiar de arma...) se hace automáticamente!
Esto mismo se puede usar para crear nuevos tipos de personajes con características diferentes, niveles, objetos... DLL. vamos a hablar de DLLs. DLL es el acronimo de "Dinamic Link library" (Biblioteca de enlace dinamico), para ser breves y claros, podemos definirla como un peque?o programa hecho en un lenguaje compatible (Delphi, C/C++, Pascal... ) que contiene funciones que pueden ser llamadas desde GM y nos sirven para cubrir aquellas necesidades que no pueden ser satisfechas con GM o que pueden ser optimizadas. (Como reproducir diferentes archivos de audio, ver animaciones flash, embeber paginas html en tus juegos... etc) Como cada DLL contendra diferentes funciones, su uso varia, ya que el numero de argumentos que recoja cada funcion y el tipo de dato devuelto sera diferente. En cualquier caso, el uso especifico de cada DLL en particular siempre debe venir documentado junto con el archivo. ¿Entonces nos vamos a limitar a explicar que es una DLL y nada mas? No, vamos a dar un pasito mas. Vamos a explicar el procedimiento basico para utilizar dos DLLs ficticias, para que os vayais familiarizando con la definicion y llamada de DLLs en GML. Primero es necesario definir la DLL, para ello GM nos proporciona la siguiente funcion: external_define (dll,name,calltype,restype,argnumb,arg1type,arg2type, …) Define una función externa. dll es el nombre del archivo dll. name es el nombre de las funciones. calltype es la convención de llamada empleada. Usa dll_cdecl o dll_stdcall. restype es el tipo del resultado. Usa ty_real o ty_string. argnumb es el número de argumentos (0-11). Después, para cada argumento debes especificar su tipo. Para ello usa nuevamente ty_real o ty_string. Cuando hay más de 4 argumentos todos ellos deben ser de tipo ty_real. Despues de definir la DLL, ya podemos hacer uso de sus funciones, invocandolas con la siguiente funcion: external_call(id,arg1,arg2,…)
Llama a la función externa con el id y los argumentos dados. Necesitas proporcionar el número correcto de argumentos del tipo correcto (real o string). La función devuelve el resultado de la función externa. Nota: Como habreis observado, GM solo envia y recibe argumentos del tipo numero real o cadenas de texto. Tened esto en cuenta a la hora de desarrollar vuestras DLLs. Ahora voy a exponer dos ejemplos de definicion y llamada de dos DLLs, una se llama "Mensaje.DLL" y mostraria una ventana con el titulo y texto especificados en los argumentos de entrada tipo "string". La segunda DLL trabaja con datos numericos "Divide.DLL" toma dos numeros reales como argumentos y los divide, devolviendonos el resultado. Vemos ambos codigos de llamada al completo: //Aquí tienes el código de llamada para la DLL “Mensaje” : { global.Mensaje= external_define(Mensaje.DLL','_Mensaje',dll_stdcall,ty_string, 2,ty_string,ty_string); external_call(global.Mensaje,"Titulo","Texto") } //c?digo de llamada para la DLL “divide” : { global.divide= external_define('divide.DLL','divide',dll_stdcall,ty_real,2,ty_re al,ty_real); result = external_call(global.divide,12,12) }
Bueno, con este pequeño articulo "¿Como se hace?" espero que hayais podido ampliar un poco mas vuestros conocimientos sobre el uso de DLLs con GM. Si creeis que se ha quedado algo importante en el tintero, no dudeis en responder y compartirlo con nosotros. Estamos aqui para aprender juntos.
Taller de Programación con GM PLANIFICACI?N DEL JUEGO Y CREACI?N DE SPRITES
En este taller aprenderemos a realizar nuestro propio videojuego: nuestra visi?n del famoso y archiconocido comecocos (PACMAN). Para ello nos valdremos de dos programas principalmente, GraphicsGale para la creaci?n de sprites (los personajes del videojuego) y Gamemaker, la aplicaci?n con la que programaremos y montaremos nuestro videojuego. Programar un videojuego no es una tarea sencilla, no se trata de ponernos a ello y sale s?lo. Es necesaria toda una planificaci?n detallada, visualizar el videojuego en nuestra mente y cada uno de los detalles que intervienen en ?l antes de empezar a programarlo y plasmarlo sobre papel, hacer un gui?n en el que no debemos dejar nada al azar: Historia, personajes, escenarios, N?mero de fases, elementos que intervienen, puntuaci?n, vidas… Y lo m?s importante, las reglas del juego. Pensemos en nuestro juego. Historia: No es muy complicada. Pacman, un comecocos (monstruillo que come cocos) recorre unos escenarios de estructura laber?ntica engullendo todos los cocos que encuentra a su paso. El objetivo de nuestro amigo es comerse todos los cocos para pasar de nivel. Pero en el escenario hay otros monstruillos, los fantasmas, a los que no les hace ninguna gracia que Pacman se zampe los cocos de su territorio. Los fantasmas intentaran impedirle a Pacman que se coma los cocos y lo perseguir?n sin tregua por toda la pantalla para com?rselo. Pero Pacman podr? defenderse: Repartidos por la pantalla hay un peque?o n?mero de supercocos que s?per vitaminizan a Pacman por un peque?o espacio de tiempo y que lo hacen invulnerable y con la capacidad de comerse a los fantasmas. Reglas del juego: - Cada vez que pacman toca un coco se lo come sumando 10 puntos a su marcador de puntuaci?n. - Si pacman toca un fantasma o un fantasma toca a pacman si que este est? s?per vitaminizado pacman muere y se descuenta una vida del contador de vidas. - Si pacman est? s?per vitaminizado y toca a un fantasma o un fantasma lo toca a ?l el fantasma morir? y pacman sumar? 50 puntos a su marcador de puntuaci?n. - Cuando pacman muere vuelve a aparecer en el punto de inicio de pacman.
- Cuando un fantasma muere vuelve a aparecer en el ?rea de salida de fantasmas. - Si pacman muere tres veces acaba la partida. - Si pacman logra comerse todos los cocos de un escenario pasa al siguiente escenario. - Si pacman come una fruta especial suma 10 puntos a su marcador de puntuaci?n. Escenario y elementos que intervienen: Esta historia se desarrolla en un escenario y con unos elementos que vamos a definir. - Un escenario de estructura laber?ntica cuyos pasillos est?n sembrados de cocos. Definimos de cuantos escenarios constar? nuestro juego: 4 - Un punto de inicio de pacman donde aparecer? el personaje que controla el jugador al inicio de cada pantalla y donde reaparecer? si este muere. Definimos donde estar? el punto de inicio: En el centro, en la parte inferior de la pantalla. - Un ?rea de salida de fantasmas que ser? el punto de inicio de los fantasmas. Desde all? empiezan a perseguir al comecocos al inicio de cada pantalla y all? aparecer?n cuando mueren. Definimos donde estar? el ?rea de salida de los fantasmas: En la parte superior de la pantalla, en el centro. Definimos el n?mero de fantasmas: 4 (uno de cada color: verde, azul, rojo, lila). Definimos cuantos supercocos habr? en cada escenario y donde se encontrar?n: 4, en las esquinas. Todos estos elementos que hemos visto se encuentran en el ?rea de juego, que es donde se desarrolla la acci?n. Hay otra ?rea, el ?rea de marcadores, que se encontrar? en la parte superior de la pantalla y donde colocaremos los siguientes elementos: - Marcador de vidas restantes. Nos indica cuantas vidas nos quedan. Empezamos la partida con 3 vidas.
- Marcador de puntuaci?n. Nos indica en todo momento nuestra puntuaci?n. En la creaci?n de un videojuego intervienen tres apartados bien diferenciados que tendremos que tener en cuenta a la hora de planificar nuestro videojuego: El apartado gr?fico, el apartado sonoro y el apartado de programaci?n. Veamos que necesitaremos para nuestro juego en cada apartado: APARTADO GR?FICO - Unos personajes: nuestro amigo el comecocos y los fantasmas que lo persiguen. - Unos objetos que no son otros que los cocos, los supercocos y las frutas que recolecta el comecocos por el escenario. - Unos muros que forman los pasillos del laberinto por donde se mueven los personajes. - Unas pantallas donde se encuentran el ?rea de juego y el ?rea de marcadores. - Un marcador de puntuaci?n que nos indica cuantos puntos llevamos en la partida. - Un marcador de vidas que nos indica las vidas restantes. APARTADO SONORO - Un tema musical para cada escenario. - Efectos sonoros para las siguientes acciones: comer coco, comer fantasma, morir, comer supercoco, acabar partida, pasar de pantalla. Para el apartado sonoro no nos complicaremos la vida. Bajaremos tanto los temas musicales como los efectos sonoros de Internet. APARTADO DE PROGRAMACI?N Esta es la parte m?s complicada. Necesitaremos programar unas rutinas que hagan lo siguiente: IA de los personajes
- Los fantasmas persigan al comecocos. - Si el comecocos esta s?per vitaminizado los fantasmas huyen de ?l. Rutinas de detecci?n de colisi?n de sprites - Si el fantasma intercepta al comecocos y este no esta s?per vitaminizado el comecocos muere. - Si el comecocos intercepta al fantasma sin estar s?per vitaminizado el comecocos muere. - Si el comecocos est? s?per vitaminizado y intercepta al fantasma este ?ltimo muere. - Si un fantasma topa con un muro del escenario no se queda bloqueado y sigue su camino persiguiendo al comecocos. - Si el comecocos intercepta un coco se lo come y sube 10 puntos el marcador de puntuaci?n. - Si el comecocos intercepta un supercoco se lo come y sube 50 puntos el marcador de puntuaci?n a la vez que puede comer fantasmas y estos huyen de ?l. Otros que ya iremos viendo. CREACI?N DE SPRITES CON GRAPHICSGALE Con el programa GraphicsGale procederemos a crear los siguientes elementos gr?ficos del juego: -
El Comecocos Los Fantasmas Los cocos Los supercocos La fruta
Todos los elementos los crearemos en un lienzo con una resoluci?n de 32 x 32 p?xeles a 8 bits (256 colores) y los guardaremos en formato tga. La creaci?n de los personajes en este caso es sencilla. Hemos de pensar que en el caso del comecocos y el fantasma debemos de crear una imagen diferente para cada pose: Cuando suben por un pasillo, cuando baja, cuando van hacia la derecha, cuando van hacia la izquierda.
??Manos a la obra!!! INTRODUCCI?N A GAMEMAKER. SPRITES, OBJETOS Y ROOMS. Game Maker es un programa con un intuitivo y f?cil interfaz que nos permite crear nuestros propios juegos r?pidamente. Podemos importar y crear im?genes, los sprites (im?genes animadas) y los sonidos y utilizarlos. Tambi?n podemos definir f?cilmente los objetos del juego e indicar su comportamiento, definir pantallas atractivas con fondos en movimiento. Mediante un sencillo lenguaje de programaci?n tendremos control completo sobre lo que va ocurriendo en nuestro juego. Esta es la pantalla que nos encontramos al ejecutar Game Maker. Vamos a explicar los diferentes elementos del interfaz. A la izquierda nos encontramos las carpetas siguientes: Sprites, Sounds, Backgrounds, Paths, Scripts, Fonts, Time Lines, Objects,Rooms. Nos centraremos de momento en las siguientes: Sprites: Las im?genes (animadas) que se utilizan para representar los objetos en nuestro juego. Objetos: Todas las cosas que se ven en el juego (a excepci?n del fondo) son objetos. Los personajes, los monstruos, las bolas, las paredes, etc. son todos los objetos. Tambi?n puede haber objetos que no se ven pero que controlan ciertos aspectos del juego. Tiene que quedar clara la diferencia entre los sprites y los objetos. Los Sprites son las im?genes (animadas) que no tienen ning?n comportamiento. Los objetos tienen normalmente un sprite para representarlas pero los objetos tienen un comportamiento. Rooms: Las pantallas en las que se desarrolla el juego. Sounds: Los sonidos que se utilizan, como la m?sica de fondo o los efectos sonoros. Backgrounds: Las im?genes usadas como fondo de las pantallas (Rooms). En el men? de archivo (file) puedes encontrar las opciones de cargar y guardar archivos, m?s algunas especiales:
Nuevo. Elije esta opci?n para comenzar a crear un nuevo juego. Abrir. Abre un archivo de un juego. Los archivos de los juegos tienen gm6 como extensi?n. Archivos Recientes. Utiliza este submen? para abrir de nuevo archivos del juego que se abrieron recientemente. Guardar. Guarda el dise?o del juego bajo su nombre actual. Si no se especific? ning?n nombre antes, te pide un nuevo nombre. Guardar como. Guarda el archivo del dise?o del juego bajo el nombre que le queramos dar. Crear Ejecutable. Una vez que tu juego este listo deber?s distribuirlo en forma de ejecutable para que otros puedan jugarlo. Modo Avanzado. Cambia entre el modo simple y avanzado. En modo avanzado los comandos y los recursos adicionales est?n disponibles. El modo avanzado s?lo se encuentra disponible en la versi?n de pago de GameMaker. Salir. Para salir de GameMaker. El men? de edici?n (Edit) contiene un n?mero de opciones que se relacionan con el recurso actualmente seleccionado (objeto, sprite, sonido, etc.). Dependiendo del tipo de recurso algunos de las opciones pueden no estar disponibles. Insertar. Inserta un nuevo elemento del tipo que este actualmente seleccionado. Se abrir? una ventana en la cual podremos cambiar las caracter?sticas de este elemento. Duplicar. Hace una copia del elemento actualmente seleccionado y la agrega. Borrar. Suprime el elemento actualmente seleccionado (o el grupo de elementos). Renombrar. Para dar al elemento un nuevo nombre. Propiedades. Utiliza esta opci?n para cambiar las propiedades de cualquier elemento. El men? de agregar (Add) nos permite agregar nuevos elementos de cada uno de los diferentes tipos: Sprites, sonidos, fondos…
El men? ejecutar (Run) se utiliza para hacer funcionar el juego. Hay dos maneras de hacer funcionar un juego. Funcionamiento normal o en modo debug para revisar el funcionamiento del juego paso a paso y corregir errores. A?ADIENDO SPRITES Para a?adir un sprite, elije a?adir Sprite del men? a?adir, o utiliza el bot?n correspondiente en la barra de herramientas. Te aparecer? una ventana. En esta ventana podr?s indicar el nombre del sprite. Todos los sprites (y el resto de los elementos que intervienen en nuestro juego) tienen un nombre que tendremos que dar de forma descriptiva. Para cargar el gr?fico de un sprite, haz click en el bot?n “Load Sprite”. Una vez cargada la imagen se demuestra a la derecha. La opci?n “transparente” indica si el fondo rectangular de la imagen del sprite se debe considerar como transparente. La mayor?a de los sprites son transparentes. Con el bot?n Editar Sprite puedes corregir el sprite, o crear uno totalmente nuevo. DEFINIENDO LOS OBJETOS DEL JUEGO Los objetos son los elementos del juego que hacen cosas. La mayor?a del tiempo tienen un sprite como representaci?n gr?fica de modo que se pueden ver. Tienen comportamiento porque pueden reaccionar a ciertos acontecimientos. Todas las cosas que ves en el juego (a excepci?n del fondo) son objetos. Los personajes, los monstruos, las bolas, las paredes, son todos objetos. Tambi?n puede haber ciertos objetos que no se ven pero que controlan ciertos aspectos del juego. Para Agregar un objeto a tu juego, elije agregar objeto del men? de agregar (Add). Aparecer? la siguiente pantalla: A la izquierda hay informaci?n de car?cter general sobre el objeto. En el centro hay la lista de los eventos que pueden suceder al objeto. En la derecha hay las diversas acciones que el objeto puede realizar. Lo primero que tienes que hacer es darle al objeto un nombre. Despu?s elegir el sprite para el objeto. Luego indicaras si el objeto es visible (en la mayor?a de los casos si lo es) y si es s?lido (como una pared). CREANDO LAS PANTALLAS (ROOMS) Elegimos “a?adir room” del men? de a?adir (Add). Indicaremos el tama?o de las celdas de la rejilla. Tambi?n podemos indicar si queremos que la rejilla se muestre o si no, si queremos que el fondo se muestre o que quede oculto, etc. A veces es ?til ocultar temporalmente ciertos elementos del cuarto para trabajar mejor.
Deberemos fijar la medida de la pantalla. Esto se hace desde la pesta?a “Settings”. Desde la pesta?a “Objects” a?adiremos todos los objetos a la pantalla. Desde la pesta?a “Backgrounds” podremos a?adir las im?genes de fondo para la pantalla. A la derecha de la ventana vemos la pantalla. Al principio esta vac?a y s?lo vemos un fondo gris. Para agregar objetos al cuarto, primero selecciona la pesta?a de los objetos, despu?s el objeto que desees agregar. La imagen del objeto aparecer? a la izquierda. Ahora selecciona con el bot?n izquierdo del rat?n el sitio de la pantalla (a la derecha) donde quieres que aparezca el objeto. Veras como el objeto aparece alineado a la rejilla. Puedes borrar objetos de la pantalla haciendo click sobre ellos con el bot?n derecho del rat?n. Puedes arrastrar objetos por la pantalla si mantienes pulsado el bot?n “Control” del teclado m?s el bot?n izquierdo del rat?n. Desde la pesta?a “settings”, adem?s de fijar la anchura y la altura de la pantalla podemos fijar la velocidad del juego. ?ste es el n?mero de frames por segundo. Cuanta m?s alta es la velocidad, mas suave es el movimiento. Tambi?n deberemos fijar un nombre y una descripci?n para cada una de las pantallas que creemos. Sigue las indicaciones del profe. Procederemos a crear las pantallas de nuestro juego. PROPIEDADES DE LOS OBJETOS: EVENTOS Y ACCIONES A?ADIENDO MUSICA Y SONIDO SCORE E INDICADOR DE VIDAS EVENTOS Game Maker emplea lo que se conoce como programaci?n orientada a eventos. Esto es, en todo tipo de situaciones las instancias de los objetos reciben eventos (como mensajes que indican que algo ha sucedido). Entonces los objetos pueden reaccionar a estos mensajes ejecutando ciertas acciones. Para cada objeto debes indicar a qu? eventos responder? y qu? acciones debe realizar. Puede parecer complicado pero en realidad es bastante sencillo. Primero que nada, para la mayor?a de los eventos los objetos no tienen que hacer nada. Para los eventos donde algo suceda puedes usar muy simples comandos de arrastrar y soltar para indicar las acciones. En medio de la ventana de propiedades de objeto hay una lista de los eventos a los cuales el objeto puede reaccionar. Al principio est?
vac?a. Puedes agregar eventos presionando el bot?n Add Event. Aparecer? un peque?o men? con todos los diferentes tipos de eventos. Aqu? debes seleccionar el evento que deseas agregar. En ocasiones se mostrar? un nuevo men? con opciones extra. Por ejemplo, para el evento del teclado debes seleccionar la tecla. M?s abajo encontrar?s una completa lista con descripciones de los eventos. Seleccionaremos un evento de la lista. Este ser? el evento que modificaremos. Puedes cambiar el evento seleccionado haciendo clic sobre ?l. A la derecha est?n todas las acciones representadas por peque?os iconos. Se encuentran agrupadas en varias p?ginas / fichas de opciones. Entre los eventos y las acciones se encuentra la lista. Esta lista contiene las acciones para el evento actual. Para agregar acciones a la lista, arr?stralas desde la derecha a la lista. Ser?n colocadas una bajo la otra, con una breve descripci?n. Para cada acci?n se te pedir?n algunos par?metros (Las acciones). Despu?s de agregar algunas acciones, tendr?as algo como esto: Ahora puedes agregar acciones a otro evento. Haz clic con el bot?n izquierdo del rat?n sobre el evento adecuado para seleccionarlo y arrastra la acci?n a la lista. Puedes cambiar el orden de las acciones en la lista arrastrando los iconos. Si mantienes presionada la tecla mientras arrastras una acci?n, crear?s una copia de dicha acci?n. Puedes inclusive arrastrar acciones entre diferentes listas de diferentes objetos. Cuando haces clic con el bot?n derecho sobre una acci?n, se muestra un men? desde el cual puedes eliminar la acci?n (tambi?n puedes hacer esto usando la tecla ), o copiar y pegar acciones. Cuando mantienes el cursor del rat?n sobre una acci?n, se muestra una descripci?n m?s detallada sobre la misma. Para eliminar el evento seleccionado y todas sus acciones, presiona el bot?n Delete. (Los eventos sin acciones son eliminados autom?ticamente cuando cierras la ventana por lo que no hay necesidad de que lo hagas tu mismo). Si deseas asignar las acciones a un evento diferente (porque por ejemplo, has decidido emplear una tecla diferente para las acciones) presiona el bot?n Change y selecciona el nuevo evento. (?El nuevo evento no debe haber sido empleado antes!). Como se mencion? arriba, para agregar un evento, presiona el bot?n Add Event. Se muestra la siguiente ventana: Aqu? seleccionas el evento que deseas agregar.
ACCIONES Las acciones indican lo que sucede en un juego creado con el Game Maker. Las acciones se colocan en los eventos de los objetos. Cuando el evento ocurre estas acciones se llevan a cabo, resultando en cierto comportamiento para las instancias del objeto. Hay una gran cantidad de acciones disponibles y es importante que entiendas lo que hacen. Todas las acciones se encuentran en las p?ginas / fichas a la derecha de la ventana de propiedades de objeto. Hay siete grupos de acciones. Puedes ver el grupo haciendo clic en la ficha correcta. A la derecha de la ventana de propiedades de objeto podemos ver las siete pesta?as correspondientes a los diferentes tipos de acciones. Cuando mantienes el rat?n sobre una de las acciones, se muestra una breve descripci?n para recordar su funci?n. Para colocar una acci?n en un evento, solo arr?strala de las p?ginas / fichas a la lista de acci?n. Puedes cambiar el orden de la lista, arrastrando y soltando los iconos. Si presionas la tecla mientras arrastras puedes hacer una copia de la acci?n. (Puedes arrastrar y copiar acciones entre las listas en diferentes ventanas de propiedades de objetos). Usa el bot?n derecho del rat?n para borrar acciones (o usa la tecla ) o para copiar y pegar acciones. Cuando colocas una acci?n, la mayor?a de las veces aparece una ventana de di?logo, en la cual puedes especificar ciertos par?metros para la acci?n. Dos tipos de par?metros aparecen en muchas acciones. En la parte superior puedes indicar a qu? instancia aplica la acci?n. El valor por defecto es self, que es la instancia para la cual se est? realizando la acci?n. La mayor?a de las veces esto es lo adecuado. En el caso de un evento collision, puedes especificar tambi?n si se aplica la acci?n a la otra instancia involucrada en la colisi?n. De esta forma puedes por ejemplo destruir la otra instancia. Finalmente, puedes elegir si aplicas la acci?n a todas las instancias de un objeto. De esta manera puedes por ejemplo cambiar todas las pelotas rojas por azules. El segundo tipo de par?metro es la casilla marcada Relative. Al marcar esta casilla, los valores que introduzcas ser?n relativos a los valores actuales. Nota del autor: La descripci?n de los eventos y las acciones est? sacada de la traducci?n del manual original de Game Maker 5.0 del ingl?s al castellano realizada por ZonaMakers.com
Iremos viendo los eventos y acciones a medida que los vayamos utilizando. A?ADIENDO MUSICA Y SONIDO Deberemos bajar 4 temas musicales en formato mid, uno por cada nivel de los que consta nuestro juego. Estos temas musicales seran las m?sicas de fondo de los diferentes niveles. En la siguiente direcci?n de Internet encontrareis gran variedad de archivos mid: http://www.midiworld.com/ Bajaremos tambi?n efectos sonoros para cada una de las siguientes acciones: - Para cuando el comecocos come un coco - Para cuando el comecocos come un supercoco - Para cuando el comecocos se come a un fantasma - Para cuando el comecocos se come una cereza - Para cuando el fantasma se come al comecocos - Para cuando pasamos de nivel En la siguiente direcci?n de Internet encontrareis tambi?n multitud de efectos de sonido que bajaremos en formato wav: http://www.flashkit.com/soundfx/ Despu?s a?adiremos las m?sicas y las carpetas dentro de la secci?n Sounds de la misma forma que a?ad?amos las im?genes dentro de la secci?n Sprites (bot?n derecho del rat?n encima de Sounds y “Add Sound”). Crearemos una carpeta para los efectos sonoros tal como vemos en la imagen de abajo (“Add Group”). Ahora trabajaremos con las m?sicas de fondo, los archivos mid. Los efectos sonoros se manejan por medio de acciones, por lo que los dejaremos para m?s adelante. Para incluir una m?sica de fondo en uno de nuestros niveles (pantallas) basta con ir a esa pantalla (“Room”) y hacer clic en la pesta?a “Settings”. Dentro de esta pantalla haremos clic en el bot?n “Creation code” y a?adiremos lo siguiente: Donde “Fase1” es el nombre que di?ramos a el archivo mid que corresponde a la primera pantalla. La instrucci?n “sound loop” indica que la m?sica se repita cuando el tema llegue a su fin de forma que no acabe de sonar hasta que no
indiquemos lo contrario con otra instrucci?n. Repetiremos la operaci?n con cada una de las pantallas (“rooms”) SCORE E INDICADOR DE VIDAS A continuaci?n haremos la parte de la pantalla donde aparecen la puntuaci?n del jugador (“score”) y las vidas que le quedan (“indicador de vidas”) para finalizar la partida. - Crearemos e insertaremos en nuestro juego un sprite que servira de fondo a la zona donde se mostraran los indicadores de score y vidas. Se llamar? “controlvidas”. El sprite no ser? m?s que una imagen igual de ancha que nuestra room y que no sea m?s alta que la zona que habilitamos cuando hicimos la habitaci?n para dar cabida a estos indicadores. En una room de 800 por 600 pixeles la imagen tendr?a unas dimensiones de 800 pixeles de ancho por 48 de alto aproximadamente: Dentro de las propiedades del sprite s?lo debe de estar marcada la casilla “Preload texture”. - Crearemos un objeto con el sprite “controlvidas” F?jate en las propiedades del objeto. Tienes que quedar igual: - Visible - Depth -10000 - Persistente (El objeto no se destruye al pasar de un nivel a otro, la puntuaci?n sigue sumando en vez de empezar de cero en cada pantalla). Iremos explicando cada opci?n en clase. A continuaci?n seguiremos los siguientes pasos: Clic en “Add Event” y luego en “Create”. Haremos clic en la pesta?a “Score” Arrastraremos la acci?n “Set the escore” a la ventana de “Actions”. Haremos doble clic en el icono y daremos un valor de inicio al score de 0. Arrastraremos la acci?n “Set the number of lives” a la ventana de “Actions”. Haremos doble clic en el icono y fijaremos las vidas del jugador en 3. Arrastraremos la acci?n “Set the Windows caption info” a la ventana de “Actions”. Haremos doble clic en el icono y la editaremos de forma que quede as?: A?adiremos otro evento (clic en “Add Event” y luego en “Other”) Dentro del men? que se nos aparece seleccionaremos “No more lives”. As? le indicaremos al programa qu? tiene que hacer cuando al jugador no le queden m?s vidas.
Arrastramos a la ventana de “Actions” las siguientes acciones: “Show the high score table” de la pesta?a “Score”. “Restart the game” de la pesta?a “main 2”. De esta forma le indicamos al programa que si el jugador se queda sin vidas le muestre la pantalla de records y luego vuelva a empezar otra partida reiniciando el juego. Ahora a?adimos un nuevo evento: “Draw” Una vez lo hayamos hecho arrastramos las siguientes acciones a su ventana de acciones: “Draw a sprite image” de la pesta?a “Draw”. Lo editamos (doble clic) de forma que quede as?: A continuaci?n arrastramos las siguientes acciones (pesta?a “Score”): “Draw the value of score” “Draw the lives as image” As?, mediante las coordenadas (x,y) indicamos la posicion en pantalla de los indicadores de puntuaci?n y de vidas. Otras acciones que podemos a?adir para este evento son: “Set the font” “Set the color” De esta forma podremos variar el tama?o y color del indicador de puntuaci?n(“Score”). Previamente tendremos que haber a?adido una fuente en “fonts”. Vamos a probar a?adiendo la fuente “Jokerman” a tama?o 18. A continuaci?n editamos el primero de nuestros niveles. En las propiedades de room, “objects” seleccionamos el objeto “controlvidas” y lo colocamos en alg?n lugar de la pantalla como vemos en la imagen (es el circulito con el interrogante): Veamos como va quedando nuestro juego (apretamos tecla F5 para ejecutarlo). PREPARADO LAS ROOMS Colocaremos nuestros objetos en sus posiciones de inicio en las “rooms”: Seleccionamos cada objeto desde la pesta?a “Objects” de la ventana de edici?n de la room, mediante el men? de la parte inferior: Una vez seleccionado tan solo debemos hacer clic en el lugar de la “room” donde deseemos colocar dicho objeto. EVENTOS Y ACCIONES DE NUESTROS OBJETOS A estas alturas deber?amos haber definido los siguientes objetos: Veamos las propiedades, eventos y acciones de cada uno: Ladrillo: Visible. S?lido. Coco: Visible. Supercoco: Visible. Comecocos: Visible, Depth -1
CREATE Create > Change sprite into comecocos_quiet Indicamos que el comecocos aparece inicialmente con la forma del sprite “Comecocos_quiet” y a velocidad 0.5. STEP Step>If the number of instances is a value Indicamos que si el numero del objeto coco es igual a 0 Inicio de bloque Ejecutar sonido de “nivel acabado” Sleep 2000 miliseconds (esperar 2 segundos). Si existe una siguiente room…. Ir a la siguiente room… Aqu? podemos definir una transici?n: Un efecto que se produce al cambiar de pantalla. De lo contrario… (De lo contrario, de no haber mas rooms significaria que el jugador ha terminado el juego, por lo tanto…) Inicio de un bloque. Ense?ar la tabla de records. Reiniciar el juego. Final del bloque 1. Final del bloque 2. COLLISION EVENT WITH OBJECT LADRILLO Si el comecocos colisiona con un ladrillo: (con speed 1) COLLISION EVENT WITH OBJECT COCO Cuando el comecocos se come un coco que suene el sonido de comerse un coco. (Applies to other) Se destruye el coco. (Relative) Se aumenta el marcador de puntos en 10. COLLISION EVENT WITH OBJECT FANTASMA Que suene el sonido de muerte del comecocos Esperar segundo y medio. Que todos los fantasmas vuelvan a la posici?n de inicio. Que todos los fantasmas miedosos vuelvan a la posici?n de inicio.
(Relative)
COLLISION EVENT WITH OBJECT FANTASMA_ASUSTADO
COLLISION EVENT WITH OBJECT SUPERCOCO
MOVIMIENTO DEL COMECOCOS KEYBOARD EVENT FOR LEFT Obligamos al objeto a estar centrado en la cuadricula de nuestra room.
… Si el objeto esta alineado con la cuadricula (32*32) y apretamos la flecha izquierda del teclado el objeto avanza hacia la izquierda a velocidad 4. El sprite que representa al objeto pasa a ser “comecocos_izq” (comecocos avanzando hacia la izquierda). Procederemos a realizar los mismos pasos con Up, Rigth y Down. OUTSIDE ROOM Si el objeto sale fuera de la pantalla ejecutar el script outside_wall (o como se llame nuestro script). Fantasmas (para todos) CREATE Cambia el sprite que representa al objeto a “normal”, esto es, a cuando el comecocos no ha comido un supercoco.
STEP (32*32) Si el objeto est? alineado con la rejilla ejecutar el script “adap_direction” que indica al fantasma hacia donde tiene que moverse.
COLLISION EVENT WITH OBJECT LADRILLO Si el fantasma colisiona con un ladrillo…
Cambia la direcci?n hacia el lado contrario, tanto horizontal como verticalmente.
OUTSIDE ROOM Si el objeto sale fuera de la pantalla ejecutar el script outside_wall (o como se llame nuestro script). FANTASMA ASUSTADO ALARM 0
BONUS CREATE
ALARM 0
ALARM 1
COLLISION EVENT WITH OBJECT COMECOCOS
[b]SCRIPTS[/b] OUTSIDE_WRAP { if (x < 0 && hspeed <0> room_width && hspeed > 0) x = sprite_width + sprite_xoffset; if (y < 0 && vspeed <0> room_height && vspeed > 0) y = sprite_height + sprite_yoffset; } ADAPT_DIRECTION
{ if (hspeed == 0) { if (random(3)<1 && place_free(x-4,y)) { hspeed = -4; vspeed = 0;} if (random(3)<1 && place_free(x+4,y)) { hspeed = 4; vspeed = 0;} } else { if (random(3)<1 && place_free(x,y-4)) { hspeed = 0; vspeed = -4;} if (random(3)<1 && place_free(x,y+4)) { hspeed = 0; vspeed = 4;} } }
Glosario.
A •
alpha Transparencia. Es un parámetro que se utiliza en las funciones de dibujo avanzadas y que indica el nivel de transparencia de lo que se está dibujando: 0 es totalmente transparente y 1 es totalmente opaco. Los valores intermedios se usan para dibujar elementos parcialmente transparentes.
•
argumento Los argumentos son los datos que se le pasan a una función para que ésta opere con ellos. Por ejemplo, una función que sume dos números necesitará que le digas que números tiene que sumar. Esos números son los argumentos de la función.
B •
background Fondo. Es la imagen que se pone de fondo al room para que sea el escenario. Puedes agregar varias imágenes distintas y hacer que cada una se mueva a diferente velocidad cuando el personaje avance, haciendo que las imágenes más cercanas se muevan más rápido que las más lejanas. Así consigues un efecto de profundidad muy vistoso. Esta técnica se llama scroll parallax.
•
blending Ver image blending.
•
bmp Es un tipo de archivo de gráficos de alta calidad (mapa de bits). Sólo es aconsejable utilizar bmps para fondos transparentes.
•
boolean Booleano. Es un tipo de variable que sólo puede tener dos valores: true o false. Es decir, si una variable booleana no es true entonces es false.
D
•
depth Profundidad. Es una propiedad de las instancias que sirve para decidir cuál debe dibujarse por encima de las demás cuando chocan, cuál se sitúa primero en el room...Puede ser positiva, negativa ó 0 y cuanto menor sea más al frente se situará la instancia.
F •
false Falso.
•
fps Frames por segundo. No es lo mismo que la room speed. La room speed es el valor ideal al que
En Game Maker false=0, es decir, es lo mismo poner false que poner 0.
queremos que el juego se actualice. Sin embargo, si ponemos muchas acciones en el evento step el juego puede ralentizarse, o si usamos unos sprites de mucha calidad puede que el juego necesite mucha memoria...esto provoca que el juego se ralentice y en lugar de dibujar todos los frames que queremos en cada segundo dibuje menos. Los fps son la medida real de la velocidad del juego en cada segundo. Te interesará que los fps estén siempre lo más cercano posible a la room speed.
•
frame Para generar la ilusión de animación, lo que se hace es dibujar la pantalla muchas veces (por ejemplo 60 veces por segundo) haciendo que su contenido cambie poco a poco. Así, al ver esos cambios de manera seguida parece que la imagen se esté moviendo. Cada instante en el que se dibuja la pantalla se denomina frame.
•
frame rate Es la velocidad con la que se pasa de un frame a otro. Cuanto mayor sea, más fluidas se verán las animaciones en la pantalla, pero se requerirá más potencia.
G •
gif Es un tipo de archivo de gráficos comprimido y animado. Ideal para sprites.
•
height Altura. Es un argumento muy común en las funciones de gráficos.
•
highscore El marcador más alto, el récord de puntuación obtenido en el juego. Puedes mostrar una lista
H
con las puntuaciones más altas (highscore list) con la acción correspondiente.
I •
image blending
Mezclado de imágenes. Cuando dos imágenes se superponen (por ejemplo, cuando
chocan dos sprites) el programa debe decidir qué es lo que va a dibujar. Si la instancia de uno de los sprites tiene menor profundidad que el otro se dibujará éste por encima. Pero si tiene mayor depth se dibujará por debajo. Si uno de los sprites es parcialmente transparente, se verá parte del otro. Si el modo de mezcla está en suma se sumarán los colores de los dos sprites en la zona en la que chocan (por ejemplo para causar un efecto de brillo), pero si está en modo resta se restarán. Como ves, en la parte de los sprites que choca se decide qué hay que hacer según el modo de mezcla que esté activo (esto también afecta a los backgrounds). Con esta técnica se pueden crear efectos visuales muy espectaculares.
J •
jpg Es un tipo de archivo de gráficos de tamaño reducido, pero que no permite transparencias. Ideal para usarlo en fondos no transparentes o para sprite sheets.
L •
left Izquierda. Es un argumento muy común en las funciones que deben especificar una posición para indicar dónde debe situarse el borde izquierdo.
M •
midi Es un formato de sonido de muy pequeño tamaño. Los midis tienen menos calidad que los wav o mp3. Sin embargo su tamaño es mucho menor, lo que hace que no consuman casi memoria. Esto hace que los midis sean ideales para las músicas de fondo del juego.
P •
path Camino o trayectoria. Game Maker te permite dibujar una trayectoria para que luego la siga una instancia. Así puedes hacer que un enemigo se mueva siguiendo este camino, o hacer que en un juego de coches los participantes sigan la carretera.
•
png Un tipo de archivo de gráficos de tamaño muy reducido creado para ser transmitido por Internet (png significa "portable network graphics" o "gráficos transportables por la red").
•
pop-up Son los mensajes o ventanas que aparecen de repente y sirven para dar o pedir información al jugador. Por ejemplo, una ventana que se abra y le pregunte al jugador cómo se llama.
R •
rar Es un tipo de archivo comprimido. Los archivos tipo rar consiguen una gran compresión aunque este formato no está tan extendido como el zip (ver zip).
•
recurso Es lo que utiliza el juego. En Game Maker los recursos se dividen en las siguientes categorías: sprites, backgrounds, sonidos y música, paths, scripts, time lines, fuentes, objetos y rooms.
•
room Habitación o cuarto. En el room es donde se sitúan todos los objetos y donde todo el juego tiene lugar. Un juego puede tener varias rooms y cada una puede ser un nivel distinto, una pantalla de menú, de ayuda...
•
room speed Es el frame rate que queremos que tenga un cuarto y se mide en frames por segundo. Por ejemplo, una room speed igual a 30 significa que en cada segundo se dibujan 30 frames.
S
•
score
Marcador. En el juego, puedes hacer que el marcador se incremente cuando el jugador recoja
monedas u otros objetos.
•
screenshot Es una foto de la pantalla en un momento determinado. Los screenshots son muy útiles para promocionar tu juego o para usarlos en tablas de récords.
•
script
Es un trozo de código que puede recibir argumentos y devolver un valor. Los scripts se utilizan
para generalizar código repetido y así ahorrar espacio y ganar velocidad.
•
scroll Es el movimiento del fondo. Game Maker te permite añadir varios fondos distintos y asignar un movimiento a cada uno, para dar la impresión de que el decorado avanza cuando el personaje se mueve.
•
sprite Un sprite es la imagen que se asocia a un objeto y que se dibuja en la pantalla. Los sprites suelen tener varias imágenes, de forma que si en cada momento se dibuja una imagen distinta parece que se mueve. El formato más utilizado para los sprites es el de los archivos gif.
•
sprite sheet Una sprite sheet (o strip) es una imagen no animada de gran tamaño en la que aparecen muchas imágenes de un personaje (o varios) mostrando todos los frames de sus animaciones. El editor de sprites de Game Maker permite crear sprites animados fácilmente a partir de una sprite sheet.
•
step
Paso. Los juegos que crees con Game Maker se ejecutan por pasos, de forma que si haces que se
pase de un paso a otro rápidamente dará la impresión de que todo se ejecuta continuamente (Ver frame).
•
strip Ver sprite sheet.
•
tile Para hacer un fondo, a veces es bueno utilizar imágenes pequeñas que, puestas una al lado de otra,
T
forman un dibujo. Esta técnica se denomina tiling. Por ejemplo: divides el room en casillas del mismo tamaño y en cada una pones una imagen más pequeña (en una un árbol, en otra una casa,...) y haces que se repitan para llenar todo el room. Así puedes conseguir fondos muy originales y que consumen muy poca memoria.
•
tile set Ver tile sheet.
•
tile sheet Es una imagen muy grande de la que se pueden extraer imágenes más pequeñas para crear tiles.
•
time line Línea de tiempo. En una time line puedes añadir acciones en momentos concretos, de forma que estas acciones se ejecutarán en el orden que tú decidas y justo cuando quieras.
•
top Arriba. Es un argumento muy común en las funciones que deben especificar una posición para indicar dónde debe situarse el borde superior.
•
true Verdadero. En Game Maker true=1, es decir, es lo mismo poner true que poner 1.
•
wav Son archivos de sonido de gran calidad, pero que por lo tanto ocupan mucho tamaño y consumen
w bastante memoria. Los archivos wav se suelen utilizar para los efectos de sonido (explosiones, gritos, disparos...).
•
width Anchura. Es un argumento muy común en las funciones de gráficos.
•
zip Es un tipo de archivo comprimido muy corriente. Al comprimir tu juego, conseguirás que ocupe menos
Z
espacio y así podrás distribuirlo más fácilmente por la red.
Este tutorial es cortesía de www.tutoek.tk www.comunidadgm.org
y Gracias a la gran comunidadgm