4. Empezar de cero

Hasta el momento hemos experimentado con los recursos que nos integra wme, hemos estado trabajando con las imágenes de la demo, ahora es el momento de tomar el control y comenzar a crear nuestro propio juego desde 0. Inicia el administrador de proyectos y presiona ctrl+N o presiona en File → New. Dale un nombre a tu proyecto y creemos nuestro propio juego.

Como podéis ver, wme crea un nuevo proyecto, y automáticamente, se crean una serie de scripts y ficheros mínimos para que el juego pueda comenzar a funcionar. Bien, lo primero que vamos a hacer, es centrar nuestra atención al fichero donde se controla toda la acción del juego. Este fichero como hemos comentado en anteriores ocasiones, es el fichero game.script y se encuentra en la carpeta scripts de nuestro proyecto.

Puedes encontrarlo fácilmente en el administrador de proyectos. Simplemente este fichero se ejecuta en el momento en que comienza el juego. En este momento, aun no hay nada definido, ni las escenas, ni las imágenes, nada, solo lo básico para que el juego arranque.

Veamos que es lo que tiene el fichero game.scripts, en pequeños bloques:

#include "scripts\base.inc"  
#include "scripts\keys.inc"

Lo primero que vemos, son las clausulas includes, estas lineas servían para incluir unos archivos dentro de otro. En este caso incluimos los archivos “base.inc” y “keys.inc”.

El primero contiene tres variables globales, actor, Scene y Keyboard. Bien, usamos este include, ya que en todo script hay que declarar la variable global Scene antes de poder usar por ejemplo Scene.GetNode(). Aparte, el fichero base.inc, incluye otro fichero llamado const.inc donde se declaran las constantes para las direcciones del actor, y la alineación de texto. Es recomendable en nuestro juego declarar las variables globales y las constantes en este fichero, asi nos ahorraremos tener que incluirlas cada vez que las necesitemos. También podemos crear otros ficheros. Pero eso ya queda a elección del diseñador.

El segundo fichero, “keys.inc” contiene la definición de las teclas y sus códigos numéricos asociados, de esta manera, podremos referenciar por ejemplo la tecla escape con el nombre VK_ESCAPE en lugar de tener que usar su código numérico, en este caso el 27.

Keyboard = Game.Keyboard;  
Scene = Game.Scene;

Como bien sabes, todas las funciones básicas, pertenecen al objeto Game. Pero hay varias razones por las que necesitamos usar variables para referenciar los objetos. Lo primero es que wme no permite la anidación, por lo que no se puede por ejemplo usar:

var e = Game.Scene.GetNode("puerta");

Y segundo, es mucho mas claro para el programador si escribes Scene.GetNode(“puerta”);

Luego si volvemos a estas lineas:

Keyboard = Game.Keyboard;  
Scene = Game.Scene;

Lo que hemos echo, es crear una variable global llamada keyboard(“base.inc”) que contenga una referencia al objeto keyboard del objeto Game (Game.Keyboard) y servirá para controlar la entrada de teclado. Una pequeña nota – este objeto siempre es el mismo cuando cambiamos entre escenas, por lo que no tienes que reasignarlo cada vez que cambias de escena, ya que esta definido en el fichero game.script, y por ello, sera usable durante todo el proceso de programación.

global WinMenu = Game.LoadWindow("interface\menu\menu.window");  
WinMenu.Visible = false;

Por defecto, estas dos lineas establecen que cuando hagas clic con el botón derecho del ratón sobre un objeto activo, salga un menú con 3 opciones, ahora vamos a eliminar estas lineas.



Que es una ventana

Una ventana es una colección de objetos gráficos(botones, imágenes, campos de edición para entradas de texto,..). Estos objetos son muy útiles a la hora de crear interfaces GUI( interfaz gráfica de usuario). Bien, hablaremos de la construcción de ventanas mas adelante, centrémonos por ahora en lo que estas dos lineas significan. La primera linea, lo que hace es cargar un fichero .windows ( contiene la definición de una ventana gráfica ) y la segunda linea, la establece como invisible. Esta ventana es agregada al objeto Game por eso aunque cambiemos de escena, no afectara a esta ventana. Nota las ventanas son diferentes a las entidades en su atributo de visibilidad, en ventanas es Visible, y en entidades es Active.

