Login x
User Name:
Password:
Social Links Facebook Twitter YouTube Steam RSS News Feeds
Watch MODSonair

Members Online

»
0 Active | 259 Guests
Online:

LATEST FORUM THREADS

»
New BF2 Mod
BF2 General

Tutorials

»
Level Scripting Tutorial 1
Versions: You must be logged in to view history.
rich_is_bored shows us the basics of the Doom 3 scripting language.
Introduction

The scripting language in Doom 3 is very powerful. You can create very complex machinery or with the aid of some trigger entities landscape altering geometry. Many of these effects can be accomplished with a small amount of code as long as you gain a good understanding of the scripting commands and their syntax.

Those of you familiar with programming will find scripting in Doom 3 fairly easy and similar to languages like C++, Java or even ActionScript. If you do happen to have experience with a programming language, congratulations, you definitely will have an edge.

Don’t let that discourage you. Not all of us are software engineers. I am no more than just a hobbyist myself. But if I can script for Doom 3 than any of you can.

You'll need the zip below.

Download this file

Basics of Programming

When you are scripting you are telling objects to perform certain tasks. But these objects don’t understand English. You need to talk to them in a manner that they can understand.

For the time being we will relate to the way people perform tasks. Let’s say you want to open a door. We all know how to do so but this simple task can be broken down into a series of basic steps.

Opening a Door

  1. Extend your hand to reach the door.
  2. Grasp the knob.
  3. Turn the knob.
  4. Pull the knob.

To even make things worse those basic steps can be broken down even further.

Extend your hand to reach the door.

  1. Raise your arm.
  2. Rotate your forearm.
  3. Open your hand.


You can see how even a simple task like opening a door is actually comprised of a lengthy list of basic actions. This is the basis of scripting in Doom 3. You cannot just tell a machine to run. You have to tell all its moving parts to perform basic actions.

Overview of Commands

The following is a list of commands and symbols used in scripts. This list is rather incomplete as the function of all the commands is not yet known. Your understanding of them is not important yet. They are merely for reference. I suggest you print them out so you can work with them at your side as you go. I will make reference to this section frequently throughout the tutorial.

EDIT: Level Script Commands have been moved to the Scripting Command Reference in an attempt to compile a more complete and accurate reference.

The remaining definitions below are related more to general syntax rather than commands.

$
...Definition: Precedes all variable references. Informs the engine that the following is a variable reference.
...Syntax:
Code:
$entity_name.rotateTo($position);


;
...Definition: Follows all commands. More than one command may exist on a single line. Informs the engine that the following is the end of a command.
...Syntax:
Code:
$entity_name.rotateTo($position);


//
...Definition: Comment. Declares the text from this point to the end of the line a comment and is not executed. Can also be used to null out a line of code for debugging.
...Syntax:
Code:
// This text will not be executed.

...Example:
Code:
//sys.print ("bO1nG!n");


/* and */
...Definition: Comment. Declares the text from /* to */ a comment and the text contained therein is not executed. Can also be used to null out a portion of code for debugging.
...Syntax:
Code:
/* This text will not be executed. */

...Example:
Code:
sys.random (2) /* + 7 */;


Beginner: Creating a Strange Spinning Box

Before you can run you have to learn to walk. You can’t just jump in and make some complex machine. You have to start small. We will start with just a simple box that rotates. Then we will fool around with its properties a little to gain a better understanding of what you’ve coded actually does.

Two Methods to Call a Script For Exectution

There are two methods to assign your script file as the intended level script for exectution in a map. The "same name" method and the "namespace" method.

Same Name

The "same name" method requires you do two things. First, you must ensure both the map and script share the same name. Second, you must place both files in the same folder. This is the method we'll be using in this tutorial because to me, it is the easiest.

Namespace

The "namespace" method is used with Doom 3's vanilla maps. This method will not work with custom maps unless you edit the file scripts/doom_main.script in pak000.pk4.

The only real benefit of using this method is that it permits you to store your script in a folder separate from your map. Just as an example, if you were to use this method with the files in this tutorial you'd have to add this line to scripts/doom_main.script...

Code:
#include "scripts/rich_scripting_tutorial_1.script"


Then you'd have to add the following key/value pair to the worldspawn entity in rich_scripting_tutorial_1.map...

Code:
Key: call
Value: rich_scripting_tutorial_1::main


Then you'd have to enclose your entire script in a namespace command like so...

Code:
namespace rich_scripting_tutorial_1 {

... code ...

} // close namespace


Personally, I suggest that you refrain from using this method with your maps. I can't really understand the logic in changing scripts inside the game's PK4s to make it work, rather than appending a modification. You risk screwing the game up trying this.

