Vehicle Camera Mouse Control - Driving and Looking
by John Eckhardt · 07/03/2009 (6:18 am) · 12 comments
Summary
This is a resource to control both the vehicle with the keyboard and the camera with the mouse separately, so you can cease paying attention to the road! It is very easy to add, and does not require any script or vehicle mesh changes.
It does not add any other files, it simply modifies vehicle.cc, adding one variable to keep track of where the vehicle camera is looking. Then, it changes the mouse to change this camera variable and the keyboard to control the vehicle's steering. The camera rotates around the vehicle's Cam node; this works great for WheeledVehicles.
Code
First, let's add all the required variables in vehicle.h.
In struct VehicleData After:
Add:
In class Vehicle After:
Add:
After:
Add:
Now, let's move over to vehicle.cc.
Give the datablock members initial values. In VehicleData::VehicleData() After:
Add:
Make sure they're passed to the client. In VehicleData::packData(BitStream* stream) After:
Add:
Make sure they unpack in the same order. In VehicleData::unpackData(BitStream* stream) After:
Add:
Make them persistant, so you can change them from the datablock. In VehicleData::initPersistFields() After:
Add:
Now, give the camera rotation an initial value. In Vehicle::Vehicle() After:
Add:
Replace Vehicle::interpolateTick function with the following. It smoothes out the camera, which is a good thing.
Add this function somewhere in the file. It tells the camera to rotate around Cam node, staying cameraMinDist-cameraMaxDist (in datablock) away from it. Usually constant at the max value. If you used the default ShapeBase version, the camera would rotate AT the Cam node.
In Vehicle::updateMove(const Move* move) Replace:
with:
Now we just have to make sure that mCameraRot is sent across the network. In Vehicle::writePacketData(GameConnection *connection, BitStream *stream) After:
Add:
And unpack it then. In Vehicle::readPacketData(GameConnection *connection, BitStream *stream) After:
Add:
Likewise, in Vehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream) Change:
To be:
In Vehicle::unpackUpdate(NetConnection *con, BitStream *stream) After:
Add:
Compile the engine now. It's all done! If you wish to change the vehicle camera rotation constraints, add these fields to your VehicleData datablock:
Remember, if you set freeCam___ to true, it will ignore the min and max values, and rotate the full 360 degrees. Values are in radians, where 0 is looking straight ahead (actually straight where Cam node is pointing), negative values are to the left and positive values are to the right.
You are done!
Note: This changes the control scheme to something different; it will work fine for wheeled vehicles, but for FlyingVehicles, the mouse will make the camera turn, rather than the vehicle turn. It's possible to make this the control scheme for only wheeled vehicles or whatever you like, it'll just take a bit more work. I leave that up to you, because I'm rather tired now, I spent 13 hours on this today.
Thanks to TomB who was quite helpful and enlightened me to the fact that mHead is actually the camera vector for the player.
Enjoy!
~John Eckhardt
This is a resource to control both the vehicle with the keyboard and the camera with the mouse separately, so you can cease paying attention to the road! It is very easy to add, and does not require any script or vehicle mesh changes.
It does not add any other files, it simply modifies vehicle.cc, adding one variable to keep track of where the vehicle camera is looking. Then, it changes the mouse to change this camera variable and the keyboard to control the vehicle's steering. The camera rotates around the vehicle's Cam node; this works great for WheeledVehicles.
Code
First, let's add all the required variables in vehicle.h.
In struct VehicleData After:
F32 collDamageThresholdVel; F32 collDamageMultiplier;
Add:
bool freeCamPitch; // true = ignore values and rotate 360 degrees. Rather funny effect with this one. F32 minCamPitchAngle; // Lowest angle (radians) the player can look F32 maxCamPitchAngle; // Highest angle (radians) the player can look bool freeCamYaw; // true = ignore values and rotate 360 degrees. F32 minCamYawAngle; // Min (left) angle the player can look F32 maxCamYawAngle; // Max (right) angle the player can look
In class Vehicle After:
Point3F mCameraOffset; ///< 3rd person camera
Add:
Point3F mCameraRot; //the rotation of the camera
const Point3F& getCameraRotation() { return mCameraRot; } //just in case, never know when you need it.After:
void advanceTime(F32 dt);
Add:
void getRenderEyeTransform(MatrixF* mat);
Now, let's move over to vehicle.cc.
Give the datablock members initial values. In VehicleData::VehicleData() After:
shadowEnable = true; shadowCanMove = true; shadowCanAnimate = true;
Add:
freeCamPitch = false; minCamPitchAngle = -1.0f; maxCamPitchAngle = 1.4f; freeCamYaw = false; minCamYawAngle = -3.0f; maxCamYawAngle = 3.0f;
Make sure they're passed to the client. In VehicleData::packData(BitStream* stream) After:
for (i = 0; i < Body::MaxSounds; i++)
if (stream->writeFlag(body.sound[i]))
stream->writeRangedU32(packed? SimObjectId(body.sound[i]):
body.sound[i]->getId(),DataBlockObjectIdFirst,
DataBlockObjectIdLast);Add:
stream->write(freeCamPitch); stream->write(minCamPitchAngle); stream->write(maxCamPitchAngle); stream->write(freeCamYaw); stream->write(minCamYawAngle); stream->write(maxCamYawAngle);
Make sure they unpack in the same order. In VehicleData::unpackData(BitStream* stream) After:
for (i = 0; i < Body::MaxSounds; i++) {
body.sound[i] = NULL;
if (stream->readFlag())
body.sound[i] = (AudioProfile*)stream->readRangedU32(DataBlockObjectIdFirst,
DataBlockObjectIdLast);
}Add:
stream->read(&freeCamPitch); stream->read(&minCamPitchAngle); stream->read(&maxCamPitchAngle); stream->read(&freeCamYaw); stream->read(&minCamYawAngle); stream->read(&maxCamYawAngle);
Make them persistant, so you can change them from the datablock. In VehicleData::initPersistFields() After:
addField("collisionTol", TypeF32, Offset(collisionTol, VehicleData));
addField("contactTol", TypeF32, Offset(contactTol, VehicleData));Add:
addField("freeCamPitch", TypeBool, Offset(freeCamPitch, VehicleData));
addField("minCamPitchAngle", TypeF32, Offset(minCamPitchAngle, VehicleData));
addField("maxCamPitchAngle", TypeF32, Offset(maxCamPitchAngle, VehicleData));
addField("freeCamYaw", TypeBool, Offset(freeCamYaw, VehicleData));
addField("minCamYawAngle", TypeF32, Offset(minCamYawAngle, VehicleData));
addField("maxCamYawAngle", TypeF32, Offset(maxCamYawAngle, VehicleData));Now, give the camera rotation an initial value. In Vehicle::Vehicle() After:
mDelta.cameraOffset.set(0,0,0); mDelta.cameraVec.set(0,0,0); mDelta.cameraRot.set(0,0,0); mDelta.cameraRotVec.set(0,0,0);
Add:
mCameraRot = mDelta.cameraRot;
Replace Vehicle::interpolateTick function with the following. It smoothes out the camera, which is a good thing.
void Vehicle::interpolateTick(F32 dt)
{
Parent::interpolateTick(dt);
if(dt == 0.0f){
setRenderPosition(mDelta.pos, mDelta.rot[1]);
mCameraRot = mDelta.cameraRot; //vehicle camera added this line
} else {
mCameraRot = mDelta.cameraRot + mDelta.cameraRotVec * dt; //vehicle camera added this line
QuatF rot;
rot.interpolate(mDelta.rot[1], mDelta.rot[0], dt);
Point3F pos = mDelta.pos + mDelta.posVec * dt;
setRenderPosition(pos,rot);
}
mDelta.dt = dt;
}Add this function somewhere in the file. It tells the camera to rotate around Cam node, staying cameraMinDist-cameraMaxDist (in datablock) away from it. Usually constant at the max value. If you used the default ShapeBase version, the camera would rotate AT the Cam node.
void Vehicle::getRenderEyeTransform(MatrixF* mat)
{
// Eye transform in world space. We only use the eye position
// from the animation and supply our own rotation.
MatrixF pmat,xmat,zmat;
xmat.set(EulerF(mCameraRot.x, 0.0f, 0.0f));
zmat.set(EulerF(0.0f, 0.0f, mCameraRot.z));
pmat.mul(zmat,xmat);
F32 *dp = pmat;
F32* sp;
if (mDataBlock->eyeNode != -1)
{
sp = mShapeInstance->mNodeTransforms[mDataBlock->eyeNode];
}
else
{
Point3F center;
mObjBox.getCenter(¢er);
MatrixF eyeMat(true);
eyeMat.setPosition(center);
sp = eyeMat;
}
const Point3F& scale = getScale();
dp[3] = sp[3] * scale.x;
dp[7] = sp[7] * scale.y;
dp[11] = sp[11] * scale.z;
mat->mul(getRenderTransform(), pmat);
}In Vehicle::updateMove(const Move* move) Replace:
F32 y = move->yaw;
mSteering.x = mClampF(mSteering.x + y,-mDataBlock->maxSteeringAngle,
mDataBlock->maxSteeringAngle);
F32 p = move->pitch;
mSteering.y = mClampF(mSteering.y + p,-mDataBlock->maxSteeringAngle,
mDataBlock->maxSteeringAngle);with:
F32 lr = (move->x * M_PI_F/24); //move->x is the A/D keys!
mSteering.x = mClampF(mSteering.x + lr,-mDataBlock->maxSteeringAngle,
mDataBlock->maxSteeringAngle);
mDelta.cameraRotVec = mCameraRot;
F32 p = move->pitch;
if (p > M_PI_F)
p -= M_2PI_F;
if (mDataBlock->freeCamPitch)
mCameraRot.x += p;
else
mCameraRot.x = mClampF(mCameraRot.x + p, mDataBlock->minCamPitchAngle, mDataBlock->maxCamPitchAngle); //constrain to datablock values
F32 y = move->yaw;
if (y > M_PI_F)
y -= M_2PI_F;
if (mDataBlock->freeCamYaw)
mCameraRot.z += y;
else
mCameraRot.z = mClampF(mCameraRot.z + y, mDataBlock->minCamYawAngle, mDataBlock->maxCamYawAngle); //constrain to datablock values
mDelta.cameraRot = mCameraRot;
mDelta.cameraRotVec -= mCameraRot;Note the first line of code where it multiplies move->x * M_PI_F/24. The M_PI_F/24 can be changed to any value, it determines how quickly the wheels turn, higher values mean quicker transition to max turn, lower values mean slower transition. M_PI_F/24 was a good value for me and seems pretty standard. It's not jerky and it's not too slow as to impact gameplay.Now we just have to make sure that mCameraRot is sent across the network. In Vehicle::writePacketData(GameConnection *connection, BitStream *stream) After:
stream->writeFlag(mDisableMove); stream->setCompressionPoint(mRigid.linPosition);
Add:
stream->write(mCameraRot.x); stream->write(mCameraRot.z);
And unpack it then. In Vehicle::readPacketData(GameConnection *connection, BitStream *stream) After:
mDisableMove = stream->readFlag(); stream->setCompressionPoint(mRigid.linPosition);
Add:
stream->read(&mCameraRot.x); stream->read(&mCameraRot.z); mDelta.cameraRot = mCameraRot;
Likewise, in Vehicle::packUpdate(NetConnection *con, U32 mask, BitStream *stream) Change:
if (stream->writeFlag(mask & PositionMask))
{
stream->writeCompressedPoint(mRigid.linPosition);
mathWrite(*stream, mRigid.angPosition);
mathWrite(*stream, mRigid.linMomentum);
mathWrite(*stream, mRigid.angMomentum);
stream->writeFlag(mRigid.atRest);
}To be:
if (stream->writeFlag(mask & PositionMask))
{
stream->writeSignedFloat(mCameraRot.x / 3.0, 6); //vehicle cam added
stream->writeSignedFloat(mCameraRot.z / 3.0, 6); //vehicle cam added
stream->writeCompressedPoint(mRigid.linPosition);
mathWrite(*stream, mRigid.angPosition);
mathWrite(*stream, mRigid.linMomentum);
mathWrite(*stream, mRigid.angMomentum);
stream->writeFlag(mRigid.atRest);
}In Vehicle::unpackUpdate(NetConnection *con, BitStream *stream) After:
mSteering.x = (2 * yaw * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
mSteering.y = (2 * pitch * mDataBlock->maxSteeringAngle) - mDataBlock->maxSteeringAngle;
mDelta.move.unpack(stream);
if (stream->readFlag()) {Add:
mCameraRot.x = stream->readSignedFloat(6) * 3.0;//db integration - mDataBlock->maxLookAngle; mCameraRot.z = stream->readSignedFloat(6) * 3.0;//db integration - mDataBlock->maxLookAngle; mDelta.cameraRot = mCameraRot; mDelta.cameraRotVec.set(0.0f, 0.0f, 0.0f);
Compile the engine now. It's all done! If you wish to change the vehicle camera rotation constraints, add these fields to your VehicleData datablock:
freeCamPitch = false; minCamPitchAngle = -1.2; maxCamPitchAngle = 1.4; freeCamYaw = true; minCamYawAngle = -1.5; maxCamYawAngle = 1.5;
Remember, if you set freeCam___ to true, it will ignore the min and max values, and rotate the full 360 degrees. Values are in radians, where 0 is looking straight ahead (actually straight where Cam node is pointing), negative values are to the left and positive values are to the right.
You are done!
Note: This changes the control scheme to something different; it will work fine for wheeled vehicles, but for FlyingVehicles, the mouse will make the camera turn, rather than the vehicle turn. It's possible to make this the control scheme for only wheeled vehicles or whatever you like, it'll just take a bit more work. I leave that up to you, because I'm rather tired now, I spent 13 hours on this today.
Thanks to TomB who was quite helpful and enlightened me to the fact that mHead is actually the camera vector for the player.
Enjoy!
~John Eckhardt
About the author
Recent Blogs
• Text Control Linked to Player Health and Energy• Stun Player
• RTS Kit Basic Pathfinding
#3
07/03/2009 (12:27 pm)
John, I'm already married, and I'm a guy anyway. I can't spare my firstborn either, but I'm owing you a beer or two for this! Thank you.
#4
Parent::updateMove(move);
in the flying vehicle and hover vehicle
with the entire contents of the Vehicle::updateMove
you get the regular vehicle with cam look,
and the hover and flying vehicle with the original steering functionality.
testing it now...
07/03/2009 (2:51 pm)
it seems that if you replace the lineParent::updateMove(move);
in the flying vehicle and hover vehicle
with the entire contents of the Vehicle::updateMove
you get the regular vehicle with cam look,
and the hover and flying vehicle with the original steering functionality.
testing it now...
#5
Thanks for sharing mate
07/03/2009 (3:59 pm)
this looks to be a very good resource, i think i may just have to try it out. Thanks for sharing mate
#6
The first step in customizing would be to grab all the stuff we added to Vehicle::updateMove(const Move* move) and place it in WheeledVehicle.cc's updateMove. Then, change the other vehicle's updateMove to be whatever your control scheme is.
If you like, I can modify the resource to work with all vehicle types (Wheeled, Hover and Flying) later.
07/03/2009 (4:11 pm)
Like I said, this works well for wheeled vehicles, for hover vehicles it half-works, but for flying vehicles, it really messes up the controls.The first step in customizing would be to grab all the stuff we added to Vehicle::updateMove(const Move* move) and place it in WheeledVehicle.cc's updateMove. Then, change the other vehicle's updateMove to be whatever your control scheme is.
If you like, I can modify the resource to work with all vehicle types (Wheeled, Hover and Flying) later.
#7
yeah John,
that would be great.
I'm having so much fun with this resource
07/03/2009 (5:16 pm)
Quote: If you like, I can modify the resource to work with all vehicle types (Wheeled, Hover and Flying) later.
yeah John,
that would be great.
I'm having so much fun with this resource
#8
07/03/2009 (6:01 pm)
Meh, i cant do this. Can you just send me the files?
#9
that's a cool resource.
but i spot something that you don't understand well the input.
there are two types of control objects:
1. gameconnection ones
2. shapebase ones
gameconnection can set a 'base' control object.
The idea of it is this one object can act on the input directly.
shapebase control objects are 'sub' control objects.
they act(read) on the input indirectly through a 'base' one.
The meaning of it is like the base camera of the player.
The player is the base control object.
In its updateMove it sends the sends the move collection that way:
A base control object can use its own move.
So 'N' moves can be collected on a single base control object.
'N' control objects can use a single move.
Each of the 'N' objects can set their sub control objects.
'N' objects can control a sub one.
One object can control 'N' sub ones.
07/03/2009 (7:45 pm)
Johj,that's a cool resource.
but i spot something that you don't understand well the input.
there are two types of control objects:
1. gameconnection ones
2. shapebase ones
gameconnection can set a 'base' control object.
The idea of it is this one object can act on the input directly.
shapebase control objects are 'sub' control objects.
they act(read) on the input indirectly through a 'base' one.
The meaning of it is like the base camera of the player.
The player is the base control object.
In its updateMove it sends the sends the move collection that way:
// Manage the control object and filter moves for the player
Move pMove,cMove;
if (mControlObject) {
if (!move)
mControlObject->processTick(0);
else {
pMove = NullMove;
cMove = *move;
if (isMounted()) {
// Filter Jump trigger if mounted
pMove.trigger[2] = move->trigger[2];
cMove.trigger[2] = false;
}
if (move->freeLook) {
// Filter yaw/picth/roll when freelooking.
pMove.yaw = move->yaw;
pMove.pitch = move->pitch;
pMove.roll = move->roll;
pMove.freeLook = true;
cMove.freeLook = false;
cMove.yaw = cMove.pitch = cMove.roll = 0.0f;
}
mControlObject->processTick((mDamageState == Enabled)? &cMove: &NullMove);
move = &pMove;
}
}The camera i set to be a sub control object of the player.A base control object can use its own move.
So 'N' moves can be collected on a single base control object.
'N' control objects can use a single move.
Each of the 'N' objects can set their sub control objects.
'N' objects can control a sub one.
One object can control 'N' sub ones.
#10
I know I'm not a pro C++ coder, and would appreciate any advice especially about networking. Please explain?
07/03/2009 (10:26 pm)
Picasso, I am not sure what point you are trying to make. This code works if you use %player->setControlObject, I haven't tested it with gameconnection->setControlObject(), but I don't see any reason why it wouldn't work.I know I'm not a pro C++ coder, and would appreciate any advice especially about networking. Please explain?
#11
I have to say that you have used the most difficult way to work out your idea.
07/04/2009 (8:58 pm)
The idea of my point is that if you demarcate the vehicle and the camera as an individual control objects,you could provide the same effect(control the vehicle with the keys,camera pitch/yaw with the mouse) with 10-15 lines of code and disable all the network stuff and transform operations you used.I have to say that you have used the most difficult way to work out your idea.
#12
Btw: John, Thank you so much for this Resource!
07/05/2009 (12:58 am)
Picasso, Your method sounds more efficient than the above and any less overhead and ease-of-installation would be greatly appreciated from all of us Torque-ers, any chance of sharing the code with us?Btw: John, Thank you so much for this Resource!
Torque 3D Owner deepscratch