Spawning an archery target using position data from a file
by Bart van Greevenbroek · in Torque 3D Beginner · 03/19/2014 (9:05 am) · 11 replies
So, I'm trying to make a shooting gallery where targets spawn on specific locations that are read from a file. The targets should disappear after a certain number of hits or shots fired, and a new target should be spawned after that.
What type of datablock do I need?
I've made a textured target model in blender and exported it, I create one in the editor, put it wherever I want, but how can I do that using a script? Where are the objects that you use in the editor stored and located? (Maybe that will help with some insight)
Do I need to use the .dts file or the .dae file for the body?
Any help is greatly appreciated.
What type of datablock do I need?
I've made a textured target model in blender and exported it, I create one in the editor, put it wherever I want, but how can I do that using a script? Where are the objects that you use in the editor stored and located? (Maybe that will help with some insight)
Do I need to use the .dts file or the .dae file for the body?
Any help is greatly appreciated.
About the author
Recent Threads
#2
03/21/2014 (12:27 am)
Quote:What type of datablock do I need?I just want to jump in here and make sure you understand that the position of each target won't be related to the datablock it uses. Many objects can share one datablock - and obviously they won't all be at the same position! Think of a datablock like a blueprint.
#3
Daniel, if the datablock is only a blueprint, how is it possible to assign a certain position to a target? Remember, I would like to spawn them from a script, not place them all in the editor, since they would all overlap!
Thanks very much for you time.
05/09/2014 (5:13 am)
Okay, then how do I make sure that a script creates a new target from a long list of positions? My plan is to have appoximately 900 targets spawn, all facing the same point, while the user can only look with the oculus, not walk.Daniel, if the datablock is only a blueprint, how is it possible to assign a certain position to a target? Remember, I would like to spawn them from a script, not place them all in the editor, since they would all overlap!
Thanks very much for you time.
#4
A object, uses the datablock to define a set of "common" properties. While object specific properties are still controlled by the object itself.
Quick example:
05/09/2014 (1:43 pm)
I think you're just misunderstanding what a datablock versus an object is. A datablock, simply put, is something that remains constant between the server and every client connected to the game, IE: the sever has the authority over what this is. These will simply be things such as weapons, players, projectiles, etc.A object, uses the datablock to define a set of "common" properties. While object specific properties are still controlled by the object itself.
Quick example:
datablock DatablockType(DBName) {
//Properties
}
%myObj = new Type(Name) {
datablock = DBName;
position = "x y z";
rotation = "x y z w";
//so on, so forth
}
#5
05/09/2014 (4:03 pm)
So, to continue the blueprint analogy - my car and your car might be built from the same blueprint, but they're different objects with their own position and so on. I'll trim down Richard's example a bit:function createObjects(%fileName)
{
// create a file object
%file = new FileObject();
// open the file
if(%file.openForRead(%fileName))
{
while(!%file.isEOF())
{
// read our file line by line
%line = %file.readLine();
%line = trim(%line);
%obj = new StaticShape() { // Or whatever class you want to use
datablock = ArcheryTargetData; // Or whatever it's called
position = %line;
};
MissionCleanup.add(%obj);
}
%file.close();
}
%file.delete();
}Assuming your text file looks something like3 50 9 10.4 80 3 1 2 0That will place a ShaticShape at the coordinates given by each line.
#6
05/11/2014 (12:06 pm)
Quote:since they would all overlapNot an issue with spawn spheres - they're invisible. But you want all of them to spawn at once? To all be visible at the same time? Then place them so they don't overlap. If they're not all "present" at the same time then it wouldn't matter if they overlap or not. You can spawn them at coordinates from a list, or at spawnspheres, or simply place them all and then switch their visibility. In all three cases the only time they'll visibly overlap is if you spawn two objects (or set them both to visible) such that they overlap.
#7
The reason for spawning from a list is that the positions are guaranteed to have a certain distribution, and have the same distribution for every test subject.
The reason for spawning one at a time is so that the user sometimes has to look around (with the oculus)to look for a new target.
So, judging from your replies, I would need to modify Daniel's code in such a way that a cursor is remembered (to remember where we are in the list) and a function needs to be called every time a target is hit an x number of times.
Now, I'm a bit unclear on calling functions across different scripts. In C++, in order to call a function from another class, you would need an instance of that class or a pointer to it, then call that function. In this script, it seems all functions are thrown on one pile. Just call the functions, no Object1->function1() or anything. Is this correct? Can you just call SpawnNewTarget() from the target that was just destroyed, while defining SpawnNewTarget() in the target spawning script?
Thanks for helping me understand.
05/13/2014 (5:37 am)
What I want is to spawn them one by one from a list. Shooting one an x number of times should spawn the next one on the list. The reason for spawning from a list is that the positions are guaranteed to have a certain distribution, and have the same distribution for every test subject.
The reason for spawning one at a time is so that the user sometimes has to look around (with the oculus)to look for a new target.
So, judging from your replies, I would need to modify Daniel's code in such a way that a cursor is remembered (to remember where we are in the list) and a function needs to be called every time a target is hit an x number of times.
Now, I'm a bit unclear on calling functions across different scripts. In C++, in order to call a function from another class, you would need an instance of that class or a pointer to it, then call that function. In this script, it seems all functions are thrown on one pile. Just call the functions, no Object1->function1() or anything. Is this correct? Can you just call SpawnNewTarget() from the target that was just destroyed, while defining SpawnNewTarget() in the target spawning script?
Thanks for helping me understand.
#8
As for keeping your place in the list, a global index variable should do nicely.
You can indeed have global functions - those in my examples are exactly that. Script class methods work in a fashion similar to C++ and are called against script objects.
05/19/2014 (8:33 am)
I'll verify that one - at the time that the level loads all scripts are in memory. Torque can load new scripts at any time, so if you load a new script in a callback somewhere I suppose that my initial statement becomes false....As for keeping your place in the list, a global index variable should do nicely.
$MyGame::targetIndex = 0; // at the beginning
$MyGame::maxTargetIndex = 900; // replace this with actual count
function LoadTarget()
{
SpawnTarget($MyGame::targetIndex++); // stand-in for function that gets position
// from list, then creates it in game
$MyGame::targetIndex++;
if ($MyGame::targetIndex > $MyGame::maxTargetIndex)
$MyGame::targetIndex = 0; // reset list
}Now your function automatically walks the list and "rolls over" when all of them have been placed.You can indeed have global functions - those in my examples are exactly that. Script class methods work in a fashion similar to C++ and are called against script objects.
doMyGlobalTask(); // just calls a function - works from anywhere in script
%myThing.doMyThingTask(); // calls a method defined in %myThing's script class.
//This is defined in %myThing's datablock.
datablock ShapeBaseData(MyThingData)
{
class = "MyThingClass";
//...
}
function MyThingClass::doMyThingTask(%this)
{
// %this is a semi-explicit C++ this - you don't have to pass it, but it is
// always the first parameter to a method and you need to put in in the argument
// list if you want to use it.
}I just used ShapeBaseData because I believe that they can take damage. If that's so, you just give them health and define their onDestroyed() method to call a cleanup function. I usually use a ScheduleManager object for this because odd things happen when you try to delete an object from within its own callback. Using a ScheduleManager lets me put the responsibility on a known "third-party" object that can wait a few milliseconds for the "dying" object to finish processing before deletion.
#9
The array[] syntax in TorqueScript is just sugar for creating lots of differently-named variables. For example, %obj.array[0] is the same as %obj.array_0, just with the ability to put a variable in the expression (you can't write, for example, %obj.array_%i).
05/19/2014 (9:41 pm)
Bart - I recommend modifying the script I demonstrated above, so that each line of the file is saved into the property of an object TorqueScript does have some limited OO capabilities. To whit:// Create a named object. Object's names act as a link in their namespace
// hierarchy, so we can define methods for it. See below.
new ScriptObject(MyObject);
// Create a method for the namespace of objects called MyObject.
function MyObject::loadFromFile(%this, %file) {
%this.index = 0;
... create file object
.. iterate
%this.coordinates[%i] = %currentRow;
...
}
// Call a method on the object. %this is passed implicitly
MyObject.loadFromFile("happy.txt");
// Another method!
function MyObject::nextCoordinate(%this) {
%coord = %this.coordinates[%this.index];
%this.index++;
return %coord;
}
// Print first three coordinates
echo(MyObject.nextCoordinate());
echo(MyObject.nextCoordinate());
echo(MyObject.nextCoordinate());
// Reset 'pointer'
MyObject.index = 0;Note that I forgot, in my example above, to close and delete the file object. I've added that now.The array[] syntax in TorqueScript is just sugar for creating lots of differently-named variables. For example, %obj.array[0] is the same as %obj.array_0, just with the ability to put a variable in the expression (you can't write, for example, %obj.array_%i).
#10
Anyway, the annoying part is still generation of that list of coordinates. Is the "ground" flat? If so, it's considerably less annoying but you still probably have to enter them by hand. Unless you have an algorithm that generates the coordinates while guaranteeing your distribution.
05/20/2014 (7:37 am)
@Daniel - you cheater! You're just moving the target from place to place! Great idea, actually....Anyway, the annoying part is still generation of that list of coordinates. Is the "ground" flat? If so, it's considerably less annoying but you still probably have to enter them by hand. Unless you have an algorithm that generates the coordinates while guaranteeing your distribution.
#11
05/20/2014 (2:06 pm)
Oh, I imagined using the MyObject class to just generate coordinates. So in ArcheryTarget::onDestroyed or whatever the callback is called, you'd create the new target with MyObject.nextCoordinate().
Torque Owner Richard Ranft
Roostertail Games
Almost everything in the mission is stored in a text file in game/levels with a .mis extension - for instance, a full template game already has Empty Room.mis and Empty Terrain.mis along with preview images and decal data files. Terrain data is stored in game/art/terrains.
If this is the case then use the file functions - the following example is used to search script files for specific data (Datablock name and shapefile information) and so should be modifiable for your purpose.
// the %dataStorage object is expected to be a simset or simgroup function readDatablockInfo( %dataStorage, %fileName ) { // create a ScriptObject to carry our data fields %infoObject = new ScriptObject(); %infoObject.shapeFileInfo = ""; %infoObject.datablockName = ""; // create a file object %file = new FileObject(); %validFile = false; // open the file if ( %file.openForRead( %fileName ) ) { %validFile = true; %inInfoBlock = false; %dbNameFound = false; while ( !%file.isEOF() ) { // read our file line by line %line = %file.readLine(); %line = trim( %line ); // searching for the start of the datablock %tempLine = strreplace(%line, "datablock PlayerData(", ""); if( %line !$= %tempLine ) // deduction by negatives - this is the start of a PlayerData datablock %inInfoBlock = true; else if( %inInfoBlock && %line $= "};" ) { %inInfoBlock = false; %infoObject = %infoObject @ %line; break; } if( %inInfoBlock ) { if (!%dbNameFound) { // find datablock name %schloc = strchr(%tempLine, ":"); %dbName = trim(strreplace(%tempLine, %schloc, "")); %infoObject.datablockName = %dbName; %dbNameFound = true; } else { %tempLine = strreplace(%line, "shapeFile = ", ""); if (%line !$= %tempLine) // found the shapefile entry { %sfName = trim(strreplace(%tempLine, """, "")); %sfName = strreplace(%sfName, ";", ""); %infoObject.shapeFileInfo = trim(%sfName); } } } } %file.close(); } else { %validFile = false; error("Datablock file " @ %fileName @ " not found."); } %file.delete(); %dataStorage.add(%infoObject); }Your version will probably be simpler - you can just put each set of coordinates on a separate line and just read them in:I would use this format so that you can just assign the line that is read from the file directly to the position property of the object, but it is of course up to you. You might also want to make the z coordinate very high (higher than any point on the terrain at least) and then when you create the object use its created position to cast a ray straight down and find the terrain height at the current x/y coordinates, then move the object to that new position. Otherwise you might as well just place them in the editor, since it would be difficult to know the terrain height before hand.
You know, you could just make them invisible, or place spawnspheres and spawn your targets at the spheres....
Don't worry about that - when you import the file it creates the .dts file. Every time it needs "myshape.dae" it first looks for "myshape.cached.dts" and uses it automatically if it is present.