Game Development Community

Object Oriented Programming in Torque Script

by Bryan Edds · in Technical Issues · 07/05/2004 (5:41 pm) · 85 replies

(NEW) IMPORTANT NOTE: Ben and I are trying to get this OO stuff to work by modifying the back end of the engine. There's no promise as to whether this will work out of not, but we will be finding that out soon. And if it works, you can expect a great tutorial frrom me very soon!

This is a rough draft for the tutorial I'm making. The tutorial aims to teach people how to program Torque Script in an object-oriented fashioned. If you find that your script's complexity is ballooning out of control with too many global variables and functions, then you may wish to organize your code with with the object orientation paradigm. But, it may be even better to put that code in C++ instead of OO script if it requires more than one layer of inheritance.

First of all, here is a highly commented template which will show a general pattern for organizing a Torque script class and explains how the object oriented features are implemented. Note you will substitute the word Class with the name of the class you wish to use, and substitute varName with the name you want the variable to be -

/////////////////////////
// CLASS Class
/////////////////////////
	
	/////////////////////////
	// INITIALIZATION:
	/////////////////////////
	function Class::create(%objectName)
	{
		// you should usually use "new ScriptObject(...)" when creating a new class
		%this = new ScriptObject(%objectName)
		{
			class = Class;
		}
	
		// create member variables and optionally initialize them to parameters.
		// once a member is created here, it persists for the life of %this object.
		
		/////////////////////////
		// PUBLIC MEMBERS:
		/////////////////////////
		%this.varName2 = 0;
		
		/////////////////////////
		// PRIVATE MEMBERS:
		/////////////////////////
		%this._varName = 0;
	
		return %this;
	}
	
	function Class::delete(%this)
	{
		// call the parent class's delete method as well
		parent::delete(%this);
	
		// insert rest of deletion code here...
	}
	// you MAY want to override this function to handle destruction, but as
	// it stands, you must ALWAYS call delete on any OO class you make
	// if you want to be certain delete() is called.
	
	/////////////////////////
	// PUBLIC INTERFACE:
	/////////////////////////
	
	// you can optionally make an accessor and mutator function for each of your
	// private members if you want them to be accessible and / mutable to the 
	// client. Remember, you do not need to make accessor or mutators functions 
	// for public members since they are already accessible as they are.
	
	function Class::getVarName(%this)
	{	return %this._varName;}
	
	function Class::setVarName(%this, %value)
	{	%this._varName = %value;}
	
	/////////////////////////
	// PRIVATE INTERFACE:
	/////////////////////////
	
	function Class::_incrementAge(%this)
	{	%this._age++;}
	// because this is a private function, it should only be called
	// by code in this class
	
/////////////////////////	
// END CLASS Class
/////////////////////////
#61
07/11/2004 (12:28 am)
Here you go Ben. If you can get these to work, then you've accomplished the objectives we've set out to... accomplish. :)

I've tested the code as well as I could. It SHOULD be free of errors other than the ones that are supposed to be there without the proposed engine modification.

BTW, copying and pasting code from these message boards gives hideous results. I've e-mailed you a copy of this code so you don't have to demanglefy what you find here. If anyone else wants a copy, e-mail me at the addy on my profile.
/////////////////////////
// CLASS MyClass
/////////////////////////
	
	/////////////////////////
	// INITIALIZATION:
	/////////////////////////
	function MyClass::create(	%objectName,
										%optionalParam1, 
										%optionalParam2)
	{
		%this = new ScriptObject(%objectName)
		{
			// there is no need to specify superClass since this is implicitly derived
			// from ScriptObject already
			class = MyClass;
		};
		
		// to initialize the class properly and set up its member variables,
		// call the protected protInit() funtion for this class
		MyClass::protInit(%this, %optionalParam1, %optionalParam2);
		
		// note that protected members and functions are those which may only be
		// accessed by the class that holds them or a subclass of that class.
		// as you can see, protect members and functions start with a prefix
		// of "prot"
		
		return %this;
	}
	
	function MyClass::protInit(%this, 
										%optionalParam1, 
										%optionalParam2)
	{
		// this is where object members are created and initialized.
		// next we create member variables and optionally initialize them to params.
		// once a member is created here, it persists for the life of %this object.
		
		/////////////////////////
		// PUBLIC MEMBERS:
		/////////////////////////
		%this.varName = %optionalParam1;
		// notice when using a member variable that you should ALWAYS explicitly
		// specify the member variable's scope with a %this identifier
		
		/////////////////////////
		// PRIVATE MEMBERS:
		/////////////////////////
		%this._varName2 = %optionalParam2;
	}
	
	function MyClass::delete(%this)
	{
		// call the parent class's delete method as well
		Parent::delete(%this);
	
		// insert rest of deletion code here if any...
	}
	// you MAY want to override this function to handle destruction, but as
	// it stands, you must ALWAYS call delete on any OO class you make
	// if you want to be certain delete() is called.
	
	/////////////////////////
	// PUBLIC INTERFACE:
	/////////////////////////
	
	// you can optionally make an accessor and mutator function for each of your
	// private members if you want them to be accessible and / mutable to the 
	// client. Remember, you do not need to make accessor or mutators functions 
	// for public members since they are already accessible as they are.
	
	// returns varName numeric value
	function MyClass::getVarName(%this)
	{	return %this._varName2;}
	
	function MyClass::setVarName(%this, %value)
	{	%this._varName2 = %value;}
	
	/////////////////////////
	// PRIVATE INTERFACE:
	/////////////////////////
	
	function MyClass::_incrementVarName2(%this)
	{	%this._varName2++;}
	// because this is a private function, it should only be called
	// by code in this class
	
