Game Development Community

dev|Pro Game Development Curriculum

Over-the-shoulder camera

by Kory Imaginism · 06/03/2013 (6:30 am) · 17 comments

player.h

after this code:

F32 minLookAngle; ///< Lowest angle (radians) the player can look
F32 maxLookAngle; ///< Highest angle (radians) the player can look
F32 maxFreelookAngle; ///< Max left/right angle the player can look

Add:
F32 cameraLag; ///< Amount of camera lag (lag += velocity * lag)
F32 cameraDecay;///< Rate at which camera returns to target pos.
Point2F cameraOffset;///< horizontal and vertical offset


After this code:

StateDelta delta;///< Used for interpolation on the client.
S32 mPredictionCount; ///< Number of ticks to predict

Add:
Point3F mCameraOffset; ///< 3rd person camera offset


After this code:
void setTransform(const MatrixF &mat);
void getEyeTransform(MatrixF* mat);
void getRenderEyeTransform(MatrixF* mat);
void getCameraParameters(F32 *min, F32 *max, Point3F *offset, MatrixF *rot);

Add:
void getCameraTransform(F32* pos,MatrixF* mat);


player.cpp


after this code:
renderFirstPerson = true;
pickupRadius = 0.0f;
minLookAngle = -1.4f;
maxLookAngle = 1.4f;
maxFreelookAngle = 3.0f;
maxTimeScale = 1.5f;


add:
cameraLag = 0;
cameraDecay = 0;
cameraOffset = Point2F( 0,0 );



after this code:
addField("renderFirstPerson", TypeBool, Offset(renderFirstPerson, PlayerData));
addField("minLookAngle", TypeF32, Offset(minLookAngle, PlayerData));
addField("maxLookAngle", TypeF32, Offset(maxLookAngle, PlayerData));
addField("maxFreelookAngle", TypeF32, Offset(maxFreelookAngle, PlayerData));

add:
addField("cameraLag", TypeF32, Offset(cameraLag, PlayerData));
addField("cameraDecay", TypeF32, Offset(cameraDecay, PlayerData));
addField("cameraOffset", TypePoint2F, Offset(cameraOffset, PlayerData));


after this code:

delta.pos = mAnchorPoint = Point3F(0,0,100);
delta.rot = delta.head = Point3F(0,0,0);
delta.rotOffset.set(0.0f,0.0f,0.0f);
delta.warpOffset.set(0.0f,0.0f,0.0f);
delta.posVec.set(0.0f,0.0f,0.0f);
delta.rotVec.set(0.0f,0.0f,0.0f);
delta.headVec.set(0.0f,0.0f,0.0f);
delta.warpTicks = 0;
delta.dt = 1.0f;
delta.move = NullMove;

add:

mCameraOffset.set(0,0,0);


after this code:

// update camera effects. Definitely need to find better place for this - bramage
if( isControlObject() )
{
if( mDamageState == Disabled || mDamageState == Destroyed )
{
// clear out all camera effects being applied to player if dead
gCamFXMgr.clear();
}
}


add:

mCameraOffset -= (mCameraOffset * mDataBlock->cameraDecay +
getVelocity() * mDataBlock->cameraLag) * dt;

after this code:

void Player::getCameraParameters(F32 *min,F32* max,Point3F* off,MatrixF* rot)
{
if (!mControlObject.isNull() && mControlObject == getObjectMount()) {
mControlObject->getCameraParameters(min,max,off,rot);
return;
}
const Point3F& scale = getScale();
*min = mDataBlock->cameraMinDist * scale.y;
*max = mDataBlock->cameraMaxDist * scale.y;
off->set(0.0f, 0.0f, 0.0f);
rot->identity();
}


add:

void Player::getCameraTransform(F32* pos,MatrixF* mat)
{
// Returns camera to world space transform
// Handles first person / third person camera position
if (isServerObject() && mShapeInstance)
mShapeInstance->animateNodeSubtrees(true);

if (*pos == 0) {
getRenderEyeTransform(mat);
return;
}

// Get the shape's camera parameters.
F32 min,max;
MatrixF rot;
Point3F offset;
getCameraParameters(&min,&max,&offset,&rot);

// Start with the current eye position
MatrixF eye;
getRenderEyeTransform(&eye);

// Build a transform that points along the eye axis
// but where the Z axis is always up.
MatrixF cam(1);
VectorF x,y,z(0,0,1);
eye.getColumn(1, &y);
mCross(y, z, &x);
x.normalize();
mCross(x, y, &z);
z.normalize();
cam.setColumn(0,x);
cam.setColumn(1,y);
cam.setColumn(2,z);
mat->mul(cam,rot);

// Camera is positioned straight back along the eye's -Y axis.
// A ray is cast to make sure the camera doesn't go through
// anything solid.
VectorF vp,vec;
vp.x = vp.z = 0;
vp.y = -(max - min) * *pos;
vp += VectorF( mDataBlock->cameraOffset.x, mDataBlock->cameraOffset.y, 0.0f ); //kenan
eye.mulV(vp,&vec);

// Use the camera node as the starting position if it exists.
Point3F osp,sp;
if (mDataBlock->cameraNode != -1)
{
mShapeInstance->mNodeTransforms[mDataBlock->cameraNode].getColumn(3,&osp);
getRenderTransform().mulP(osp,&sp);
}
else
eye.getColumn(3,&sp);

// Make sure we don't hit ourself...
disableCollision();
if (isMounted())
getObjectMount()->disableCollision();

// Cast the ray into the container database to see if we're going
// to hit anything.
RayInfo collision;
Point3F ep = sp + vec + offset + mCameraOffset;
if (mContainer->castRay(sp, ep,
~(WaterObjectType | GameBaseObjectType | DefaultObjectType),
&collision) == true) {

// Shift the collision point back a little to try and
// avoid clipping against the front camera plane.
F32 t = collision.t - (-mDot(vec, collision.normal) / vec.len()) * 0.1;
if (t > 0.0f)
ep = sp + offset + mCameraOffset + (vec * t);
else
eye.getColumn(3,&ep);
}
mat->setColumn(3,ep);

// Re-enable our collision.
if (isMounted())
getObjectMount()->enableCollision();
enableCollision();

// Apply Camera FX.
mat->mul( gCamFXMgr.getTrans() );
}





