Game Development Community

Class Problems in Level Editor

by Cristopher Boyer · in Torque Game Builder · 05/04/2006 (9:21 pm) · 14 replies

Howdy all!

I've been struggling to come to terms with the pseudo-class implementation in Torque as well as the play between scripts and the Level Editor in Torque since picking it up again a few weeks back, and I've been making steady progress. However, a mystery has reared its head that has me stumped. Essentially, I want to assign classes to sprites that I place in the level in the Level Builder. I start with a base class, FieldSprite, and have two subclasses off of that, PCSprite and EnemySprite. However, the problem I'm running into is before I even get that far, and appears to be an issue in the order of execution. Just for testing purposes, I've been trying to go directly to the PlayerSprite object, which looks like this:

new ScriptObject(PCSprite)
{
idleDownAnimation = playerIdleDownAnimation;
idleUpAnimation = playerIdleUpAnimation;
idleRightAnimation = playerIdleRightAnimation;
idleLeftAnimation = playerIdleLeftAnimation;
walkDownAnimation = playerWalkDownAnimation;
walkUpAnimation = playerWalkUpAnimation;
walkRightAnimation = playerWalkRightAnimation;
walkLeftAnimation = playerWalkLeftAnimation;

name = "PCSprite";
};

This piece of script is contained in the file objectTypes.cs, which is executed from the exec.cs file, which is called in the StartGame() function...

function startGame(%level)
{
// Set The GUI.
Canvas.setContent(mainScreenGui);
Canvas.setCursor(DefaultCursor);

exec("./exec.cs");
onStartUp();

moveMap.push();
sceneWindow2D.loadLevel(%level);

}

Now, in order to make the sprite in the Level Builder inheret the values from the PCSprite class, I paste a sprite representing the main character into the level builder and name it player : PCSprite . However, when I try to launch the level in TGB, I get the following error:

Field/data/levels/testLevel.t2d (0): Unable to find parent object PCSprite for t2dAnimatedSprite.

Now on further observation, it would seem this happens because TGB loads and compiles the level before the exec() call in startGame(), so the level runs before the PCSprite class is initialized. Out of curiousity I tried putting the class definition in the testLevel.t2d file, but then when I run Torque again the player object successfully inherits the variables in PCSprite, but then just hard codes those variables into the player object and erases the PCSprite SceneObject from the file entirely.

I mucked around the main.cs and some of the common/gameScripts files looking for a better place to initialize the objects, but that smells like a risky fix that's just asking for troubles down the line. So is there a better way to go around this? Or, through my independent messings, am I going about this in a completely wrong way?

Thanks in advance!

#1
05/04/2006 (9:56 pm)
I don't have torque on this computer to test right now but I thought I saw an option where you can set the class for an object in the level editor. It's a field next to where you set the name for the object to be used in the script. I haven't played with it and I've been wondering what it does but could that have something to do with what you're trying to do?
#2
05/05/2006 (8:35 am)
Kodafox is pointing in the right direction--the use of ScriptObject for this type of technique is no longer required--you simply set the Class/SuperClass field of your t2dAnimatedSprite when you define it in the level builder, and have a method you would call like PCSPrite::init() in your scripts.
#3
05/05/2006 (9:13 am)
Thanks for the help so far.

Yes, the Level Editor does have a field to set the name for an object, and that's actually where I was trying to set the name to " player : PCSprite ", which didn't work since the class PCSprite won't typically be initialized before Torque tries to make the player object.

The "PCSprite::init()" idea is one I hadn't entertained before and it would definitely work. Only problem is that I seem to lose some of the convenience because I have to make sure that every FieldSprite object I create gets its init() called, whereas naming an object "player : PCSprite" in the Level Editor would automatically inherit the variables from the PCSprite ScriptObject. Is this still possible, or am I better off just going with the PCSprite::init() path?

Also, a related question: I'm finally understanding the limitations about what Torque can and can't do in terms of its OOP-esque implementation, but using the PCSprite::init() method, how deep will the inheritence go? For instance, if I put an object into the Level Editor with the class FieldSprite, another object with the class PCSprite and superclass FieldSprite, and another object with the class BobSprite and superclass PCSprite, and I have the methods FieldSprite::move(), PCSprite::takeDamage() and BobSprite::goCrazy(), will the object with the class BobSprite and superclass PCSprite be able to use all three of those functions?

Also, if there are init() methods for FieldSprite, PCSprite, and BobSprite, and in the Level Editor I make a new AnimatedSprite object named Bob with class BobSprite and superclass PCSprite, and later on I call Bob.init(), will it run the BobSprite::init() method or will it fall back to the earlier naming?

