So far we’ve been experimenting with prebuilt demo, but now it’s the time to go back to the roots and create a completely new project of our liking. Start the project manager and press Ctrl+N (alternatively click on the new project icon or choose new project from the File menu). Name your project as you wish and let WME create it.
You can see that WME creates a new project with putting there some preset material and the starting of our new game can’t be easier as we can immediately run this minimalistic project and it works. What we’re going to do though is to turn our focus to the point where the game actually begins. This point is a simple file called game.script (located in data→scripts).
You can easily check in your Project Manager, that the game has one script attached to it and it won’t be surprise for you, that this attached script is … game.script. So simply said the very first thing WME does after initializing all windows system stuff is to execute this file. I am speaking about it so broadly, because I want you to fully understand how versatile WME is. At this point nothing is decided, how the game will look, how the game will play etc.
Let’s try to uncover what game script does by little blocks:
#include "scripts\base.inc" #include "scripts\keys.inc"
First we include the file base.inc, which contains 3 global variables - actor, Scene and Keyboard. This way we don’t have in every single script declare global Scene; before calling for example Scene.GetNode(); Base.inc in turn includes another file called const.inc which contains constants for actor directions and text alignment. In course of our game creation we will add global variables and constants in these files to keep our game tidy. We can also separate our includes into more files if we’re so inclined. Second file contains definition to keyboard codes so we don’t have to guess numbers and simply type their textual representation like VK_ESCAPE instead of 27 if we want to test for Esc key.
Keyboard = Game.Keyboard; Scene = Game.Scene;
As you already should know by now, all basic objects are members of the Game object. But there are two reasons why we need variables to hold those nested objects. First WME doesn’t allow nesting so you can’t use for example:
var e = Game.Scene.GetNode("Door");
and second it looks much more clean if you write Scene.GetNode("Door"); anyway.
So back to those lines – we’ve created a global variable Keyboard which contains a reference to Game.Keyboard object and which will be used for querying keyboard entry and global variable which holds reference to Scene object. As a little side note - this object is still the same even if you’re changing scenes so you don’t have to reassign it every time you changed the scene.
global WinMenu = Game.LoadWindow("interface\menu\menu.window"); WinMenu.Visible = false;
By default you have the preset behavior that if you right click on a hotspot, little menu appears where you can choose from three actions. Now do me a favor and delete those two lines. We’ll try to go barebones.
You may ask, what is a window? Window is a collection of graphical items (buttons, images, edit fields for textual input) which is very practical for forming GUI (graphical user interface). The big advantage is that it’s Scene independent so you can have it on screen for every scene without explicitly forming the elements every time. We’ll look on the windows building later, so let’s focus for now what those two lines mean. First line loads a window file (which contains a definition of the graphical window) and second sets it invisible. The window is attached to the Game object so scene changing can’t affect it. Note that windows are an exception. Entities use Active, Windows use Visible.
var win = Game.LoadWindow("interface\system\caption.window"); global WinCaption = win.GetWidget("caption");
And another load of window! This one is meant for captions whenever you hover your mouse over the hotspot. Also we introduce a brand new method for getting a reference to specific part of the window. This one is the actual field which holds the text. Now do me a favor – GetWidget is ancient way how to get the control. Now we use much more intuitive GetControl so rewrite the second line to:
global WinCaption = win.GetControl("caption"); //You can also remember the parallel Scene.GetNode(name); Window.GetControl(name);
next comes:
global MenuObject = null;
We store a null to MenuObject which will be used later to see what we clicked. Null is a special value, which indicates that there really is nothing. And you can delete this line as well.
actor = Game.LoadActor("actors\molly\molly.actor"); Game.MainObject = actor;
Now those are important lines. Variable “actor” is a global variable defined in base.inc which will now be filled with the definition of our .actor file. Again later on we’ll look how the actor files are constructed. For now let’s live with the fact that we have molly and she can do whatever we want.
Also as a side note, we can use second function Scene.LoadActor(actor); which loads the actor but attaches it to the current scene and cthe actor is destroyed when you leave the scene.
Last we set up the attribute MainObject to our freshly loaded actor, which does the only thing – when actor moves and the scene is supposed to scroll, the scrolling is oriented to this actor. In case there were more switchable actors (like for example in Day of the Tentacle) this attribute will provide correct scrolling for selected character. You can set also null to this attribute.
Game.AttachScript("scripts\game_loop.script"); //formerly known as a game_daemon.script
We’ll attach the core processes, which take care of caption displaying and inventory appearing when you scroll your mouse up. This will be our second file we’ll examine in a minute. Again you can see, that this file is globally attached for the whole game.
And now for something formerly extremely important - now settable from the project manager:
Game.ChangeScene("scenes\Room\Room.scene");
This is the point where you define in what scene starts your game. Usually it will be some sort of Menu screen unless you start with some FMV intros or such. Now comes the main cleansing part. As we’re stripping our game down, we’ll now remove all inventory interactions only to add them later when we’ll be more skilled.
So simply modify on “LeftClick” event to read:
on "LeftClick" { var ActObj = Game.ActiveObject; if(ActObj!=null) { ActObj.ApplyEvent("LeftClick"); } else { Scene.ApplyEvent("LeftClick"); } }
When we left click in the scene, we first look if the click was in the hotspot. WME knows if there is a hotspot under our mouse cursor and fills attribute ActiveObject accordingly.
Now we simplified our logic to two states. If we clicked on an object, we’ll call his “LeftClick” event. This brings us back to our experiments in the last chapter where we assigned the “LeftClick” event to door. This is the reason why that event got called in the first place. ApplyEvent is a method which is used for running events manually.
If we clicked elsewhere (no hotspot under cursor), scene “LeftClick” event is fired. I almost hear you scream now: “Wait a minute. What is a scene “LeftClick” event?!".
This is another trick by WME which comes out of Scene template. If you create a new scene, or open your room scene in scene edit and switch to the Properties tab and click on the "Scripts…" button, you’ll see that there are two scripts attached. Your favorite scene_init.script and scripts/scene.script. And since this is the template every single scene uses, let’s uncover what dark mysteries this file holds?
#include "scripts\base.inc" on "LeftClick" { actor.GoTo(Scene.MouseX, Scene.MouseY); }
HA! That’s it. if this event is fired, our actor goes to the current mouse position in the scene. So it boils down to the simple thing: If we click on a hotspot it’s left click is fired else our actor walks to the current mouse position. And that’s about the LeftClicks for now.
Let’s move on to the next event “RightClick” and erase the whole block. We don’t need it now and it would only make our head spin.
Last two events (“Keypress” and “QuitGame”) deals with menu displaying and handling the alt+f4 combo. We don’t have to bother ourselves with it for now, but rest assured, that we’ll look into them soon too.
Let’s recap new methods and attributes we learned so far in this chapter:
var window = Game.LoadWindow(window filename); - Loads a window definition, attaches it to the Game object and optionally returns a reference to it (in this case to window variable)
window.Visible = true / false; - Makes the loaded window visible/invisible on the screen
window.GetContro(controlname); - Returns a reference to a control stored in the window
Game.LoadActor(actor filename); - Loads an actor from file and attaches it to a game object.
Scene.LoadActor(actor filename); - Loads an actor from file and attaches it to a scene object destroying it after the scene is left.
Game.MainObject = actor; Sets the actor to whom the scene scrolling is synchronized.
Game.ActiveObject; - stores the reference to an object your mouse cursor is currently over.
Game.ApplyEvent(eventName); - is a method which fires the event associated to the Game object
Scene.ApplyEvent(eventName); - is a method which fires the event associated to the Scene object
Node.ApplyEvent(eventName); - is a method which fires the event associated to the node stored in the object in question.
For better understanding the last command, we could easily run in our last chapter the “LeftClick” event of the door elsewhere like this:
var door = Scene.GetNode("Door"); door.ApplyEvent("LeftClick");
I hope you got my point now… Let’s also look if you have the same *game.script* file as I do before we proceed:
#include "scripts\base.inc" #include "scripts\keys.inc" // store some of the game's attributes in global variables for convenience Keyboard = Game.Keyboard; Scene = Game.Scene; // load the "caption" window var win = Game.LoadWindow("interface\system\caption.window"); global WinCaption = win.GetControl("caption"); // load our main actor actor = Game.LoadActor("actors\molly\molly.actor"); Game.MainObject = actor; // run the "daemon" script Game.AttachScript("scripts\game_daemon.script"); // which scene to load? 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) { // 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); } } 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); }
Now test the game and you’ll see that it’s still running although we made quite some changes to it. And as we want to continue boiling our example game down to the necessary minimum, we need to look at the game_loop.script (formerly named game_daemon.script) as well.
#include "scripts\base.inc" global WinCaption; global WinMenu;
We can easily delete the line global WinMenu; because we’ve already dropped the idea of right click menu.
while(true){
This will be always true. This way we are looking at the infinite loop. To end this loop, one would have to call Game.DetachScript("scripts\game_loop.script");
var ActObj = Game.ActiveObject;
We’ve seen exactly the same line before in the game script.
if(Game.Interactive && ActObj!=null) {
So first we test if the game is interactive and our mouse cursor is over some active object. But we’ll make this condition a bit simpler so we’ll put away all inventory items logic and it’ll look like this:
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;
So what happens if we have our mouse over the object?
WinCaption.X = Game.MouseX; WinCaption.Y = Game.MouseY + 20;
WinCaption contains the reference to the text field inside of the caption window loaded in the game script. We set its position to the same coordinates as our mouse X-position and Mouse Y-position + 20 so it’ll be 20 pixels below.
WinCaption.TextAlign = TAL_LEFT; WinCaption.Text = ActObj.Caption;
Then we set text aligning to left and set the text of the caption to the node caption as defined in the scene editor.
WinCaption.SizeToFit();
We resize the caption so its size is the same as the size of the displayed text.
if(WinCaption.X + WinCaption.Width > Game.ScreenWidth) WinCaption.X = Game.ScreenWidth - WinCaption.Width;
If the caption width however extends out of the screen, we set our caption X-position to end exactly with the screen border (based on the resolution).
if(WinCaption.Y + WinCaption.Height > Game.ScreenHeight) WinCaption.Y = Game.ScreenHeight - WinCaption.Height;
We do the same with the height and the bottom border.
WinCaption.Visible = true; WinCaption.Focus();
We display our caption by making it visible and give it focus, which is in this case not necessary, because we don’t interact with it. So we can simply delete this line too.
} else WinCaption.Visible = false;
The rest means that either the game went into non interactive mode or we moved our mouse out of any active hotspots. In both cases we simply hide the caption.
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;
Those two lines deal with inventory showing / disappearing and so we simply delete them.
Sleep(20);
Remember what I’ve told you about infinite loops and hanging computers? Enough said.
So let’s look at our revised game_daemon.script
#include "scripts\base.inc" global WinCaption; // infinite loop 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); }
As you can see, those are the barebones of our starting project. No inventory, no menus, simple point and click game. Moreover you understand now, what’s the brain behind the game and as we build upon it, you’ll feel more comfortable knowing that you have the rock solid background. Next step: the actor!