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
/////////////////////////
Page «Previous 1 2 3 4 5 Last »
#1
07/05/2004 (5:42 pm)
And now here is an example of an actual class that uses the previous pattern and a little bit of code to put it into action -
//////////////////////////
// CLASS Test
//////////////////////////
 
	//////////////////////////
	// INITIALIZATION:
	//////////////////////////
 
	// returns Test instance
	function Test::create(%objectName, %age, %weight)
	{
		%this = new ScriptObject(%objectName)
		{	class = Test;};
 		
	//////////////////////////
		// PUBLIC MEMBERS:
		//////////////////////////
		%this.weight = %weight; // this is a public member
 
		//////////////////////////
		// PRIVATE MEMBERS:
		//////////////////////////
		%this._age = %age; // this is a private member

		
		return %this;
	}
 		
		
	function Test::delete(%this)
	{		Parent::delete(%this);}
 	
	//////////////////////////
	// PUBLIC INTERFACE:
	//////////////////////////
 
	function Test::getAge(%this)
	{	return %this._age;}
 		
	function Test::setAge(%this, %age)
	{	%this._age = %age;}
 
//////////////////////////
// END CLASS Test
//////////////////////////
And here is the class being put to use -
%t = Test::create("t", 60, 150);
%t.setAge(70);
echo("Age:    " @ %t.getAge());
echo("Weight: " @ %t.weight);
%t.delete();

This is an quick overview of how the object oriented features would be implemented in Torque Script. If you are new to programming in an object oriented fashion, then don't feel bad if you don't understand a lot of what's going on here. I'm going to try to write the upcoming tutorial in a way that will explain these features, why they are here, and how they work.

Please take a look over this and see if you have any questions or comments. I'd really like you all's help to make this a great methodology and tutorial!
#2
07/05/2004 (6:20 pm)
Bryan,

While I'm all for OO, encapsulation and all that good stuff, Torque script doesn't support it (and probably won't in the future as it's pretty sufficient the way it is now). So while labeling public vs private and create get/set methods is great it's extra effort that I'm not sure I see the value in? One of the reasons anyone would use encapsulation is to provide a public interface and be able to change the private implementation behind the scenes. Without that capability it's something you don't need. My philosophy on programming is the YAGNI attitude (you ain't gonna need it). If it's not adding any value then don't put it in.

Not to hamper your efforts, by all means feel free to spread the OO gospel as it's good programming practice. It's just if the language doesn't support it, you're asking people to do extra work that isn't buying them anything.
#3
07/05/2004 (6:27 pm)
I guess the best way to put it is like so - if you see value in OO, then use it. If you know and love OO and want to use it in Torque script, here is a great implementation. But if not, then what you are currently doing must be working well enough already, so why not stick with that? :)

The best way to think about myy proposal is thus - keep this possibility in the back of your head, and try to imagine how it could help with what you're doing. In the words of Hanz from SNL, "Listen to me now, and believe me later" :)

This methodology is certainly not for everybody. But as I said, if people are feeling their program's complexity is ballooning beyond human comprehension (the natural result of global variables and global functions becoming too numerous), then OO may be the only way to restore simple and understandable program structure.
#4
07/05/2004 (6:39 pm)
Fair enough. Like you said, if you like it use it so whatever floats your boat. Personally I would be confused by seeing member variables that are similar in a class without scoping capabilities so I take Torque as it is (when in Rome kind of attitude).

And don't even get me started on Hungarian notation ;)
#5
07/05/2004 (10:32 pm)
Did a bit of work on the first 2 posts. They should be a little easier to read now :)
#6
07/06/2004 (3:31 am)
This is a great idea bryan, I only code in OOP and patterns and have avoided coding in TS because it procedural. Thanks for the idea :)
Properties and members are used to keep the interface clean. If a member isn't required by users of the class, then why have an extra property that they are not use?
Properties are used so that you can carry out calculations on a member before sending it back. It is a cleaner way of returning information than using function calls.
#7
07/06/2004 (5:32 am)
@Bill

See it like programming in Perl

It can do do both procedural and OO - use it as you like best.

But dont use it to e.g. protect code for private/public access and stuff - but for organizing/structure code in a more readable fashion
#8
07/06/2004 (5:51 am)
Think yah have a bug in the example...

