Writing AI and such in C++
by Nathan Blair · in Torque 3D Professional · 12/27/2014 (2:45 am) · 9 replies
Before I start: This is a long post, helping me out may take a while, I really don't want to waste your time so if you don't feel like spending a bit of time helping me out, I won't blame you. It is all phrased terribly, I'm tired. Apologies. At the end are the clearest questions I could write.
Hey everyone. I've had a bit of time on my hands lately, and I've been touching up on my torque 3d skills. I've written a lot of stuff in TorqueScript. I would now like to move things such as AI over to C++ for performance. I've learned C++ in the past, and I like to think I still know the important parts, pointers, references, classes etc, however actually using it in the engine is slightly challenging for me at the moment. That's why I'm posting here, I'm hoping some people could teach me just the fundamentals of what I need to know in order to get moving. Feel free to keep your answers brief, I don't want to be a burden. I know how to add console methods etc, added things like getting the AI to just wander around using navPaths from WalkAbout while they aren't doing anything, but I need help with more difficult AI.
So, for practice, I've tried moving some script very similar to the zombieThink function from this resource. I'll paste the code here:
I like to think I know what I'm doing, but sadly that may not be the case. I'm fairly sure I've done the majority of it, just with a bunch of errors. I've added some new private members such as mAttacking and mLockedOn which are just booleans for the states the AI will be in, and have get and set functions to get the values. I'll post what I've done in C++ so far. Note the pointers for player are 100% wrong, it was all failing so I changed them to all possible variations and haven't changed them back. This is one area where I need to know the fundamentals. When do I dereference pointers and whatnot I'm just a little confused.:
Also, I know that isMemberOfClass is only a script function, how would I make it available in code?
I'm also aware that getTargetDistance isn't a thing, I tried to make it myself and failed miserably.
My questions probably aren't clear enough. Let me try to write them concisely:
What is the correct implementation of that AI script in C++?
How do I use functions that are exposed in script (such as isMemberOfClass) and use them in C++?
In script, you can access an object by its ID and call functions on it, like 4755.getName(). Above, when using initRadiusSearch, I'm getting a sceneObject returned. Is this just an ID like in script? how would I go about applying damage to that sceneObject that I have stored in a variable?
What are the fundamentals I just need to know when working with the Torque 3D source to allow me to figure all of this out myself and not bother all of you with it?
If you're still reading, thank you so much for taking the time to read, and if you have the time and know-how, please do try and help me out!
Thanks.
Hey everyone. I've had a bit of time on my hands lately, and I've been touching up on my torque 3d skills. I've written a lot of stuff in TorqueScript. I would now like to move things such as AI over to C++ for performance. I've learned C++ in the past, and I like to think I still know the important parts, pointers, references, classes etc, however actually using it in the engine is slightly challenging for me at the moment. That's why I'm posting here, I'm hoping some people could teach me just the fundamentals of what I need to know in order to get moving. Feel free to keep your answers brief, I don't want to be a burden. I know how to add console methods etc, added things like getting the AI to just wander around using navPaths from WalkAbout while they aren't doing anything, but I need help with more difficult AI.
So, for practice, I've tried moving some script very similar to the zombieThink function from this resource. I'll paste the code here:
function AIPlayer::zombieThink(%this)
{
if(%this.getState() $= "Dead")
{
cancel(%this.thinkCycle);
%this.stop();
%this.clearAim();
%this.lockedOn = false;
%this.isAttacking = false;
return;
}
if(%this.lockedOn == true && %this.getTargetDistance(%this.target) <= 3)
{
%this.stop();
%this.destroyThread(1);
if(!%this.isAttacking)
%this.isAttacking = true;
%sequenceNumber = getRandom(1, 2);
%sequence = "attack" @ %sequenceNumber;
%this.playThread(0, attack2);
%id = %this.getId();
%position = %this.getPosition();
%this.target.schedule(100, "damage", %id, %position, "5", ""BulletProjectile"");
%this.thinkCycle = %this.schedule(1000, zombieThink);
return;
}
else if(%this.lockedOn == true && %this.getTargetDistance(%this.target) > 3)
{
%this.playThread(1, sprint_forward);
%this.destroyThread(0);
%this.followObject(%this.target, 1);
%this.thinkCycle = %this.schedule(100, zombieThink);
return;
}
else
{
InitContainerRadiusSearch(%this.getPosition(), 45, $TypeMasks::PlayerObjectType);
while ((%targetObject = containerSearchNext()) != 0)
{
// If a player is located and it is NOT an AIPlayer (other zombies)
// AND the player isn't crouching. I.e, can be 'heard'
// begin lockon/tracking
if(%targetObject.isMemberOfClass("Player") && !%targetObject.isMemberOfClass("AIPlayer") && %targetObject.getPose() !$= "Crouch")
{
echo(%targetObject.getPose());
// Target the player
%this.lockedOn = true;
%this.target = %targetObject;
%this.followObject(%targetObject, 1);
%this.playThread(1, sprint_forward);
//Play Snarl
serverPlay3D (MonsterSnarl, %this.getPosition());
%this.thinkCycle = %this.schedule(200, zombieThink);
return;
}
}
%this.thinkCycle = %this.schedule(1000, zombieThink);
return;
}
}I like to think I know what I'm doing, but sadly that may not be the case. I'm fairly sure I've done the majority of it, just with a bunch of errors. I've added some new private members such as mAttacking and mLockedOn which are just booleans for the states the AI will be in, and have get and set functions to get the values. I'll post what I've done in C++ so far. Note the pointers for player are 100% wrong, it was all failing so I changed them to all possible variations and haven't changed them back. This is one area where I need to know the fundamentals. When do I dereference pointers and whatnot I'm just a little confused.:
void AIPlayer::zombieThink()
{
if(getStateName() == "Dead")
{
stopMove();
clearAim();
setLockedOn(0);
setAttacking(0);
return;
}
if(getLockedOn() && getTargetDistance(mAimObject) <= 3)
{
stopMove();
if(!getAttacking())
setAttacking(1);
setActionThread("attack1", true, false, false);
return;
}
else if(getLockedOn() && getTargetDistance(mAimObject) > 3)
{
setActionThread("run", true, false, false);
followObject(mAimObject, 1);
return;
}
else
{
gServerContainer.initRadiusSearch(getPosition(), 45, PlayerObjectType);
SceneObject *player = gServerContainer.containerSearchNextObject();
while (player != 0)
{
if(player->isMemberOfClass("AIPlayer")) && player->getPoseName() != "Crouch")
{
setLockedOn(1);
mAimObject = *player;
followObject(player, 1);
setActionThread("run", true, false, false);
return;
}
player = gServerContainer.containerSearchNextObject();
}
}
}Whats the right way to do this? There are errors all throughout it.Also, I know that isMemberOfClass is only a script function, how would I make it available in code?
I'm also aware that getTargetDistance isn't a thing, I tried to make it myself and failed miserably.
F32 AIPlayer::getTargetDistance( SceneObject target )
{
vectorF tgtPos = mAimObject->getPosition();
MatrixF eye;
getEyeTransform(&eye);
vectorF eyePoint = eye.getPosition();
VectorF distance = eyePoint - tgtPos;
return distance.len();
}My questions probably aren't clear enough. Let me try to write them concisely:
What is the correct implementation of that AI script in C++?
How do I use functions that are exposed in script (such as isMemberOfClass) and use them in C++?
In script, you can access an object by its ID and call functions on it, like 4755.getName(). Above, when using initRadiusSearch, I'm getting a sceneObject returned. Is this just an ID like in script? how would I go about applying damage to that sceneObject that I have stored in a variable?
What are the fundamentals I just need to know when working with the Torque 3D source to allow me to figure all of this out myself and not bother all of you with it?
If you're still reading, thank you so much for taking the time to read, and if you have the time and know-how, please do try and help me out!
Thanks.
#2
I've set the object found in the container search to a Player and just called it thePlayer instead of a SceneObject. When I go
It says that getObjectType isn't a member of Player. It also isn't a member of SceneObject or anything, so how do I actually use it?
Bonus question, in aiplayer.h, there's a member called mAimObject which is of type SimObjectPtr<gameBase>. Can't seem to remember how that works. What actually is mAimObject here? Thanks again, I shouldn't have any more problems for a while!
12/28/2014 (9:46 pm)
Awesome thanks for your help Daniel. I've fixed all of the problems, and it now works as intended! I also actually understand things now haha. Only problem I'm having now is with making sure that the AI don't go after AI. I've added a new AIPlayerObjectType in objectTypes.h, and in aiplayer.cpp, I set its mTypeMask to:mTypeMask |= PlayerObjectType | DynamicShapeObjectType | AIPlayerObjectType;
I've set the object found in the container search to a Player and just called it thePlayer instead of a SceneObject. When I go
if(!(thePlayer->getObjectType() & AIPlayerObjectType))
It says that getObjectType isn't a member of Player. It also isn't a member of SceneObject or anything, so how do I actually use it?
Bonus question, in aiplayer.h, there's a member called mAimObject which is of type SimObjectPtr<gameBase>. Can't seem to remember how that works. What actually is mAimObject here? Thanks again, I shouldn't have any more problems for a while!
#3
12/28/2014 (11:08 pm)
It should be thePlayer->getTypeMask()
#4
12/29/2014 (3:21 am)
Oh yeah mAimObject is the object that it is currently targeting.
#5
Wasn't there already an AIObjectType? Maybe it was removed from TGE or I'm misremembering.
12/29/2014 (2:36 pm)
SimObjectPtr is what's known as a 'smart pointer'. You can treat it exactly as if it were a regular pointer (a GameBase*), but it does other stuff. In the case of SimObjectPtr, if the object it points to is deleted, the pointer will automatically be reset to NULL - so make sure you check the pointer before you use it! I think SimObjectPtr has an isNull method to check for that, though you should also be able to use the implicit cast to bool, or comparison to a regular pointer.Wasn't there already an AIObjectType? Maybe it was removed from TGE or I'm misremembering.
#6
That's a good pick-up (definitely not in MIT version), the AIPlayer class is just using the inherited type mask from the Player class. Would be handy to have access to AIPlayerObjectType mask actually.
12/29/2014 (3:45 pm)
Quote:
Wasn't there already an AIObjectType? Maybe it was removed from TGE or I'm misremembering.
That's a good pick-up (definitely not in MIT version), the AIPlayer class is just using the inherited type mask from the Player class. Would be handy to have access to AIPlayerObjectType mask actually.
#7
I always thought that a good way to extend it would be for a new function that gets maskType and then gets classType (eg: player -> aiplayer). That could provide a fairly infinite number of available types with a single function.
Not that I've ever had need for more than the stock 32 masks mind, hence why I didn't do it myself.
Anyhow, just thinking aloud on the typeMask limit thingy ...
12/30/2014 (8:31 am)
Actually just to mention masks ...I always thought that a good way to extend it would be for a new function that gets maskType and then gets classType (eg: player -> aiplayer). That could provide a fairly infinite number of available types with a single function.
Not that I've ever had need for more than the stock 32 masks mind, hence why I didn't do it myself.
Anyhow, just thinking aloud on the typeMask limit thingy ...
#8
Is your AI script causing a bottleneck? It is possible to have around 400 units on screen and fighting (pistols/rifles are best - fewer effects) and still stay above 16 FPS on moderate hardware - I've done it with these scripts. Admittedly, these scripts are very rudimentary - find enemy, attack enemy, when enemy dies, find enemy. I honestly don't remember, but they might seek health and ammo. I had that working but I don't remember if it got into this version.
12/31/2014 (9:01 am)
Quote:
I would now like to move things such as AI over to C++ for performance.
Is your AI script causing a bottleneck? It is possible to have around 400 units on screen and fighting (pistols/rifles are best - fewer effects) and still stay above 16 FPS on moderate hardware - I've done it with these scripts. Admittedly, these scripts are very rudimentary - find enemy, attack enemy, when enemy dies, find enemy. I honestly don't remember, but they might seek health and ammo. I had that working but I don't remember if it got into this version.
#9
Daniel, thanks for the explanation on the smart pointers, handy to know. Can't seem to get them working right though in this instance so I'll just stick to using a normal pointer, seems to do the job. Also, didn't see any AIPlayer object type anywhere in objectTypes.
@Richard, you're right, it's not much of a bottleneck, but every bit helps. with about 100 AI players running, I'm noticing about 5-7 ish fps improvement using c++ instead of scripts, if my tests were accurate (unlikely haha). But I'm also doing it just to practice my c++ skills so that I'll eventually be able to do bigger and better things.
01/02/2015 (11:59 pm)
Thanks Timmy, yeah getTypeMask is working nicely.Daniel, thanks for the explanation on the smart pointers, handy to know. Can't seem to get them working right though in this instance so I'll just stick to using a normal pointer, seems to do the job. Also, didn't see any AIPlayer object type anywhere in objectTypes.
@Richard, you're right, it's not much of a bottleneck, but every bit helps. with about 100 AI players running, I'm noticing about 5-7 ish fps improvement using c++ instead of scripts, if my tests were accurate (unlikely haha). But I'm also doing it just to practice my c++ skills so that I'll eventually be able to do bigger and better things.
Torque Owner Daniel Buckmaster
T3D Steering Committee
In this case, usually in C++ objects' typemasks are used instead of testing isMemberOfClass. Try if(player->getObjectType() & PlayerObjectType).
A general tip - in C++, don't compare strings with == and !=. You're comparing pointers, not the string contents. use dStrcmp, or StringTableEntry strings, which *should* be comparable using ==.
Also, your distance function looks pretty fine - just a couple of capitalisation errors, and instead of a SceneObject, the parameter should be a SceneObject*.
Yes, a pointer is kind of like the ID you get in torquescript, except it's an actual memory location, not a made-up number. However, since you have a pointer to a SceneObject, you can only call methods on it that exist in the SceneObject class, or any classes that SceneObject inherits from (like SimObject, ConsoleObject and others). Applying damage exists in the ShapeBase class, which is a descendent of the SceneObject class. What you need to do in this case is check the object's typemask to make sure it's actually a ShapeBase (use ShapeBaseObjectType, I think), and then use a dynamic_cast to get the pointer into the correct type to perform ShapeBase method calls on.
I know that's a lot, but I'm pushed for time - hope it helps somewhat!