var win = Game.LoadWindow("interface\system\caption.window");  
global WinCaption = win.GetWidget("caption");

otras dos lineas para cargar una ventana, en este caso esta se trata de una ventana para mostrar los subtítulos cuando pasas el ratón por encima de algún objeto. Podemos ver un nuevo método, GetWidget() que lo que hace es crear una referencia a una parte especifica de la ventana. Este método es una forma ambigua de hacerlo, ahora se usa el método GetControl, por lo tanto vamos a modificar esta linea:

global WinCaption = win.GetWidget("caption");

Por esta otra

global VentSubtitulos = win.GetControl(“caption”);

La siguiente linea a analizar es esta:

global MenuObject = null;

En esta linea, lo que hacemos es asignar el valor null a la variable global MenuObject que se usará despues para saber donde clikeamos. Null es un valor especial, que indica que esa variable esta vacía. Y si queremos podemos eliminar esta linea.

Seguidamente, analizaremos estas lineas:

actor = Game.LoadActor("actors\molly\molly.actor");  
Game.MainObject = actor;

Estas dos lineas, son importantes. La variable actor, es una variable global definida en el fichero base.inc, bien lo que hacemos aquí, es asignar a esa variable el fichero .actor que vayamos a usar, si queremos usar un actor que nosotros hayamos creado, esta linea es la que debes cambiar:

actor = Game.LoadActor("Fichero .actor");

Por ahora como no disponemos de otro, vamos a dejar el que viene por defecto, “actors\molly\molly.actor”.

Nota: también es posible usar la función LoadActor(actor); sobre el objeto Scene, es decir Scene.LoadActor(actor); con la diferencia de que este se añadirá solo sobre la escena donde lo definamos, o en caso de que lo hagamos en el fichero game.script, el actor se cargara cada vez que comencemos una escena, y se descargará cuando la escena acabe.

Lo siguiente que hacemos, es asignar al atributo MainObject del objeto Game a nuestro actor recién cargado. Así cuando el actor se mueva por la pantalla, y sea necesario que se realice scroll( esto es cuando la escena no se ve completamente, y el actor al moverse en una dirección, hace que la pantalla se mueve en la misma dirección), el actor será el objeto que mantendrá el foco de atención. En el caso de que fueran múltiples actores( como por ejemplo el día del tentáculo), este atributo asigna automáticamente esta característica al actor que hayamos seleccionado. También puedes asignar el valor null a este atributo, por ejemplo en caso de que en nuestro juego no exista el scrolling.

Game.AttachScript("scripts\game_loop.script");

Bien, lo que hacemos aquí, como ya vimos, es cargar un script, este script se encarga de controlar mayormente todos los procesos que están continuamente ejecutándose durante el juego, como por ejemplo mostrar el inventario, los títulos de los objetos sobre los que pasamos el ratón, música, etc. Seguidamente lo investigaremos. Pero como ves este fichero se añade globalmente al juego, por lo que tendrá códigos que se estarán ejecutando durante todo el juego.

Veamos ahora algo extremadamente importante:

Game.ChangeScene("scenes\Room\Room.scene");

Esta linea define la escena donde empezará nuestro juego. Normalmente, suele ser un menu, a no ser que realicemos algún vídeo de introducción o algo. Ahora viene una parte importante. Ahora eliminaremos todo el código relacionado con el inventario, después cuando adquiramos un poco de nivel, las añadiremos por nosotros mismos. Luego debemos modificar el evento on “LeftClick” hasta que se vea de la siguiente manera:

on "LeftClick"  
{  
  var ActObj = Game.ActiveObject;  
  if(ActObj!=null)  
  {  
     ActObj.ApplyEvent("LeftClick");  
  }  
  else  
  {  
    Scene.ApplyEvent("LeftClick");  
  }  
}

