Player position on client/server differs
by Justin Mosiman · in Torque Game Engine Advanced · 11/18/2007 (10:43 pm) · 3 replies
Hello,
I am creating a new class with a parent of Player, and it is able to be added and everything it's just that the client's and server's position varies by a small degree (and then as you move, the amount that it varies by increases). For example, I am printing out the x and y positions for the client and server I get:
server 533.825867 531.033386
client 533.820068 531.030029
I have tried to read through the Player class to see exactly where I am going wrong, but that class is so big I am getting lost as to what is needed and what's not. The major method that I am changing with this class is the processtick (so I can use A* pathfinding), and that code is posted below (loosely based off of the RTS starter kit). For packUpdate and unpackUpdate I am just sending it straight to the parent, so something probably needs to be changed there... but I tried adding the position and that didn't change anything.
I am creating a new class with a parent of Player, and it is able to be added and everything it's just that the client's and server's position varies by a small degree (and then as you move, the amount that it varies by increases). For example, I am printing out the x and y positions for the client and server I get:
server 533.825867 531.033386
client 533.820068 531.030029
I have tried to read through the Player class to see exactly where I am going wrong, but that class is so big I am getting lost as to what is needed and what's not. The major method that I am changing with this class is the processtick (so I can use A* pathfinding), and that code is posted below (loosely based off of the RTS starter kit). For packUpdate and unpackUpdate I am just sending it straight to the parent, so something probably needs to be changed there... but I tried adding the position and that didn't change anything.
void RTSUnit::processTick(const Move *move)
{
// We first need to check if we are within stopping/firing distance because if we are within firing distance
// then we want to play client side animation. But if we go any further we wouldn't be able to play
// that animation because mRTSConn will be NULL on the client.
Point3F location = getPosition();
Point3F rotation = getRotation();
F32 xDiff = mMoveDestination.x - location.x;
F32 yDiff = mMoveDestination.y - location.y;
// There are two cases in which we want to stop the move:
// 1. The object is so close to its target destination that it is within its move tolerance
// 2. The object is within firing distance (remembering that 0 is infinite distance)
if(mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance ||
(mAimObjectSet && mShellRange > 0 && mFabs(xDiff) < mShellRange && mFabs(yDiff) < mShellRange)){
stopMove();
return Parent::processTick(move);
}
// If the mRTSConn is NULL, we are probably on the client
if(mRTSConn == NULL){
return Parent::processTick(move);
}
// If we are not in the commander mode, we are in the player mode so the unit should act as a
// normal player.
//if(mRTSConn->inCommanderMode() == false || mPathfindInstance == NULL){
if(mRTSConn->inCommanderMode() == false){
return Parent::processTick(move);
}
if(getDamageState() == Disabled){
return;
}
Move *movePtr = new Move();
*movePtr = NullMove;
// Orient towards destination
if(mMoveState == ModeMove){
F32 xDiff = mNextNode.x - location.x;
F32 yDiff = mNextNode.y - location.y;
if(!isZero(xDiff) || !isZero(yDiff)){
// First do Yaw
// use the cur yaw between -Pi and Pi
F32 curYaw = rotation.z;
while(curYaw > M_2PI)
curYaw -= M_2PI;
while(curYaw < -M_2PI)
curYaw += M_2PI;
// find the yaw offset
F32 newYaw = mAtan(xDiff, yDiff);
F32 yawDiff = newYaw - curYaw;
// make it between 0 and 2PI
if(yawDiff < 0.0f)
yawDiff += M_2PI;
else if(yawDiff >= M_2PI)
yawDiff -= M_2PI;
// now make sure we take the short way around the circle
if(yawDiff > M_PI)
yawDiff -= M_2PI;
else if(yawDiff < -M_PI)
yawDiff += M_2PI;
// Before our first move, we want to yaw
if(mFabs(yawDiff) > mYawTolerance){
F32 maxYawDiff = M_PI2 / 10 ;
if(yawDiff > maxYawDiff)
yawDiff = maxYawDiff;
else if(yawDiff < -maxYawDiff)
yawDiff = -maxYawDiff;
movePtr->yaw = yawDiff;
}else
mStopForYaw = false;
// Next do pitch.
}
}Continues next post...
#2
Ask somebody of the employees here, i take interested in that too.
11/19/2007 (11:08 am)
There always is a difference, i noticed it long ago. Just left unexplained.Ask somebody of the employees here, i take interested in that too.
#3
Finally, due to the concepts of network interpolation, the client is always approaching (hopefully rather accurately) the update position being sent by the server each networking cycle, but again by the nature of the synchronization process, it's never going to be 100% synchronized.
11/19/2007 (12:24 pm)
By definition, the client and the server are never on the same time slice, so the server's position is "ahead" of the client. In addition, for your control object, the move generated every update cycle is applied first to the client, then delivered to the server, and then a corrective update (the server is authoritative) is sent back to the client.Finally, due to the concepts of network interpolation, the client is always approaching (hopefully rather accurately) the update position being sent by the server each networking cycle, but again by the nature of the synchronization process, it's never going to be 100% synchronized.
Torque Owner Justin Mosiman
Opsive
// Move towards the destination if(mMoveState == ModeMove && mStopForYaw == false){ F32 xDiff = mNextNode.x - location.x; F32 yDiff = mNextNode.y - location.y; // Check if we should mMove, or if we are 'close enough' if(mFabs(xDiff) < mMoveTolerance && mFabs(yDiff) < mMoveTolerance){ mNextNode = mPathfindInstance->getNextNode(); }else{ // Build move direction in world space if(isZero(xDiff)){ movePtr->y = (location.y > mNextNode.y)? -1 : 1; }else{ if(isZero(yDiff)){ movePtr->x = (location.x > mNextNode.x)? -1 : 1; }else{ if(mFabs(xDiff) > mFabs(yDiff)){ F32 value = mFabs(yDiff / xDiff); movePtr->y = (location.y > mNextNode.y)? -value : value; movePtr->x = (location.x > mNextNode.x)? -1 : 1; }else{ F32 value = mFabs(xDiff / yDiff); movePtr->x = (location.x > mNextNode.x)? -value : value; movePtr->y = (location.y > mNextNode.y)? -1 : 1; } } } // We need to slow down if we are getting close xDiff = mMoveDestination.x - location.x; yDiff = mMoveDestination.y - location.y; if(sqrt(xDiff*xDiff+yDiff*yDiff) < mSlowDownTolerance){ F32 slowDown = 2.4f / sqrt(xDiff*xDiff+yDiff*yDiff); movePtr->x /= slowDown; movePtr->y /= slowDown; } // Rotate the move into object space (this really only needs // a 2D matrix) Point3F newMove; MatrixF moveMatrix; moveMatrix.set(EulerF(0, 0, -(rotation.z + movePtr->yaw))); moveMatrix.mulV(Point3F(movePtr->x, movePtr->y, 0), &newMove); movePtr->x = newMove.x; movePtr->y = newMove.y; } } // Replicate the trigger state into the move so that // triggers can be controlled from scripts. for(int i = 0; i < MaxTriggerKeys; i++) movePtr->trigger[i] = getImageTriggerState(i); return Parent::processTick(movePtr); }Sorry for it being so long, but does anybody see where I am going wrong?
Thanks,
Justin