Getting on With it Already

I have already created the map we will be writing a script for. It was setup using the "same name" method for simplicity's sake. We will view this map in the editor to gain some familiarity with the basic requirements for a script to function. All you'll be required to do is ensure your map and script share the same name and location.

Extract rich_scripting_tutorial_1.map to [Your Doom 3 Directory]basemaps. Open rich_scripting_tutorial_1.map in the editor. This map is a simple room with one light, a player starting point, and a func_mover which will be controlled with rich_scripting_tutorial_1.script.

Select the func_mover in the center of the room. Select the Inspector window and click the entity tab to bring up the entity property sheet. Notice that the name is set to cube. The model key will also change when this key is set. By referencing this name we can instruct the box to perform actions.

Now open example.script in a text editor. I suggest you get a good text editor such as editpad but notepad will work fine for level scripts. Just know that notepad is not very good at interpreting EOL markers and such so many DOS based text files may appear jumbled.

A link that is currently good is http://tucows.kr.psi.net/files/EditPadClassic.zip

A basic script is composed like this script. Don’t try and figure it out yet even though the comments do a pretty good job of explaining things. This is not the script that we will be using to control our cube. You will be coding that. This is just an example. I will explain the basics to you but first I need to get you into a habit of coding readable script.

Code:
// sets up moving parts for motion

void setup_objects()
{
$block.bind($light1);
}

// function for moving block up

void block_move_up ()
{
$block.move(UP, 20);
sys.waitFor ($block);
}

// function for moving block down

void block_move_down ()
{
$block.move(DOWN, 20);
sys.waitFor ($block);
}

// function that executes when script is loaded

void main ()
{
setup_objects ();
block_move_up ();
block_move_down ();
}


First off, I add a lot of comments. I suggest you do the same because as you get into making more and more complex scripts these comments will keep you from getting lost in your own code.

Second learn to use the tab key! Indent your code so that you can tell just by looking what code is contained in the curly brackets. If you don’t use comments, at least indent. This will aid you the most when trying to debug your script by preventing you from getting confused.

Now go ahead and take a look at the first line.

Code:
// sets up moving parts for motion


// will declare the remaining portion of the line a comment. Everything between the double forward slash and the end of the line will not be executed. I hate to reiterate but, take advantage of comments! Describe in your own words what is taking place by using comments.


The next few lines of code define a function. A function is basically a way of isolating a segment of commands so that they can be executed with one command. A function can be named anything that you choose as long as it forms one word. Again be descriptive when naming your functions so you can tell just by looking what it does.

Code:
void setup_objects()
{
$block.bind($light1);
}


Now anytime in the script if I were to write the statement setup_objects (); the program would execute the code contained within the curly brackets above. In this case it would execute the line.

Code:
$block.bind($light1);


Now examine the functions block_move_up and block_move_down and try and determine what they do based on their names and comments alone.

If you guessed that they move a block your right. Now, without even knowing what the code within those two functions do, you have a pretty good idea of what happens when they are called.

Now let’s look at the end of the script. Look at the function named main.

Notice that the functions setup_objects, block_move_up, and block_move_down are called within the function main. This function is run when your script is loaded.

So, what happens when a map with the necessary entities and key values is loaded? I’ll give you a quick run through.


  1. The game looks in the running map directory for a script that shares the same name.
  2. It loads all the functions defined in the script to memory.
  3. It executes the function main.
4a. The function setup_objects is run.
4b. The function block_move_up is run.
4c. The function block_move_down is run.


Now with a basic understanding of how to script we will get into the details of writing the script to rotate the cube in rich_scripting_tutorial_1.map. Remember the script must share the same name as the map and be stored in the map's directory. In this case, the script will be named rich_scripting_tutorial_1.script and will be stored in [Your Doom 3 Directory]basemaps.

So, create a file named rich_scripting_tutorial_1.script, using your text editor, in the folder mentioned above. Now type the following code into your text editor and save.

Code:
// function that executes when the script is loaded

void main ()
{
}


Now we have created a basic script with no real code to execute. As it is now the map would run without errors but nothing would happen. We need to define a function to spin the cube.

Before we do that notice that there is no function to setup objects. There is no need to do so if your sole intent is to make a spinning cube. But, take note because we will be adding one later when we experiment with our script.

Now add the following code to your script above everything else.

Code:
// function that rotates cube once 360 degrees

void cube_rotate ()
{
$cube.rotate('0 360 0');
}


This code will define a function cube_rotate and when called will rotate the cube once 360 degrees around the Y axis. Notice that in this case I am calling cube with $cube inserting a period and then the command rotate with its associated parameters followed by a semi-colon. The command rotate is similar to the command sway in that it does not need to be called more than once. It will continue to rotate until another map is loaded.

