Scripting a simple "switch" to learn TS
by Justin Woodman · in General Discussion · 12/07/2009 (3:52 pm) · 19 replies
I'm attempting to script a simple "switch" (an object which, when clicked on, performs a function).
So far, I've added a "switches" datablock to /art/datablocks:
I've added a "switches" script to /server/scripts (containing the "activate" function of the class "BasicSwitch"):
And then in "PlayGui.cs", I've added a method of calling the "Activate" function (by clicking on the object with the mouse):
needless to say, it doesn't work. Instead, I get the warning:
scripts/gui/playGui.cs (74): Unknown command activate.
Object (3228) StaticShape -> ShapeBase -> GameBase -> SceneObject -> NetObject -> SimObject
which I don't understand. I've tried to read through the scripting tutorials, but I learn "hands on" and am still quite confused here. I guess I am missing something about the way datablocks and classes work, but I can't seem to find it.
If somebody wouldn't mind sort of explaining what I'm doing wrong, and how this type of thing should be used, I would be quite thankful.
So far, I've added a "switches" datablock to /art/datablocks:
datablock StaticShapeData(RockSwitch)
{
category = "Switches";
className = "BasicSwitch";
// Basic Item properties
shapeFile = "art/shapes/rocks/rock1.dts";
};I've added a "switches" script to /server/scripts (containing the "activate" function of the class "BasicSwitch"):
//When the player interacts with the switch...
function BasicSwitch::Activate(%this, %obj)
{
//Change the switch visibly
%obj.delete();
//Display switch activation in console
echo("Switch activated");
}And then in "PlayGui.cs", I've added a method of calling the "Activate" function (by clicking on the object with the mouse):
function PlayGui::onMouseDown(%this, %pos, %start, %ray)
{
%ray = VectorScale(%ray, 1000);
%end = VectorAdd(%start, %ray);
// find target tag...
%scanTarg = ContainerRayCast( %start, %end, 1, %this );
echo(%scanTarg);
%scanTarg.Activate();
}needless to say, it doesn't work. Instead, I get the warning:
scripts/gui/playGui.cs (74): Unknown command activate.
Object (3228) StaticShape -> ShapeBase -> GameBase -> SceneObject -> NetObject -> SimObject
which I don't understand. I've tried to read through the scripting tutorials, but I learn "hands on" and am still quite confused here. I guess I am missing something about the way datablocks and classes work, but I can't seem to find it.
If somebody wouldn't mind sort of explaining what I'm doing wrong, and how this type of thing should be used, I would be quite thankful.
#2
I suppose I am having trouble with the concept of a datablock. In my code, I said "ClassName = BasicSwitch". I thought this meant that I have created a new class called "BasicSwitch". Therefore, the "Activate" function is part of the [u]class[/u] "BasicSwitch". So if a datablock contains data about an object, then is the function "Activate" considered part of that data? I never considered a function "data"...
12/07/2009 (6:47 pm)
Okay, I get it. So is the datablock the best place to assign the "Activate" function, assuming there will be many of these throughout the game world?I suppose I am having trouble with the concept of a datablock. In my code, I said "ClassName = BasicSwitch". I thought this meant that I have created a new class called "BasicSwitch". Therefore, the "Activate" function is part of the [u]class[/u] "BasicSwitch". So if a datablock contains data about an object, then is the function "Activate" considered part of that data? I never considered a function "data"...
#3
TorqueScript doesn't actually care whether an object is a datablock or not (as far as defining functions goes). TorqueScript will let you create a function in any namespace, which includes class names (like StaticShapeData) and instance names (like your RockSwitch). Furthermore the variable "className" allows you to arbitrarily assign yet another namespace identifier to a given object. Using that approach, you could in theory create two different objects of different classes and assign them the same className thus allowing both of them to share a common function defined with that name.
As an example, in your code above RockSwitch is an instance of StaticShapeData assigned a className "BasicSwitch". Torque will check each of those names when looking for a function/variable associated with that object. So if you were to call RockSwitch.Activate(), Torque would look for
Any of those would work. Which way you choose to set it up is up to you, and it depends on how you want to organize your objects and functions. Do you want Activate() defined for ALL objects of class StaticShapeData? Or for ONLY the one called RockSwitch? Or do you want to make it more flexible by defining Activate() for BasicSwitch, which is a name you can assign to whatever object you want? They will all work.
Now, like I said before, TorqueScript technically doesn't care whether an object is a datblock or not when it comes to defining functions. In the above examples, Activate() was defined multiple ways for an object that was a datablock. Nothing says you HAVE to use a datablock for defining functions. You could use
That's where the datablocks come in. By defining Activate() for the RockSwitch datablock and setting your scripts up to call the function on the datablock, you get an automatic association between the image you see in-game and the Activate() function. In the editor now you just say "give me a new StaticShape of type RockSwitch" and you get a rock1.dts switch already linked to Activate(). Better still is the way you have it now where the function is defined through the className. That way if you decide you want a "rock2.dts" switch, you can create a second datablock using that dts and the same className "BasicSwitch". Now both RockSwitch and RockSwitch2 are associated with the same Activate() function.
In the end, you can set it up however you want. It's just a matter of organization.
Essentially, yes. In this case that's probably best.
[edit: a few small additions]
12/07/2009 (7:55 pm)
Well, a datablock is still a separate object, it's just not the object you see in your game. The idea is that you define one datablock, then you can create multiple objects that each get references to that same datablock. This way variables (and functions too) can be associated with multiple objects through the shared datablock, instead of having to define the same variable multiple times for each object.TorqueScript doesn't actually care whether an object is a datablock or not (as far as defining functions goes). TorqueScript will let you create a function in any namespace, which includes class names (like StaticShapeData) and instance names (like your RockSwitch). Furthermore the variable "className" allows you to arbitrarily assign yet another namespace identifier to a given object. Using that approach, you could in theory create two different objects of different classes and assign them the same className thus allowing both of them to share a common function defined with that name.
As an example, in your code above RockSwitch is an instance of StaticShapeData assigned a className "BasicSwitch". Torque will check each of those names when looking for a function/variable associated with that object. So if you were to call RockSwitch.Activate(), Torque would look for
function RockSwitch::Activate()or
function StaticShapeData::Activate()or
function BasicSwitch::Activate()
Any of those would work. Which way you choose to set it up is up to you, and it depends on how you want to organize your objects and functions. Do you want Activate() defined for ALL objects of class StaticShapeData? Or for ONLY the one called RockSwitch? Or do you want to make it more flexible by defining Activate() for BasicSwitch, which is a name you can assign to whatever object you want? They will all work.
Now, like I said before, TorqueScript technically doesn't care whether an object is a datblock or not when it comes to defining functions. In the above examples, Activate() was defined multiple ways for an object that was a datablock. Nothing says you HAVE to use a datablock for defining functions. You could use
function StaticShape::Activate()instead, if you wanted to. You probably don't though, because then Activate() is defined for ALL of your StaticShapes, whether they be switches or oil drums or chairs. That doesn't make a great deal of sense from a code-organization standpoint. Alternately, you could give any one of your StaticShape objects its own name. Say you have a switch (which is an instance of StaticShape class that's using your RockSwitch datablock) and you give it a name "BravoOutpostMasterPowerSwitch". You could define a
function BravoOutPostMasterPowerSwitch::Activate()which would then be valid ONLY for that ONE switch object. That approach could have its use. But it's more limited. If you then decide you want a CharlieOutpost with a similar switch, you'll need to define the same function again for your CharlieOutpostMasterPowerSwitch. Not very efficient if both functions are the same.
That's where the datablocks come in. By defining Activate() for the RockSwitch datablock and setting your scripts up to call the function on the datablock, you get an automatic association between the image you see in-game and the Activate() function. In the editor now you just say "give me a new StaticShape of type RockSwitch" and you get a rock1.dts switch already linked to Activate(). Better still is the way you have it now where the function is defined through the className. That way if you decide you want a "rock2.dts" switch, you can create a second datablock using that dts and the same className "BasicSwitch". Now both RockSwitch and RockSwitch2 are associated with the same Activate() function.
In the end, you can set it up however you want. It's just a matter of organization.
Quote:So is the datablock the best place to assign the "Activate" function, assuming there will be many of these throughout the game world?
Essentially, yes. In this case that's probably best.
[edit: a few small additions]
#4
I've read through every piece of documentation intended to help understand these concepts, and I finally get it!
Hopefully this thread will help more than just me in the future. I'm sure I'll be asking more questions, but this is a great start.
12/07/2009 (9:41 pm)
Thank you for being so patient!I've read through every piece of documentation intended to help understand these concepts, and I finally get it!
Hopefully this thread will help more than just me in the future. I'm sure I'll be asking more questions, but this is a great start.
#5
This got me thinking, would it be better to make a general "ClickableObject" datablock with an "onClick" and "onDoubleClick" functions and a "command" field?
regardless, I have very little idea on how I would create such a field (never mind making it accessible through the editor).
Just thoughts. If anyone has input I'd love to hear it!
12/08/2009 (12:57 am)
Alright, so the next step in this might be to allow the "switch" to call a function, much like a GuiButtonControl does (with it's "command" field).This got me thinking, would it be better to make a general "ClickableObject" datablock with an "onClick" and "onDoubleClick" functions and a "command" field?
regardless, I have very little idea on how I would create such a field (never mind making it accessible through the editor).
Just thoughts. If anyone has input I'd love to hear it!
#6
EDIT: Note that you don't really need to do that. If you go into the editor and select any object, you can add 'dynamic fields' to it, which are basically properties like %obj.command or %obj.mySpecialThing. But by initialising it in onAdd, I think it will be visible in the editor when the object is selected - so you don't have to manually add a new field for all your objects of that type, just edit the field that's already been created. If that makes sense.
12/08/2009 (1:37 am)
Quote:regardless, I have very little idea on how I would create such a field (never mind making it accessible through the editor).You can override the onAdd method in your BasicSwitch namespace to add fields to objects. I think it could look something like this:
function BasicSwitch::onAdd(%this,%obj)
{
//%this: the datablock
//%obj: the object instance
//Initialise this so it's visible in the editor
%obj.command = "myFunction();";
}onAdd is called whenever an object is created with a specific datablock. Now you should be able to create a StaticShape with the BasicSwitch datablock, select it in the editor, and edit its 'command' field.EDIT: Note that you don't really need to do that. If you go into the editor and select any object, you can add 'dynamic fields' to it, which are basically properties like %obj.command or %obj.mySpecialThing. But by initialising it in onAdd, I think it will be visible in the editor when the object is selected - so you don't have to manually add a new field for all your objects of that type, just edit the field that's already been created. If that makes sense.
#7
12/08/2009 (2:02 pm)
So when I add a field to an object, how do I access it form another function in that objects datablock? aka. how would I "eval" that function from "BasicSwitch::Activate"?
#8
I ask because I'm trying to do this:
12/08/2009 (6:45 pm)
Alright, so adding a field like "%obj.command" makes it a member variable, so it can be accessed from any other "function BasicSwitch::Whatever(%this, %obj)", right?I ask because I'm trying to do this:
function BasicSwitch::onAdd(%this, %obj)
{
%obj.mTestVar = "Hello";
}
//When the player interacts with the switch...
function BasicSwitch::Activate(%this, %obj)
{
//Display switch activation in console
echo("Switch activated");
echo(%obj.mTestVar);
}However, when "BasicSwitch::Activate" gets called, the output is:3228 2.37588 5.47508 241.46 0.316197 -0.948693 0 Switch activated(where "Hello" should be, there is a blank space).
#9
12/08/2009 (7:21 pm)
Curious. echo(%obj.getId()); in onAdd and Activate. Ideally you should get an echo from both indicating the same id.
#10
12/08/2009 (7:32 pm)
Both echo the same ID: 3228.
#11
(keep in mind that id number may change if you modify your mission)
12/08/2009 (8:02 pm)
Well. That should work then. How very strange. Try echo(%obj.mTestVar) in onAdd? Also perhaps try echo(3228.mTestVar) in various locations. Or 3228.dump()(keep in mind that id number may change if you modify your mission)
#12
from "onAdd" I get:
but from "Activate" I get:
edit: When I do a 3228.dump(), mTestVar is listed under "Tagged Fields", not "Menber Fields"... But I don't know if that is relevant or not.
edit again: What does work, is using "echo(%obj.getId().mTestVar);" but it doesn't seem like I should have to do that :/
12/08/2009 (8:09 pm)
Okay, I placed these echoes in "onAdd" and "Activate":echo(%obj.getId()); echo(%obj.mTestVar); echo(3228.mTestVar);
from "onAdd" I get:
3228 Hello Hello
but from "Activate" I get:
3228 Hello
edit: When I do a 3228.dump(), mTestVar is listed under "Tagged Fields", not "Menber Fields"... But I don't know if that is relevant or not.
edit again: What does work, is using "echo(%obj.getId().mTestVar);" but it doesn't seem like I should have to do that :/
#13
You shouldn't have to do that, no. That is strange. I've never seen such behavior from the script engine.
Couple questions: What version of Torque are you working with?
And what do you get if you just echo(%obj) in both functions?
12/08/2009 (9:03 pm)
o_O You shouldn't have to do that, no. That is strange. I've never seen such behavior from the script engine.
Couple questions: What version of Torque are you working with?
And what do you get if you just echo(%obj) in both functions?
#14
And I think I see the problem now that you had me do that (but I still have no idea how to fix it...)
I added "echo(%obj)" to both functions, and from "onAdd" I get:
but from "Activate" I get:
all I know is that they don't match :\
12/08/2009 (9:12 pm)
I am using T3D v.1.0.1And I think I see the problem now that you had me do that (but I still have no idea how to fix it...)
I added "echo(%obj)" to both functions, and from "onAdd" I get:
3228
but from "Activate" I get:
3228 0.258232 4.77524 241.512 -0.148013 -0.988985 0
all I know is that they don't match :\
#15
You answered the question I was just going to ask.
ContainerRayCast returns a collection of values; the first of which is the hit object id. The rest of the values are the hit position and normal. That explains why the script engine is getting confused.
I should have seen that from the start. Where you call Activate, instead of
what you need to do is isolate the object id like this:
(edit: wait, I think it's just "firstWord". fixed now.)
(also, you could use getWord(%scanTarg, 0) where 0 is the word number 0,1,2,3,etc. It's a zero index identifier)
12/08/2009 (9:15 pm)
Ah, but of course.You answered the question I was just going to ask.
ContainerRayCast returns a collection of values; the first of which is the hit object id. The rest of the values are the hit position and normal. That explains why the script engine is getting confused.
I should have seen that from the start. Where you call Activate, instead of
%scanTarg.getDataBlock().Activate(%scanTarg);
what you need to do is isolate the object id like this:
%obj = firstWord(%scanTarg); %obj.getDataBlock().Activate(%obj);
(edit: wait, I think it's just "firstWord". fixed now.)
(also, you could use getWord(%scanTarg, 0) where 0 is the word number 0,1,2,3,etc. It's a zero index identifier)
#16
I'm glad this came about, it help me understand the way %obj works and how it's used and passed around. And I guess I recall that ContainerRayCast returns the coordinates of the object detected, but why? Couldn't you just do something like getLocation(%obj) to get that after? it almost seems combersome to have ContainerRayCast return anything other than the tag...
But anyways, Thanks again for the help! I don't know how people just pick up TS without a ridiculous amount of background knowledge of the engine.
12/08/2009 (10:25 pm)
Okay, so when I "passed" the %scanTag value to BasicSwitch.Activate, every time it used %obj, it used the tag+coordinates to reference the object?I'm glad this came about, it help me understand the way %obj works and how it's used and passed around. And I guess I recall that ContainerRayCast returns the coordinates of the object detected, but why? Couldn't you just do something like getLocation(%obj) to get that after? it almost seems combersome to have ContainerRayCast return anything other than the tag...
But anyways, Thanks again for the help! I don't know how people just pick up TS without a ridiculous amount of background knowledge of the engine.
#17
Yes, and that's why it was getting confused.
No. Because that would get you the location of the object that was hit. What ContainerRayCast is giving you is the location of where the hit occurred (as well as the normal of the face that was hit). Consider that the location of an object is based on that object's local "origin" point. The 0,0,0 point of the object's local coordinate space. This is normally near the center or base of the object. That is not necessarily where the ray hits the object. A Player object, for example, may have a "location" that is by its feet, near to the ground. If the ray hits the Player in the head, the hit point is some distance from its feet.
12/08/2009 (10:44 pm)
Quote:so when I "passed" the %scanTag value to BasicSwitch.Activate, every time it used %obj, it used the tag+coordinates to reference the object?
Yes, and that's why it was getting confused.
Quote:Couldn't you just do something like getLocation(%obj) to get that after?
No. Because that would get you the location of the object that was hit. What ContainerRayCast is giving you is the location of where the hit occurred (as well as the normal of the face that was hit). Consider that the location of an object is based on that object's local "origin" point. The 0,0,0 point of the object's local coordinate space. This is normally near the center or base of the object. That is not necessarily where the ray hits the object. A Player object, for example, may have a "location" that is by its feet, near to the ground. If the ray hits the Player in the head, the hit point is some distance from its feet.
#18
If that's the case, it seems like ShapeBase would have an "onMouseDown" function, for simple detection of mouseclicks on the object (for all I know it does...).
12/08/2009 (10:59 pm)
Ohhhh I see. So I imagine this would be useful in the case where you wanted to shoot at a water tank, and have water spray out from the point of impact, away from the direction of impact?If that's the case, it seems like ShapeBase would have an "onMouseDown" function, for simple detection of mouseclicks on the object (for all I know it does...).
#19
Exactly right.
As far as an onMouseDown function goes, I could be wrong but I don't think such a function exists for 3d objects. Reason being that the 3d variants of Torque (flexible though they may be) are designed primarily for First Person Shooter style games where the only interaction you have with objects in the world is either shooting them or running into them, not generally clicking on them. I'm not very familiar with TGEA/T3D, but I rather doubt they're much different from TGE in that regard. The editor interface does implement onClick events for 3d objects, but those events are not accessible outside of the editor interface. Still, as you've discovered it is possible to add such a function through script.
12/09/2009 (12:35 am)
Quote:So I imagine this would be useful in the case where you wanted to shoot at a water tank, and have water spray out from the point of impact, away from the direction of impact?
Exactly right.
As far as an onMouseDown function goes, I could be wrong but I don't think such a function exists for 3d objects. Reason being that the 3d variants of Torque (flexible though they may be) are designed primarily for First Person Shooter style games where the only interaction you have with objects in the world is either shooting them or running into them, not generally clicking on them. I'm not very familiar with TGEA/T3D, but I rather doubt they're much different from TGE in that regard. The editor interface does implement onClick events for 3d objects, but those events are not accessible outside of the editor interface. Still, as you've discovered it is possible to add such a function through script.
Torque 3D Owner Scott Richards