Thought I'd make a new thread to detail my findings on a manual character controller (CC), as I think I've cracked it.
A quick explanation for anyone who reads this and hasn't seen mine and Matty's discussions: character controllers are great for quickly getting a character to run around the level, but as soon as you want your CCs to be pushed around by other objects (moving platforms, lifts, boulders, moving walls etc) they're useless, as they don't offer this functionality. They can't be affected by the movement of dynamic actors in the scene.
I tried quite a long time to add in custom code to get them to do what I wanted, but in the end, and after some research, the general consensus seems to be to not use CCs. They're fine for quick testing and simple games, but when you need to start doing more, drop them. Don't try to force them.
My Solution
The general principle is a dynamic capsule, which cannot rotate in anyway. It hovers above the ground (to allow it to climb stairs and not get caught on them), and it's velocity is manually set, rather than applying forces, so you have total control over the speed. You could use forces. I'm sure that'd work too, although I would expect the speeds would be hard to predict.
Firstly, you need to setup your own capsule to act as the CC. Here's mine, with some custom functions but it should give you an idea.
int intTempObj = GenF::getFreeObject();
dbMakeObjectBox(intTempObj,0.35,0.8,0.35); // Make a box to attach our capsule too
// Custom function to offset all the vertices up, to shift the origin of the box downwards
GenF::offsetObject(intTempObj,0,0.8,0);
dynMakeCapsule(intTempObj,0.8f,0.25f,EdGlobals::DENSITY_PLAYER);
// Now assign capsule object to your player model
dynSetToNewObject(dbObj,0,intTempObj,0);
// Don't allow it to rotate in any way
dynFreezeRotationX(dbObj,true);
dynFreezeRotationY(dbObj,true);
dynFreezeRotationZ(dbObj,true);
// Switch off gravity
dynEnableGravity(dbObj,false);
dbDeleteObject(intTempObj);
// Assign it into its own collision group
dynSetGroup(dbObj,PhysicsLibrary::COLLISIONGROUP_PLAYER);
The height of the capsule and the vertical shift of the box is key. This will define where the capsule sits within your player model. You don't want the bottom of the capsule to be at the base of the player's feet, as we're going to hover the capsule above the ground to allow the player to climb stairs. So basically, this area is where you make adjustments for the steepness of stairs you want to be able to climb.
Here's the offset object function, in case it's useful to you:
void GenF::offsetObject(int dbObj, float fltX, float fltY, float fltZ){
float x,y,z;
dbPerformChecklistForObjectLimbs(dbObj);
for(int l=0; l<dbChecklistQuantity(); l++){
dbLockVertexDataForLimb(dbObj,l,2);
for(int v=0; v<dbGetVertexDataVertexCount(); v++){
x = dbGetVertexDataPositionX(v);
y = dbGetVertexDataPositionY(v);
z = dbGetVertexDataPositionZ(v);
dbSetVertexDataPosition(v,fltX+x,fltY+y,fltZ+z);
}
dbUnlockVertexData();
}
dbCalculateObjectBounds(dbObj);
}
Now your custom controller is setup, you move it around the level using this command:
Quote: "dynSetLinearVelocity(dbObj,fltXSpd,fltYSpd,fltZSpd);"
How you calculate your X,Y,Z speeds is up to you and there are loads of different ways of doing this.
The last step is to hover the controller above the ground so that it can move smoothly and climb stairs, and it also allows you to add jumping. The basic principle is to cast a ray down from the character capsule and find out where the floor is. Then reposition the capsule so it's hovering above the floor if it's too close. Here's my code stub for that ...
// Get a position inside our object. We don't want the base. We want a point above the base.
dbSetVector3(VECTOR3_1,dbObjectPositionX(dbObj),dbObjectPositionY(dbObj)+0.5,dbObjectPositionZ(dbObj));
dbSetVector3(VECTOR3_2,0,-1,0); // Set vector down
dynRaycastSetMaxDistance(2); // We only need to ray cast down a few units
// Group mask needs to represent all the object groups your CC can stand on,
// and EXCLUDE your character's group
dynRaycastClosestShape(VECTOR3_1,VECTOR3_2,NX_ALL_SHAPES,groupMask);
if (dynRaycastHitGetData()){ // The floor was hit by the ray
dynRaycastHitGetWorldImpact(VECTOR3_1);
float hitY = dbYVector3(VECTOR3_1);
float yDistDiff = dbObjectPositionY(dbObj) - hitY;
// If the character controller is below the ground, position it back on the ground
// In reality, because of our initial setup, positioning it on the ground actually
// has the capsule above the surface
if (yDistDiff < 0){
dynSetPosition(dbObj,dbObjectPositionX(dbObj),hitY,dbObjectPositionZ(dbObj));
}
// If the player is very close to the ground, provide the opportunity to jump
// or set the y speed to 0. It's important to have this little buffer of space,
// otherwise you get jittering.
if (yDistDiff < 0.1){
if (GenF::KeyPressed(KEYTYPE_PLAYER_JUMP,false)){ // My jumping related stuff
fltYSpd = fltJumpSpeed;
}else{
fltYSpd = 0;
}
// In all other instances, the player is above the ground, so put your code for
// gravity in here
}else{
fltYSpd -= fltFallAccel * EdGlobals::gameTimer.fltElapsed;
}
}else{
fltYSpd -= fltFallAccel * EdGlobals::gameTimer.fltElapsed;
}
With your own movement code for x,y,z speeds, that should have you moving about.
The last thing is character rotation. Since the yRotation of the capsule is locked, you will have to manually set the yRotation of your character model every loop. Dynamix will overwrite this rotation and set it to the rotation of the capsule every loop, so you'll have to keep track of the angles in variables and set the model yourself.
So to summarize:
1. Configure a capsule for your character, such that, with the origin of the character model at it's feet and on the floor, the capsule is floating above ground height. Lock out all rotations and gravity.
2. Move the capsule around by setting its velocity.
3. Make sure it hovers above the ground by casting a ray downwards and repositioning it when it gets too close to the floor.
And that's it. I appreciate this isn't a working example but hopefully these pointers will give you an idea of how to implement your own controller and do away with the limiting standard PhysX CC.