Lo que conseguimos con esto, es que cuando pulsamos click con el botón izquierdo del ratón, comprobamos si el el click fue echo sobre un punto caliente esto lo sabemos mediante el atributo ActiveObject del objeto Game, que lo que hace es devolver el objeto que esta activo en cada momento. Por lo que reduciremos nuestras opciones a 2, si hacemos click en un objeto, aplicaremos el evento “LeftClick” sobre el objeto. Por otro lado, si hacemos clic sobre un punto de la escena, se aplicara el evento “LeftClick” sobre la escena.

Si clickeamos en algún punto de la pantalla, donde no hay ningún cursor debajo, se dispara el evento “LeftClick” de la escena.y te preguntaras, ¿“LeftClick” de escena?

Esto es otro de los trucos de WME, si vas a tu escena en el editor de escenas y vas a la tabla de propiedades de la escena, pulsa sobre Scripts y te sale una ventana donde puedes ver que hay dos scripts agregados: scene_init.script y scene.script. Y esta es la plantilla que cada escena nueva contendrá. El scene_init.script, ya lo habíamos estudiado en el capitulo anterior(contenía todas las instrucciones necesarias que queríamos que se ejecutaran al iniciar la escena). Ahora veamos que contiene el fichero “scene.script”:

#include "scripts\base.inc"  
 
on "LeftClick"  
{  
  actor.GoTo(Scene.MouseX, Scene.MouseY);  
}

Pues simplemente lo que vemos, cuando hacemos clic en algún lugar de la escena que no es un punto caliente se lanza el evento que se encuentra en este script, si hacemos clic en un punto caliente, el evento on “LeftClick” que se lanzará, sera el de ese objeto, región, entidad,… que pulsemos.

Bueno, pues cuando se lanza el evento de la escena, simplemente lo que hará, sera ejecutar esta instrucción:

actor.GoTo(Scene.MouseX, Scene.MouseY);

Que simplemente se encarga de mover a nuestro personaje al punto de la pantalla donde hayamos clickeado.

Veamos ahora el próximo evento, el “RightClick”, vayamos al fichero game.script, y eliminemos todo el código que se encuentra en el evento on “RightClick”, ya que lo único que hará sera marearnos y no nos es necesario, por lo tanto, borremos todo.

Los últimos dos eventos, son “Keypress” y “QuitGame” estos dos controlan la pulsación de teclas, en este caso, cuando pulsamos escape, nos sale el menú del juego, pues esto es controlado por el evento “Keypress” y el evento “QuitGame” se encarga de todas las operaciones necesarias cuando se cierra el juego, no os preocupéis mucho por ahora por estos eventos, pues pronto los estudiaremos.

Espero que sigáis bien las instrucciones, y lo mas importante que lo vayáis entendiendo, espero que vayáis entendiendo bien hasta aquí.

Nota, no olvidar que cuando modificamos algún script, es necesario guardarlo, sino los cambios no servirán de nada.

Bueno, recapitulemos un poco, y veamos que métodos y atributos hemos visto hasta ahora:

var window = Game.LoadWindow(window filename); → carga un archivo de definicion de ventana y lo añade al objeto Game, y opcionalmente devuelve una referencia a el, que es posible almacenar en una variable.

window.Visible = true / false; → es un atributo del objeto window, que hace que la ventana sea visible o invisible en la pantalla.

window.GetControl(controlname); → devuelve una referencia a un control almacenado en una ventana.

Game.LoadActor(actor filename); → carga un actor de un fichero de actor y lo añade al objeto juego.

Scene.LoadActor(actor filename); → carga un actor de un fichero y lo añade al objeto escena.

Game.MainObject = actor; → selecciona el actor como el objeto principal del juego y sera el que hara el seguimiento del scrolling.

Game.ActiveObject; → almacena una referencia al objeto sobre el objeto sobre el que se posa el raton en cada momento.

