T3D 1.2 (and earlier) - AIPlayers move without animating **Possible Fix included
by Guy Allard · in Torque 3D Professional · 07/11/2012 (6:33 am) · 23 replies
Build: T3D 1.2
Platform: vista
Target: game
Issues:
There are numerous posts on the forum where people describe AIPlayers moving without animating (gliding). This has been an issue with all versions of T3D, and has been logged as resolved, but I'm seeing it a lot more frequently in T3D 1.2.
The issue is random, sometimes they will play the correct movement animation, sometimes not.
I am experiencing this with an unmodified Player/AIPlayer so the issue is not related to any modifications to those classes.
Steps to repeat:
Launch the game, using the emptyRoom.mis
Spawn an AIPlayer.
Make it move somewhere.
If it animates correctly, delete it and repeat. Eventually one will fail to animate and slide along the ground.
Notes:
Looking into this, I have found that when the AIPlayer slides, it is due to pickActionAnimation returning PlayerData::RootAnim because the mContactTimer for the AIPlayer is >= sContactTickTime. This should not be true if the player is on the ground.
Digging further, the contactTimer is updated in updateMove() which is called from processTick.
Debugging shows that the sliding AIPlayers are not calling updateMove from processTick, and so are not correctly determining that they are on the ground.
Digging some more, the reason that updateMove is not being called, is that for the AIPlayers, delta.warpTicks is almost always non-zero. This is true even for AIPlayers which are stationary. in processTick, if delta.warpTicks > 0, some warping is performed instead of calling updateMove.
To confirm this yourself, add the following to Player::processTick, right after the Parent::processTick(move) and before the if(delta.warpTick..............
So it looks like something is screwed up down in the bowels of the player warping code when it's being used by an AIPlayer.
Also, I have noticed that with a correctly animating AIPlayer, if an impulse is applied, lifting him into the air at the same time as he's moving horizontally, he continues to play the movement animation while in the air. He shouldn't, as if he's in the air, pickActionAnimation should return RootAnim. This issue is again due to what I've discussed above.
It seems to be that the only reason that AIPlayers behave correctly most of the time is pure chance - at some point their warpTicks were 0 and updateMove got called while they were on a surface.
***Edit***
See post #17 for a workaround
Platform: vista
Target: game
Issues:
There are numerous posts on the forum where people describe AIPlayers moving without animating (gliding). This has been an issue with all versions of T3D, and has been logged as resolved, but I'm seeing it a lot more frequently in T3D 1.2.
The issue is random, sometimes they will play the correct movement animation, sometimes not.
I am experiencing this with an unmodified Player/AIPlayer so the issue is not related to any modifications to those classes.
Steps to repeat:
Launch the game, using the emptyRoom.mis
Spawn an AIPlayer.
Make it move somewhere.
If it animates correctly, delete it and repeat. Eventually one will fail to animate and slide along the ground.
Notes:
Looking into this, I have found that when the AIPlayer slides, it is due to pickActionAnimation returning PlayerData::RootAnim because the mContactTimer for the AIPlayer is >= sContactTickTime. This should not be true if the player is on the ground.
Digging further, the contactTimer is updated in updateMove() which is called from processTick.
Debugging shows that the sliding AIPlayers are not calling updateMove from processTick, and so are not correctly determining that they are on the ground.
Digging some more, the reason that updateMove is not being called, is that for the AIPlayers, delta.warpTicks is almost always non-zero. This is true even for AIPlayers which are stationary. in processTick, if delta.warpTicks > 0, some warping is performed instead of calling updateMove.
To confirm this yourself, add the following to Player::processTick, right after the Parent::processTick(move) and before the if(delta.warpTick..............
if(mIsAiControlled && !isServerObject())
Con::printf("warpTicks: %d", delta.warpTicks);Then run the game with an AIPlayer and look at the console. The AIPlayer is in a continual state of warp (cool).So it looks like something is screwed up down in the bowels of the player warping code when it's being used by an AIPlayer.
Also, I have noticed that with a correctly animating AIPlayer, if an impulse is applied, lifting him into the air at the same time as he's moving horizontally, he continues to play the movement animation while in the air. He shouldn't, as if he's in the air, pickActionAnimation should return RootAnim. This issue is again due to what I've discussed above.
It seems to be that the only reason that AIPlayers behave correctly most of the time is pure chance - at some point their warpTicks were 0 and updateMove got called while they were on a surface.
***Edit***
See post #17 for a workaround
About the author
Recent Threads
#2
old one is celeron 2.26 with 512mb ram and 64mb built in Vram.
also i have tested it in other pc.
on those pc,it happens when large number of resource is using by other services.
so my conclusion is "t3d's player animation system is resource hungry".
nice and hard catch.
thanks.
i will test it with my old one.but that is now packed up.
07/11/2012 (7:30 am)
it(gliding) always happened to my other pc.hardly it plays run animation.the only difference between 2 pc is configuration.old one is celeron 2.26 with 512mb ram and 64mb built in Vram.
also i have tested it in other pc.
on those pc,it happens when large number of resource is using by other services.
so my conclusion is "t3d's player animation system is resource hungry".
nice and hard catch.
thanks.
i will test it with my old one.but that is now packed up.
#3
Back in TGE, I merged Player and AIPlayer, and ran into lots of networking issues, especially when the player's Player was under AI control. I seem to remember networking everything like aim and move destinations, and calling getAIMove on the client as well.Not sure if it was this particular issue, or whether I just thought it would result in less net traffic :P.
07/11/2012 (7:58 am)
Quote:The AIPlayer is in a continual state of warpI don't deeply understand Torque's networking (still!), but might this have something to do with the fact that all the moves for the AI are generated server-side? The client-side objects have no idea what's going on on the server, so they have no choice but to wait for updates and warp their ghosts accordingly.
Back in TGE, I merged Player and AIPlayer, and ran into lots of networking issues, especially when the player's Player was under AI control. I seem to remember networking everything like aim and move destinations, and calling getAIMove on the client as well.Not sure if it was this particular issue, or whether I just thought it would result in less net traffic :P.
#4
07/11/2012 (10:13 am)
updateMove() is called,most likely your collision detection fails.
#5
My machine i'm seeing this on is also not high spec, and I'm pretty sure that the core of this issue is in someway related to throughput of something (vague lol).
@Ivan,
When the AIPlayers are gliding, on the client their updateMove is not getting called and so their contactTimer is not getting reset. This is because, at that time this is happening, their delta.warpTicks > 0, and that stops updateMove from being called from within processTick.
if you look in processTick, there's a part that basically states:
07/11/2012 (10:23 am)
@Ahsan,My machine i'm seeing this on is also not high spec, and I'm pretty sure that the core of this issue is in someway related to throughput of something (vague lol).
@Ivan,
When the AIPlayers are gliding, on the client their updateMove is not getting called and so their contactTimer is not getting reset. This is because, at that time this is happening, their delta.warpTicks > 0, and that stops updateMove from being called from within processTick.
if you look in processTick, there's a part that basically states:
if(delta.warpTicks > 0) {
do some warp stuff;
}
else {
do some other stuff including updateMove(move);
}
#6
Also, Guy, just as a test, increase the weight to around 300 and the friction to 1.5 and see if you notice any change. (thinking about the contactTimer here)
07/11/2012 (6:01 pm)
I wonder if this is also related to the "have to setTransform at Ai's position to have them update their pose" issue.Also, Guy, just as a test, increase the weight to around 300 and the friction to 1.5 and see if you notice any change. (thinking about the contactTimer here)
#7
btw I posted the solution for the gliding aiPlayers a few months ago.
Use getControllingClient() to split pickActionAnimation().
For AIs just ignore the contact timer check and pick an animation based on their velocity.I've never seen gliding players after that.
if(mVelocity.len() < 0.1f)
action = PlayerData::RootAnim;
else
action = PlayerData::RunForwardAnim;
07/11/2012 (11:43 pm)
Guy, I thought that you are hitting a different issue.btw I posted the solution for the gliding aiPlayers a few months ago.
Use getControllingClient() to split pickActionAnimation().
For AIs just ignore the contact timer check and pick an animation based on their velocity.I've never seen gliding players after that.
if(mVelocity.len() < 0.1f)
action = PlayerData::RootAnim;
else
action = PlayerData::RunForwardAnim;
#8
That solution will almost work, but there are a couple of issues with it - updateMove is also used to determine if the player is jumping, falling, jetting, swimming and uses those later on in the animation selection. Because updateMove isn't getting called reliably for the ghost, the AIPlayer may not pick the correct action in those circumstances.
@Steve, possibly, there's a lot of stuff done with pose in updateMove. As for the mass, I'll try that later, but today only about 1 in 30 of the AIPlayers are gliding. Maybe it depends on the weather.........
07/12/2012 (1:17 am)
@Ivan, I searched for your post but couldn't find it.That solution will almost work, but there are a couple of issues with it - updateMove is also used to determine if the player is jumping, falling, jetting, swimming and uses those later on in the animation selection. Because updateMove isn't getting called reliably for the ghost, the AIPlayer may not pick the correct action in those circumstances.
@Steve, possibly, there's a lot of stuff done with pose in updateMove. As for the mass, I'll try that later, but today only about 1 in 30 of the AIPlayers are gliding. Maybe it depends on the weather.........
#9
AIPlayer is stationary in the level.
The position and velocity of the server and ghost version are identical.
For some reason, the ghost has delta.warpTicks = 3 (sMaxWarpTicks), which means that the ghost thinks that it is not in sync and should warp.
Surely, when ghost and server version are in sync, delta.warpTicks should be 0, as no warping is needed. Then the updateMove and pickActionAnimation would function correctly.
This code in Player::unpackUpdate makes me uncomfortable:
if the average velocities of the client and server objects are 0 (i.e. they are identical and the object isn't moving), delta.warpTicks gets set to sMaxWarpTicks, indicating that it needs to warp. That doesn't make sense.
07/12/2012 (2:36 am)
Now this is bugging me:AIPlayer is stationary in the level.
The position and velocity of the server and ghost version are identical.
For some reason, the ghost has delta.warpTicks = 3 (sMaxWarpTicks), which means that the ghost thinks that it is not in sync and should warp.
Surely, when ghost and server version are in sync, delta.warpTicks should be 0, as no warping is needed. Then the updateMove and pickActionAnimation would function correctly.
This code in Player::unpackUpdate makes me uncomfortable:
// Determine number of ticks to warp based on the average
// of the client and server velocities.
delta.warpOffset = pos - delta.pos;
F32 as = (speed + mVelocity.len()) * 0.5f * TickSec;
F32 dt = (as > 0.00001f) ? delta.warpOffset.len() / as: sMaxWarpTicks;
delta.warpTicks = (S32)((dt > sMinWarpTicks) ? getMax(mFloor(dt + 0.5f), 1.0f) : 0.0f);if the average velocities of the client and server objects are 0 (i.e. they are identical and the object isn't moving), delta.warpTicks gets set to sMaxWarpTicks, indicating that it needs to warp. That doesn't make sense.
#10
This AIPlayer animated OK.
Notice that in the beginning, while he's falling, updateMove is being called, and because he's not touching the ground contactTimer is being incremented. At some point, a network update causes his warpTicks to increase, and from then on he doesn't call updateMove any more. Also note that he has no idea that he's reached the ground, but he animates correctly because his contactTimer did not reach 10 (the value at which pickActionAnimation considers it to be 'off ground')
This one glided along the ground after being given a move destination. This one also never detected that he hit the ground on the client, and before he did, his warpTicks became 3 and he stopped processing updateMove from that point onwards. His contactTimer reached 17, and because he no longer did updateMove, it didn't get reset when he was on the ground. pickActionAnimation returns NullMove if the contactTimer >= 10, so when he was given a moveDestination, he glided.
Not shown here, is that at some point much later, there was a delay of 4 ticks between receiving network packets. At that point, his warpTicks were able to decrease to 0, causing him to do an updateMove This made him find a runSurface, reset his contactTimer to 0 and then he started animating correctly.
I guess an important question to ask is:
Is updateMove supposed to be called for the AIPlayer ghost each tick?
The animation stuff and the pose stuff assume that it is, but in reality it doesn't get called very often for AIPlayer ghosts because it is usually blocked by delta.warpTicks being > 0.
07/12/2012 (4:18 am)
Here's a couple of images of some debug output for an AIPlayer that animated OK, and one that didn't. Both were spawned just above the ground plane. The output relates to what is happening client side.
This AIPlayer animated OK. Notice that in the beginning, while he's falling, updateMove is being called, and because he's not touching the ground contactTimer is being incremented. At some point, a network update causes his warpTicks to increase, and from then on he doesn't call updateMove any more. Also note that he has no idea that he's reached the ground, but he animates correctly because his contactTimer did not reach 10 (the value at which pickActionAnimation considers it to be 'off ground')
This one glided along the ground after being given a move destination. This one also never detected that he hit the ground on the client, and before he did, his warpTicks became 3 and he stopped processing updateMove from that point onwards. His contactTimer reached 17, and because he no longer did updateMove, it didn't get reset when he was on the ground. pickActionAnimation returns NullMove if the contactTimer >= 10, so when he was given a moveDestination, he glided.Not shown here, is that at some point much later, there was a delay of 4 ticks between receiving network packets. At that point, his warpTicks were able to decrease to 0, causing him to do an updateMove This made him find a runSurface, reset his contactTimer to 0 and then he started animating correctly.
I guess an important question to ask is:
Is updateMove supposed to be called for the AIPlayer ghost each tick?
The animation stuff and the pose stuff assume that it is, but in reality it doesn't get called very often for AIPlayer ghosts because it is usually blocked by delta.warpTicks being > 0.
#11
There's a lot of code in updateMove dealing with pose selection, they aren't transferred over the network.
If the ghost isn't running updateMove, then it isn't picking the correct pose.
The reason that calling setTransform gets things going again is that Player::setTransform also sets the noWarpMask maskbit, which causes the ghosts warpTicks to be reset to zero during the next unpack, thereby allowing it to call updateMove again and set the correct pose.
@Daniel, the moves for the AIPlayer are generated server side, yes, but they are sent to the ghost during its packUpdate (delta.move.pack(stream);), so the ghost has full access to the move that was generated by the AIPlayer getAIMove code. Unfortunately, that info gets handled by, you guessed it, updateMove, which we all know by now, doesn't get called reliably on the ghost.....
07/12/2012 (8:45 am)
@Steve, relating to the issue of the pose update, I'm pretty sure that these are due to the same root problem.There's a lot of code in updateMove dealing with pose selection, they aren't transferred over the network.
If the ghost isn't running updateMove, then it isn't picking the correct pose.
The reason that calling setTransform gets things going again is that Player::setTransform also sets the noWarpMask maskbit, which causes the ghosts warpTicks to be reset to zero during the next unpack, thereby allowing it to call updateMove again and set the correct pose.
@Daniel, the moves for the AIPlayer are generated server side, yes, but they are sent to the ghost during its packUpdate (delta.move.pack(stream);), so the ghost has full access to the move that was generated by the AIPlayer getAIMove code. Unfortunately, that info gets handled by, you guessed it, updateMove, which we all know by now, doesn't get called reliably on the ghost.....
#12
I made the following alterations to player.cpp
Having said that, I was always intending to mod a system like Ivan mentions above, where animation is picked depending on velocity (and pose) but haven't gotten around to it yet.
07/12/2012 (9:10 am)
Disclaimer: I'm only using AiPlayers and no real Players ... and don't really know what I'm doing with cpp ... (and I'm still using 1.1F) ... so have no idea if this is helpful or cobblers ...I made the following alterations to player.cpp
// Chooses new action animations every n ticks. static const F32 sNewAnimationTickTime = 8.0f;//4 original; yorks static const F32 sMountPendingTickWait = 15.0f * F32(TickMs); // Number of ticks before we pick non-contact animations static const S32 sContactTickTime = 2048;//12 original; yorks
Having said that, I was always intending to mod a system like Ivan mentions above, where animation is picked depending on velocity (and pose) but haven't gotten around to it yet.
#13
It won't help with the poses, it won't help with AIPlayers running in the air, it won't help with them not playing the jet anim correctly, or with not picking the swimming animation correctly.
07/12/2012 (9:17 am)
@steve, yeah, that will most likely stop them gliding in 99.9% of cases (until you hit the one that somehow manages to increase its contactTimer to 2048 or more - unlikely but not impossible). But, it's there for a reason, which is to decide if the object is or is not in contact with a surface. By setting it to 2048, the player would have to not be in contact with a surface for 65 seconds before it is registered that he's not in contact with a surface.It won't help with the poses, it won't help with AIPlayers running in the air, it won't help with them not playing the jet anim correctly, or with not picking the swimming animation correctly.
#14
07/12/2012 (11:43 am)
There's serious issues with this, time after time I've pulled out my hair (none left now!) in trying to fix AI players from gliding, and even though the animations are in script they don't seem to load up, you need to add them manually in the shape editor.
#15
The issues so far are:
Gliding when moving,
Running when they should be swimming,
Running when airbone,
Not playing jet anim when jetting,
Not assuming the correct pose
Interestingly, in this thread from 2002, Tim Gift, who worked on the original code stated that he wasn't sure if the interpolation math was correct (the code is virtually unchanged to date), and that he intended to change the player interpolation code to bring it in line with the vehicle interpolation code (which was never done).
07/12/2012 (11:58 am)
All of the issues I'm seeing can be traced back to the warpTicks being set in the interpolation part of unpackUpdate preventing updateMove from being called on the ghost.The issues so far are:
Gliding when moving,
Running when they should be swimming,
Running when airbone,
Not playing jet anim when jetting,
Not assuming the correct pose
Interestingly, in this thread from 2002, Tim Gift, who worked on the original code stated that he wasn't sure if the interpolation math was correct (the code is virtually unchanged to date), and that he intended to change the player interpolation code to bring it in line with the vehicle interpolation code (which was never done).
#16
07/12/2012 (12:10 pm)
Oh yeah, I should have mentioned that I'd scrapped jumping ... obviously.
#17
If we assume that the ghost is working as intended, and isn't supposed to be regularly calling updateMove, and that the warp code is behaving as it should, then the reason that the AIPlayer is not choosing the correct animation or pose is because it has insufficient state information available to reliably pick the correct action.
In Player::packData, mFalling is networked. This flag is used by the ghost in pickActionAnimation, and I've never seen an AIPlayer fail to play the fall animation correctly. So at some point, someone decided that this was a good idea. If we do the same with the other flags that the ghost needs, we can ensure that the correct animations are chosen.
in Player::packUpdate,
after stream->writeFlag(mFalling);
add:
then in Player::unpackUpdate,
after mFalling = stream->readFlag();
add:
For me, this fixes ALL of the issues I mentioned in thread #15.
There is of course a trade-off here, we're adding 6 extra bits to the MoveMask portion of the player networking, which is called frequently. 6 extra bits is not a lot compared to the combined ShapeBase + Player network useage. For me, being able to have lots of AIPlayers animating correctly is more important than having a few more AIPlayers doing weird things.
You can of course cut parts of the code above out if you're not using those features. If you don't use jetting, comment out the lines above that refer to it and save yourself an extra bit per player.
07/13/2012 (11:28 am)
Here's a possible fix for all of the issues I mentioned above.If we assume that the ghost is working as intended, and isn't supposed to be regularly calling updateMove, and that the warp code is behaving as it should, then the reason that the AIPlayer is not choosing the correct animation or pose is because it has insufficient state information available to reliably pick the correct action.
In Player::packData, mFalling is networked. This flag is used by the ghost in pickActionAnimation, and I've never seen an AIPlayer fail to play the fall animation correctly. So at some point, someone decided that this was a good idea. If we do the same with the other flags that the ghost needs, we can ensure that the correct animations are chosen.
in Player::packUpdate,
after stream->writeFlag(mFalling);
add:
// GUY ghost anim fix >> stream->writeFlag(mContactTimer == 0); stream->writeFlag(mSwimming); stream->writeFlag(mJetting); stream->writeInt(mPose, NumPoseBits); // GUY ghost anim fix <<
then in Player::unpackUpdate,
after mFalling = stream->readFlag();
add:
// GUY ghost anim fix >> if(stream->readFlag()) mContactTimer = 0; else mContactTimer ++; mSwimming = stream->readFlag(); mJetting = stream->readFlag(); Pose newPose = (Pose)(stream->readInt(NumPoseBits)); setPose(newPose); // GUY ghost anim fix <<
For me, this fixes ALL of the issues I mentioned in thread #15.
There is of course a trade-off here, we're adding 6 extra bits to the MoveMask portion of the player networking, which is called frequently. 6 extra bits is not a lot compared to the combined ShapeBase + Player network useage. For me, being able to have lots of AIPlayers animating correctly is more important than having a few more AIPlayers doing weird things.
You can of course cut parts of the code above out if you're not using those features. If you don't use jetting, comment out the lines above that refer to it and save yourself an extra bit per player.
#18
- Dave
11/21/2012 (8:16 am)
I've added this thread as a reference for the following ticket on GitHub: Client player crouching is delayed on host I think they are related.- Dave
#19
You can send the pose when it is changed, not all the time.
Sending a contact timer flag is a bad idea. You have the velocity transmitted, you have the collision on the server. That means your simulation differ on client and server, your networked players do not update their timers correctly.
11/26/2012 (1:48 am)
Adding swimming and jetting is fine.You can send the pose when it is changed, not all the time.
Sending a contact timer flag is a bad idea. You have the velocity transmitted, you have the collision on the server. That means your simulation differ on client and server, your networked players do not update their timers correctly.
#20
That would be true IF the client ghost was being simulated, but most of the time it isn't. Any time the ghost warpTicks > 0, then the client simulation for the ghost is bypassed, and it is just set to the server position / velocity.
11/28/2012 (1:48 am)
@Ivan,That would be true IF the client ghost was being simulated, but most of the time it isn't. Any time the ghost warpTicks > 0, then the client simulation for the ghost is bypassed, and it is just set to the server position / velocity.
Torque Owner Lukas Joergensen
WinterLeaf Entertainment