Si estás leyendo esto te preguntarás acerca de los mecanismos que existen bajo WME, o eres Mnemonic comprobando las meteduras de pata que escribo .
Hoy he decidido guiarte a través de los scripts que incluyen los ejemplos de WME y mostrarte cómo funcionan. Pero empecemos, porque hay bastantes cosas que cubrir.
Vete a la carpeta projects/wme_demo/data del kit de desarrollo WME y empezaremos por ahí.
Verás tres archivos, que son default.game, startup.settings and string.tab.
default.game - es generado por el Project Manager y contiene la configuración básica del juego. Puedes ajustar estos valores en la ventana izquierda del Project Manager, donde puedes editarlos a mano. Este es el método recomendado a los principiantes.
startup.settings - es generado por el Project Manager y contiene la configuración de la ventana de inicio y ajustes básicos, como la ruta del registro de Windows, etc. Las reglas de edición son las mismas que default.game.
string.tab - es muy importante para la localización del juego a otros idiomas. En resumen, es un archivo que contiene una lista de ID - textos. En el juego puedes utilizar estas cadenas de texto con la siguiente instrucción:
actor.Talk("/TXT0001/Hola");
Normalmente, Molly dice Hola, pero si en el archivo string.tab tienes una línea como esta:
TXT0001[TAB]Bon giorno.
será escrita en su lugar. Ten en cuenta que, en algunos casos, la cadena de texto necesita ser traducida a la fuerza.
Game.Msg("/TXT0001/Hola"); // Esto muestra /TXT0001/Hola // Para esto, usaremos la función Game.ExpandString Game.Msg(Game.ExpandString("/TXT0001/Hola.")); // que mostrará el texto correcto, y además es la // forma correcta de manipular cadenas
Así que, cuando pienses en manipular cadenas de texto que serán traducidas a otros idiomas, lo mejor es usar esta función al principio.
Bien. Ahora nos vamos a ir a la carpeta scripts. Aquí encontrarás tres archivos include. Estos son base.inc, const.inc y keys.inc.
base.inc está incluído por defecto en todos los scripts que creas, y debe contener las variables globales del juego. He escrito sobre esto en el tutorial Variables y objetos. base.inc también incluye el archivo const.inc, que tiene las constantes del juego y es mejor mantenerlas separadas de las variables. Ten en cuenta que también son variables, pero son reiniciadas en cada inclusión de script. Así que no caigas en la trampa de crear una variable global (que no va a ser constante) en base.inc. De lo contrario, haría una bonita constante con ella. keys.inc es para la definición de algunos códigos de teclado especiales. Afortunadamente, Mnemonic ya encontró estos números y nos ha ahorrado algunos problemas.
Una vez resuelto el tema de los archivos include, vamos a algo más serio. Cuando nuestro juego arranca, WME inicializa el objeto Game principal y ejecuta el script que tiene vinculado (puedes cambiar el nombre de este archivo en el Project Manager, en la ventana izquierda, en Game Settings → Scripts). Aquí es donde comenzamos, así que abre el archivo y vamos a echarle un vistazo línea a línea.
#include "scripts\base.inc" // cosas básicas #include "scripts\keys.inc" Keyboard = Game.Keyboard; Scene = Game.Scene;
También incluímos el archivo keys.inc, porque vamos a comparar algunos códigos de teclado y los nombres simbólicos son más fáciles de recordar que los números.
A continuación, inicializamos dos variables globales (debes conocerlas de base.inc, donde están definidas), y lo hacemos así porque WME no soporta anidar funciones, así que no puedes escribir Game.Scene.GetEntity(); por ejemplo.
// carga el menú del botón derecho del ratón global WinMenu = Game.LoadWindow("interface\menu\menu.window"); WinMenu.Visible = false; // carga el título de la ventana var win = Game.LoadWindow("interface\system\caption.window"); global WinCaption = win.GetWidget("caption"); // carga las pistas de la demo global WinHints = Game.LoadWindow("interface\demo\demo_hints.window"); WinHints.Visible = true; // carga los créditos global WinCredits = Game.LoadWindow("interface\credits\credits.window"); WinCredits.Visible = true; global MenuObject = null;
Además, ya ha sido comentado por Mnemonic, el comportamiento de la ventana se define en los archivos de la ventana, no en game.script, y están vinculados a las ventanas a través de archivos separados. Por último, hemos preparado un objeto vacío con el que trabajaremos más tarde.
// carga nuestro actor principal actor = Game.LoadActor("actors\molly\molly.actor"); Game.MainObject = actor;
Ahora vemos que MainObject es nuestro actor principal, pero podemos cargar tantos actores como deseemos y cambiar entre ellos con Game.MainObject, así conseguimos el cambio de actor principal. Ten en cuenta que para los modelos en tiempo real necesitas usar Game.LoadActor3D(); en su lugar.
Y ahora, vayamos a la línea más importante:
// ejecuta el script "daemon" Game.AttachScript("scripts\game_daemon.script");
Como debes saber, si vinculamos un script al objeto Game, será válido para todo el juego así que los scripts que definamos aquí serán usados durante toda la aventura. Podemos vincular tantos como deseemos, pero en esta demo sólo hay uno. Ya le echaremos un vistazo de cerca más adelante.
// objetos iniciales Game.TakeItem("money");
De nuevo, está bastante claro. Pero recuerda que debes tener un actor principal vinculado antes de intentar darle dinero ("money").
Game.ChangeScene("scenes\room\room.scene");
Al final conseguimos algo presentable: cargamos nuestra primera escena que está definida en el Scene Manager. La siguiente parte del game.script es la definición de los eventos del juego.
on "LeftClick" { // ¿qué hemos seleccionado? var ActObj = Game.ActiveObject; if(ActObj!=null) { // seleccionando un objeto del inventario con el ratón if(ActObj.Type=="item" && Game.SelectedItem==null) { Game.SelectedItem = ActObj; } // usando un objeto del inventario sobre otro else if(Game.SelectedItem != null && Game.SelectedItem!=ActObj) { var Item = Game.SelectedItem; if(ActObj.CanHandleEvent(Item.Name)) ActObj.ApplyEvent(Item.Name); else if(Item.CanHandleEvent("default-use")) Item.ApplyEvent("default-use"); else if(ActObj.CanHandleEvent("default-use")) ActObj.ApplyEvent("default-use"); else actor.Talk("I can't use these things together."); } // sólo es un click else ActObj.ApplyEvent("LeftClick"); } // de lo contrario, envía el evento LeftClik a la escena else { Scene.ApplyEvent("LeftClick"); } }
La primera comprobación es para el evento del botón izquierdo del ratón. Game.ActiveObject devuelve el objeto que hay bajo el cursor del ratón, así que lo asignamos a una variable. Si es null, quiere decir que no hay ningún objeto, así que enviamos el evento a la escena para casos especiales (como el control de regiones en el archivo scene.script). Si, por el contrario, recibimos un objeto, entonces ejecutamos los if's para saber qué hacer con él. Resumiendo: si el objeto está en el inventario, lo asignamos como objeto activo para manipularlo después. Si pinchamos con un objeto del inventario sobre otro objeto de la pantalla, hacemos otras comprobaciones. Primero nos aseguramos de si el objeto sobre el que pinchamos tiene un método con el nombre del objeto definido. Si tuviésemos una manzana en la pantalla, cogemos un cuchillo del inventario y lo usamos sobre la manzana, entonces es de esperar que haya un controlador on "cuchillo" {} .
La parte del Else If tiene un evento definido como on "default-use" { } (uso por defecto), y será ejecutado (puede ser utilizado para acciones por defecto, como "¡no quiero cortar eso!" o "¡está ATASCADA!") si la acción que queremos hacer no está definida en el objeto. El juego buscará el evento on "default-use" { } , si no existe mostrará algún mensaje estándar como No puedo usar estas cosas juntas..
Si pinchamos en un objeto sin haber seleccionado otro objeto del inventario, entonces enviaremos un evento "LeftClick" al script vinculado a la región (es de esperar que tenga definido el evento on "LeftClick" {} para poder hacer algo).
on "RightClick" { // ¿está seleccionado el objeto del menú? deselecciónalo if (Game.SelectedItem != null) { Game.SelectedItem = null; return; } var ActObj = Game.ActiveObject; // ¿es visible el menú del botón derecho del ratón? ocúltalo if(WinMenu.Visible == true) WinMenu.Visible = false; else if(ActObj!=null) { // si el objeto seleccionado puede manejar alguno de los "verbos", muestra el menú del botón derecho del ratón if(ActObj.CanHandleEvent("Take") || ActObj.CanHandleEvent("Talk") || ActObj.CanHandleEvent("LookAt")) { // almacena el objeto seleccionado en la variable global MenuObject MenuObject = Game.ActiveObject; var Caption = WinMenu.GetWidget("caption"); Caption.Text = MenuObject.Caption; // ajusta la posición del menú WinMenu.X = Game.MouseX - WinMenu.Width / 2; if(WinMenu.X < 0) WinMenu.X = 0; if(WinMenu.X+WinMenu.Width>Game.ScreenWidth) WinMenu.X = Game.ScreenWidth-WinMenu.Width; WinMenu.Y = Game.MouseY - WinMenu.Height / 2; if(WinMenu.Y<0) WinMenu.Y = 0; if(WinMenu.Y+WinMenu.Height>Game.ScreenHeight) WinMenu.Y = Game.ScreenHeight-WinMenu.Height; // y muestra el menú del botón derecho del ratón WinMenu.Visible = true; // detiene al actor independientemente de lo que fuera a hacer actor.Reset(); } else ActObj.ApplyEvent("RightClick"); } }
El código está comentado y es bastante claro. Primero, deseleccionamos el objeto del inventario cuando pulsamos el botón derecho del ratón. Comprobamos si el menú del botón derecho es visible. Si lo es, lo oculta. Si no hemos pinchado en alguna región asignada, enviamos el evento a la escena (como hicimos con el botón izquierdo). La parte más grande de código comprueba que si hay algo que podamos hacer con la región en la que hemos pinchado (soporta algunas acciones como Coger, Hablar o Mirar a), entonces mostramos el menú. También se posiciona el menú en la pantalla y, por último, detiene al actor.
on "Keypress" { // pulsando Esc o F1 if(Keyboard.KeyCode==VK_ESCAPE || Keyboard.KeyCode==VK_F1) { // carga y muestra la ventana del menú principal WinCaption.Visible = false; var WinMainMenu = Game.LoadWindow("interface\system\mainmenu.window"); WinMainMenu.Center(); WinMainMenu.GoSystemExclusive(); Game.UnloadObject(WinMainMenu); } }
Esta función controla las pulsaciones de teclado (¿has visto nuestros útiles nombres para las teclas?). Lo que hacemos (pulsando Escape o F1) es cargar el Menú Principal (cargar, guardar, salir, etc.), centrarlo en la pantalla y cambiar al modo exclusivo. Esto significa que todo se detiene hasta que se cierre la ventana. Cuando se cierra el menú, se descarga de la memoria.
La última función de eventos de game.script sirve para controlar el menú de salida y cerrar el juego.
on "QuitGame" { // con Alt+F4 (se cierra la ventana) // carga y muestra la ventana de confirmación de salida WinCaption.Visible = false; var WinQuit = Game.LoadWindow("interface\system\quit.window"); WinQuit.Center(); WinQuit.GoSystemExclusive(); // y si el usuario elige Sí if(WinQuit.xResult) { // sale del juego Game.QuitGame(); } // ed lo contrario, borra de la memoria y cierra la ventana else Game.UnloadObject(WinQuit); }
Como puedes ver, funciona igual que la ventana del Menú Principal. Si seleccionamos Si, xResult se marca como True y llama a Game.QuitGame();. No es física cuántica.
—-
Bien… ¿todavía no tenéis suficiente? En la segunda parte de este tutorial vamos a echarle un vistazo al archivo game_daemon.script.
Puedes comprobar que, después de algunas declaraciones, comienza un bucle infinito. Eso quiere decir que se ejecutará hasta que el juego acabe o se desvincule el script. La parte más divertida de los bucles infinitos es que debes detenerlos con la instrucción Sleep(); o tomarán el control del juego que se acabará colgando. No es una experiencia agradable…
// guarda el objeto activo para recuperarlo más tarde var ActObj = Game.ActiveObject; // maneja el rótulo flotante estándar if(Game.Interactive && ActObj!=null) { if (Game.SelectedItem==null) { WinCaption.X = Game.MouseX; WinCaption.Y = Game.MouseY + 20; WinCaption.TextAlign = TAL_LEFT; WinCaption.Text = ActObj.Caption; // mantiene el rótulo en pantalla WinCaption.SizeToFit(); if(WinCaption.X + WinCaption.Width > Game.ScreenWidth) WinCaption.X = Game.ScreenWidth - WinCaption.Width; if(WinCaption.Y + WinCaption.Height > Game.ScreenHeight) WinCaption.Y = Game.ScreenHeight - WinCaption.Height; }
La primera parte del código controla los rótulos que aparecen cuando pasas el ratón sobre algunas regiones sin tener seleccionado un objeto del inventario. Se posiciona, asigna el rótulo que esté definido en Scene Edit, lo escala y lo prepara.
// maneja los rótulos cuando quieres usar un objeto con otro else { var Item = Game.SelectedItem; WinCaption.X = 0; WinCaption.Y = 580; WinCaption.Width = Game.ScreenWidth; WinCaption.TextAlign = TAL_CENTER; WinCaption.Text = "Use " + Item.Caption + " with " + ActObj.Caption; } WinCaption.Visible = true; WinCaption.Focus();
Si, por otro lado, tienes un objeto asignado, el rótulo es preparado de la siguiente manera: Usar cuchillo con manzana. De nuevo, el código es bastante explicativo. El común denominador es que el rótulo es mostrado y consigue el foco. Si estamos sobre un lugar vacío y no tenemos un objeto de inventario en la mano, ocultamos el rótulo con la instrucción WinCaption.Visible = false;. Ya es bastante para los rótulos.
La última parte controla el inventario.
if(Game.Interactive && Game.MouseY < 45 && !Game.ResponsesVisible && !WinMenu.Visible) Game.InventoryVisible = true; else if(Game.MouseY > 100 || Game.ResponsesVisible || !Game.Interactive) Game.InventoryVisible = false;
Si movemos el ratón a la parte superior de la pantalla, mostramos el inventario (mientras el juego no esté en modo interactivo, el menú principal no sea visible y no estemos en modo diálogo). Cuando salimos del inventario, entramos en modo diálogo o el juego no es interactivo, lo ocultamos. Es un poco lioso y deberías mirar en Preguntas y respuestas para conocer algunos trucos al manejar el inventario.
Finalmente, ejecutamos la instrucción Sleep que mencionamos antes.
El tutorial finaliza aquí y espero que lo encuentres útil.