Steve_Yorkshire Posted March 7, 2023 Share Posted March 7, 2023 (edited) This is the cleaned up version of the previous "lock-on camera" resource which I posted. It includes a check for client interpolation to try and synchronize with the server but I still feel it's not quite getting their somehow, but does clean up a lot of the target object and target shapeName jitter. Thanks to @Azaezelfor the tips. As for ease of use, the code below includes the third person free camera resource I wrote earlier which the lock-on system uses. Here's what it looks like in play: As ever, follow the comment tag "yorks" for changes. Player.h Inside "protected" //... SimObjectPtr<ShapeBase> mControlObject; ///< Controlling object SimObjectPtr<ShapeBase> mLockOn;//yorks - gamebase/shapebase object id we are locked on to, this keeps the camera pointing at this object /// @name Animation threads & data //... And then down the bottom under public: declare_conObject Player; //... ShapeBase* getControlObject(); //yorks block F32 getLockOnHorizontal(ShapeBase* obj); F32 getLockOnVertical(ShapeBase* obj); void setLockOn(ShapeBase *obj); ShapeBase* getLockOn();// { return (mLockOn); } void clearLockOn(); // void updateWorkingCollisionSet(); //... On to player.cpp //... Player::Player() { mTypeMask |= PlayerObjectType | DynamicShapeObjectType; //... dMemset( mSplashEmitter, 0, sizeof( mSplashEmitter ) ); mLockOn = NULL;// 0;//yorks for locking on a shapeBase/gameBase id mUseHeadZCalc = true; //.. This is the part in UpdateMove, where I seperate activated lock-on, from my previous third person free camera resource. void Player::updateMove(const Move* move) { struct Move my_move; //... else setImageAltTriggerState( 0, move->trigger[sImageTrigger1] ); } //yorks >>>>>>>>>>>>>>>>>>>>>>>>>>>> F32 camZ = 0.0f; GameConnection* con = getControllingClient(); bool thirdP = false; if (con && !con->isFirstPerson()) thirdP = true;//yorks end <<<<<<<<<<<<<<<<<<<<<<<<<<<< // Update current orientation if (mDamageState == Enabled) { //... if (doStandardMove) { F32 p = move->pitch * (mPose == SprintPose ? mDataBlock->sprintPitchScale : 1.0f); if (p > M_PI_F) p -= M_2PI_F; //mHead.x = mClampF(mHead.x + p,mDataBlock->minLookAngle, // mDataBlock->maxLookAngle); F32 y = move->yaw * (mPose == SprintPose ? mDataBlock->sprintYawScale : 1.0f); if (y > M_PI_F) y -= M_2PI_F; //yorks start >>>>>>>>>>>>>>>>>>>>>>>>>> //if (((move->freeLook && isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson())))//out no freelook now if (thirdP)//if not default to first person stock code below { if (!mLockOn.isNull()) { //think we might need a check to see if mLockOn is a real thing? if (!mLockOn->isProperlyAdded()) setLockOn(NULL); } else { //yorks moved down here mHead.x = mClampF(mHead.x + (p * 0.5f), mDataBlock->minLookAngle, mDataBlock->maxLookAngle);//yorks added 0.5f to slow p down a bit //let us run the camera around in a big old never ending circle F32 camRot = mHead.z + (y * 0.5f);//slow y down a bit, techincally you should do this input script mouse sensitivity, also see p above //constrain the range of camRot within pi*2 /*if (camRot > M_PI_F) camRot -= M_2PI_F; else if (camRot < -M_PI_F) camRot += M_2PI_F;*/ camRot = mWrapF(camRot, 0.0f, M_2PI_F);//yorks use the new method mHead.z = camRot; // mHead.z = mClampF(mHead.z + y, // -mDataBlock->maxFreelookAngle, // mDataBlock->maxFreelookAngle);//yorks end //Con::printf("camRot/mHead.z %f", camRot); } } else { //yorks original code moved down here mHead.x = mClampF(mHead.x + p), mDataBlock->minLookAngle, mDataBlock->maxLookAngle);//note min-max angle is replaced with -/+M_HALFPI_F see pack/unpack mRot.z += y; // Rotate the head back to the front, center horizontal // as well if we're controlling another object. mHead.z *= 0.5f; if (mControlObject) mHead.x *= 0.5f; } }//yorks end <<<<<<<<<<<<<<<<<<<<<< // constrain the range of mRot.z while (mRot.z < 0.0f) //... //yorks remove the entire statement which deals with movement speed and direction and replace with this if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled && !isAnimationLocked()) { F32 yawDiff = 0.0f;//yorks start if (thirdP) { GameBase* cam = con->getCameraObject(); if (cam) { //get the camera's transform F32 pos = 1; MatrixF camTrans; cam->getCameraTransform(&pos, &camTrans); //flatten the transform to X&Y (so when the camera //is facing up/down the move remains horizontal) camTrans[9] = 0; camTrans.normalize(); //create a move vector and multiply by the camera transform VectorF temp(move->x, move->y, 0); camTrans.mulV(temp, &moveVec); // get the z rotation of the camera for working with right stick aiming - don't need this here but have it for debugging below camZ = camTrans.toEuler().z; if (camZ < 0.0f) camZ += M_2PI_F; // Constrain the range of camZ within pi*2 camZ = mWrapF(camZ, 0.0f, M_2PI_F);//yorks use the new method if ((!mIsZero(move->y)) || (!mIsZero(move->x))) { // This is make the player turn towards the direction that they are moving Point3F transVec = getTransform().getForwardVector(); //getVelocity(); F32 tran = mAtan2(transVec.x, transVec.y); if (tran < 0.0f) tran += M_2PI_F; mWrapF(tran, 0.0f, M_2PI_F); F32 moveDir = mAtan2(getVelocity().x, getVelocity().y); if(moveDir < 0.0f) moveDir += M_2PI_F; yawDiff = moveDir - tran; //ignore very small rotations if (mFabs(yawDiff) < 0.005f && mFabs(yawDiff) > -0.005f)//yorks now less than a third of 1 degree//0.001f)// yawDiff = 0.0f; if (!mIsZero(yawDiff)) { // now make sure we take the short way around the circle if (yawDiff > M_PI_F) yawDiff -= M_2PI_F; else if (yawDiff < -M_PI_F) yawDiff += M_2PI_F; F32 slower;//slower exists to slow the turn down slower = 0.15f; mRot.z += yawDiff * slower; mHead.z -= yawDiff* slower;//the camera is still attached to the player so we need to compensate for the player's new rotation } } if (!mLockOn.isNull()) { F32 testVal = 0.0f; testVal = getLockOnHorizontal(mLockOn); mHead.z +=testVal; //---------------------- F32 headV = 0.0f; headV = getLockOnVertical(mLockOn); mHead.x += headV; } } F32 dirSpeed = 0.0f; if (yawDiff < 0.785f)//0-45deg { if (mSwimming) dirSpeed = mDataBlock->maxUnderwaterForwardSpeed; else if (mPose == CrouchPose) dirSpeed = mDataBlock->maxCrouchForwardSpeed; else if (mPose == SprintPose) dirSpeed = mDataBlock->maxSprintForwardSpeed; else dirSpeed = mDataBlock->maxForwardSpeed; } else if (yawDiff > 5.497f)//315-360deg { if (mSwimming) dirSpeed = mDataBlock->maxUnderwaterForwardSpeed; else if (mPose == CrouchPose) dirSpeed = mDataBlock->maxCrouchForwardSpeed; else if (mPose == SprintPose) dirSpeed = mDataBlock->maxSprintForwardSpeed; else dirSpeed = mDataBlock->maxForwardSpeed; } else if (yawDiff > 0.784f && yawDiff < 2.356f) { if (mSwimming) dirSpeed = mDataBlock->maxUnderwaterSideSpeed; else if (mPose == CrouchPose) dirSpeed = mDataBlock->maxCrouchSideSpeed; else if (mPose == SprintPose) dirSpeed = mDataBlock->maxSprintSideSpeed; else dirSpeed = mDataBlock->maxSideSpeed; //secSpeed = mDataBlock->maxSideSpeed; //Con::printf("side right 0.784-2.356; %f", yawDiff); } else if(yawDiff < 5.496f && yawDiff > 3.926f) { if (mSwimming) dirSpeed = mDataBlock->maxUnderwaterSideSpeed; else if (mPose == CrouchPose) dirSpeed = mDataBlock->maxCrouchSideSpeed; else if (mPose == SprintPose) dirSpeed = mDataBlock->maxSprintSideSpeed; else dirSpeed = mDataBlock->maxSideSpeed; } else { if (mSwimming) dirSpeed = mDataBlock->maxUnderwaterBackwardSpeed; else if (mPose == CrouchPose) dirSpeed = mDataBlock->maxCrouchBackwardSpeed; else if (mPose == SprintPose) dirSpeed = mDataBlock->maxSprintBackwardSpeed; else dirSpeed = mDataBlock->maxBackwardSpeed; } moveSpeed = dirSpeed; } else {//yorks original movement code below zRot.getColumn(0, &moveVec); moveVec *= (move->x * (mPose == SprintPose ? mDataBlock->sprintStrafeScale : 1.0f)); VectorF tv; zRot.getColumn(1, &tv); moveVec += tv * move->y; // Clamp water movement if (move->y > 0.0f) { if (mSwimming) moveSpeed = getMax(mDataBlock->maxUnderwaterForwardSpeed * move->y, mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x)); else if (mPose == PronePose) moveSpeed = getMax(mDataBlock->maxProneForwardSpeed * move->y, mDataBlock->maxProneSideSpeed * mFabs(move->x)); else if (mPose == CrouchPose) moveSpeed = getMax(mDataBlock->maxCrouchForwardSpeed * move->y, mDataBlock->maxCrouchSideSpeed * mFabs(move->x)); else if (mPose == SprintPose) moveSpeed = getMax(mDataBlock->maxSprintForwardSpeed * move->y, mDataBlock->maxSprintSideSpeed * mFabs(move->x)); else // StandPose moveSpeed = getMax(mDataBlock->maxForwardSpeed * move->y, mDataBlock->maxSideSpeed * mFabs(move->x)); } else { if (mSwimming) moveSpeed = getMax(mDataBlock->maxUnderwaterBackwardSpeed * mFabs(move->y), mDataBlock->maxUnderwaterSideSpeed * mFabs(move->x)); else if (mPose == PronePose) moveSpeed = getMax(mDataBlock->maxProneBackwardSpeed * mFabs(move->y), mDataBlock->maxProneSideSpeed * mFabs(move->x)); else if (mPose == CrouchPose) moveSpeed = getMax(mDataBlock->maxCrouchBackwardSpeed * mFabs(move->y), mDataBlock->maxCrouchSideSpeed * mFabs(move->x)); else if (mPose == SprintPose) moveSpeed = getMax(mDataBlock->maxSprintBackwardSpeed * mFabs(move->y), mDataBlock->maxSprintSideSpeed * mFabs(move->x)); else // StandPose moveSpeed = getMax(mDataBlock->maxBackwardSpeed * mFabs(move->y), mDataBlock->maxSideSpeed * mFabs(move->x)); } }//yorks added to segregate original code from freecam // Cancel any script driven animations if we are going to move. if (moveVec.x + moveVec.y + moveVec.z != 0.0f && (mActionAnimation.action >= PlayerData::NumTableActionAnims || mActionAnimation.action == PlayerData::LandAnim)) mActionAnimation.action = PlayerData::NullAnimation; }//this is all stock code down here else { moveVec.set(0.0f, 0.0f, 0.0f); moveSpeed = 0.0f; } //... Okidoki, time to actually get the data from server to client with write/read Packets. void Player::writePacketData(GameConnection *connection, BitStream *stream) { Parent::writePacketData(connection, stream); //... //yorks, at the bottom if (!mLockOn.isNull()) { S32 ghost = connection->getGhostIndex(mLockOn); if (stream->writeFlag(ghost != -1)) stream->writeRangedU32(ghost, 0, NetConnection::MaxGhostCount); } else stream->writeFlag(false);//yorks end } void Player::readPacketData(GameConnection *connection, BitStream *stream) { Parent::readPacketData(connection, stream); //... //yorks at the bottom if (stream->readFlag()) { S32 ghost = stream->readRangedU32(0, NetConnection::MaxGhostCount); ShapeBase* lock = static_cast<ShapeBase*>(connection->resolveGhost(ghost));//static_cast as we have a check for shapebase object when we assign it mLockOn = lock;// setLockOn(lock); } else mLockOn = NULL;// setLockOn(NULL); } Whilst doing all of this I decided to make some changes to pack/unpack - mostly because the new third person free camera is not limited by the player datablocks freeview settings. I also tried to pack/unpack the mLockOn ghost, but it always returned -1, so I gave that up as a bad job ... U32 Player::packUpdate(NetConnection *con, U32 mask, BitStream *stream) { U32 retMask = Parent::packUpdate(con, mask, stream); //... // constrain the range of mRot.z while (mRot.z < 0.0f) mRot.z += M_2PI_F; while (mRot.z > M_2PI_F) mRot.z -= M_2PI_F; } //stream->writeFloat(mRot.x / M_PI_F, 7);//yorks? nah with delay turn it jitters stream->writeFloat(mRot.z / M_2PI_F, 7); stream->writeSignedFloat(mHead.x / (-M_HALFPI_F - M_HALFPI_F), 6);//yorks//(mDataBlock->maxLookAngle - mDataBlock->minLookAngle), 6); stream->writeSignedFloat(mHead.z / M_2PI_F, 6);//yorks//mDataBlock->maxFreelookAngle, 6); mDelta.move.pack(stream); stream->writeFlag(!(mask & NoWarpMask)); //... void Player::unpackUpdate(NetConnection *con, BitStream *stream) { Parent::unpackUpdate(con,stream); //... rot.y = rot.x = 0.0f; //rot.y = 0.0f;//yorks //rot.x = stream->readFloat(7) * M_PI_F;//yorks ?nah with delay turn it jitters rot.z = stream->readFloat(7) * M_2PI_F; mHead.x = stream->readSignedFloat(6) * (-M_HALFPI_F - M_HALFPI_F);//yorks// (mDataBlock->maxLookAngle - mDataBlock->minLookAngle); mHead.z = stream->readSignedFloat(6) * M_2PI_F;//yorks// mDataBlock->maxFreelookAngle; mDelta.move.unpack(stream); mDelta.head = mHead; mDelta.headVec.set(0.0f, 0.0f, 0.0f); //... Right at the bottom of Player.cpp and all the following: I split up the maths for getting hold of the horizontal and vertical camera when locked on. //yorks start to end of file F32 Player::getLockOnHorizontal(ShapeBase* obj) { //face to look at the object's center - no look at it's base to keep the camera higher Point3F centerLock = mLockOn->getPosition(); //get the vector from us to the target and break it down to x (up/down) and z horizontal rotation Point3F ourCenter = getBoxCenter();// use our center to their base for additional vert rotation VectorF lookAtVec = centerLock - ourCenter; lookAtVec.normalize();//just in case //get the angle in radians and then invert it as we want to be at the far side of our player --- apparently doesn't need inverting F32 viewRad = 0.0f; viewRad = mAtan2(lookAtVec.x, lookAtVec.y); viewRad = mWrapF(viewRad, 0.0f, M_2PI_F);//yorks use the new method F32 totalRad = mHead.z + mRot.z; totalRad = mWrapF(totalRad, 0.0f, M_2PI_F);//yorks use the new method F32 testVal = 0.0f; testVal = viewRad - totalRad; //ignore very small rotations if (mFabs(testVal) < 0.001f && mFabs(testVal) > -0.001f)//yorks //0.017f = 1 degree; 0.005f = 0.2864789 of 1 degree//0.001f testVal = 0.0f; if (!mIsZero(testVal)) { // now make sure we take the short way around the circle while (testVal > M_PI_F) testVal -= M_2PI_F; while (testVal < -M_PI_F) testVal += M_2PI_F; //testVal = mWrapF(testVal, 0.0f, M_2PI_F);//this is not the fast turn above!!!! Must not have this for interlope if (testVal > 0.05f)//stagger the turn if large testVal = 0.05f; else if (testVal < -0.05f) testVal = -0.05f; } return testVal; } F32 Player::getLockOnVertical(ShapeBase* obj) { //face to look at the object's center - no look at it's base to keep the camera higher Point3F centerLock = mLockOn->getPosition(); //get the vector from us to the target and break it down to x (up/down) and z horizontal rotation Point3F ourCenter = getBoxCenter();/use our center to their base for additional vert rotation VectorF lookAtVec = centerLock - ourCenter; lookAtVec.normalize();//just in case ;) //mHead.x = mWrapF(mHead.x, -M_HALFPI_F, M_HALFPI_F);lock it between half -Pi (-1.57) and half +Pi (+1.57)think it works better without F32 vertZ; F32 headV = 0.0f; F32 lenTo = lookAtVec.len();// len() or lenSquared() ? vertZ = mAtan2(mFabs(lookAtVec.z), mFabs(lenTo)); if (ourCenter.z < centerLock.z) vertZ *= -1; vertZ = mWrapF(vertZ, -M_HALFPI_F, M_HALFPI_F);//yorks use the new method - lock it between half -Pi (-1.57) and half +Pi (+1.57) F32 totalRad = mHead.x + mRot.x;//mRot.x should be zero totalRad = mWrapF(totalRad, -M_HALFPI_F, M_HALFPI_F); headV = vertZ - totalRad; //ignore very small rotations if (mFabs(headV) < 0.001f && mFabs(headV) > -0.001f)//yorks //0.017f = 1 degree; 0.005f = 0.2864789 of 1 degree//0.001f headV = 0.0f; if (!mIsZero(headV)) { // now make sure we take the short way around the circle while (headV > M_HALFPI_F) headV -= M_PI_F; while (headV < -M_HALFPI_F) headV += M_PI_F; //headV = mWrapF(headV, -M_HALFPI_F, M_HALFPI_F);//<<<<<<<<<<<this is not the above fast turn!!!! Must not have this for interlope if (headV > 0.05f)//stagger the turn if large headV = 0.05f; else if (headV < -0.05f) headV = -0.05f; } return headV; } And then the rest of the relevant lockon functions. void Player::setLockOn(ShapeBase *obj) { if (!obj) { mLockOn = NULL; Con::printf("setLockOn no obj or not shapebase"); } else if (!obj->isProperlyAdded()) { mLockOn = NULL; Con::printf("setLockOn obj not properly added"); } else if (obj->getDamageStateName() != "Enabled") { mLockOn = NULL; Con::printf("setLockOn obj dead"); } else { if (bool(mLockOn))//Az thought this might help { clearProcessAfter(); clearNotify(mLockOn); } mLockOn = obj;//set the lockOn object if (bool(mLockOn))/Az thought this might help { processAfter(mLockOn); deleteNotify(mLockOn); } } } ShapeBase* Player::getLockOn() { return mLockOn; } void Player::clearLockOn() { mLockOn = NULL; } DefineEngineMethod(Player, setLockOnTarget, void, (ShapeBase *obj), , "set lockOn target object") { object->setLockOn(obj); } DefineEngineMethod(Player, getLockOnTarget, ShapeBase*, (), , "@brief Get player's lock on object. return 0 if no lockOn.") { return object->getLockOn(); } DefineEngineMethod(Player, clearLockOnTarget, void, (), , "@brief Clear player's lock on object.") { object->clearLockOn(); } And finally, and the bit that I'm really not sure about ... is getting client side predication to stop jitter. So we go all the way back up the file to interpolateTick. void Player::interpolateTick(F32 dt) { if (mControlObject) mControlObject->interpolateTick(dt); // Client side interpolation Parent::interpolateTick(dt); //yorks start >>>>>>>>>>>>>>>>>>>> ShapeBase* targetObj = static_cast<ShapeBase*>(mLockOn);//in tick seems to work best here, bad above or below if ((bool)targetObj && isFirstPerson() == false)//enters the shadow realm in fps so we need the safety { //this orientates movement to the target and camera follows - hopefully F32 horz, vert; vert = getLockOnVertical(targetObj) * dt; horz = getLockOnHorizontal(targetObj) * dt; //mDelta.head.x += vert;// mWrapF(vert, -M_HALFPI_F, M_HALFPI_F); //mDelta.head.z += horz;// mWrapF(horz, 0.0f, M_2PI_F); if (horz != 0.0f || vert != 0.0f) { //Con::printf("interpolateTick 1: mDelta.head %f %f %f;", mDelta.head.x, mDelta.head.y, mDelta.head.z); mDelta.head += Point3F(vert, 0.0f, horz); //Con::printf("interpolateTick 2: mDelta.head %f %f %f;", mDelta.head.x, mDelta.head.y, mDelta.head.z); } //targePos += (targePos - delta.targePos) * dt ????example but I'm not using the actual positions //mDelta.head = (targetObj->getPosition() - getBoxCenter()) * dt;//lol no }//yorks end <<<<<<<<<<<<<<<<<<<<<< Point3F pos = mDelta.pos + mDelta.posVec * dt; Point3F rot = mDelta.rot + mDelta.rotVec * dt; //... So, there it is, hopefully it helps someone. Edited March 7, 2023 by Steve_Yorkshire Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.