Game Development Community

dev|Pro Game Development Curriculum

Adding custom objects to the World Editor Creator

by Warren Carroll · 03/25/2005 (1:11 pm) · 3 comments

Ok, first things first, I'm not going to spend any time explaining Torque Script, or how to use the World Editor, so I would recommend you have a reasonably good grasp of these things first before trying out my example. This resource assumes you're already pretty comfortable with Torque and have an understanding of how scripts work, datablocks, etc.

I was recently looking at the best ways to add a system for having different gametypes linked to missions in an object oriented fashion. I designed a gametype object that handled all the details such as victory conditions, and tracking objectives, but then I really wanted a simple way for the mission builders in my team to add these gametype objects to their missions without having to mess around directly with any code. The ideal way, I figured, would be to have a Gametype object in the World Editor, that could easily be dropped into the world in the same fashion that things like triggers and particle emitters are. After a bit of digging around in the scripts, I figured out how to do this, and now I'm going to show you how to do it too! :)

Note: This tutorial is primarily about how to make your own custom objects visible in the World Editor. This tutorial is not about how to create gametype system, thats way beyond the scope of this resource.

Part 1 - To defeat the enemy, first you must understand the enemy

Before we start making any changes, lets first understand exactly how the World Editor populates its list of items. All the script and gui files for the different editors are in the "editor" directory, under the "common" directory, under the "examples" directory, which is under whatever directory you installed torque into. The first file to look at is called EditorGui.cs. Scroll about halfway down and find the function Creator::init(). This is the function that builds the tree view (with all the Interiors, Shapes, Static Objects and Mission Objects) for the World Creator part of the editor. Now scroll through the function until you get to this section:

// *** OBJECTS - do the objects now...
	%objGroup[0] = "Environment";
	%objGroup[1] = "Mission";
	%objGroup[2] = "System";
	//%objGroup[3] = "AI";

	%Environment_Item[0]  = "Sky";
	%Environment_Item[1]  = "Sun";
	%Environment_Item[2]  = "Lightning";
	%Environment_Item[3]  = "Water";
	%Environment_Item[4]  = "Terrain";
	%Environment_Item[5]  = "AudioEmitter";
	%Environment_Item[6]  = "Precipitation";
	%Environment_Item[7]  = "ParticleEmitter";
	%Environment_Item[8]  = "fxSunLight";
	%Environment_Item[9]  = "fxShapeReplicator";
	%Environment_Item[10] = "fxFoliageReplicator";
	%Environment_Item[11] = "fxLight";
	
	%Mission_Item[0] = "MissionArea";
	%Mission_Item[1] = "Path";
	%Mission_Item[2] = "PathMarker";
	%Mission_Item[3] = "Trigger";
	%Mission_Item[4] = "PhysicalZone";
	%Mission_Item[5] = "Camera";
	//%Mission_Item[5] = "GameType";
	//%Mission_Item[6] = "Forcefield";

	%System_Item[0] = "SimGroup";

	//%AI_Item[0] = "Objective";
	//%AI_Item[1] = "NavigationGraph";

	// objects group
	%base = %this.addGroup(0, "Mission Objects");

	// create 'em
	for(%i = 0; %objGroup[%i] !$= ""; %i++)
	{
		%grp = %this.addGroup(%base, %objGroup[%i]);

		%groupTag = "%" @ %objGroup[%i] @ "_Item";

		%done = false;
		for(%j = 0; !%done; %j++)
		{
			eval("%itemTag = " @ %groupTag @ %j @ ";");
			if(%itemTag $= "")
				%done = true;
			else
				%this.addItem(%grp, %itemTag, "ObjectBuilderGui.build" @ %itemTag @ "();");
		}
	}

Dont worry, it looks a lot worse than it is (mostly). The top half of this code creates a series of arrays which, if you go into one of the Torque examples and open up the World Editor, you will notice correspond to the items in the different branches of the "Mission Objects" group. After the arrays are created we get into the code that creates the actual items themselves, this is a bit more complicated.

