Inset screenshots are from Lockdown [Alex Varanese, 2002, Game Scripting Mastery].
Introduction
This article presents Dungeon of Despair (DOD), a complete game application intended as a demonstration of the Conscript scripting engine presented in an earlier article. The game is developed as a borderless Windows application using GDI+ to render graphics.
Inspiration
DOD is inspired from Lockdown, a game developed by Alex Varanese as a demo for the Xtremescript scripting engine developed in his book, Game Scripting Mastery. Screenshots for Lockdown are provided inset within the above screenshot. In Lockdown, you are a droid trapped in a station of some sort and you must locate four colour-coded keys, placing them in a central control room to unlock the station and escape to safety. The player sees the station one room at a time, with each room leading to one or more rooms via doorways. Protecting the keys and control rooms are enemy droids with varying levels of intelligence, some of which can not only harm the player on touch but also fire at the player in some cases. The player can fire back and can rest to restore the droid's health meter. The game ends if the player looses all his or her health or when the station is unlocked. Lockdown illustrates the application of scripting by implementing ambient effects -- such as flickering lighting -- and enemy droid A.I. as scripts.
The gameplay mechanics for DOD are very similar to Lockdown, albeit within a fantasy setting inspired from classic 2D RPGs. In DOD, the player is a mage trapped in a magical dungeon from which escape is only possible via the activation of a magic portal. The portal in turn can only be activated by locating four elemental vials and placing them on the portal. The player must, however, face a number of enemies, the type of which determines the overall intelligence and hence threat level. The player looses health on touching enemies or being hit by enemy spells. Health is represented as a coloured health meter at the top left of the screen. Four icons on the top right of the screen represent an inventory of vials collected by the player.
Application of scripting
Like Lockdown, DOD is a game application complex enough to warrant the use of scripting while still being limited in scope as an illustrative example. DOD employs Conscript scripts primarily to implement enemy A.I. There is no specific scripting for ambient effects. However, the dungeon layout and its contents are defined by a script-defined data structure. The game also illustrates a simple script-driven cutscene system, whereby entry to each room triggers an optional cutscene script function to help immerse the player in the game.
Artwork
Background art was generated from a number of (heavily!) processed photographs. The character sprites and dialogue face sets were generated using the online RPG sprite generator found here.
Game Instructions
To run the game, compile the attached solution and execute the resulting binary. On execution, the game application displays a combined title/menu screen where the user can select to start a game session or exit the application. Menu item selection may be performed via the cursor keys and the space bar or return key. Once a game session is started, the user may move the player around using the cursor keys and fire spells using the space bar. During cutscenes, player controls are temporarily disabled. The user may quit the game session and return to the title screen any time by hitting the Esc key. Game Over screens may be exited by hitting the space bar or return key.
Background
Embedded scripting engines have proved to be a valuable tool in game production. They empower the game designers to build content for the game without requiring the intervention of the game programmers who, in turn, are free to concentrate on other, more technical aspects of game development. Another benefit is that production turn-around times are substantially reduced as designers can alter game content without requiring new builds of the game application. Scripting engines are also the cornerstone of "moddable" games that encourage communities to extend the longevity of games via user-created content.
Implementation
The game application follows an OO design paradigm where the game's main components are represented by objects that interact with each other. The application uses a borderless main window, rendering graphics using GDI+ and reading player input via keyboard events. The main states of the game -- that is, the title screen, play sessions, game over screen, etc. -- are implemented via game state classes deriving from a common GameState
class. The application maintains a reference to the currently active state class that in turn handles its own behaviour. Each state may also request a transition to a new state, such as when starting a new game from the title screen or ending a play session and displaying the Game Over screen.
Dungeon Representation
The game world, represented by a Dungeon
class, consists of a collection of interconnected rooms represented by a Room
class. Each room has links to one or more other rooms via a Doors
class instance that maintains room links for the four main compass points. Additional room properties provide decorative features to introduce some variety in the rooms, such as flickering torches placed around the room or a circular pedestal, typically placed in junction rooms. One room in particular contains the elemental portal, and this is represented by a room reference in the Dungeon
class. Similar references are also used for the starting room and the placement of the four elemental vials around the dungeon.
The dungeon data is defined in a Conscript script Dungeon.cns
as a multi-level associative array with a structure similar to the Dungeon
, Room
and Doors
instance hierarchy. The script is loaded and executed on switching to the play session state. The resulting structure is extracted from the script's variable dictionary and processed to construct the dungeon layout. The following is an excerpt from the dungeon layout script:
var s_dungeon;
function main()
{
var rooms = {};
rooms.R00 = {};
rooms.R00.Pedestal = true;
rooms.R00.Doors = {};
rooms.R00.Doors.East = "R10";
rooms.R00.Doors.South = "R01";
rooms.R00.TorchNE = true;
rooms.R00.TorchSW = true;
rooms.R00.Enemies = {};
rooms.R00.Enemies.Enemy0 = {};
rooms.R00.Enemies.Enemy0.Type = "Wizard";
rooms.R00.Enemies.Enemy0.Quantity = 1;
rooms.R00.Enemies.Enemy1 = {};
rooms.R00.Enemies.Enemy1.Type = "Pig";
rooms.R00.Enemies.Enemy1.Quantity = 1;
rooms.R00.Enemies.Enemy2 = {};
rooms.R00.Enemies.Enemy2.Type = "Lizard";
rooms.R00.Enemies.Enemy2.Quantity = 1;
rooms.R10 = {};
rooms.R10.Doors = {};
rooms.R10.Doors.West = "R00";
rooms.R10.Doors.East = "R20";
rooms.R10.TorchNE = true;
rooms.R10.TorchSE = true;
rooms.R10.Enemies = {};
rooms.R10.Enemies.Enemy0 = {};
rooms.R10.Enemies.Enemy0.Type = "Wizard";
rooms.R10.Enemies.Enemy0.Quantity = 1;
rooms.R10.Enemies.Enemy1 = {};
rooms.R10.Enemies.Enemy1.Type = "Pig";
rooms.R10.Enemies.Enemy1.Quantity = 2;
s_dungeon = {};
s_dungeon.Rooms = rooms;
s_dungeon.StartingRoom = "R34";
s_dungeon.PortalRoom = "R30";
s_dungeon.RedVialRoom = "R20";
s_dungeon.GreenVialRoom = "R40";
s_dungeon.YellowVialRoom = "R44";
s_dungeon.BlueVialRoom = "R24";
}
Entities
The player, enemies and projectiles all derive from a common Entity
class that provides basic services such as placement and movement. The player is represented by a Player
class that contains properties for the player's health and flags for possession of the vials. Each enemy entity is represented by an Enemy
class that includes properties for the enemy's health, a ScriptContext
instance referencing an appropriate A.I. script for the enemy and a speed property for the enemy's rate of movement.
Each enemy script is designed to execute in parallel with the rest of the application and hence consists of a main loop in which the environment is sensed and appropriate actions are taken. In order to drive the enemy instance associated with the script context, a number of host functions are registered, with the enemy instance assigned as the function handler. The following is the list of enemy A.I. host functions:
int GetRandom(int iMin, int iMax)
- returns a random integer in a given range
Move(int iX, int iY)
- moves the enemy to the given coordinates
SetDirection(int iDirection)
- turns the enemy towards the given direction
SetAutomaticDirection(bool bFlag)
- enables/disables automatic direction based on velocity
{"X":int, "Y":int} GetPosition()
- gets the enemy's coordinates
{"X":int, "Y":int} GetPlayerPosition()
- gets the player's coordinates
CastSpell()
- casts a spell in the direction faced by the enemy
Cutscenes
Whenever the player enters a room, the game application checks if a cutscene is available, in which case, control of the player is temporarily relinquished from the user while the cutscene is played. Cutscenes are implemented as script-defined functions in a script file Cutscenes.cns
. The application locates the function on the assumption that the function's name is of the format Cutscene_RNN
where RNN is the room identifier. The cutscene function consists of a sequence of host function calls that control the character interactions during the scene. The following script function defines the cutscene for the central room R32
. This particular scene encourages the player to explore the North exit:
function Cutscene_R32()
{
if (s_canSeeLight != null) return;
s_canSeeLight = true;
Player_SetActive(false);
Player_MoveTo(320, 160);
while(Player_Moving()) yield;
Player_Say("I can see strange lights coming beyond this doorway!");
while(TextWindow_IsActive()) yield;
Player_MoveTo(320, 170);
Player_Say("Maybe I should have a look...");
Player_SetActive(true);
}
To support scripted cutscenes, the following host functions are defined in the game application:
Player_SetActive(bool bFlag)
- enables/disables player control
Player_SetPosition(int iX, int iY)
- sets the player's position
Player_MoveTo(int iX, int iY)
- requests the player to move to the given position
bool Player_Moving()
- returns true if the player is moving
Player_Say(String strMessage)
- makes the player say something in the chat window
bool Player_HasAllVials()
- returns true if the player has collected all four vials
Boss_SetActive(bool bFlag)
- shows/hides the enemy boss
Boss_SetPosition(int iX, int iY)
- sets the boss' position
Boss_MoveTo(int iX, int iY)
- requests the enemy boss to move to the given position
bool Boss_Moving()
- returns true if the enemy boss is moving
Boss_Say(String strMessage)
- makes the boss say something in the chat window
Boss_SpawnEnemy(String strType)
- spawns an enemy of the given type
bool TextWindow_IsActive()
- returns true if the chat window is still open
Points of Interest
Conceptually, DOD is a very simple game that could have easily been developed without the use of embedded scripting. However, thanks to scripting, it is possible to not only alter the dungeon layout, but also the A.I. behaviour of the enemies and the game's overall plotline. Handing more power to the scripting system increases the "moddability" of the game. In the next article, I will present a complete IDE application for Conscript that demonstrates the use of the Conscript API in a more sophisticated way, such as the provision of debugging functionality and the implementation of a host function module plug-in mechanism.
Related Articles
- EezeeScript: A simple embeddable scripting language for .NET
- Conscript: An embeddable, compiled scripting language for .NET
- Conscript IDE: An Integrated Development Environment (IDE) implementation for the Conscript scripting language.
History
- 20/Jul/2007 - First version released