Game.ApplyEvent(eventName); → es un metodo que lanza un evento asociado al objeto Game.

Scene.ApplyEvent(eventName); → igual que el anterior pero un evento asociado al objeto scene.

Node.ApplyEvent(eventName); → lanza un evento asociado al objeto node sobre el que lo aplicamos.

Para entender mejor estos últimos métodos, veamos un ejemplo aplicado a la puerta que vimos en el capitulo anterior:

var door = Scene.GetNode("Door");  
door.ApplyEvent("LeftClick");

Con esto lo que haríamos sera hacer un evento LeftClick sobre la puerta, sin que el jugador físicamente tenga que pulsar, esto a veces es útil en momentos de animación, por ejemplo cuando un jugador entra en una escena y el personaje automáticamente se va hacia un punto de la escena, y hace algo, pues estos eventos son útiles para eso.

Antes de seguir, veamos si tu fichero game.script es como el mio.

#include "scripts\base.inc"  
#include "scripts\keys.inc"  
  
// almacena algunos atributos del juego en variables globales  
Keyboard = Game.Keyboard;  
Scene = Game.Scene;  
  
// load the "caption" window  
var win = Game.LoadWindow("interface\system\caption.window");  
global WinCaption = win.GetControl("caption");  
  
// carga el actor principal 
actor = Game.LoadActor("actors\molly\molly.actor");  
Game.MainObject = actor;  
  
// ejecuta el script de demonios 
Game.AttachScript("scripts\game_daemon.script");  
  
// carga la escena inicial  
Game.ChangeScene("scenes\Room\Room.scene");  
  
  
  
on "LeftClick"  
{  
  var ActObj = Game.ActiveObject;  
  if(ActObj!=null)  
  {  
     ActObj.ApplyEvent("LeftClick");  
  }  
  else  
  {  
    Scene.ApplyEvent("LeftClick");  
  }  
}  
  
on "Keypress"  
{  
  // on Esc or F1 key  
  if(Keyboard.KeyCode==VK_ESCAPE || Keyboard.KeyCode==VK_F1)  
  {  
    // carga y muestra la ventana del menu principal 
    WinCaption.Visible = false;  
    var WinMainMenu = Game.LoadWindow("interface\system\mainmenu.window");  
    WinMainMenu.Center();  
    WinMainMenu.GoSystemExclusive();  
    Game.UnloadObject(WinMainMenu);  
  }  
}  
  
on "QuitGame"  
{  
  // on Alt+F4 (window close)  
  // carga y muestra la ventana de salir del juego  
  WinCaption.Visible = false;  
  var WinQuit = Game.LoadWindow("interface\system\quit.window");  
  WinQuit.Center();  
  WinQuit.GoSystemExclusive();  
  // and if the user selected Yes  
  if(WinQuit.xResult)  
  {  
    // sale del juego  
    Game.QuitGame();  
  }  
  // en otro caso descarga la ventana de la memoria y del juego 
  else Game.UnloadObject(WinQuit);  
}

Bien, ahora probemos el juego, y vemos, que aun funciona, solo hemos hecho unos pequeños cambios sobre el.

Ahora para continuar, veamos el fichero game_loop.script.

#include "scripts\base.inc"  
 
global WinCaption;  
global WinMenu;

Podemos eliminar fácilmente la linea global WinMenu; ya que hemos decidido eliminar el menú en el botón derecho.