Look in the Overview of Commands section for a detailed description of rotate, ".", "$", and ";". There you will learn their basic syntax and function.

Now, add this line to your main function.

Code:
cube_rotate();


The code should look like this.

Code:
// function that rotates cube once 360 degrees

void cube_rotate ()
{
$cube.rotate('0 360 0');
}

// function that executes when the script is loaded

void main ()
{
cube_rotate();
}


If you haven’t already saved do so. Now the script should run. First run Doom 3 and then, in the console, type dmap mapsrich_scripting_tutorial_1.map. This will compile the map.

Now we can type map rich_scripting_tutorial_1.map and the map will load. You should see a box spinning in the center of the room around the Y axis.

Now we are going to have some fun. If you are in full screen switch to windowed mode by pressing ALT + ENTER. We are going to edit the script and see its effects real time.

Switch to your text editor by pressing ALT + TAB. Now we will try changing the values in this line.

Code:
$cube.rotate('0 360 0');


Try changing 360 to -360. Save the script. Switch back to Doom 3 and reload the map. Because the map has not changed and only the script, you only have to reload the map to see your changes.

Now the cube should be spinning the opposite direction on the Y axis. Have some fun experimenting with changing any of the 3 values and see what the result is. When you are done change the line back to its original values.

Because the cube appears to never stop you may not have noticed that it makes one rotation every second. But in fact, it does stop. It just does not stay that way long enough for you to see it. This is the default time that all new objects follow. Regardless of the movement, be it rotational or linear, it will always take one second to complete the motion.

Now we are going to change a couple of the time values.

Change your code to look like the following.

Code:
// function that rotates cube 360 degrees

void cube_rotate ()
{
$cube.accelTime(3);
$cube.decelTime(3);
$cube.time (10);
while (1) {
$cube.rotate('0 360 0');
sys.waitFor ($cube);
}
}

// function that executes when the script is loaded

void main ()
{
cube_rotate();
}


I have added quite a few commands at once. I needed to in order for the script to function properly. Now save the script and reload the map.

Now you will see the cube slowly accelerate for 3 seconds, maintain its speed for 4 seconds and then decelerate for 3 seconds. But it no longer rotates the cube just 360 degrees. Why? Because I have assigned a time of 10 seconds to the cube.

Well, we have to analyze the changes that have been made to determine this. I have added four commands to the script. Refer to the Overview of commands for a detailed definition and syntax.


  • accelTime
  • decelTime
  • time
  • while


The three commands accelTime, decelTime and time are used in conjunction to calculate the speed of the cube during its rotation. The command time will assign the total time it takes to complete the movement. accelTime and decelTime are the ranges of time that the cube will need to speed up and then slow down.

Because the cube now has a time assigned to it, it will not continue rotating. In order to make the cube continue to rotate we must enclose the rotation portion of code in a loop.
I did that with the while loop.

Now we are near complete. Remember when I mentioned about creating a function to setup your moving objects. Well, now we are going to bind two objects together. No, don’t start back up the editor to add something to the map. We don’t need to. We already have a light to bind to the cube. Change your code to look like the following.

Code:
// function that binds the light to the cube

void setup_objects ()
{
$light.bind ($cube);
}

// function that rotates cube 360 degrees

void cube_rotate ()
{
$cube.accelTime(3);
$cube.decelTime(3);
$cube.time (10);
while (1) {
$cube.rotate('0 0 360');

sys.waitFor ($cube);
}
}

// function that executes when the script is loaded

void main ()
{
setup_objects();
cube_rotate();
}


First a function was created to setup the objects before any movement occurs and added too our main function. Then the rotation was changed to rotate the cube on the Z axis.

Let’s look at the practical application of the bind command.

A bind command is used to bind the objects together at there respective distances. The object before the period becomes the child of the object in the parenthesis. This means that anything I tell the parent object to do from this point forward the child will do also as if it were attached.

So when I attached the light to the cube, the light then rotated with the cube around the cubes origin.

Congratulations, you’ve finished the tutorial.


Conclusion

By now you should have a very basic understanding and should be able to experiment and make some very simple scripts for your levels. All you really need to be able to make more complex scripts is a little time to study the Overview of Commands section of this tutorial.

Latest Syndicated News

»
Codutility.com up and runn...
Nice, and there still using the logo and template for the screenshots, which...
Codutility.com up and runn...
dundy writes...Quote:Call of Duty modding and mapping is barly alive only a ...
Codutility.com up and runn...
Mystic writes...Quote:It seems to me the like the site is completely dead? ...
Codutility.com up and runn...
It seems to me the like the site is completely dead?

Partners & Friends

»