After creating the "Mission Objects" group in the editor, it loops through the %objGroup array, which represents the next level of the group. This is the level that has the "Environment", "Mission" and "System" groups in the World Editor. The function adds each group to the "Mission Objects" tree in the World Editor, and then uses the current group name to build another array name which represents the items inside that branch of the tree. The code then initiates a second loop, which loops through the second array, using eval() to evaluate the array name and get the names of the items. Each item is added to the World Editor's tree, under the appropriate group. The item is setup so that when it is clicked, a function in the ObjectBuilderGui class is called. This function name is also created on the fly, using the name of the current item. For example, if the current item was called "Trigger", the function that would be called when the item is clicked in the World Editor would be ObjectBuilderGui.buildTrigger().

Now we have to open up the objectBuilderGui.gui file, also in the "example\common\editor" directory. This is the file that contains all the ObjectBuilderGui::buildxxxx() functions referred to by the items created in the Creator::init() function previously. Know how whenever you select an object from the "Mission Objects" group, such as a Particle Emitter, you get that little popup window that lets you enter all the necessary parameters to create the object? Well that window is what the objectBuilderGui.gui file defines.

Each item in the "Mission Objects" part of the World Editor has a corresponding build function in this file. If you scroll right down to the bottom of the file you will see build functions for triggers, simgroups, cameras, and many other things. There are three things that every build function does: tells the object builder the class name of the object you want to build, sets up initial field values for the object, and then calls the ObjectBuilderGui::process() function which processes the details and displays the Object Builder popup. For example, the function for building a Camera is as follows:

function ObjectBuilderGui::buildCamera(%this)
{
   %this.className = "Camera";

   %this.addField("position", "TypePoint3", "Position", "0 0 0");
   %this.addField("rotation", "TypePoint4", "Rotation", "1 0 0 0");
   %this.addField("dataBlock", "TypeDataBlock", "Data block", "CameraData Observer");
   %this.addField("team", "TypeInt", "Team", "0");

   %this.process();
}

Setting a classname and calling the process() function are mandatory steps, adding field values is optional (unless the object requires a datablock, then you will need to add at least that field). Field values are added via the use of the ObjectBuilderGui::addField() function. This function takes four arguments. The first argument is the name of the field inside the object, the second is the data type of the field (more on that in a second), the third is the label to use for the field in the Object Builder popup and the fourth is the initial value to use for the field. Each field you add in this fashion will have a corresponding control in the Object Builder popup that allows you to change the value for the field.

The second parameter to the ObjectBuilderGui::addField() function determines what control will be used to represent the field in the Object Builder popup. This field can be "TypeBool" which represents the field with a checkbox, "TypeDataBlock" which represents the field with a dropdown list of datablocks, and "TypeFile" which uses a control that lets you search for a file. Any other value passed as the datatype argument will be represented by a text field. You will notice a lot of the build functions use special types like (in the Camera example above), "TypePoint3", "TypePoint4" and "TypeInt". These are all just processed as strings and represented by text fields. None of those types alter the behaviour of the Object Builder popup, the names are just used to make the code more readable and understandable.

Ok, now that we understand the mechanics of the show, lets see if we can't mix things up a bit.


Part 2 - Making Magic

I'll be using a really basic Gametype object to demonstrate how to add an object to the Mission Builder. Actually, basic is a bit of an understatement, it pretty much does nothing except define a couple of different datablocks so we've got something to play with. Any furthur functionality is, you guessed it, beyond the scope of this example :) Create a new file called gametypes.cs in your game's "\server\scripts" directory and paste the following code in. Once you have done this, edit the game.cs script, also in the "\server\scripts" directory of your game, and add a line to execute the gametypes.cs script.

datablock GameBaseData(GameTypeCTF) {
	className = GameTypeData;
	gameType = "CTF";
};

datablock GameBaseData(GameTypeTDM) {
	className = GameTypeData;
	gameType = "Team Deathmatch";
};

datablock GameBaseData(GameTypeDM) {
	className = GameTypeData;
	gameType = "Deathmatch Free-For-All";
};

