Problems with animations
by Diego Perez · in Torque 3D Professional · 04/29/2010 (9:27 am) · 17 replies
Dear all,
I am experiencing some problems while trying to play different animations. I am using this resource (www.dexsoft-games.com/models/elite_trooper.html) for my new character and I have followed the instructions Picasso gave in this post (www.torquepowered.com/community/forums/viewthread/73223).
In short, I copied Player class and renamed it as a SoldierPlayer, so I can modify it to put my own ActionAnimationList. I know that thread was for TGE and I am using T3D, but I was hoping that the solution could be similar.
My 'pickActionAnimation()' method looks like this:
This is intended to work in the following way:
I am using a flag variable that stores different events that can happen to the player (at this stage, this implementation is used to test the system and wonder how it works). First time this method is called, the action selected is SoldierPlayerData::WalkNoRifleAnim, because no flags are on. When I shoot at the bot, one of the flags LeftHitEvent or RightHitEvent is set to on (depending on where the bot was hit) and consequently, the action selected is a different one (SoldierPlayerData::HitLeftAnim or SoldierPlayerData::HitRightAnim resp.).
If I debug this, I can see that the setActionThread() call at the last line of the method is called with the correct action value, so as far as I understand, this is working ok.
What it is not working is what it is actually playing. The first animation (WalkNoRifle) is playing correctly. However, the second animation that enters (whichever hit animation) is never played.
I have tried every possible combination of parameters for setActionThread() function without any success. What could I be missing?
Any hint would be really appreciated. Thank you in advance for your help.
Diego.
I am experiencing some problems while trying to play different animations. I am using this resource (www.dexsoft-games.com/models/elite_trooper.html) for my new character and I have followed the instructions Picasso gave in this post (www.torquepowered.com/community/forums/viewthread/73223).
In short, I copied Player class and renamed it as a SoldierPlayer, so I can modify it to put my own ActionAnimationList. I know that thread was for TGE and I am using T3D, but I was hoping that the solution could be similar.
My 'pickActionAnimation()' method looks like this:
void SoldierPlayer::pickActionAnimation()
{
// Only select animations in our normal move state.
if (mState != MoveState || mDamageState != Enabled)
return;
if (isMounted())
{
// Go into root position unless something was set explicitly from a script.
if (mActionAnimation.action != SoldierPlayerData::Idle2StandAnim &&
mActionAnimation.action < SoldierPlayerData::NumTableActionAnims)
setActionThread(SoldierPlayerData::Idle2StandAnim,true,false,false);
return;
}
bool forward = true;
bool fsp = true;
U32 action = SoldierPlayerData::WalkNoRifleAnim;
if(mState == MoveState)
{
if(m_eventFlags & LeftHitEvent)
{
action = SoldierPlayerData::HitLeftAnim;
}
else if(m_eventFlags & RightHitEvent)
{
action = SoldierPlayerData::HitRightAnim;
}
else
{
//No events, walk
action = SoldierPlayerData::WalkNoRifleAnim;
}
}
setActionThread(action,forward,false,false,fsp);
}This is intended to work in the following way:
I am using a flag variable that stores different events that can happen to the player (at this stage, this implementation is used to test the system and wonder how it works). First time this method is called, the action selected is SoldierPlayerData::WalkNoRifleAnim, because no flags are on. When I shoot at the bot, one of the flags LeftHitEvent or RightHitEvent is set to on (depending on where the bot was hit) and consequently, the action selected is a different one (SoldierPlayerData::HitLeftAnim or SoldierPlayerData::HitRightAnim resp.).
If I debug this, I can see that the setActionThread() call at the last line of the method is called with the correct action value, so as far as I understand, this is working ok.
What it is not working is what it is actually playing. The first animation (WalkNoRifle) is playing correctly. However, the second animation that enters (whichever hit animation) is never played.
I have tried every possible combination of parameters for setActionThread() function without any success. What could I be missing?
Any hint would be really appreciated. Thank you in advance for your help.
Diego.
About the author
#2
Many thanks for your response. I debugged what you suggested and everything looks to work "fine". When setActionThread is called, the actionAnimation fields are assigned properly and, after that, setSequence is called. I have even looked at this method and it seems to apply correctly the sequence number to the thread of the shape...
About the preload() method, it also seems to be ok. It reads the sequence for every animation properly. These are my ActionAnimationList structures:
... and ...
So... still with the problem :(
Thanks,
Diego.
05/05/2010 (8:12 am)
Hi Chris,Many thanks for your response. I debugged what you suggested and everything looks to work "fine". When setActionThread is called, the actionAnimation fields are assigned properly and, after that, setSequence is called. I have even looked at this method and it seems to apply correctly the sequence number to the thread of the shape...
About the preload() method, it also seems to be ok. It reads the sequence for every animation properly. These are my ActionAnimationList structures:
enum {
// *** WARNING ***
// These enum values are used to index the ActionAnimationList
// array instantiated in player.cc
// The first several are selected in the move state based on velocity
Idle2StandAnim,
Idle1StandAnim,
IdleCrouchAimAnim,
IdleShootAnim,
WalkNoRifleAnim,
WalkCrouchRifleAnim,
RunStrafeLeftAnim,
RunRifle1NAnim,
RunRifle2NAnim,
JumpAnim,
FallAnim,
HitLeftAnim,
HitRightAnim,
DeathAnim,
//
NumMoveActionAnims = RunRifle2NAnim + 1,
NumTableActionAnims = DeathAnim + 1,
NumExtraActionAnims = 512 - NumTableActionAnims,
NumActionAnims = NumTableActionAnims + NumExtraActionAnims,
ActionAnimBits = 9,
NullAnimation = (1 << ActionAnimBits) - 1
};... and ...
// Action Animations:
SoldierPlayerData::ActionAnimationDef SoldierPlayerData::ActionAnimationList[NumTableActionAnims] =
{
// *** WARNING ***
// This array is indexed using the enum values defined in player.h
// Root is the default animation
{ "Idle_2Stand" }, // Idle2StandAnim,
{ "Idle_1Stand" }, // Idle1StandAnim,
{ "Idle_Crouch_Aim" }, // IdleCrouchAimAnim,
{ "Idle_Shoot" }, // IdleShootAnim,
// These are selected in the move state based on velocity
{ "Walk_NO_Rifle", { 0.0f, 1.0f, 0.0f } }, // WalkNoRifleAnim,
{ "Walk_Crouch_Rifle", { 0.0f, 1.0f, 0.0f } }, // WalkCrouchRifleAnim,
{ "Run_Strafe_Left", { -1.0f, 0.0f, 0.0f } }, // RunStrafeLeftAnim,
{ "Run_Rifle_1N", { 0.0f, 1.0f, 0.0f } }, // RunRifle1NAnim,
{ "Run_Rifle_2N", { 0.0f, 1.0f, 0.0f } }, // RunRifle1NAnim,
{ "Jump" }, //JumpAnim,
{ "Fall" }, //FallAnim,
{ "Hit_Left" }, //HitLeftAnim
{ "Hit_Right" }, //HitRightAnim
{ "Death" }, //DeathAnim,
};So... still with the problem :(
Thanks,
Diego.
#3
Have you confirmed that setActionThread is not being called again with a different sequence? You could try putting a Con::printf statement inside the function to print out the name of the sequence that is assigned:
Maybe some other part of the code is unexpectedly changing the thread afterwards? Doesn't the player class automatically choose an animation to play based on velocity?
05/05/2010 (4:20 pm)
OK. All good so far then.Have you confirmed that setActionThread is not being called again with a different sequence? You could try putting a Con::printf statement inside the function to print out the name of the sequence that is assigned:
Con::printf("setActionThread (%d): %s", action, anim.name);Maybe some other part of the code is unexpectedly changing the thread afterwards? Doesn't the player class automatically choose an animation to play based on velocity?
#4
That makes a lot of sense. I tried to print to console the following line:
In this way, I can be sure that the animations are trying to be played for my bot (and not for the player). In that case, its id is 3283 and what I get is the following:
setActionThread for 3283 (0): Idle_2Stand <--- This is at the very beginning.
setActionThread for 3283 (12): Hit_Right <--- This is when I hit him.
And that's all. The bot remains in the idle position and this method is never called again! About playing animations based on velocity, I have deactivated that part of the code (in pickActionAnimation()) to be sure that that this does not interfere with Hit animation in any case.
The only thing I can think about is that the animation is not being played because of the
parameters this function receives in both cases. I did something similar with these parameters, printing them to console, and the result is the following:
Parameters for Idle_2Stand: act: 0, fwd: 1, hold: 0, wait: 0, fsp: 0, forceSet: 0
setActionThread for 3283 (0): Idle_2Stand
Parameters for Hit_Right: act: 12, fwd: 1, hold: 0, wait: 1, fsp: 0, forceSet: 0
setActionThread for 3283 (12): Hit_Right
I have tried different combinations of parameters, although I understand that these are the correct ones, without any kind of luck...
Many thanks!
Diego.
05/06/2010 (2:40 am)
Hi Chris,That makes a lot of sense. I tried to print to console the following line:
Con::printf("setActionThread for %u (%d): %s", getId(), action, anim.name);In this way, I can be sure that the animations are trying to be played for my bot (and not for the player). In that case, its id is 3283 and what I get is the following:
setActionThread for 3283 (0): Idle_2Stand <--- This is at the very beginning.
setActionThread for 3283 (12): Hit_Right <--- This is when I hit him.
And that's all. The bot remains in the idle position and this method is never called again! About playing animations based on velocity, I have deactivated that part of the code (in pickActionAnimation()) to be sure that that this does not interfere with Hit animation in any case.
The only thing I can think about is that the animation is not being played because of the
parameters this function receives in both cases. I did something similar with these parameters, printing them to console, and the result is the following:
Parameters for Idle_2Stand: act: 0, fwd: 1, hold: 0, wait: 0, fsp: 0, forceSet: 0
setActionThread for 3283 (0): Idle_2Stand
Parameters for Hit_Right: act: 12, fwd: 1, hold: 0, wait: 1, fsp: 0, forceSet: 0
setActionThread for 3283 (12): Hit_Right
I have tried different combinations of parameters, although I understand that these are the correct ones, without any kind of luck...
Many thanks!
Diego.
#5
Do you mean the bot continues to play the idle animation? Or that he gets stuck in the root (non-animated) pose?
Just as a sanity check, do the HitLeft/Right animations play correctly in the shape editor?
Do you know whether you are calling setActionThread on the server or the client? Could you try adding:
After the call to setActionThread in pickActionAnimation().
05/06/2010 (11:49 am)
Quote:The bot remains in the idle position and this method is never called again!
Do you mean the bot continues to play the idle animation? Or that he gets stuck in the root (non-animated) pose?
Just as a sanity check, do the HitLeft/Right animations play correctly in the shape editor?
Do you know whether you are calling setActionThread on the server or the client? Could you try adding:
setMaskBits(ActionMask);
After the call to setActionThread in pickActionAnimation().
#6
The idle animation is playing before AND after the bot receives the shot. So, the animation continues playing without any change.
Yes, they play correctly in the shape editor.
And about adding the setMaskBits() function after the call to setActionThread... no luck :(
What is this supposed to do?
Many thanks again for your help.
Diego.
05/07/2010 (3:39 am)
You are right, maybe I was not clear enough. Quote:
Do you mean the bot continues to play the idle animation? Or that he gets stuck in the root (non-animated) pose?
The idle animation is playing before AND after the bot receives the shot. So, the animation continues playing without any change.
Quote:
Just as a sanity check, do the HitLeft/Right animations play correctly in the shape editor?
Yes, they play correctly in the shape editor.
Quote:
Just as a sanity check, do the HitLeft/Right animations play correctly in the shape editor?
And about adding the setMaskBits() function after the call to setActionThread... no luck :(
What is this supposed to do?
Many thanks again for your help.
Diego.
#7
I wondered whether setActionThread was only being called on the server, and the change was not being replicated to the client. Setting that bit should trigger a network update to the client. Was only a guess though => the original player code doesn't need to do this.
05/07/2010 (8:07 pm)
Quote:And about adding the setMaskBits() function after the call to setActionThread... no luck :(
What is this supposed to do?
I wondered whether setActionThread was only being called on the server, and the change was not being replicated to the client. Setting that bit should trigger a network update to the client. Was only a guess though => the original player code doesn't need to do this.
#8
05/08/2010 (6:07 pm)
Still no real clue what could be wrong here. Could you see if forcing the animation to play from script has any effect?%obj.playThread(0, "Hit_Left");
#9
Well, this is strange! Forcing to call playThread from script DOES work. I am going to try to find the differences between both cases to see what is happening underneath... But I thought that playThread was more appropriate for animating textures and setActionThread should be used for 'physics' animations, or at least that is what I read in other posts in the forums.
Another weird thing happens though: the hit animation moves the character back a pair of meters, what it is ok. But the actual collision remains in the same spot where the bot was before. Probably this is another different problem, however...
Many thanks,
Diego.
05/10/2010 (2:43 am)
Hi again!Well, this is strange! Forcing to call playThread from script DOES work. I am going to try to find the differences between both cases to see what is happening underneath... But I thought that playThread was more appropriate for animating textures and setActionThread should be used for 'physics' animations, or at least that is what I read in other posts in the forums.
Another weird thing happens though: the hit animation moves the character back a pair of meters, what it is ok. But the actual collision remains in the same spot where the bot was before. Probably this is another different problem, however...
Many thanks,
Diego.
#10
Also play your animations this way:
setActionThread(SoldierPlayerData::HitLeftAnim, true, false, true,false,true);
05/11/2010 (11:56 am)
Diego,try with fsp = false;Also play your animations this way:
setActionThread(SoldierPlayerData::HitLeftAnim, true, false, true,false,true);
#11
I am still trying to solve this without any success. I must be missing something that I can not see...
I have tried this and does not work. I have debugged the function:
...where the sequence is set properly. And also this one:
where the correct sequence is being updated. But it still does not play the animation I am trying to play!
I have also thought that it could be those animations that have a problem, but that is not the case apparently either. For instance, I have one animation for walking and another one for running, and I play them depending on certain circumstances.
The point is that both of them work BUT, if I start playing one of them, is then the other animation the one that does not work, even while I am seeing that the proper sequence is being set.
I don't know if this information can be useful.
Many thanks for your help,
Diego.
05/17/2010 (2:24 am)
Hi guys!I am still trying to solve this without any success. I must be missing something that I can not see...
Quote:
fsp = false;
setActionThread(SoldierPlayerData::HitLeftAnim, true, false, true,false,true);
I have tried this and does not work. I have debugged the function:
TSShapeInstance::setSequence(TSThread * thread, S32 seq, F32 pos){...}...where the sequence is set properly. And also this one:
TSThread::advancePos(F32 delta){...}where the correct sequence is being updated. But it still does not play the animation I am trying to play!
I have also thought that it could be those animations that have a problem, but that is not the case apparently either. For instance, I have one animation for walking and another one for running, and I play them depending on certain circumstances.
The point is that both of them work BUT, if I start playing one of them, is then the other animation the one that does not work, even while I am seeing that the proper sequence is being set.
I don't know if this information can be useful.
Many thanks for your help,
Diego.
#12
I was trying to understand more deeply how the 'sequence' variable works in tsThread when I noticed that other entity was modifying this value after I called mShapeInstance->setSequence(...) with the animation I wanted to play. Digging a bit in this, I have found out that I have two objects (id:3289 and id:3327, its ghost), and it is the latter one who is modifying the sequence (and hence the animation).
I am choosing one animation or other depending on some events, that I store in a variable inside SoldierPlayer. But this changes are made only in the 3289 bot, and NOT in its ghost. So when I call pickActionAnimation, this events are not recorded for the ghost and therefore the same animation is not played. (It plays IdleAnimation, and I think that is the reason why the animation I set before wasn't being played)
I have tried to include this to see what happens:
And now the animations are played as I want. But, this is obviously not a viable solution.
My knowledge about ghosts, and how Torque works with them, is pretty limited, but it seems the problem is related with that.
Thanks again,
Diego.
05/17/2010 (9:05 am)
Ok, I have found something quite interesting...I was trying to understand more deeply how the 'sequence' variable works in tsThread when I noticed that other entity was modifying this value after I called mShapeInstance->setSequence(...) with the animation I wanted to play. Digging a bit in this, I have found out that I have two objects (id:3289 and id:3327, its ghost), and it is the latter one who is modifying the sequence (and hence the animation).
I am choosing one animation or other depending on some events, that I store in a variable inside SoldierPlayer. But this changes are made only in the 3289 bot, and NOT in its ghost. So when I call pickActionAnimation, this events are not recorded for the ghost and therefore the same animation is not played. (It plays IdleAnimation, and I think that is the reason why the animation I set before wasn't being played)
I have tried to include this to see what happens:
if(myId == 3327)
{
SimObject* so = Sim::findObject(3289);
SoldierPlayer* sp = dynamic_cast<SoldierPlayer*>(so);
m_eventFlags = sp->getEventFlags();
}And now the animations are played as I want. But, this is obviously not a viable solution.
My knowledge about ghosts, and how Torque works with them, is pretty limited, but it seems the problem is related with that.
Thanks again,
Diego.
#13
So, anyway, as you saw in that post you linked to, the game calls updateActionThread every tick (for server and client ghosts) which in turn calls pickActionAnimation when it needs a new animation to play. Obviously you've replaced the normal animation picker code with some more specific stuff, but that all looks fine.
It seems to me that the problem lies in how you are setting, networking, or getting the data contained in m_eventFlags. Your last bit of code suggests this is all in singleplayer, because that's the only way you could be performing an action on the ghost object (3327 in this case) and then go find the server object with Sim::findObject. While, in theory, you could function *in singleplayer* using a more dynamic version of this, ie by doing the appropriate lookup to find out what the server object ID of any given ghost actually is, it's really not a great solution. Instead, you want to get the data from m_eventFlags networked to the ghosts automatically.
Every server object exists once, on the server. It "ghosts" itself to each client, making a copy of itself on that local client which does not do things like create move events, handle damage, or do authoritative physics. Ghosts basically just do a sort of predictive interpolation to keep them moving smoothly between its server->client update packets. They also animate based on whatever data is available in the ghost copy of (in this case) a Player Class Object.
Because a ghost object is not the server object, nor is it actually a real copy of the server object, everything it knows about its server copy is whatever the object decided to stream to it in the previous packet. At this point you should go ahead and look at SoldierPlayer::packUpdate. This function sends an update packet to ghosts of itself. Unlike writePacketData this update goes to everyone scoping this object, not just its control client (as a bot, it has no controlling client or object).
Anyway, you'll see a lot of conditions in here where the server object may or may not decide to send certain types of data to the client ghost. Generally this involves a maskBit being set. This is the correct way to determine whether to update a value because it works with lost packets. It's too confusing to get into, but basically if you send data because (mask & ActionMask) instead of, say (m_animUpdate == true) and that packet doesn't make it through, the server will know that it needs to try this again next time it sends an update.
I'm assuming the events that alter m_eventFlags generally occur on the server. If they already were happening on the client this wouldn't matter. So, what you want to do is send an update on m_eventFlags anytime it changes. In theory this might be the time to add a new maskBit, but for the sake of simplicity let's just piggyback on ActionMask for now. From now on, anytime you change m_eventFlags (I assume you use a function called SoldierPlayer::setEventFlags so that would be a good place) you should also call:
At least it will once you add some code there. Go to SoldierPlayer::packUpdate, near the top for simplicity. Right after "U32 retMask = ......" go ahead and add this:
That'll send the contents of m_eventFlags to the client. Now, it still needs code to read it, otherwise all we've done is thrown the whole outgoing packet out of whack with what it expects to read. Never add a stream->write* event without a corresponding read, or a read without a write! Head down to SoldierPlayer::unpackUpdate ... this is the mirror image of the previous function, reading the packets on the client side.
Right after Parent::unpackUpdate(con,stream); add this:
And that should do it. In theory. Obvious I couldn't test this specific code, but that's how I send data through packUpdate. Hope that helps.
05/17/2010 (2:45 pm)
I'm not 100% how you're setting your animations. I mean, it's clear that you've introduced a new mask-type variable called m_eventFlags and a series of functions for setting and getting this value, but how/when these flags are being set is a bit unclear.So, anyway, as you saw in that post you linked to, the game calls updateActionThread every tick (for server and client ghosts) which in turn calls pickActionAnimation when it needs a new animation to play. Obviously you've replaced the normal animation picker code with some more specific stuff, but that all looks fine.
It seems to me that the problem lies in how you are setting, networking, or getting the data contained in m_eventFlags. Your last bit of code suggests this is all in singleplayer, because that's the only way you could be performing an action on the ghost object (3327 in this case) and then go find the server object with Sim::findObject. While, in theory, you could function *in singleplayer* using a more dynamic version of this, ie by doing the appropriate lookup to find out what the server object ID of any given ghost actually is, it's really not a great solution. Instead, you want to get the data from m_eventFlags networked to the ghosts automatically.
Every server object exists once, on the server. It "ghosts" itself to each client, making a copy of itself on that local client which does not do things like create move events, handle damage, or do authoritative physics. Ghosts basically just do a sort of predictive interpolation to keep them moving smoothly between its server->client update packets. They also animate based on whatever data is available in the ghost copy of (in this case) a Player Class Object.
Because a ghost object is not the server object, nor is it actually a real copy of the server object, everything it knows about its server copy is whatever the object decided to stream to it in the previous packet. At this point you should go ahead and look at SoldierPlayer::packUpdate. This function sends an update packet to ghosts of itself. Unlike writePacketData this update goes to everyone scoping this object, not just its control client (as a bot, it has no controlling client or object).
Anyway, you'll see a lot of conditions in here where the server object may or may not decide to send certain types of data to the client ghost. Generally this involves a maskBit being set. This is the correct way to determine whether to update a value because it works with lost packets. It's too confusing to get into, but basically if you send data because (mask & ActionMask) instead of, say (m_animUpdate == true) and that packet doesn't make it through, the server will know that it needs to try this again next time it sends an update.
I'm assuming the events that alter m_eventFlags generally occur on the server. If they already were happening on the client this wouldn't matter. So, what you want to do is send an update on m_eventFlags anytime it changes. In theory this might be the time to add a new maskBit, but for the sake of simplicity let's just piggyback on ActionMask for now. From now on, anytime you change m_eventFlags (I assume you use a function called SoldierPlayer::setEventFlags so that would be a good place) you should also call:
setMaskBits(ActionMask);... this will let packUpdate know that m_eventFlags needs to be sent.
At least it will once you add some code there. Go to SoldierPlayer::packUpdate, near the top for simplicity. Right after "U32 retMask = ......" go ahead and add this:
if(stream->writeFlag(mask & ActionMask))
{
stream->write(getEventFlags());
}That'll send the contents of m_eventFlags to the client. Now, it still needs code to read it, otherwise all we've done is thrown the whole outgoing packet out of whack with what it expects to read. Never add a stream->write* event without a corresponding read, or a read without a write! Head down to SoldierPlayer::unpackUpdate ... this is the mirror image of the previous function, reading the packets on the client side.
Right after Parent::unpackUpdate(con,stream); add this:
if(stream->readFlag())
{
stream->read(&m_eventFlags);
}And that should do it. In theory. Obvious I couldn't test this specific code, but that's how I send data through packUpdate. Hope that helps.
#14
Without too much info, it sounded from your last post like you hadn't really had a chance to explore the networking of server->client objects yet, so I figured this was likely the issue. Even in singleplayer, Torque runs server and client instances. You were able to reach across this line directly in code because -- I assume -- you're in singleplayer, where server and client objects both exist in the same space.
Let me know if this was confusing or just didn't help.
05/17/2010 (2:52 pm)
Ran out of space in the reply, so just a quick double post. In the long run you might want to look into adding a mask bit specifically for your animation values, so you might want to search for enum MaskBits in ... I'm guessing soldierPlayer.h in your case (player.h). In stock T3D there aren't many free bits left because of all the class hierarchy, but there should at least be a couple. Add a new one to the list if you like, but make sure NextFreeMask is always the last (and increase the # of any mask after the one you add).Without too much info, it sounded from your last post like you hadn't really had a chance to explore the networking of server->client objects yet, so I figured this was likely the issue. Even in singleplayer, Torque runs server and client instances. You were able to reach across this line directly in code because -- I assume -- you're in singleplayer, where server and client objects both exist in the same space.
Let me know if this was confusing or just didn't help.
#15
packUpdate is called on the server, and unpackUpdate is called on the client; these functions are used to communicate state changes between the two simulations.
There are two approaches to the general problem of keeping the client in sync with the server:
1) Server sends all state information to the client. ie. in packUpdate, write any required state information, then the client reads it back in unpackUpdate.
2) Give the client only the information needed to 'predict' the state changes that occur on the server. In your case, I suspect you need to send the client the m_eventFlags value. Look at pack/unpackUpdate where the mState value is written/read.
05/17/2010 (9:47 pm)
I wondered if that was the issue in my earlier comment. Take a look at the packUpdate and unpackUpdate methods => if the animation is changed on the server (id:3289 in your case), you need a way to notify the client to change animation as well (id:3327).packUpdate is called on the server, and unpackUpdate is called on the client; these functions are used to communicate state changes between the two simulations.
There are two approaches to the general problem of keeping the client in sync with the server:
1) Server sends all state information to the client. ie. in packUpdate, write any required state information, then the client reads it back in unpackUpdate.
2) Give the client only the information needed to 'predict' the state changes that occur on the server. In your case, I suspect you need to send the client the m_eventFlags value. Look at pack/unpackUpdate where the mState value is written/read.
#17
@Henry: That was an impressive explanation! Now I understand much better how this server-client duality works.
@Chris: Apparently that was it, you were right then in your earlier comment (and now I understand what did you mean with executing on the server or the client).
Now it works perfectly! I have made the proper modifications in packUpdate() and unpackUpdate(), and added a new mask bit for animations as Henry suggested. Thank you VERY much, guys!
I am going to put here how my code is, the version that works, so if anybody else reads this thread they could understand how we could solve it.
I have added a new mask bit (AnimationMask), so the enum in soldierPlayer.h (old player.h) looks like this:
I also have two functions to modify the value of my flags (although it could be done with only one):
At the beginning of packUpdate(...), now it looks like this:
And finally, the beginning of unpackUpdate(...) looks like:
I hope this will be useful for somebody else too.
Thanks again!
Diego.
05/18/2010 (2:21 am)
Wow guys! Thanks a lot!@Henry: That was an impressive explanation! Now I understand much better how this server-client duality works.
@Chris: Apparently that was it, you were right then in your earlier comment (and now I understand what did you mean with executing on the server or the client).
Now it works perfectly! I have made the proper modifications in packUpdate() and unpackUpdate(), and added a new mask bit for animations as Henry suggested. Thank you VERY much, guys!
I am going to put here how my code is, the version that works, so if anybody else reads this thread they could understand how we could solve it.
I have added a new mask bit (AnimationMask), so the enum in soldierPlayer.h (old player.h) looks like this:
/// Bit masks for different types of events
enum MaskBits {
ActionMask = Parent::NextFreeMask << 0,
MoveMask = Parent::NextFreeMask << 1,
ImpactMask = Parent::NextFreeMask << 2,
AnimationMask = Parent::NextFreeMask << 3,
NextFreeMask = Parent::NextFreeMask << 4
};I also have two functions to modify the value of my flags (although it could be done with only one):
void SoldierPlayer::addFlag(EventFlags a_event)
{
m_eventFlags |= a_event;
setMaskBits(AnimationMask);
}
void SoldierPlayer::removeFlag(EventFlags a_event)
{
m_eventFlags &= ~a_event;
setMaskBits(AnimationMask);
}At the beginning of packUpdate(...), now it looks like this:
U32 SoldierPlayer::packUpdate(NetConnection *con, U32 mask, BitStream *stream)
{
U32 retMask = Parent::packUpdate(con, mask, stream);
//Masks for animations, changes in m_eventFlags
if(stream->writeFlag(mask & AnimationMask))
{
stream->write(getEventFlags());
}
if (stream->writeFlag((mask & ImpactMask) && !(mask & InitialUpdateMask)))
stream->writeInt(mImpactSound, SoldierPlayerData::ImpactBits);
...
}And finally, the beginning of unpackUpdate(...) looks like:
void SoldierPlayer::unpackUpdate(NetConnection *con, BitStream *stream)
{
Parent::unpackUpdate(con,stream);
if(stream->readFlag())
{
stream->read(&m_eventFlags);
}
if (stream->readFlag())
mImpactSound = stream->readInt(SoldierPlayerData::ImpactBits);
...
}I hope this will be useful for somebody else too.
Thanks again!
Diego.
Associate Chris Robertson
Not sure what could be wrong from your description, but here are some things to try:
1. Debug into setActionThread, and confirm that either transitionToSequence or setSequence is called (this changes the sequence played by the animation thread).
2. In PlayerData::preload (or your equivalent if you have copied this datablock), check that this line:
succeeds in finding the left/right hit animations (dp->sequence should be >= 0). It may be that you have a typo in your ActionAnimationList.