datablocks/player.cs

after this code:

datablock PlayerData(DefaultPlayerData)
{
renderFirstPerson = true;

className = Armor;
shapeFile = "art/shapes/actors/Gideon/gideon.dts";
cameraMaxDist = 1.5;//3;
computeCRC = true;

canObserve = true;
cmdCategory = "Clients";

cameraDefaultFov = 100.0;
cameraMinFov = 5.0;
cameraMaxFov = 120.0;

add:

cameraOffset = "0.4 0.4"; // offset from camera mount point
cameraLag = 0.16; // Velocity lag of camera
cameraDecay = 1.85; // Decay per sec. rate of velocity lag



And that�¢ï¿½ï¿½s it.
cameraOffset modifies the horizontal and vertical offset.
cameraLag determines how fast it keeps up with the player based on the player�¢ï¿½ï¿½s velocity, higher value is slower to catch up.
cameraDecay determines how fast the camera catches up to the player based on offset distance, higher is faster.

Those are some sample numbers above that appear to works pretty well. The cameraLag and cameraDecay were added to give the camera a more natural feel but if you want it very rigid then set those two values to 0.0

Lastly, cameraMaxDist, already in player datablock determines how far from the player the 3rd person camera is. I set it to 1.5 to get a really close over the shoulder camera when I was testing.


VIDEO:



#1
06/03/2013 (6:56 am)
Very nice of you to share this code, I've always wanted a Gears of War/Resident Evil style camera. I've just tried it and it works great, thank you again.
#2
06/03/2013 (7:16 am)
Your welcome glad I could help. BTW I haven't check it past 1.2, could you let me know if the zoom works when the fire button is pushed?

Thanks
#3
06/03/2013 (7:26 am)
Zoom appears to be working fine in a clean 3.0 build.
#4
06/03/2013 (2:49 pm)
Where's the crosshair?
#5
06/03/2013 (3:52 pm)
You my have to find a way to add it, it was one of the issues I had as well. Just haven't had time to add it.
#6
06/03/2013 (4:09 pm)
I got the crosshair visible in third person by commenting out this if statement in guiCrossHairHud.cpp
if (!control || !(control->getTypeMask() & ObjectMask) || !conn->isFirstPerson())
return;
#7
06/03/2013 (8:46 pm)
Thanks Kory. And thanks Paul, for the crosshair implementation and feedback re 3.0 :D
#8
06/09/2013 (7:16 pm)
haha this looks familiar, didn't I set this up for you?
#9
06/09/2013 (8:14 pm)
Kenan,
That would be right!
#11
06/14/2013 (11:01 am)
Nice touch to adjust bullet trajectory so it still hits the crosshairs. Good work!
#12
06/17/2013 (9:14 pm)
Thanks for this, have it working in 2.0/AFX and 3.0...
#13
07/19/2013 (12:56 pm)
Does this work in multiplayer? I have added it and it works for the server but for the client the camera doesn't offset like the server.
#14
08/20/2013 (7:32 am)
I got this working on Multiplayer, I don't have the code available at this post, however. If you go through Player.cpp and look at the code around the new camera lines and search for those further down player.cpp you should see what code needs to be added (IE copy paste those stream codes and change them to the camera ones).
#15
08/22/2013 (8:25 pm)
Glenn
Whatever changes you did to get it working with multiplayer please post them whenever you get a chance. I wouldn't mine getting it working with multiplayer myself.
#16
08/24/2013 (6:35 am)
Add
void PlayerData::packData()
stream->write(minLookAngle);
   stream->write(maxLookAngle);
   stream->write(maxFreelookAngle);
   stream->write(cameraLag); //ADDED
   stream->write(cameraDecay); //ADDED
Add
void PlayerData::unpackData()
stream->read(&minLookAngle);
   stream->read(&maxLookAngle);
   stream->read(&maxFreelookAngle);
   stream->read(&cameraLag); //ADDED
   stream->read(&cameraDecay); //ADDED
I'm fairly sure that is it.
#17
03/19/2014 (9:19 pm)
great work..... t3d 3.5.1mit
#18
03/19/2014 (9:21 pm)
Did you test in multiplayer as well, Donnie? Thanks