This is an old revision of the document!
—-
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 más de cerca pronto.
// objetos iniciales Game.TakeItem("money");
Again it's self explanatory, but just remember to have a main actor already attached before trying to give him some money.
Game.ChangeScene("scenes\room\room.scene");
Finally we get to something presentable, we load our first scene, which is defined in the Scene Manager. Next part of the game.script is some handler definition for the whole game.
on "LeftClick" { // what did we click? var ActObj = Game.ActiveObject; if(ActObj!=null) { // clicking an inventory item if(ActObj.Type=="item" && Game.SelectedItem==null) { Game.SelectedItem = ActObj; } // using an inventory item on another object 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."); } // just a simple click else ActObj.ApplyEvent("LeftClick"); } // else propagate the LeftClick event to a scene else { Scene.ApplyEvent("LeftClick"); } }
Our first handler is for the Left mouse click. Game.ActiveObject returns the object which is currently under the mouse cursor, so we assign it to some variable. If it's null which means there is no object under the cursor, we send the event to the Scene for some special cases (like region based handling in scene.script file). If we on the other hand recieve some object we then go through the few if's as what to do with the object, although it's selfexplanatory I just recapitulate: if the object is inventory item inside the inventory box, we assigned it as an active object for further manipulation. If we click with the inventory item on some other item on the screen, we first check some handlers. First one is if the object we clicked on has a method by the name of the item defined. If we had an apple on the screen, took the knife from inventory and use it on apple, it would then expect some on "knife" { } handler.
Else if Item in its script has on "default-use" { } handler defined, it will be executed (It can be used for some default actions, like I don't want to cut that! or It's STUCK!) If this isn't defined for the Item, game looks for on "default-use" { } handler of the object we clicked on. If this doesn't exist, it just issue some standard line like I can't use these things together. in our case.
If we clicked on the object without any inventory item attached, we just send a Left click event to the script attached to the region
(It will then expect on "LeftClick" {} defined for doing anything).
on "RightClick" { // if inventory item selected? deselect it if (Game.SelectedItem != null) { Game.SelectedItem = null; return; } var ActObj = Game.ActiveObject; // is the righ-click menu visible? hide it if(WinMenu.Visible == true) WinMenu.Visible = false; else if(ActObj!=null) { // if the clicked object can handle any of the "verbs", display the right-click menu if(ActObj.CanHandleEvent("Take") || ActObj.CanHandleEvent("Talk") || ActObj.CanHandleEvent("LookAt")) { // store the clicked object in a global variable MenuObject MenuObject = Game.ActiveObject; var Caption = WinMenu.GetWidget("caption"); Caption.Text = MenuObject.Caption; // adjust menu's position 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; // and show the right-click menu WinMenu.Visible = true; // stop the actor from whatever he was going to do actor.Reset(); } else ActObj.ApplyEvent("RightClick"); } }
This is so well commented that again I am just stating the obvious. First we deselect potentional Inventory Item when we issue a right click. Then we check if our Right Click menu is not visible. If it is, it gets hidden. If we didn't click on any assigned regions on screen, we just forward the right click to the scene (as we did with the left click before). The biggest code portion is that if we actually can do anything with the region (it supports some of the handlers like Take, Talk or LookAt) we show the menu. There is some obvious positioning of the menu on the screen and lastly it stops the actor.
on "Keypress" { // on Esc or F1 key if(Keyboard.KeyCode==VK_ESCAPE || Keyboard.KeyCode==VK_F1) { // load and display the main menu window WinCaption.Visible = false; var WinMainMenu = Game.LoadWindow("interface\system\mainmenu.window"); WinMainMenu.Center(); WinMainMenu.GoSystemExclusive(); Game.UnloadObject(WinMainMenu); } }
Another handler handles all keystrokes (see our handy key symbolic names?). We just (upon escape or F1) load the Main Menu (load, save, exit etc), center it on the screen and switch to exclusive mode which means, that everything else is stopped until this window ends. When we close the menu window, it gets unloaded again.
Last handler (phewwww) of the game.script is just handling the quit dialog and game quitting.
on "QuitGame" { // on Alt+F4 (window close) // load and display the quit confirmation window WinCaption.Visible = false; var WinQuit = Game.LoadWindow("interface\system\quit.window"); WinQuit.Center(); WinQuit.GoSystemExclusive(); // and if the user selected Yes if(WinQuit.xResult) { // quit the game Game.QuitGame(); } // otherwise just unload the quit window from memory else Game.UnloadObject(WinQuit); }
As you can see, it works similary to Main Menu window and when we clicked on yes, it sets xResult to true so Game.QuitGame(); is called. Not exactly a rocket science.
—-
Ok… Still don't have enough? In the second part of this tutorial we will have a look at game_daemon.script file.
You can see that after some declaration it starts with infinite loop. It means that it will just go and on until the game is ended or you Detach the script. Funny part about infinite loops is, that you should end them with some little Sleep(); or it will take the whole control over the game end results in freezing. Not nice experience…
// save the active object for later var ActObj = Game.ActiveObject; // handle the standard foating caption 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; // keep the caption on screen 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; }
First portion of the daemon handles with the Captions when you hover with a mouse over some defined regions with no inventory item attached. It positions it, assign a caption according to caption defined in Scene Edit, scales it and prepares it.
// handle the caption when you want to use an object with another 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();
If you on the other hand have some item assigned, the caption is prepared in a manner of Use knife with apple. Again it is pretty obvious from the code itself. Common denominator is that Caption is displayed and gets focus. If we are over empty space with no inventory item in hand we hide the caption with WinCaption.Visible = false;. So much for captions.
Last portion of the daemon is to handle the inventory behavior.
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;
If we position mouse into upper part of the screen, we show the inventory (provided the game is in interactive mode, main menu is not visible, and we are not in dialogue mode). When we get out of the inventory box or we are in the dialogue mode or game is not interactive, we hide it. It's a bit crude and you should look at faq on some inventory handling tips.
Lastly the aformentioned Sleep is issued and we are set.
That concludes it for this tutorial and I hope you'll find it at least a bit useful.
