Game Development Community

Trouble calling an external function

by Bruno Steppuhn · in iTorque 2D · 07/25/2011 (1:39 pm) · 19 replies

Hello all,

I have a game with a plane that passes over and drops enemies that the player shoots out of the sky. I'm have issues spawning the enemies and, being new to iTorque with a C++ background, am curious on the best way to do it.

I have been googling for hours on how to do it and this is what I've come up with:

My initial design had a script called "enemyManager.cs" this was designed to spawn enemies when a function in it was called and then manage the enemies in an array of sorts, or some other sort of management system. I was trying to test this by calling the function, "spawnNewEnemy" in my game.cs script:
// Create a new enemy using the new keyword, of a given type
// %pos - The position where the enemy will be spawned
// %dropSpeed - the speed the enemy is falling.
function spawnNewEnemy( %pos, %dropSpeed )
{
    // Create a new enemy with the provided position and drop Speed.
    %newEnemy = new ScriptObject()
    {
        class = EnemyClass;
        position = %pos;
        dropSpeed = %drop;
        ID = 0;
    }

    // Assign the enemy a new ID from our pool.
    %newEnemy.ID = assignID();

    // store the enemy into our array
    ....
}

Initially, I wanted to make this sort of a global manager with somewhat encapsulated variables, so I didn't put the function in the enemyManager namespace and it was working fine when calling it from game.cs until I made the "assignID()" function. I had originally placed the assignID function after the spawnNewEnemy function, it would enter the spawnNewEnemy(), but would say that it couldn't find the assignID() function. As a suggestion from a friend I place the assignID function above the spawnNewEnemy function and it was able to find that function, but it wasn't able to find the spawnNewEnemy function. Why is that?

Trying to fix it, I put both the functions into the enemyManager:: namespace and tried calling the functions from game.cs like so:

enemyManager.spawnNewEnemy();

note: omitted parameters

Still wasn't finding the function. I then thought that maybe enemyManager had to be instantiated to be able to call these functions, so I made it a member of our planeBehavior.cs. Initializing it like so:

function planeBehavior::onBehaviorAdd(%this)
{
    %this.enemyManager = new ScriptObject()
    {
        class = enemyManager;
    };
}

note: also tried
className = "enemyManager";
and
class = "enemyManager";


I have still been unable to find the spawnNewEnemy function after commenting out all the code in the function to avoid any syntax errors and also moving the file to the same folder. I believe I may not understand a crucial piece of information about iTorque and I'm hoping someone might be able to shed some light since I have had a lot of trouble finding decent documentation for the scripting in iTorque 2d.

Any help, tips, criticism, or the like would be greatly appreciated.

Thank you,

#1
07/25/2011 (2:41 pm)
@Bruno - You are on the right path, but there are a couple of problems. If I were to rewrite your code, I would go with something like this:

// Create a new enemy using the new keyword, of a given type
// %pos - The position where the enemy will be spawned
// %dropSpeed - the speed the enemy is falling.
function enemyManager::spawnNewEnemy( %pos, %dropSpeed )
{
    // Create a new enemy with the provided position and drop Speed.
    %newEnemy = new t2dSceneObject()
    {
        class = EnemyClass;
        position = %pos;
        dropSpeed = %drop;
        ID = 0;
    }

    // Assign the enemy a new ID from our pool.
    %newEnemy.ID = assignID();

    // store the enemy into our array
    ....
}

function planeBehavior::onBehaviorAdd(%this)
{
    %this.enemyManager = new ScriptObject()
    {
        class = enemyManager;
    };

   %pos = "0 0";
   %dropSpeed = 1.0;

   %this.enemyManager.spawnNewEnemy(%pos, %dropSpeed);
}