/////////////////////////	
// END CLASS MyClass
/////////////////////////
#62
07/11/2004 (12:29 am)
/////////////////////////
// CLASS MySubClass
/////////////////////////
	
	/////////////////////////
	// INITIALIZATION:
	/////////////////////////
	function MySubClass::create(	%objectName,
											%optionalParam1, 
											%optionalParam2, 
											%optionalParem3)
	{
		// since this class is derived from MyClass, that is implemented by specifying
		// MyClass as the superClass in this initialization block
		%this = new ScriptObject(%objectName)
		{
			superClass = MyClass;
			class = MySubClass;
		};
		
		MySubClass::protInit(%this,
									%optionalParam1, 
									%optionalParam2, 
									%optionalParem3);
		
		return %this;
	}
	
	function MySubClass::protInit(%this, 
											%optionalParam1, 
											%optionalParam2, 
											%optionalParem3)
	{
		// Since this class is a subclass of another OO class, you have to call the
		// immediate super class's protInit() function like this -
		MyClass::protInit(%this, %optionalParam1, %optionalParam2);
		// or, depending on how Ben implements inheritance in Torque Script, you may
		// be able to do this at your option instead -
		// Parent::protInit(%this, %optionalParam1, %optionalParam2);
		
		/////////////////////////
		// PUBLIC MEMBERS:
		/////////////////////////
		%this.varName3 = %optionalParam3;
		
		/////////////////////////
		// PRIVATE MEMBERS:
		/////////////////////////
		// no private members in this particular class
	}
	
	function MySubClass::delete(%this)
	{
		MyClass::delete(%this);
		// or preferably this if Ben makes it possible -
		// Parent::delete(%this);
	}
	
	/////////////////////////
	// PUBLIC INTERFACE:
	/////////////////////////
	
	// returns varName numeric value
	function MySubClass::getVarName(%this)
	{
		// overrides the old function
		return "String! " @ %this._varName2;
	}
	
	/////////////////////////
	// PRIVATE INTERFACE:
	/////////////////////////
	// no private interface in this particular class
	
/////////////////////////	
// END CLASS MySubClass
/////////////////////////
#63
07/11/2004 (12:29 am)
/////////////////////////
// CLASS MySubSubClass
/////////////////////////
	
	/////////////////////////
	// INITIALIZATION:
	/////////////////////////
	function MySubSubClass::create(	%objectName,
												%optionalParam1, 
												%optionalParam2, 
												%optionalParem3)
	{
		%this = new ScriptObject(%objectName)
		{
			superClass = MySubClass;
			class = MySubSubClass;
		};
		
		MySubSubClass::protInit(%this,
										%optionalParam1, 
										%optionalParam2, 
										%optionalParem3);
		
		return %this;
	}
	
	function MySubSubClass::protInit(%this, 
												%optionalParam1, 
												%optionalParam2, 
												%optionalParem3)
	{
		MySubClass::protInit(%this, 
									%optionalParam1, 
									%optionalParam2, 
									%optionalParam3);
		// or, depending on how Ben implements inheritance in Torque Script, you may
		// be able to do this at your option instead -
		// Parent::protInit(%this, %optionalParam1, %optionalParam2, %optionalParam3);
		
		/////////////////////////
		// PUBLIC MEMBERS:
		/////////////////////////
		%this.varName3 = %optionalParam3;
		
		/////////////////////////
		// PRIVATE MEMBERS:
		/////////////////////////
		// no private members in this particular class
	}
	
	function MySubSubClass::delete(%this)
	{
		MySubClass::delete(%this);
		// or preferably this if Ben makes it possible -
		// Parent::delete(%this);
	}
	
	/////////////////////////
	// PUBLIC INTERFACE:
	/////////////////////////
	// no public interface in this particular class
	
	/////////////////////////
	// PRIVATE INTERFACE:
	/////////////////////////
	// no private interface in this particular class
	
