Started by: metamorphium
Date: 18. 9. 2005
Prerequisities: WME development kit installed
If you are reading this, you must be either the one asking yourself about the basic underlying WME script mechanics or Mnemonic who is checking my post for some terrible mistakes I made . Today I've decided to walkthrough you through scripts from WME demo and demostrate you what's going on. But let's begin, there is quite something to cover.
Switch yourself to the projects/wme_demo/data of your WME distribution and we will start right there.
There are three files and those are default.game, startup.settings and string.tab.
default.game - is generated by Project Manager and contains the basic game settings. You can set these values in the Project Manager on the left side or you can edit them by hand in this file. The former approach is definitely recommended for beginners.
startup.settings - is generated by Project Manager and contains settings for the … … (I wonder if you guessed it by now) … Startup dialog box and system basic settings like Windows registry path etc. Editing rules are the same as with default.game
string.tab - is very important file for game localization. Just to recapitulate, it's a [tab] delimited file of ID - texts. in game you reference these string using folowing mechanisms:
actor.Talk("/TXT0001/Hello");
Normally molly says Hello, but if you have in your string.tab file listed line which reads
TXT0001[TAB]Bon giorno.
it will be written instead. Note that in some cases the string needs to be forced to translated.
Game.Msg("/TXT0001/Hello"); // This displays /TXT0001/Hello // For this we will use Game.ExpandString function Game.Msg(Game.ExpandString("/TXT0001/Hello.")); // Which shows the correct text, this is also // necessary approach for string manipulation
So when you thing in your game about some possible string manipulation, which will be then translatable, better use this function from the very beginning.
Ok, this having off the table, let's move into scripts folder. There you will find three include files. These are base.inc, const.inc and keys.inc.
The base.inc is by default included to all scripts you create and it should contain global variables for the whole game. I wrote more about that in my Variables and Objects tutorial. base.inc already includes the other file which is called const.inc which contains constants for the whole game and is wise to be split from variables. Note that they are also variables but will be reinitialized during each script inclusion. So don't fall into that trap initializing some global variable which is not meant to be constant in base.inc file. You would make a nice constant out of it. keys.inc is definition of some of more special keycodes. Luckily for us Mnemonic already find the numbers for us and thus saved us some hassle.
Having our include files resolved, let's move to some more serious topic. When our WME game starts, it initializes the main Game object and executes the script which is attached to it. (you can change this script filename in Project Manager on the left side in Game Settings → Scripts). This is the where we do start so open this file and let's look at it step by step.
#include "scripts\base.inc" // just basic stuff #include "scripts\keys.inc" Keyboard = Game.Keyboard; Scene = Game.Scene;
We include also keys.inc file, because we will do some keycode comparison and symbolic names are way more easy to cope with than just some numbers.
Next two global variables initialization (you should know them from base.inc where there are defined) is there for convenience mainly because WME doesn't support nesting so you can't write Game.Scene.GetEntity(); for example.
// load the right-click menu global WinMenu = Game.LoadWindow("interface\menu\menu.window"); WinMenu.Visible = false; // load the "caption" window var win = Game.LoadWindow("interface\system\caption.window"); global WinCaption = win.GetWidget("caption"); // load the demo hints global WinHints = Game.LoadWindow("interface\demo\demo_hints.window"); WinHints.Visible = true; // load credits fader global WinCredits = Game.LoadWindow("interface\credits\credits.window"); WinCredits.Visible = true; global MenuObject = null;
Although it's commented already by Mnemonic I will only note that window behavior is defined in window files and don't look for those things in the game.script, they are already attached to windows through separate files. Lastly we prepared empty object with what we will deal later.
// load our main actor actor = Game.LoadActor("actors\molly\molly.actor"); Game.MainObject = actor;
Ok, now we see, that MainObject is our main actor, but we can load as many actors as we wish and then just switch them in the Game.MainObject so we achieve the main actor switching. Also note that for realtime models you need to use Game.LoadActor3D(); instead.
Now we come to the really important line:
// run the "daemon" script Game.AttachScript("scripts\game_daemon.script");
As you may know already, if we attach script to Game object, it is valid for the whole game so these are typically scripts which we use during the course of the whole game. We can attach them as many as we wish, but in this demo, there is just one. We'll have a closer look on it soon.
// initial items 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.