I changed the declaration of spawnNewEnemy and changed the object being spawned to a t2dSceneObject.
#2
07/25/2011 (2:42 pm)
edit: must have posted seconds behind Michael. I might listen to him too. Something tells me he knows what he is talking about.
::)(((




/////
Without going too much into what you have done, myself, just began prgramming 2 year ago, with Torque, I had little programming beyond some Flash and HTML in my background.


To do what you want to do, I would just use behaviors. I am not even sure if by placing your funtion in the game.cs is appropriate. (again, not sure,, as it may very well be). The purpose of behaviors, is simply attaching scripts to objects, the object a behavior is attached to is the owner. so...

if you want "enemies" to jump out of a plane on command I would create a planeManagerBehavior.cs or something to that effect. This behavior would simply call for either the creation of or placement and veleocity of the enemy you want to fall.


You could crreate a pool of ememies and simple pull them on screen when needed or create them on call.




Roughly, my code would look like this...
///plane managet behavior, attached to the plane.
function planeManagerBehavior::createEnemyJumper(%this)
{
%planePosition = %this.owner.getPosition();  ///where owner is the plane.

//assuming you have a pool of enemy jumpers (enemy1,2,3...)
%this.enemy1.setPosition(%planePosition);
%this.enemy1.setConstantForceY($Gravity);
}



not including anything like animations, specific position enemy must jump from in reference to plane, wind etc. This is basically what my code would look like.






#3
07/25/2011 (2:46 pm)
Would also change this:
function planeBehavior::onBehaviorAdd(%this)  
{  
    %this.enemyManager = new ScriptObject()  
    {  
        class = enemyManager;  
    };  
}
to this:

planeBehavior.enemyManager = new ScriptObject(enemyManager){};
or:
new ScriptObject(enemyManager){};
function planeBehavior::onBehaviorAdd(%this)  
{  
    %this.enemyManager = enemyManager;
}
#4
07/25/2011 (9:45 pm)
Wow, some great suggestions and I appreciate the quick response. Some follow up questions however:

a) what's the difference between a t2dScriptObject and a ScriptObject?
b) when you create a script object like:
planeBehavior.enemyManager = new ScriptObject(enemyManager){};
Does that create an object of that enemy Manager class or does it just create an object named "enemyManager?"
c). I'm going to be looking at possibly using datablocks for different enemies or what-not, where would be the best place to store/create these datablocks?

Again, thanks for the help guys!
#5
07/25/2011 (10:39 pm)
a. I am not sure there is anything in torque called a t2dScriptObject.
b. you can create any object like this...
///t2dSceneObject could be any type of object Torque supports. 
%enemyJumper = new t2dSceneObject(nameReference){
///in here you can do all sorts of stuff
class = "enemyJumperClass";
position = %this.planePosition;
size = "32 32"
mass = "130";
///etc. just consult the docs.
};

c. datablocks in iTorque are two fold. there is one main datablock, automatically created on project creation. it is stored in your managed folder. then there are your level datablocks. These, are created and stored in your levels/datablocks folder. they are created, only when you transfer the datablock info, from your main datablock into your level datablock so only pass datablocks you need for that particular level.

There is a handy btn in the iTorque editor. It is positioned along the header bar, and is the 4th btn from the left. It looks like a chain, with a plus sign. Press that. It opens up a window with 2 panels. on the left are all of your projects, managed, datablocks. simply select the datablocks you need for that level (that you are working on) and pass them over. iTorque will automatically save that xLevel_datablock.cs in the data/levels/datablocks folder. With out doing this you will not be able to see your work when you run the project.
#6
07/26/2011 (5:50 am)
b) Does that create an object of that enemy Manager class or does it just create an object named "enemyManager?"

Tricky question, due to how torquescript handle objects. It creates an object named "enemyManager", but you can make use of all the methods created for that class. Specifically, any methods of the namespace enemyManager will be able to be called on it.

For example
function enemyManager::echoSomething(%this)
{
   echo("enemyManager echoing Something!!");
}

//called like this:
enemyManager.echoSomething();
#7
07/26/2011 (5:58 am)
Worth noting that if you use both, a name object, and a class field in the object, you'll have two namespaces from where to use methods.
For instance,
new ScriptObject(enemyManager){ class = MyClass };

function MyClass::echoSomethingClass(%this)
{
   echo("MyClass echoing Something!!");
}

function enemyManager::echoSomethingObject(%this)
{
   echo("enemyManager echoing Something!!");
}

// you can do both following calls
enemyManager.echoSomethingObject();
enemyManager.echoSomethingClass();