class = Test;};
#9
07/06/2004 (7:47 am)
I agree with Bill.

Actually I never understood why OO-lovers like this private/public separation. If what you are handling over is an executable or a library the 3rd party guy who messes with it really deserves the bugs it creates. OTOH if what you're handling over source code ( wonder what product would delver source code :-) ) you can just change what's private to public and don't bother about it...

But, anyway, the tutorial seems very nice...

P.S. Now it's the point where OO-Lovers will come in and say : private/public stuff is there to improve the code's re-usability, yadda yadda yadda, etc...

Yeah, right, just give me one practical example for it..
#10
07/06/2004 (8:35 am)
@Bruno: I don't want this to turn into a holy OO war as that's not the intent of the original poster. A simple example of showing value with encapsulation would be something like this (this is C++, but any OO languge will work the same):

class MyClass
{
private:
  float salary;
  float calculateWage()
  {
    salary = 100000;    
  };

public:
  float GetSalary()
  {
    calculateWage();
    return salary;
  }
};

I know, silly example. The point here is that I can do whatever I want in calculateWage() to perform the calculation. The user of this class only has to call GetSalary() and will always get back the salary value. I can, behind the scenes implement database/SAP calls, complex calculations, etc. and change it whenever I want without the end user having to change his code.

Okay, so this doesn't *really* address code re-usability but hopefully explains something.
#11
07/06/2004 (9:15 am)
I already use TGE in an object oriented manner in the way it already supports. It has "fake oop".

Remember that OOP is more than just encapsulation, its a grab bag of techniques, some of which *are* supported in torque script to limited degrees or using faked-out conventions.

One part of OOP is name scoping. TGE script already supports this. You dont have to make all your methods global...they can exist in the space of a datablock. I do this all the time. Lets say i have my own Player "class" (meaning datablock and namespace) and I want to run an animation when that object is added to the scene:

datablock PlayerData(MyPlayer)
{
stuff...

};

function MyPlayer::onAdd(%this,%obj)
{
wanna do stuff here...
}

I can just make up my own methods like this:

function MyPlayer::updateExtraDamageAnims(%this,%obj)
{
run extra anims based on damage...
}

I can call this in onAdd like this

function MyPlayer::onAdd(%this,%obj)
{
%this.updateExtraDamageAnims(%obj);

more stuff...
}

So TGE already supports scoping.

Another OOP concept is polymorphism. TGE supports this for datablocks so you can have some data based on other data:

datablock Explosion(BaseExplosion)
{
stuff common to all explosions
};

datablock Explosion(RedExplosion : Explosion)
{
same fields but uses RED particles...
};

datablock Explosion(BlueExplosion : Explosion)
{
same fields but uses BLUE particles...
};


Polymorphism through inheritence of member functions is also supported in the method you outline here, Bryan. I use that often but do not attempt to mark datamembers as private or public I just code that way without any support from the language.

If I had more time I would post more examples.

Hope this was useful to someone. :-)
#12
07/06/2004 (12:27 pm)
@Ron - That line of code looks weird, but that's because I took out some whitespace that you are probably used to seeing with new. Don't worry, it's not a bug :)

As to the private vs. public argument - it's totally up to you. If you see value in making all of your members public, then you can easily do so. That's no problem. Think of public members as an extension of your object's interface - it gives the client more control over the object. Of course there's a downside to making everything public, like, say, a publicizing a member that keeps count of how many instances of a class there are or some other data that the client could accidentally change and thereby destabilize your code. But the choice is totally up to you whether to use this particular feature or not, and where exactly you want to use it.

@FruitInBatShades - properties are functions that are really just written in a standard way. Visual Basic.NET implements properties in a nice way, and makes it look like properties are not really functions, but in reality they are. In the end, an accessor ( getMember() ) and mutator ( setMember() ) for a private member are all that is needed to implement a property. And if the programmer wants the member to be publically immutable (unalbe to be changed by the client), then he need only not include the mutator for that member. So long as the client respects privacy of the properly notated member (any member that starts with an unerscore), property-style functions should work out as you expect them to.

@Paul - The reason I use OO is not because of particular features, but because of the engineering capabilies that are realized when the features of OO are holistically combined. In not so many words, OO allows me to structure my code in a way that I am familiar with, and simplifies my code use and reuse. For me, it all comes down to being able to package program responsibilies into interfaces, and decoupling interfacee from implementation.