/////////////////////////	
// END CLASS MySubSubClass
/////////////////////////
	
	
// some example code -
%collection[max] = 3;
%collection[0] = MyClass::create("xxx",1,2);
%collection[1] = MySubClass::create("yyy",3,4,5);
%collection[2] = MySubSubClass::create("zzz",6,7,8);
	
for (%i = 0; %i < %collection[max]; %i++)
{
	echo(%collection[%i].getVarName());
	%collection[%i].delete();
}
Here is also what could be known as a DataClass. It has some relevance to making SimObjects have as much inheritability as ScriptObjects, or at least one layer, which would be a good thing IMO.
//////////////////////////
// DATACLASS Ruby
//////////////////////////
	
	//////////////////////////
	// INITIALIZATION:
	//////////////////////////
	
	datablock ItemData(RubyData)
	{
		shapeFile = "~/data/shapes/items/hear.dts";
		mass = 1;
		friction = 1;
	};
	
	// returns DataTest instance
	function RubyData::create(%objectName, %color, %points)
	{
		%this.item = new Item(%objectName)
		{	
			datablock = RubyData;
			className = Ruby;
			
			// @ BEN - it would also be nice if you could get true inheritance working
			// for SimObjects as well. That way a subclass of this could use
			// "superClass = Ruby;" to derive from this object.
		};

		Ruby::protInit(%this);
		
		// for datablocks, you'll definitely want to register it with Mission
		// Cleanup... I think.
		//MissionCleanup.add(%this); // BUG - for some reason, this line won't compile
 		
		return %this;
	}
	
	function Ruby::protInit(%this)
	{	
		/////////////////////////
		// PUBLIC INTERFACE:
		/////////////////////////
		// no public members in this particular class
		
		/////////////////////////
		// PRIVATE INTERFACE:
		/////////////////////////
		// no private members in this particular class
	}
 	
	/////////////////////////
	// PUBLIC INTERFACE:
	/////////////////////////
	// no public interface in this particular class
	
	/////////////////////////
	// PRIVATE INTERFACE:
	/////////////////////////
	// no private interface in this particular class
	
//////////////////////////
// END DATACLASS Ruby
//////////////////////////
#64
07/11/2004 (11:47 am)
@Brian:

I just granted you a Torque Indie License. I'm at home and don't have access to the getting started email, but you now have access to the official Torque docs which have all of the instructions. If you look in you MyGarage area, you will see that you can get Torque, plus you have access to the Private SDK forums.

Have fun!

-Jeff Tunnell GG
#65
07/11/2004 (12:17 pm)
In the words of Neo - "Whoa."

:O

I don't know what to say! I'm overwhelmed!!! Thank you so much!

Wow. The only thing I can think to say is thanks!

Ahh! Thanks thanks thanks! Thanks a million times a million! :D
#66
07/29/2004 (1:07 am)
Quick note on OO in Torque Script -

Thanks to Harold, it's working in all objects derived from ScriptObject.

My next project (which should take no more than a day) is to move this code into SimObject so that all Torque script objects will be OO.

That should pretty much complete TS OO functionality, and with Harold's approval, I will make it public for testing. Also during that time, I will release the OO tutorial to get people started.

This should all happen pretty quickly, and all the OO fanatics can rejoice.

I'll keep this thread updated accordingly.
#67
07/29/2004 (9:42 am)
I'm glad to see you're getting this problem solved. Nice job, Bryan.
#68
07/29/2004 (2:40 pm)
Well, moving the code from ScriptObject to SimObject was a little more difficult than I thought it would be. But after familiarizing myself with some of the ins and outs of the engine, I was able to change the offending code.

So now, OO TorqueScript is ready for release and testing. I will release it as soon as I can get ahold of Harold (which usually takes a while, but oh well :)

Currently, there are some backwards compatibility issues with old code, but I haven't been able to pinpoint why as of yet. I'm hoping for some testing assistance to help me narrow down the problem.

Anyone interested in testing should either reply here, or contact me at the e-mail address that is listed on my profile.

That's all for now :)
#69
07/29/2004 (4:28 pm)
Fixed a problem with the OP_FUNC_CALL. It was not finding parent namespaces properly, but it is now :)