I've derived them from GameBaseData because the Gametype object we will create will be of type GameBase. This is because it is the lowest level class in standard un-modified Torque that supports everything necessary to use Datablocks (at least, so far as I can tell from the documentation). I wanted a Datablock in the example just so you can see an example of how ObjectBuilderGui::addfield() works with a Datablock field.

Now, head back to the EditorGui.cs file and go back to the Objects section of the Creator::init() function. We want to add another item to the "Mission" branch of the "Mission Objects" group, so go down to where the %Mission_Item array is set up and change it so it looks like this:

%Mission_Item[0] = "MissionArea";
	%Mission_Item[1] = "Path";
	%Mission_Item[2] = "PathMarker";
	%Mission_Item[3] = "Trigger";
	%Mission_Item[4] = "PhysicalZone";
	%Mission_Item[5] = "Camera";
	%Mission_Item[6] = "Gametype";


All that we did was add another element to the end of the array. This will make a "Gametype" item appear in the menu now. Pretty easy hey!

Now the next thing we need to do is add our custom build function to the end of the objectBuilderGui.gui file. Scroll down near the bottom of the file to where the Mission build functions are, and add the following function after the ObjectBuilderGui::buildCamera() function.

function ObjectBuilderGui::buildGametype(%this) {
	%this.className = "GameBase";
	
	%this.addField("dataBlock", "TypeDataBlock", "Data Block", "GameBaseData GameTypeCTF");
	%this.addField("SomeOtherField", "TypeString", "A test string", "You can type stuff here");
	
	%this.process();
}

This is pretty straight-forward. We tell the Object Builder popup to build a new class of type GameBase, then we tell it what fields we want to be included in the popup window and what their initial values will be. Our first call to ObjectBuilderGui::addField() is used to add a datablock field to the new class. The field name "datablock" is used, and since this is a datablock we use "TypeDataBlock" for the second parameter. We then pass in a text label to display for this field in the popup window ("Data Block"), and then we pass our field specific values.

For a "TypeDataBlock" field there are two values passed, separated by spaces. The first value is a datablock class, and the second value is the name of the actual datablock to initialise the field to. The first value, the datablock class, is used to filter the list of datablocks that are available to choose from the drop down list representing this field in the Object Builder popup. For an example of how this filtering works, try creating a Particle Emitter. You will notice that for the "datablock" field you can only choose datablocks of type ParticleNodeData and for the "Particle Data" field you can only choose datablocks of type ParticleEmitterData. The second value is optional and can be left out. In this case we are setting it to default to the GameTypeCTF datablock, but we could just as easily leave it out and start the field off blank.

The second call to ObjectBuilderGui::addField() adds a field to the new object called "SomeOtherField", represented in the Object Builder popup box by a text box, with the label "A test string" and initialised to "You can type stuff here". As with the datablock field, the last argument is optional if you want to just initialise the field to a blank string instead of any specific value. After we've set our fields we then call the ObjectBuilderGui::process() function which makes it all official and displays the Object Builder on screen.

Thats all the scripting done! Now if you start up torque, load a mission, and go into the World Editor you should see a "Gametype" object listed under the "Mission" group under "Mission Objects". Click the "Gametype" object and the Object Builder will popup with a text field for the new object's name (automatically put there by the Object Builder), a drop down combo box containing the GameTypeCTF, GameTypeTDM and GameTypeDM datablocks and initialised to the GameTypeCTF datablock, and a text field with "You can type stuff here" in it. Change the values to whatever you want/need and click OK and a new object will be generated and inserted into the mission file (you should see it the tree with all the other objects currently loaded into the mission).

As always, the best way to learn more is to do a bit of research, so if you want to find out a bit more I'd suggest having a more thorough look at the objectBuilder.gui file. If you get a handle on the majority of whats going on in there you will have no trouble doing what I just did above and more (such as adding your own data types represented by different controls).

Good luck and happy coding!

About the author

Recent Blogs


#1
03/25/2005 (1:59 pm)
Thanks for the resource.
#2
03/26/2005 (11:24 pm)
nice resource thanks
#3
09/16/2007 (10:40 am)
Thanks! I've been looking for something like this.