while(true){

Esto siempre será verdadero. Se trata de un bucle infinito. Para finalizar este bucle, debemos llamar la función Game.DetachScript(“scripts\game_loop.scrip”);

var ActObj = Game.ActiveObject;

Como vemos, la misma linea que en el fichero game.script, constantemente el juego esta comprobando cual es el objeto bajo el ratón.

if(Game.Interactive && ActObj!=null)  
 {

Primero comprueba si el juego esta en modo interactivo, y después si el cursor del ratón esta sobre algún objeto activo. Hagamos esta condición mas simple.

En el cuerpo de la condicion borraremos lo que ahi y meteremos lo siguiente:

if(Game.Interactive && ActObj!=null)  
{  
      WinCaption.X = Game.MouseX;  
      WinCaption.Y = Game.MouseY + 20;  
      WinCaption.TextAlign = TAL_LEFT;  
      WinCaption.Text = ActObj.Caption;  
  
      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;  
  
     WinCaption.Visible = true;  
     WinCaption.Focus();  
}  
else WinCaption.Visible = false;

Bien,¿ que pasara ahora si el cursor se encuentra sobre algún objeto?

WinCaption.X = Game.MouseX;  
WinCaption.Y = Game.MouseY + 20;

WinCaption contiene una referencia al campo de texto que se encuentra dentro de la ventana caption cargada en game.scipt. Con estas lineas, establecemos su posicion en las mismas coordenadas que nuestra posición X del ratón y la posición Y + 20 pixeles, de esta manera, el nombre del objeto sobre el que situamos el cursor, aparece cercano a este.

WinCaption.TextAlign = TAL_LEFT;  
WinCaption.Text = ActObj.Caption;

Con estas dos lineas, establecemos el alineamiento del texto de esta ventana a la izquierda, y establecemos que el texto de esta ventana, sea el nombre que le hayamos asignado a ese objeto en nuestra escena.

WinCaption.SizeToFit();

Redimensionamos la ventana caption al mismo tamaño que el texto que se muestra.

if(WinCaption.X + WinCaption.Width > Game.ScreenWidth) WinCaption.X = Game.ScreenWidth – WinCaption.Width;

Si la anchura del campo excede de el ancho de la pantalla, establecemos la posición X de la ventana caption exactamente hasta el final, esto lo hace basándose en la resolución de la pantalla, se hace para evitar que el texto se pierda por los bordes de la pantalla.

if(WinCaption.Y + WinCaption.Height > Game.ScreenHeight) WinCaption.Y = Game.ScreenHeight – WinCaption.Height;

Exactamente hace lo mismo que la linea anterior, pero esta vez aplicado a la coordenada Y.

WinCaption.Visible = true;  
WinCaption.Focus();

Una vez que hemos aplicado todos esos cambios, hacemos la ventana caption visible y nos la activa( WinCaption.Focus() ) pero en nuestro caso no es necesario, ya que no interactuaremos sobre esa ventana, así que simplemente podemos eliminar esa linea.

 }  
else WinCaption.Visible = false;

Esto significa que si el juego esta en modo no interactivo o el cursor se mueve fuera de algún punto caliente, mantenga esta venta oculta.

 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;

Estas dos lineas, hacen que se muestre o no el inventario, y simplemente para nuestro ejemplo las eliminaremos.

Sleep(20);

Recuerdas lo que comentábamos sobre los bucles infinitos, que era necesario realizar una pequeña pausa para dejar la cpu libre a otros procesos, pues bien aquí hacemos uso de esa técnica.

Ahora revisemos como nos tiene que quedar nuestro fichero game_loop.script:

#include "scripts\base.inc"  
  
global WinCaption;  
  
//bucle infinito  
while(true){  
  var ActObj = Game.ActiveObject;  
  
  if(Game.Interactive && ActObj!=null)  
  {  
      WinCaption.X = Game.MouseX;  
      WinCaption.Y = Game.MouseY + 20;  
      WinCaption.TextAlign = TAL_LEFT;  
      WinCaption.Text = ActObj.Caption;  
      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;  
     WinCaption.Visible = true;  
  }  
  else WinCaption.Visible = false;  
  Sleep(20);  
}

Como puedes ver, esta es la columna vertebral de un proyecto. Sin inventario, sin menú, simplemente point and click game. Esto es algo que estamos viendo para que os deis cuenta la forma de funcionar de wintermute, si vais teniendo claras las ideas, podréis cambiar la forma en la que salen estas ventanas, el texto que sale, etc etc. poco a poco podréis modificar estos script y hacer cosas bastante potentes.

Ahora pasaremos al siguiente elemento de nuestro proyecto, el actor.