Flexible.
#8
07/26/2011 (8:03 am)
@rennie - I'm sorry, I made a typo, I meant: what's the difference between a t2dSceneObject and a script object, as per michael's code?

#9
07/26/2011 (8:26 am)
@Bruno. I am unsure. It may just be something custom to him. best ask @Michael.


#10
07/26/2011 (8:40 am)
A ScriptObject is exactly how it sounds. It's an object that exists at the script level. It does not actually have interaction with a scene graph, unlike t2dSceneObjects that have bounds, positioning, orientation, etc. I suggested a t2dSceneObject for the enemy because it sounds like an actual game object you will see in the level.

The suggestions of Bruno and Novack are also excellent examples of how you can achieve something using multiple approaches. TorqueScript is a flexible language.

#11
07/26/2011 (10:28 am)
I'm still getting an error saying, "Unknown command spawnNewEnemy"

this is my full code for enemyManager.cs:

// increments or initializes the IDpool and returns a new ID for the enemy
function enemyManager::assignID()
{
    // Make sure the IDpool is active
    if( %this.IDpool == NULL )
    {
        %this.IDpool = 0;
    }
    else
    {
        %this.IDpool++;
    }

    return %this.IDpool;
}

// Create a new enemy using the new keyword, of a given type
// %pos - The position where the enemy will be spawned
// %dropSpeed - the speed the enemy is falling.
function enemyManager::spawnNewEnemy( %pos, %drop )
{
    // if the array isn't initialized, initialize it.
    //if( %enemyArray == NULL )
    //{
    //   %enemyArray = TScriptArray::create(); // using James C Ford's array script
    //{

    // Create a new enemy with the provided position and drop Speed.
    //%newEnemy = new t2dSceneObject()
    //{
    //   class = EnemyClass;
    //   position = %pos;
    //   dropSpeed = %drop;
    //   ID = 0;
    //};

    // Assign the enemy a new ID from our pool.
    //%newEnemy.ID = enemyManager::assignID();

    // Push the enemy into our enemy array for management.
    //%enemyArray.push_back( %newEnemy );

    // DEBUG - Test to see if it's entering this function
    echo( "***DEBUG***: SPAWNED NEW ENEMY, EnemyID: " @ %enemyArray.get(0).ID );
}

Now, I'm not sure if, to call "assignID" I need to have the namespace there or if it's a %this.assignID() call, but it's not that important at the moment since I have it all commented anyway and it's still giving me this error.

I call the function like so:

// Function for spawning an enemy
function planeBehavior::spawnEnemy( %this, %val )
{
    // Debug code, for debugging
    echo( "**DEBUG**: entered planeBehavior::spawnEnemy" );

    // call the spawn enemy function from our enemy Manager.
    %this.enemyManager.spawnNewEnemy( "0 0", %this.fallSpeed );
}

Now, the enemy manager is created on the onBehaviorAdd function like was shown before so it shouldn't be an issue of the enemyManager not being initialized. Any suggestions?
#12
07/26/2011 (10:49 am)
If I were still in trouble, I would start with the basics:

Are you sure you're exec'ing the enemyManager.cs?
Are you spawning enemies *after* the enemyManager.cs has been exec'ed?
#13
07/26/2011 (10:53 am)
Btw:
The function declarations should look like:

function enemyManager::spawnNewEnemy(%this, %pos, %drop )

function enemyManager::assignID(%this)

The first parameter should be always the object referencing the method of that namespace, is not implicit.
#14
07/26/2011 (11:12 am)
I have triple checked the exec, and I do it in game.cs:

function startGame(%level)
{
    Canvas.setContent(mainScreenGui);

    // Execute game scripts
    exec( "./gameScripts/enemy.cs" );
    exec( "./gameScripts/enemyManager.cs" );
    exec( "./ScriptHelpers/tscriptarray.cs" );

    new ActionMap(moveMap);
    ...
}

What else might it be? The planeBehavior.cs is located in the behaviors folder, while the enemyManager.cs is in the gamescripts folder... not missing any semi-colons... however if I remove the assignID function, I enter the spawnNewEnemy function without any error. Then I place the assignID function after the spawnNewEnemy function and it can't find the assignID function at line:

%newEnemy.ID = enemyManager::assignID();

So I changed it to:

%newEnemy.ID = assignID();

and it still can't find it. I'm thinking it has something to do with the order in which the function are, but I'm curious why this is and how do I fix this?
#15
07/26/2011 (1:46 pm)
and this is even more baffling:

// DEBUG - Test to see if it's entering this function
	echo( "***DEBUG***: Entered enemyManager::spawnNewEnemy" );
	
	// if the array isn't initialized, initialize it.
	if( %enemyArray == NULL )
	{
		%enemyArray = TScriptArray::create();
		
		// DEBUG - Test to see if it's entering this function
		echo( "***DEBUG***: created a new TScriptArray" );
	}
	
	// Create a new enemy with the provided position and drop Speed.
	%newEnemy = new t2dSceneObject()
	{
		class = EnemyClass;
		position = %pos;
		dropSpeed = %drop;
		ID = 0;
	};
	
	// DEBUG - Test to see if it's entering this function
	echo( "***DEBUG***: Created a new Enemy: class: " @ %newEnemy.class @ ", position: " @ %newEnemy.position @ ", drop Speed: " @ %newEnemy.dropSpeed @ ", ID: " @ %newEnemy.ID );
	
	// DEBUG - Test to see if it's entering this function
	echo( "***DEBUG***: is IDpool Null ? " @ (%IDpool == NULL) );
	
	// Make sure the IDpool is active
	if( %IDpool == NULL )
	{
		%IDpool = 0;
		
		// DEBUG - Test to see if it's entering this function
		echo( "***DEBUG***: initialized IDpool: " @ %IDpool );
		
	}
	//else
	//{
	//	%IDpool++;
		
	//	// DEBUG - Test to see if it's entering this function
	//	echo( "***DEBUG***: incremented IDpool: " @ %IDpool );
	//}
	
	// Assign the enemy a new ID from our pool.
	%newEnemy.ID = %IDpool;
	
	// DEBUG - Test to see if it's entering this function
	echo( "***DEBUG***: Assigned a new ID to our enemy: " @ %newEnemy.ID );
	
	// Push the enemy into our enemy array for management.
	%enemyArray.push_back( %newEnemy );
	
	// DEBUG - Test to see if it's entering this function
	echo( "***DEBUG***: pushed our new Enemy to the back of the array.  " @ %enemyArray.get(0).ID );

I added the assignID function logic to this function and added all the echos to try to see where it messes up. Before I commented out the else statement, it wouldn't reach any of the debug lines below that if statement. How or why is this happening?
#16
07/26/2011 (4:03 pm)
Dude, what is with all this array stuff. ??

Why not, just create 3 things...

1. A script, which will be your plane class.
This will tell your plane to move and tell the enemyJumpers to be placed into position and "jump" out of the plane.

2. a plane object. Just pull in a sceneObject, I am thinking static prite to represent your plane.

3. a pool of enemy jumper objects. Use the plane class script to call these objects into position and make them jump!!!
#17
07/26/2011 (4:21 pm)
@rennie: well the design is to have the enemy jumpers as state machines, because they will fall for a certain distance then decide to deploy their parachute then as they fall they can be hit with 5 different types of weapons and depending on which weapon they get hit by will die in a different way.

Most of the states will mainly be to manage their animations and make sure they perform the right ones at the right times. Now whether I create a pool or not, I'll still be using up a given amount of memory, but I imagine it'll be less than having a pool of memory to pick from if I new them and create the memory when needed and delete them to clean them up when they bite the dust or I don't need them anymore.

I'm hoping I'll be able to do it this way in iTorque, but I'm fearing that it's easier said than done.
#18
07/26/2011 (5:17 pm)
I did figure it out though, I wasn't able to call the assignID function (still baffled as to why), but what I did was just move that code inline to the spawnNewEnemy function and removed the braces to make it a one liner if statement and it works fine.

If anyone has an answer as to why I wasn't able to call that function I would be eager to hear what it is, otherwise, thank you guys all for the help.
#19
07/26/2011 (5:21 pm)
good luck.



:::