News at 11.
#70
07/29/2004 (5:30 pm)
Go ahead and release the code changes I sent you, they need a wider test anyway
#71
07/29/2004 (6:54 pm)
As it turns out, SimObject is not the best place to put the OO functionality. The main reason is because SimDataBlock derives from it, and as anyone knows, SimDataBlocks are not proper candidates for object-orientation.

So here are the classes I hope to get OO working in -

SimSet
ScriptObject - Done already :)
NetObject
TCPObject
Terraformer
WorldEditor::Selection

Keep on rocking in the free world.
#72
07/29/2004 (9:54 pm)
No! No no no!

Datablocks should be able to inherit, too! Why should they not be OOP-able?
#73
07/29/2004 (11:59 pm)
Eh? Are you sure?

They don't currently seem to be compatible with the ScriptObject code...

From what I uunderstand, Datablocks have their own way of doing inheritance and such. I'd have to do some surgery to make it work. And I could do it, but it just seems like something that shouldn't really be done... Course I'm also a newb, so in reality, I'm probably wrong. I'll just see what I can do. :)

Harold, thanks, I'll get a release candidate ASAP.
#74
07/30/2004 (4:40 am)
BIG NEWS - The current build has come out well, and all script classes that derive from SimObject (with the exception of SimDatablock) now have OO functionality! And what's more, it all works with total backwards compatibility.

So how did I do this?

Pretty simple really. I just made all the classes that derived from SimObject derive from ScriptObject instead (which works because ScriptObject derives from SimObject).

So now everything is nice and neat, and the only class left out was SimDataBlock. As for that, I'm still not entirely sure that SimDatablock shoud have OO since it already seems to have it's own flavor of somthing similar. I may be wrong, but it just doesn't seem like Datablocks should do anything other than what they already do, but I'll let Ben make that call.

And now the final issue is whether or not this will work with Packages.

Unfortunately, I know only a little about packages in Torque Script. From what little I know, I don't see why it wouldn't work as is, but I still need to make some tests and etc etc...

Wish me luck!
#75
07/30/2004 (11:22 am)
@Bryan: As you're describing the details of your efforts, I'm still trying to understand what you're doing here (besides the intent stated at the beginning).

You say all classes you created derive from ScriptObject but then I can do that today with any script class. (e.g. MyClass = new ScriptObject()). As you said, ScriptObject derives from SimObject in the C++ code so I'm struggling trying to see what it is, as a game developer using Torque script, this resource is providing that people don't have today?
#76
07/30/2004 (1:06 pm)
@Bil: You can't inherit objects more than one deep. You can't always inherit properly.


@Bryan: I think you're saying "not entirely sure that SimDatablock shoud have OO since it already seems to have it's own flavor of somthing similar" without really understanding how datablocks work. I don't see that what you're doing should significantly affect datablock's functionality. Maybe we can hash this out on IRC.
#77
07/30/2004 (4:11 pm)
Hi Bill! Thanks for yout interest in my (and Harold's) project. Basically, what I'm doing #1 allows for OOP in most all script objects. That includes all classes derived from SimObject (with the current exception of SimDataBlock). So, take a look at all the classes in the Graphical Class Hierarchy which derive from SimObjeect, and imagine them being able to use OO in all script objects in the same way that ScriptObject can, but with the added ability to subclass to more than one layer. This is acheived by extending ScriptObjecct's functionaliy (and modifying an OP operation), then making all classes that derived from SimObject to derive from ScriptObject instead.

Ben - As to the SimDataBlock, I think I have figured out why I'm having problems with it, and it has nothing to do with what I thought it was ealier. I will try this, and if I still can't get it to work, then I'll contact you.
#78
07/30/2004 (4:20 pm)
Thanks guys. Interesting to see the end result. Cheers!
#79
07/30/2004 (10:09 pm)
Ben, I think I finally see what you are saying about the Datablock class. Currently, I have decided to just modify what's in the GameBase::onAss() method and give this particualar class the same enhancement made to ScriptObject while short-circuiting the onAdd()'s Parent::onAdd() call to go back to SimObject::onAdd() instead.

This is probably going to be one of the bigger challenges, but I imagine I'll whip it out tonight.

Torque on!
#80
07/30/2004 (10:31 pm)
Ben, I'm having a bit of trouble, so I give you this easy question -

Do you think I should base GameDataBlock's inheritance on the className namespace, or should I add a class namespace and use it instead? For simplicity's sake, I'd prefer to use className, but if there's a better way, I'm all ears (... and perhaps some toes)