Jump to content

Steve_Yorkshire

Contributor
  • Posts

    366
  • Joined

  • Last visited

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

Steve_Yorkshire's Achievements

  1. Sorry I didn't see this earlier, took a bit of searching but found it. https://yorkshirerifles.com/downloads/GPI_CC_Pack_Vol1.zip
  2. Probably a bit late, but I too have some ancient flying vehicle stuff from TGE, it might be the same as yours. 5071.flyingvehicle.zip
  3. Updated to the latest version of Torque 4.0 as of the 11th August 2023 https://yorkshirerifles.com/downloads/DxShaderLibraryTorque4.zip And on discord: https://discord.com/channels/358091480004558848/783126530511339571/1139546553367920640 DxShaderLibraryTorque4.zip
  4. Minor Fix: I noticed that when the player runs into something, they turn away from the point of collision. This is because the player's movement is in X and Y velocity, which is world space. When the velocity changes due to an obstruction, then the forward vector will rotate. To get around this we need to swap X and Y velocity for the player's input move->x/y. Player move input is in screenspace, but by simply adding the camera Z transform, which we already have, we can get the relevant world space value. In player.cpp, inside our above code in Player::UpdateMove, find where we are using the x/y velocity. F32 moveDir = mAtan2(getVelocity().x, getVelocity().y); We want to replace that with this: //... //yorks out now, this will change due to collisions and turn the player when we don't want him to //F32 moveDir = mAtan2(getVelocity().x, getVelocity().y); F32 moveDir = mAtan2(move->x, move->y);//player input move is screenSpaced//<<<<<<<<<<<<<<<<<<<<<< new! moveDir += camZ;//add the camera Z axis view to make screenSpace into worldSpace//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< new! if (moveDir < 0.0f) moveDir += M_2PI_F; //... And that's it, 1 line out and 2 lines in. Don't think about using the mWrapF as it can cause the player object to choose the wrong direction to rotate on movement changes, stick with the += 2PI for both "moveDir" and the forwardVector transform "tran" above it. Now the player will bravely faceplant into walls rather gutlessly turn away!
  5. Third Person Free Camera With ScreenSpace Movement And Lock On Target, that works in Torque 4 devhead as of today. This is the FINAL (hopefully) version of this resource, I have now fixed my client-server predication issues. Needless to say, I had been looking the wrong place, and it was pretty much a 1 line fix. Player.h //in 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 //... //down in public //... ShapeBase* getControlObject(); //yorks block F32 getLockOnHorizontal(ShapeBase* obj); F32 getLockOnVertical(ShapeBase* obj); void setLockOn(ShapeBase* obj); ShapeBase* getLockOn(); void clearLockOn();//yorks end // void updateWorkingCollisionSet(); //... 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; //.. And into the player's main player::updateMove where the action happens. void Player::updateMove(const Move* move) { struct Move my_move; //... 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) { F32 prevZRot = mRot.z; mDelta.headVec = mHead; bool doStandardMove = true; bool absoluteDelta = false; //GameConnection* con = getControllingClient();//yorks moved above now #ifdef TORQUE_EXTENDED_MOVE //... Now find the doStandardMove and replace it. if(doStandardMove) { //yorks new F32 p = move->pitch; F32 y = move->yaw; //yorks >>>>>>>>>>>>>>>>>>>>>>> moved below for first person only /*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;*/ /*if (move->freeLook && ((isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson()))) { mHead.z = mClampF(mHead.z + y, -mDataBlock->maxFreelookAngle, mDataBlock->maxFreelookAngle); } else { 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 of moved below for first person <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */ // 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), -1.4f, 0.9f);//direct values now//yorksmDataBlock->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 p *= 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); y *= mPose == SprintPose ? mDataBlock->sprintYawScale : 1.0f; if (y > M_PI_F) y -= M_2PI_F; 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) mRot.z += M_2PI_F; while (mRot.z > M_2PI_F) mRot.z -= M_2PI_F; } mDelta.rot = mRot; //... This is good for both first person view, our new third person camera, and AiPlayers. Now replace the whole of; if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled && !isAnimationLocked()) //... 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; //yorks this is what we forgot before - to update the headVec for a second time because we have altered it mDelta.headVec -= Point3F(headV, 0.0f, testVal); for (U32 i = 0; i < 3; ++i)//yorks safety { if (mDelta.headVec[i] > M_PI_F) mDelta.headVec[i] -= M_2PI_F; else if (mDelta.headVec[i] < -M_PI_F) mDelta.headVec[i] += M_2PI_F; } } } 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; } 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; } //... Write and Read the packets to send our lockOn target's ghost over net. 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); } At the bottom of the file add the maths and functions for the lockOn target. /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; } 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; } And finally after that, the script calls. 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 there you have it, a third person free camera with a movement to screenSpace with a target lock on system. Now you too can be Sleeping Torquedogs, Torque Ring or Grand Theft Kork 6!
  6. Nice! The amount of trouble I've had getting the images the correct way around ...
  7. 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.
  8. Fixed the rather fugly code for horizontal rotation and compressed it down from >70 to ~10 lines, oh the horrors of brain fatigue Paste over the whole of if (!mLockOn.isNull()) part of the function with this new version. This also includes the the vertical rotation now. The "look at" position is now from the player's worldBoxCenter to the target's floor, which helps the camera look slightly down when level. if (!mLockOn.isNull()) { //face to look at the object's center - no look at it's base to keep the camera higher Point3F centerLock = mLockOn->getPosition();// getBoxCenter();// //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.005f && mFabs(testVal) > -0.005f)//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 if (testVal > M_PI_F) testVal -= M_2PI_F; else if (testVal < -M_PI_F) testVal += M_2PI_F; //testVal = mWrapF(testVal, 0.0f, M_2PI_F);// <<<<<<<<<< seems to work with or without, shrug } mHead.z += testVal; //---------------------- //now do up and down // minLookAngle -1.4; maxLookAngle +0.9, so can be negative ... //negative is look up, so move camera down, positive is look down so move camera up mHead.x = mWrapF(mHead.x, -M_HALFPI_F, M_HALFPI_F);// -1.5, 1.5f); - lock it between half -Pi (-1.57) and half +Pi (+1.57) F32 vertZ;// = ourCenter.z - centerLock.z; F32 headV = mHead.x; 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) if (vertZ < (headV - 0.005f) || vertZ > (headV + 0.005f))//0.017f = 1 degree headV = vertZ; mHead.x = headV; } It is a little bit jittery, so could probably use some newton physics stuff from newton mode camera, but apart from that it works fine.
  9. Added the vertical movement for the lock on camera, again the code is fugly but working. Had more trouble than expected because I forgot that the min/maxFreeLook values are negative when looking down and it really wanted the range clamping between -Pi*0.5 and +Pi*0.5 ... and there I was trying to ram Pi*2 in for most of the day . The rotation is from the player and target worldbox centers and does not take into account the camera offset, so the player will not actual have true aim to the target, however the target should appear locked just above the player's head at all but extreme heights. //find this line in my previous code above when the horizontal rotation is finally set mHead.z = testVal; //now do up and down <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< yorks start here with the vertical rotation code // minLookAngle -1.4; maxLookAngle +0.9, so can be negative ... //negative is look up, so move camera down, positive is look down so move camera up mHead.x = mWrapF(mHead.x, -M_HALFPI_F, M_HALFPI_F);//lock it between half -Pi (-1.57) and half +Pi (+1.57) F32 vertZ;// = ourCenter.z - centerLock.z; F32 headV = mHead.x; F32 lenTo = lookAtVec.len();// len() or lenSquared() ? toBe() ? or !toBe() ? vertZ = mAtan2(mFabs(lookAtVec.z), mFabs(lenTo)); if (ourCenter.z < centerLock.z) { vertZ *= -1; //Con::printf("note we are lower inverting; vertZ pitch %f, mHead.x %f", vertZ, mHead.x); } //else //Con::printf("note we are higher; vertZ pitch %f, mHead.x %f", vertZ, mHead.x); vertZ = mWrapF(vertZ, -M_HALFPI_F, M_HALFPI_F);//lock it between half -Pi (-1.57) and half +Pi (+1.57) if (vertZ < (headV - 0.02f) || vertZ > (headV + 0.02f)) { if (headV < vertZ)//want to look up, minLookAngle stock -1.4 { headV = mHead.x + 0.02f; //Con::printf("vertZ LESS %f; mHead.x %f", vertZ, mHead.x); } else//want to look down, maxLookAngle stock +0.9 { headV = mHead.x - 0.02f; //Con::printf("vertZ MORE %f; mHead.x %f", vertZ, mHead.x); } } //else //Con::printf("no move vertZ %f; headV %f", vertZ, headV); mHead.x = headV; } Anyone who fancies cleaning this up to not look like poop on a stick, can be my guest
  10. This is a continuation of the Elden Ring/GTA/Sleeping Dogs (the only one I have actually played) screenSpace movement resource that I added here. The camera locks on a target and then follows it, the code presented here is my initial test case and moves slower than the player movement so you can see that the camera orientates correctly, this makes it look janky as hell. Warning: this code was written at some god-forsaken time when my brain had died and is fugly as hell, feel free to pretty it up, also there is no X (vertical) axis. Code: Player.cpp and Player.h Player.h Somewhere under 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 way down under public ... //... ShapeBase* getControlObject(); //yorks ShapeBase* getLockOn();// { return (mLockOn); } void setLockOn(ShapeBase *obj); void clearLockOn(); //yorks end // void updateWorkingCollisionSet(); //... 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; //... void Player::updateMove(const Move* move) { //... 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 - this is my older camera screen space code; see link in forum post above! //if (((move->freeLook && isMounted() && getMountNode() == 0) || (con && !con->isFirstPerson()))) if (thirdP)// { if (!mLockOn.isNull())//yorks - this is the new stuff!!!!!! we check to make sure it's still a good target { //think we might need a check to see if mLockOn is a real thing? Safety first! 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 //... //in the middle of the previous screenSpace move code //... 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())//yorks this is the new lockon code <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< beware fugly sleep deprivation { //face to look at the object's center Point3F centerLock = mLockOn->getBoxCenter();// getWorldBox().getCenter(); //get the vector from us to the target and break it down to x (up/down) and z horizontal rotation Point3F ourCenter = getBoxCenter();// getWorldBox().getCenter(); 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; F32 testVal = mHead.z;// 0.0f; totalRad = mWrapF(totalRad, 0.0f, M_2PI_F);//yorks use the new method //Con::printf("viewRad %f; mHead.z %f; mRot.z %f, totalRad %f", viewRad, mHead.z, mRot.z, totalRad); //works but god it's fugly if (totalRad >= viewRad) { if ((viewRad + 0.02f) >= (totalRad)) testVal = mHead.z;// 0.0f; else { if ((mHead.z - 0.02f) <= 0.0f) { if ((viewRad + M_PI_F) > totalRad) testVal = mHead.z + 0.02f; else testVal = mHead.z - 0.02f; testVal = mWrapF(testVal, 0.0f, M_2PI_F);//yorks use the new method //Con::printf("viewRad LESS totalRad; += 0.02f; mHead.z = %f; viewRad = %f; totalRad %f;", mHead.z, viewRad, totalRad); } else { if((viewRad + M_PI_F) < totalRad) testVal = mHead.z + 0.02f; else testVal = mHead.z - 0.02f; testVal = mWrapF(testVal, 0.0f, M_2PI_F);//yorks use the new method //Con::printf("viewRad LESS totalRad; -= 0.02f; mHead.z = %f; viewRad = %f; totalRad %f;", mHead.z, viewRad, totalRad); } } } else { if ((viewRad - 0.02f) <= (totalRad)) testVal = mHead.z;// 0.0f; else { if ((mHead.z + 0.02f) >= M_2PI_F) { if ((viewRad + M_PI_F) > totalRad) testVal = mHead.z - 0.02f; else testVal = mHead.z + 0.02f; testVal = mWrapF(testVal, 0.0f, M_2PI_F);//yorks use the new method //Con::printf("viewRad MORE totalRad; -= 0.02f; mHead.z = %f; viewRad = %f; totalRad %f;", mHead.z, viewRad, totalRad); } else { if ((viewRad - M_PI_F) > totalRad) testVal = mHead.z - 0.02f; else testVal = mHead.z + 0.02f; testVal = mWrapF(testVal, 0.0f, M_2PI_F);//yorks use the new method //Con::printf("viewRad MORE totalRad; += 0.02f; mHead.z = %f; viewRad = %f; totalRad %f;", mHead.z, viewRad, totalRad); } } } mHead.z = testVal; } } } if (thirdP)//yorks note; you can remove this and put it all under the above if(thirdP) from the other code snippet if you want { //get facing movement direction as yawDiff F32 dirSpeed = 0.0f; //F32 secSpeed = 0.0f; if (yawDiff < 0.785f)//0-45deg { //... void Player::writePacketData(GameConnection *connection, BitStream *stream) { Parent::writePacketData(connection, stream); //.. if (mControlObject) { S32 gIndex = connection->getGhostIndex(mControlObject); if (stream->writeFlag(gIndex != -1)) { stream->writeInt(gIndex,NetConnection::GhostIdBitSize); mControlObject->writePacketData(connection, stream); } } else stream->writeFlag(false); //yorks 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 } oid Player::readPacketData(GameConnection *connection, BitStream *stream) { Parent::readPacketData(connection, stream); //... obj->readPacketData(connection, stream); } else setControlObject(0); //yorks 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); } //... //right at the bottom of the file and all of this 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 mLockOn = obj; } 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 NULL if no lockOn.") { return object->getLockOn(); } DefineEngineMethod(Player, clearLockOnTarget, void, (), , "@brief Clear player's lock on object.") { object->clearLockOn(); } So there you have it, lock-on camera with screenSpace movement (and a delay which was part of debugging). Remember you need the code for screenSpace input movement first, which can be found here.
  11. After my previous Third Person camera movement system which I have edited to label as a shooter style, I decided to expand it and make an actual Elden Ring/GTA/Sleeping Dogs (the last one I actually have played) style third person free camera with screenSpace based movement input; eg: move left makes the player move screen left. The player also turns to face the direction that they are moving in. In the video this is exaggerated by having side and backwards speed set very low and forward speed set very high. Standard code of Move->x and Move->y are not taken into account and movement speed is based purely on player datablock move speeds and I didn't include "prone" as a pose. void Player::updateMove(const Move* move) { struct Move my_move; //... 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);//yorks out 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()))) if (thirdP)// { //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 } 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 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; } } //... if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled && !isAnimationLocked()) { F32 yawDiff = 0.0f; 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 camZ = camTrans.toEuler().z; if (camZ < 0.0f) camZ += M_2PI_F; //Con::printf("Camz %f", camZ); // 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(); 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);//it's the x and y of player velocity we want if(moveDir < 0.0f) moveDir += M_2PI_F; yawDiff = moveDir - tran; //Con::printf("moveDir %f; tran %f; yawDiff %f", moveDir, tran, yawDiff); //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 (thirdP)//yorks this is broken up a bit for debug testing { //get facing movement direction as yawDiff 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; //Con::printf("forward 0-0.785; %f", yawDiff); } 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; //Con::printf("forward 5.497-6.283; %f", yawDiff); } 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; //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; //Con::printf("side left 3.926-5.496; %f", yawDiff); } else { if (mSwimming) dirSpeed = mDataBlock->maxUnderwaterBackwardSpeed; else if (mPose == CrouchPose) dirSpeed = mDataBlock->maxCrouchBackwardSpeed; else if (mPose == SprintPose) dirSpeed = mDataBlock->maxSprintBackwardSpeed; else dirSpeed = mDataBlock->maxBackwardSpeed; //Con::printf("back 2.356-5.496; %f", yawDiff); } moveSpeed = dirSpeed; } else { //move the first person standard movement here - yorks 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)); } } // 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; } else { moveVec.set(0.0f, 0.0f, 0.0f); moveSpeed = 0.0f; } //... Hope this helps someone.
  12. Disclaimer: I haven't actually played GTA or Elden Ring so I've never actually experienced the camera and movement control system. But anyway - here's what I think it's like (probably). UPDATE: this camera and movement style is really more relevant to a 3rd person shooter. This is a movement test based on third person camera direction, kind of like what I did top-down for Monsters Loot Swag but the camera remains fixed to the player rather than being independant - because of that I had apply inverse rotation to the camera when the player moved and turned to face the camera's original vector. Horizontal camera rotation wraps around -/+M_PI_F rather than being constrained by the stock engine's playerDatablock maxFreeLook value, this means that the camera can loop seamlessly around the player using z axis but is still constrained to maximums when looking up and down, so it doesn't flip upside-down. Code: Player.cpp, in UpdateMove(const Move* move) void Player::updateMove(const Move* move) { //... 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) { F32 prevZRot = mRot.z; mDelta.headVec = mHead; bool doStandardMove = true; bool absoluteDelta = false; //GameConnection* con = getControllingClient();//yorks out and moved above so we can get this below too #ifdef TORQUE_EXTENDED_MOVE //... 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 if (thirdP)//from above { //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 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); } else {//yorks end block mHead.x = mClampF(mHead.x + p, mDataBlock->minLookAngle, mDataBlock->maxLookAngle);//yorks moved down here for normal cam/move mode 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 added // 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; } //... if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled && !isAnimationLocked()) { if (thirdP)//yorks start big in { 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 camZ = camTrans.toEuler().z; if (camZ < 0.0f) camZ += M_2PI_F; //Con::printf("Camz %f", camZ); // 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))) { //we are moving and so should turn the player towards the direction that the camera is facing Point3F transVec = getTransform().getForwardVector(); F32 tran = mAtan2(transVec.x, transVec.y); if (tran < 0.0f) tran += M_2PI_F; mWrapF(tran, 0.0f, M_2PI_F); F32 yawDiff = camZ - tran; //Con::printf("camToDir %f; tran %f; yawDiff %f", dir, tran, yawDiff); //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)) { //Con::printf("yawDiff %f", 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 otherwise it's instantaneous 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 } } } }//yorks end big in 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) //... And there you go, that should be it. In 3rd person view and ONLY 3rd person view you can rotate the camera horizontally around the player without it stopping at maxLook and movement is tied to the camera view, so that the player will reorientate himself to that when moving. This could be expanded by adding a lock-on feature to have the camera point constantly at the selected enemy, and also maybe add a toggle that allows the player to move relative to the camera but without turning to face the camera's vector. Hope this helps some folks.
  13. Do you like Torque? Do you like fluffy ears? Do you like waifus? Do you like murderous twin-stick fast paced action? Then you might like to wishlist - in Steam - Monsters Loot Swag!
  14. Updated to work with Torque3D 4.0 Preview (22nd January 2022). Read the readme for installation as you need to exec the file after other shaders have loaded. shaderLibraryTorque4.zip
×
×
  • Create New...