Making functions like onReachDestination
by Caleb · in Torque Game Engine · 07/01/2006 (7:46 am) · 6 replies
Like the name says, I want to make a function similer to onReachDestination or onCollision. My function would be somthing like, onInRange(). I need onInRange() to check if my player is in a minimal range, if the player is, call an AI chase function that I just made.
If my player is dead or out of range, then call an AIPlayer::onOutOfRange to stop. I want it to look like,
AIPlayer::onInRange(%this, %obj)
{
%obj.Chase(myGuy.getPosition());
}
and
AIPlayer::onOutOfRange(%this, %obj)
{
%obj.stop();
}
So how do I make a function like those.
Thanks.
If my player is dead or out of range, then call an AIPlayer::onOutOfRange to stop. I want it to look like,
AIPlayer::onInRange(%this, %obj)
{
%obj.Chase(myGuy.getPosition());
}
and
AIPlayer::onOutOfRange(%this, %obj)
{
%obj.stop();
}
So how do I make a function like those.
Thanks.
#2
or anything else for that matter.
Could someone show me how to use it? Or how to set up conditions for at least.
From what I can tell I would do somthing like this (correct me if I'm wrong)
Thanks.
07/01/2006 (11:03 am)
Really, I've seen this in torque code before and didn't know what it did since I've never seen it in DirectX,or anything else for that matter.
Could someone show me how to use it? Or how to set up conditions for at least.
From what I can tell I would do somthing like this (correct me if I'm wrong)
void AIPlayer::onInRange(AIPlayer*, object)
{
//somehow put my conditions here.... Please help me on this part
Con::executef(getDataBlock(), 2, onInRange, scriptThis());
}then in script,myAIGuy::onInRange(%this, %obj)
{
%obj.Chase(myPlayer.getPosition());
}does that look about right? If so please help with the conditions part.Thanks.
#3
And then in my script.
This code below, only works if "%obj" is replaced by the name of one AI and if my Player is in Range.
I have to call it every time I want to check the range, if I wanted it like that I would just put my Chase code in with a schedule that calls it over and over.
So some help, suggestions or anything that will help point me in the right direction would be nice.
07/02/2006 (8:23 am)
OK here is what I got so far.//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------
//my onInRange function not sure whats wrong
void AIPlayer::onInRange(AIPlayer* object)
{
if (!isGhost())
{
Con::executef(getDataBlock(), 2, onInRange, scriptThis(), object->scriptThis());
}
}
//-------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
//-------------------------------------------------------
//my chase function that works fine
ConsoleMethod( AIPlayer, Chase, void, 3, 4, "(Point3F goal, bool slowDown=true)"
"Tells the AI to Chase the player.")
{
Point3F v( 0.0f, 0.0f, 0.0f );
dSscanf( argv[2], "%g %g %g", &v.x, &v.y, &v.z );
//added--------------------------------------------------------------
float v0 = (v - object->getPosition()).len();
//-------------------------------------------------------------------
if (v0 < 51)
{
bool slowdown = (argc > 3)? dAtob(argv[3]): true;
object->Chase( v, slowdown);
}
else
{
object->stopMove();
}
}
//-------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------------------------------And then in my script.
This code below, only works if "%obj" is replaced by the name of one AI and if my Player is in Range.
I have to call it every time I want to check the range, if I wanted it like that I would just put my Chase code in with a schedule that calls it over and over.
function BadGuy::onInRange(%obj)
{
%obj.Chase(myGuy.getPosition());
}So some help, suggestions or anything that will help point me in the right direction would be nice.
#4
Long Answer:
First, a little background:
In c++, there is an auto-magical pointer always provided to you in a class method called "this". The pointer is always a valid pointer to the instantiation of the class that is sending the call to the method, and does not need to be set as a parameter.
ConsoleMethods are a pre-processor Macro that makes it relatively easy for humans to write code that appears to be c++, which is then converted into accurate c++ that does what the human expects. They are specific to the ability for script to call c++ code, and since that is actually a quite complex operation, we've hidden most of the low level stuff within the ConsoleObject macro.
Within a ConsoleMethod the "object" syntax is a replacement/surrogate for the "this" pointer that C++ gives you, and means the same: a pointer to the sending instantiation. The Macro takes care of mapping "object" to "this" as needed when you call a follow on class method, such as object->chase(..).
In other words, in your declaration:
You don't need to declare the (AIPlayer* object) at all. Instead, your code would function just like this (NOTE: there is an error in the usage of Con::executef() we'll get to in a minute--for now I left it intact):
In your function BadGuy::onInRange(%obj) you are only taking in one argument, where in your Con::executef() you are actually sending 3, even though you have the arguments list incorrect.
Con::executef() inserts an objectId for the "calling object" (sender) of the callback as the very first argument in the callback delivered to script, then delivers your user arguments if you have them. In the above example:
3 arguments are generated, but the third is in the long run ignored since you use a "2" for number of arguments:
--an object handle of the datablock, derived from the getDataBlock() call -- done automatically for you
--on object handle of the AIPlayer, from the scriptThis() call
--an object handle of teh AIPlayer, from the object->scriptThis() call.
Note: the last two handles are exactly the same, and the third one is redundant.
Now, given your implied use with how you set things up, there are some implied assumptions:
1) The AIPlayer in question has a datablock that is named BadGuy
2) What you really want to do here is to assign an AIPlayer a target game object and have it automatically and continuously adjust it's movement destination based on this object.
I'm going to leave assumption 2 alone, since at the end of the article I'm going to recommend you look closely at the AIPlayer code!
3) Since for now we'll go with your using a position as a raw destination value, you have a valid object handle that references your player object within the "myGuy" script name. This isn't really a flexible solution, but the main purpose of my post is to make sure you understand how to use Con::executef() to create your own callbacks.
As mentioned earlier, Con::executef() will search the namespaces associated with whatever is placed in the very first argument of the executef() itself--in your example above, you are using getDataBlock(). That means it is going to be searching the following namespaces for your script method:
BadGuy (again, assuming your AIPlayer has a datablock named BadGuy
PlayerData (Note: we don't actually have AIPlayerData, since PlayerData has the info we need, although in an advanced implementation of AI it might not be a bad idea)
ShapeBaseData
GameBaseData
SimDataBlock
SimObject
ConObject
It will be looking for the method "onInRange" in each of these namespaces for a script method such as:
BadGuy::onInRange()
PlayerData::onInRange()
etc.
when it finds one (as you have), it will populate the following arguments:
first parameter: a handle of the calling object--the simID of your AIPlayer's datablock
second param : a handle of the AIPlayer itself
third param : a second handle of the AIPlayer itself (same number as the second param)
Given the callback you are using (Con::executef() in your example, corrected for number of parameters), your script method should look like:
Now, the "better" version of the above two code segments, which corrects usage of Con::executef() :
in the C++ class:
and in script:
As final note: While you aren't actually tracking the information needed for my guess as to what your original goal was in your design, I'm guessing that what you really were hoping for during your prototyping was to have the ability to have a function callback handler like this:
However, that requires that we store a pointer to the target we are chasing, and you don't have the variables setup to maintain that information. Fortunately, the Short Answer below handles all that for us!
Short Answer: How do I chase players using the stock AI code?
The AIPlayer class already has functionality to do what the OP originally asked. All you need to do is to:
A) have a handle to your AiPlayer (the npc)
B) have a handle to your target you want to chase (the player)
Then in script, do the following:
Automatically (it's already written for you), when the AIPlayer gets in range of the target, a callback to the datablock's ::onEnterLos() call will occur, and you can commence firing, or whatever you like.
07/02/2006 (10:19 am)
Short Answer: Scroll all the way to the bottom if you don't care about theory and usage of con::executef() and namespaces, and only want to know how to get your AI's to properly chase players easily!Long Answer:
First, a little background:
In c++, there is an auto-magical pointer always provided to you in a class method called "this". The pointer is always a valid pointer to the instantiation of the class that is sending the call to the method, and does not need to be set as a parameter.
ConsoleMethods are a pre-processor Macro that makes it relatively easy for humans to write code that appears to be c++, which is then converted into accurate c++ that does what the human expects. They are specific to the ability for script to call c++ code, and since that is actually a quite complex operation, we've hidden most of the low level stuff within the ConsoleObject macro.
Within a ConsoleMethod the "object" syntax is a replacement/surrogate for the "this" pointer that C++ gives you, and means the same: a pointer to the sending instantiation. The Macro takes care of mapping "object" to "this" as needed when you call a follow on class method, such as object->chase(..).
In other words, in your declaration:
void AIPlayer::onInRange(AIPlayer* object)
{
if (!isGhost())
{
Con::executef(getDataBlock(), 2, onInRange, scriptThis(), object->scriptThis());
}
}You don't need to declare the (AIPlayer* object) at all. Instead, your code would function just like this (NOTE: there is an error in the usage of Con::executef() we'll get to in a minute--for now I left it intact):
void AIPlayer::onInRange()
{
if (!isGhost())
{
Con::executef(getDataBlock(), 2, onInRange, scriptThis(), this->scriptThis());
// NOTE: -- scriptThis() and this->scriptThis() accomplish exactly the same task--and we'll describe more below)
}
}In your function BadGuy::onInRange(%obj) you are only taking in one argument, where in your Con::executef() you are actually sending 3, even though you have the arguments list incorrect.
Con::executef() inserts an objectId for the "calling object" (sender) of the callback as the very first argument in the callback delivered to script, then delivers your user arguments if you have them. In the above example:
Con::executef(getDataBlock(), 2, onInRange, scriptThis(), object->scriptThis());
3 arguments are generated, but the third is in the long run ignored since you use a "2" for number of arguments:
--an object handle of the datablock, derived from the getDataBlock() call -- done automatically for you
--on object handle of the AIPlayer, from the scriptThis() call
--an object handle of teh AIPlayer, from the object->scriptThis() call.
Note: the last two handles are exactly the same, and the third one is redundant.
Now, given your implied use with how you set things up, there are some implied assumptions:
1) The AIPlayer in question has a datablock that is named BadGuy
2) What you really want to do here is to assign an AIPlayer a target game object and have it automatically and continuously adjust it's movement destination based on this object.
I'm going to leave assumption 2 alone, since at the end of the article I'm going to recommend you look closely at the AIPlayer code!
3) Since for now we'll go with your using a position as a raw destination value, you have a valid object handle that references your player object within the "myGuy" script name. This isn't really a flexible solution, but the main purpose of my post is to make sure you understand how to use Con::executef() to create your own callbacks.
As mentioned earlier, Con::executef() will search the namespaces associated with whatever is placed in the very first argument of the executef() itself--in your example above, you are using getDataBlock(). That means it is going to be searching the following namespaces for your script method:
BadGuy (again, assuming your AIPlayer has a datablock named BadGuy
PlayerData (Note: we don't actually have AIPlayerData, since PlayerData has the info we need, although in an advanced implementation of AI it might not be a bad idea)
ShapeBaseData
GameBaseData
SimDataBlock
SimObject
ConObject
It will be looking for the method "onInRange" in each of these namespaces for a script method such as:
BadGuy::onInRange()
PlayerData::onInRange()
etc.
when it finds one (as you have), it will populate the following arguments:
first parameter: a handle of the calling object--the simID of your AIPlayer's datablock
second param : a handle of the AIPlayer itself
third param : a second handle of the AIPlayer itself (same number as the second param)
Given the callback you are using (Con::executef() in your example, corrected for number of parameters), your script method should look like:
function BadGuy::onInRange(%this, %searcher, %searcherHandleTwo)
{
// note: is our chase function defined in our datablocks? if so, this line is correct:
%this.Chase(myGuy.getPosition());
// however, if our chase function is defined on the player itself somehow, we should use:
// %searcher.Chase(myGuy.getPosition());
}Now, the "better" version of the above two code segments, which corrects usage of Con::executef() :
in the C++ class:
void AIPlayer::onInRange()
{
if (!isGhost())
{
Con::executef(getDataBlock(), 2, onInRange, scriptThis() );
}
}and in script:
function BadGuy::onInRange(%this, %searcher)
{
// note: is our chase function defined in our datablocks? if so, this line is correct:
%this.Chase(myGuy.getPosition());
// however, if our chase function is defined on the player itself somehow, we should use:
// %searcher.Chase(myGuy.getPosition());
}As final note: While you aren't actually tracking the information needed for my guess as to what your original goal was in your design, I'm guessing that what you really were hoping for during your prototyping was to have the ability to have a function callback handler like this:
function BadGuy::onInRange(%this, %searcher, %target)
{
%this.chase(%searcher, %target.GetPosition());
}However, that requires that we store a pointer to the target we are chasing, and you don't have the variables setup to maintain that information. Fortunately, the Short Answer below handles all that for us!
Short Answer: How do I chase players using the stock AI code?
The AIPlayer class already has functionality to do what the OP originally asked. All you need to do is to:
A) have a handle to your AiPlayer (the npc)
B) have a handle to your target you want to chase (the player)
Then in script, do the following:
$myAIPlayerHandle.setAimObject($myChaseTarget);
Automatically (it's already written for you), when the AIPlayer gets in range of the target, a callback to the datablock's ::onEnterLos() call will occur, and you can commence firing, or whatever you like.
#5
I got most my info from the onCollision() function. When I looking at the onCollision function, you can put an echo() in there and see that its being called every 1 secound. Instead of using a schedule with my Chase function, I wanted to use "onInRange()".
All I want is for my AIGuy to follow me around. I've done this many ways, but every time I had to specify the AIGuy's name. If there were 5 AIs only AI 1 would chase, and the schedules often slowed my game way down.
What I need is for:
AIPlayer::onInRange to check proximity the same way onCollision checks for Collision,
in script, use BadGuy/AIPlayer::onInRange to tell it what to do when in range like telling onCollision.
Thats all.
For the short anwser, I don't think that would work without a schedule calling it over and over.
I fixed the errors that were in my code but the same thing happens. It doesn't tell the AI to chase my player without me having to type in the console, "BadGuy::onInRange(). And that doesn't work if I don't have the AI's name specified.
Those to things I listed are all I really need, but thanks for clearing up some stuff though.
EDIT- What do you mean "is the function defined in datablocks"?
07/02/2006 (11:30 am)
WOW! Lots of info...perhaps to much. With the long answer, I'm not sure thats what I needed.I got most my info from the onCollision() function. When I looking at the onCollision function, you can put an echo() in there and see that its being called every 1 secound. Instead of using a schedule with my Chase function, I wanted to use "onInRange()".
All I want is for my AIGuy to follow me around. I've done this many ways, but every time I had to specify the AIGuy's name. If there were 5 AIs only AI 1 would chase, and the schedules often slowed my game way down.
What I need is for:
AIPlayer::onInRange to check proximity the same way onCollision checks for Collision,
in script, use BadGuy/AIPlayer::onInRange to tell it what to do when in range like telling onCollision.
Thats all.
For the short anwser, I don't think that would work without a schedule calling it over and over.
I fixed the errors that were in my code but the same thing happens. It doesn't tell the AI to chase my player without me having to type in the console, "BadGuy::onInRange(). And that doesn't work if I don't have the AI's name specified.
Those to things I listed are all I really need, but thanks for clearing up some stuff though.
EDIT- What do you mean "is the function defined in datablocks"?
#6
It works, in stock, already. You -would- need to modify the processTick() to have a persistent chase, but that's trivial--just don't clear out the aim object when they are in range. Not much more I can say really :(
07/02/2006 (1:04 pm)
Quote:
For the short anwser, I don't think that would work without a schedule calling it over and over.
It works, in stock, already. You -would- need to modify the processTick() to have a persistent chase, but that's trivial--just don't clear out the aim object when they are in range. Not much more I can say really :(
Torque Owner Cinder Games
void AIPlayer::throwCallback( const char *name ) { Con::executef(getDataBlock(), 2, name, scriptThis()); }throwCallback("onReachDestination");really all you need is:
Con::executef(getDataBlock(), 2, "onKillSelf", scriptThis());
or something to that effect. but it's up to you to setup the conditions for it to be called.