Sorry if some of this seems redundant, because it sure seems redundant to me, since I thought I knew the answers to most of those later questions before Beta 2 came out. Still, thanks in advance! :)
#4
05/05/2006 (2:19 pm)
Actually, you would just want to set the object's class to "PCSprite", and set it's name to "Player" (don't want/need to do the : concatenation at all).

If you are familiar with the concept of virtual member functions in a "normal" OOP implementation, namespace usage should make a lot more sense to you if you think about it as the same in usage. For example, let's say that, using the example above, you create your object with the name of Player, the class of PCSprite, and the superclass of BaseSprite, as well as instantiating it as a t2dAnimatedSprite. This allows your object to access the following script namespaces:

Player
PCSprite
BaseSprite
t2dAnimatedSprite
t2dObject

The above is in order of which namespace is checked for script method implementation when a method is called on your object during game play. For example:

Let's say that you decide that the physics (collision specifically) for all of your objects in this family will be handled exactly the same. You could simply create a script method called BaseSprite::onCollision(), and when this callback is executed by the engine, it will search for:

Player::onCollision() -- nope, not implemented
PCSprite::onCollision() -- nope, not implemented
BaseSprite::onCollision() -- yep, we found it. the engine will call this script method, and no further.

Now, second example--our ::init. We want to have our different classes initialized with different values, so we'll write 2 different script methods: PCSprite::init() and EnemySprite::init().

Now, for each object (one of PCSprite class, and one of EnemySprite class), we have different implementations...and the byte code parser will execute the appropriate method for each of the objects.

For your specific use case, it would first look for Bob::init(), then search for BobSprite::init(), then search for PCSprite::init(), and so on up the chain until it finds an implementation. Given your example, it would execute BobSprite::init(), since Bob::init() doesn't exist.
#5
05/05/2006 (3:31 pm)
Okay, I think I'm on the level now. I'll test my understanding when I play around in Torque tonight.


Thank you very much.
#6
05/05/2006 (5:10 pm)
Ok now I have a question about this. If I make a class for my sprites to use in the level editor when do I initialize it so that the settings take. That's kinda a vague question so let me expand a bit. I made a file with a test class deffinition in it. that looks like this:

function Tester::onAdd(%this)
{
    
    echo("yo I'm a tester named" SPC %this.getname());
    %this.setAngularVelocity(80);
    %this.setPosition("0 0");
    %this.report();
}

function Tester::report(%this)
{
    echo("my position is" SPC %this.getPosition());
    echo("my AngularVelocity is" SPC %this.getAngularVelocity());
}

After this I put a sprite in the level editor and set it's class to Tester and set it's name to testBoy. When I load up the game and look in the console it says "yo I'm a tester named testBoy". the next line reports his position as "0 0" with an angular velocity as 80 just as I wanted it but in reality he's still where I dropped him with a 0 angular velocity. If I call report(); on him again it reports his position as the position that I dropped him into the level at and his AngularVelocity as 0. The explanation is pretty simple. Obviously after the sprites being initialized by my on add function the loaded settings from the level file are overwriting those settings. So how do I get my settings to be applied last? Is there a callback that tells me when the level's done loading? Or should I just put a schedule() call in my onAdd() and apply the settings at a slight delay. That second option seems a bit risky though because theoretically even if I delay the call it might sometimes get called first... just seems like kinda a hack. Any suggestions? Thanks :)
Jake
#7
05/05/2006 (7:12 pm)
I found that if I set the postion manually rather than using

%this.position = "0 0";

rather than

%this.setposition("0 0");

the values stick. That will take care of my problem for now but I don't really understand the difference. Can someone explain why this is the case?

edit:
This setting the value manually thing isn't really going to work with things like this
%this.setCollisionActive(false, true);

this is pretty confusing but take a look at this:

function PlatformClass::onAdd(%this)
{   
    
    %this.setCollisionActive(false, true);
    %this.setGraphGroup($platformGroup);
    %this.setLayer($platformLayer);
    
    echo("platform initiated:"SPC%this.getID());
}

I placed some platforms in my game and set their class to platform. When the level loads in the console it says:
platform initiated: 2092 ... etc.. but the platform is still not collidable. now if I call

2092.onAdd();

from the console... it displays again:
platform initiated: 2092
but now it is collidable. Is this a bug. Shouldn't I be able to put these calls to %this.set___() in the onAdd() function to use onAdd as sort of a constructor? Or am I misunderstanding the point of onAdd()?
#8
05/05/2006 (9:08 pm)
Ok I think I came up with a pretty decent solution to this problem.
I'm using a sim set now and on the class's onAdd(); function I just add the object in to the set. After the level is loaded I call the init function on each item in the set in a separate function by iterating through the set.
#9
05/07/2006 (12:53 am)
I'm going to have to refer this to Justin and Matt on the TGB team for in depth analysis--they've recently done a usabilty pass on the code, so I'm not sure if this has already been discovered or not!

Hopefully 1 of the 2 of them will be able to describe how the workflow should go for integrating the level builder with scripted initialization better than I am!
#10
05/07/2006 (11:05 am)
If the object is saved out in your level then you can use this callback.

function PlatformClass::onLevelLoaded(%this, %scenegraph)
{

}

One thing I noticed is the onAdd can get called before certain things get set sometimes, while onLevelLoaded gets called when your level filed is loaded and the object is successfully loaded into your level :)
#11
05/07/2006 (12:56 pm)
Thanks Matt, ::onLevelLoaded() was what I was looking for, and should be used in place of my ::init() "placeholder" described above!
#12
05/07/2006 (5:57 pm)
Just to make sure I'm understanding this tremendously helpful thread...

If I create a static sprite in the level editor and give it the Class name "testClass" under the "scripting" section of the object's properties, then the following could override properties in the level editor:

function testClass::onLevelLoaded(%this, %scenegraph)
{
   %this.setCollisionActive(true, true);
   %this.setCollisionPhysics(true, true);
   %this.setCollisionDetection("FULL");
   %this.setCollisionCallback(true);
   %this.setImmovable(true);
   %this.setAutoMassInertia(true);
   %this.setGroup(1);
}

right?
#13
05/08/2006 (4:51 am)
Yes...I am pretty sure! Hehe...I'll bump Matt tomorrow to make sure, but that sounds correct.
#14
05/08/2006 (8:41 am)
Yes that should work... though since you're setting object settings it almost seems as if you should be using a config datablock for this vs. the script callback.