Listen to me now, try to foresee and understand the implications, and make your decision later. I'm not trying to sell you on OO, rather I'm trying to sell you on a community standard way of doing it. And that latter sale is the topic of discussion ;)
#13
07/06/2004 (12:43 pm)
Quote:
I'm not trying to sell you OO, rather I'm trying to sell you one a community standard in doing it.

Oh I see... you're not trying to sell us OO... you're just trying to make a standard for the ENTIRE community that just happens to be OO, and includes writing code that has no functional use in TorqueScript.
#14
07/06/2004 (12:49 pm)
Not at all :) Unfortunately, I seemed to have mispoken - I have edited it to say -
Quote:I'm not trying to sell you on OO, rather I'm trying to sell you on a community standard way of doing it.
I am saying that if you do choose to use OO, then you should use this standard to make it easier for other people to read and use your OO code. Whether you choose to use OO is totally up to you. I'm just trying to build a consensus for a way to do OO if you choose to ;)

But other community standards could be suggested as well. I think it would be great to see a procedural coding community standard. Unfortunately, I don't know much about procedural programming, so I can't propose anything like that. It would be great if someone could, though! :)
#15
07/06/2004 (1:44 pm)
BTW, I forgot to include the segment of code putting the Test class into use. Here it is -
%t = Test::create("t", 60, 150);
%t.setAge(70);
echo("Age:    " @ %t.getAge());
echo("Weight: " @ %t.weight);
%t.delete();
I also put it above in the second post if you would prefer to read it there.
#16
07/06/2004 (3:19 pm)
Bryan,

I fear you will find that our community is endemically focused on creating games, not on programming methodologies. Still, best of luck to you.
#17
07/06/2004 (5:47 pm)
Well, the only thing I can really say to that is this - Making good games is not mutually exclusive to good programming methodology. Whether it procedural or OO. :)
#18
07/06/2004 (6:10 pm)
I, also, use Paul's methodoligies. My biggest problem/learning curve, is the absence of true inheritance on script objects. Datablocks can be inherited, but to perform encapsulated inheritance, as demonstrated, you will still have to initialize/expose fields of the object. If anyone has an example of performing true inheritance without re-writing field initializations and exposition, I would love to see it.

Bryan, I appreciate your ideas and thought on this matter. I havn't seen such a useful thread on scripting methodoligies in a while.
#19
07/06/2004 (6:22 pm)
Bah! should have read the code a bit closer AFTER my 5th cup of joe ;)

the last } looked like a ) (paren) and I assumed an open ( (paren) and possible quotes were missing *sigh*.. been playing with the engine and scripts for over 2 years and I am still blind ;)
#20
07/06/2004 (6:43 pm)
@Ron - NP! The fonts on these forums are kind of hard to see for me too :)

@Cameron - If I am understanding you correctly, then I believe this OO implementation does support what you're asking for. Take a look at the _init() code. _init() is automatically called in the class's create() function when you instantiate the object. As you can see, the _init() function is where fields are initialized -
function Test::_init(%this, %age, %weight)
	{
		//////////////////////////
		// PUBLIC MEMBERS:
		//////////////////////////
		%this.weight = %weight;
 
		//////////////////////////
		// PRIVATE MEMBERS:
		//////////////////////////
		%this._age = %age;
	}
I think what you are asking for is to have the super class's fields initialized without having to rewrite the initialization code for it. That's done pretty easily by calling the super class's _init() function inside this function like so -
function Test::_init(%this, %age, %weight)
	{
	
		// this function will automatically set up and initialize the member
		// variables of the super class. Just substitute NameOfSuperClass with 
		// the name of the class you wish to inherit from and substitute 
		// "parameters for super class" with the values super class's init
		// requires.
		NameOfSuperClass::_init(%this, "parameters for super class"...);
	

		//////////////////////////
		// PUBLIC MEMBERS:
		//////////////////////////
		%this.weight = %weight;
 
		//////////////////////////
		// PRIVATE MEMBERS:
		//////////////////////////
		%this._age = %age;
	}
For more explanation on how to actually inherit a class, see the code in the post at the very top. The example code in the second post does not demonstrate inheritance (at least not from any other object than ScriptObject.)
Page «Previous 1 2 3 4 5 Last »