[quote=“JamesK89”]The character demo is nice and all but as most of us may know dynamic force based character controllers are highly unpredictable and don’t feel natural unless that is what you are going for.
That being said I’m just wondering if anyone has come up with a kinematic character controller that uses custom movement and collision logic for that traditional FPS feel and control (? la Quake, Half-Life, Unreal, etc…)?
I’ve been tinkering with Urho3D seeing if I can come up with something using ConvexCast but I am inexperienced with the library thus far so if someone has already written something and wants to share I’d appreciate it.[/quote]
Hi James ! I saw your private message so i decided to write here so other might find it useful too.
Kinematic Controller : It’s a bad idea , since the interaction withe the physics world won’t work properly and you will need workarounds which is a bad thing i think.
ConvexCast : Thats a bad idea either beacuse this way the character controller tries to solve collision detection externally , and you will need hell of a lot work to get things working (manual force calculations , contact manual manifold sorting etc) and still you will have problems.
Here’s how i did it in Infested :
I modified Urho’s existing character controller :
- Set Friction to 0.0f ! This will disable the ice-skate simulator
2.You need to modify the MOVE_FORCE , JUMP_FORCE etc variables to your game world and character size (this will take some time to have good results and sync the movement with walk and run animations)
3.The meat and potato.I rewrote the onGround_ check. Urho uses simple intersection test (handled in HandleNodeCollision) i handle it in FixedUpdate like this :
[code]void INFCharacterController::FixedUpdate(float timeStep)
{
// Reset grounded and falling flag for next frame
onGround_ = false;
falling = false;
Vector3 planeVelocity(velocity.x_, 0.0f, velocity.z_);
Vector3 brakeForce = -planeVelocity * BRAKE_FORCE;
if (controls_.IsDown(CTRL_FORWARD)) moveDir -= Vector3::FORWARD;
if (controls_.IsDown(CTRL_BACK)) moveDir -= Vector3::BACK * 2.0f;
if (controls_.IsDown(CTRL_LEFT)) moveDir -= Vector3::LEFT;
if (controls_.IsDown(CTRL_RIGHT)) moveDir -= Vector3::RIGHT;
// Normalize move vector so that diagonal strafing is not faster
if (moveDir.LengthSquared() > 0.0f) moveDir.Normalize();
Urho3D::Vector3 inairMoveForce = rot * moveDir * INAIR_MOVE_FORCE; // if the player jumps or falling this extra force is applied to allow slight movement
// Temporarily set collision flag to 0 to avoid self collisiojn check
unsigned int flag = body->GetCollisionMask();
body->SetCollisionMask(0);
// raycast from the center of the player downwards and see if we hit something in certain distance
Urho3D::PhysicsRaycastResult prr;
pw->SphereCast(prr,Urho3D::Ray(characterCenter , Urho3D::Vector3(0,-1,0)),radius , 1000.0f);
if (prr.distance_ <= distTreshold)
{
// this means we are hitting the floor now set the onGround and fall flag to
onGround_ = true;
falling = false;
inairMoveForce = Urho3D::Vector3::ZERO; // if we are moving no inair force
if (!jumping) moveForce = rot * moveDir * (controls_.IsDown(CTRL_BACK) ? MOVE_FORCE * 0.5f : MOVE_FORCE); // Moving slower backwards...
}
else
{
onGround_ = false;
falling = true;
}
// Reset collision flag back :
body->SetCollisionMask(flag);
// This is part of the jump code : If we are in jump mode we start counting and reset our “jumping” flag to false only if some time passed and linear velocity is smaller thatn 0 (e.g the character is falling !)
if (jumping)
{
jumpTimer += timeStep;
if (jumpTimer >= 0.5f && body->GetLinearVelocity().y_ <= 0.0f)
{
jumping = false;
jumpTimer = 0.0f;
}
}
// and finally apply the necessary forces calculated this frame : (the scaleForce is a normalized value and is used to slow down or speed up the player.
// For an example : if the player walks in water the scaleForce changes. The deeper that water is the smaller the scaleForce value is…
body->ApplyImpulse(moveForce * scaleForce); // The general move force
body->ApplyImpulse(inairMoveForce); // Move force applied if the player is jumping (its 0 if not jumping)
body->ApplyImpulse(brakeForce); // and apply some brake force to avoid the character to accelerate to infinity.
}[/code]
So in nuthshell : Check wether the player stands or floor or not by doing a raycast from the center of the player downwards.If yes simply apply move force if not , apply inair move force.
When doing SphereCast you have 3 variables to set up properly the : characterCenter (the center of your shape) the radius and distTreshold.
1 the center of the character is given in world space. (bodyWorldPos.y_ += characterHeight / 2);
2 . radius is a bit smaller than your capsule’s radius (eg if capsule radius is 10 then radius is 9 or 8)
3. distTreshold is the distance that shows what is smallest distance where the player is considered to standing on floor.This is usually a bit bigger number than your capsule’s half height.
if the half height of your character is 15 this value should be 18 (you can calculate it simply distTreshold = capsuleHalgHeight * 1.2f);
- jumping : Is done simply by checking if the player already jumping ? if not apply jumpForce , or do nothing
void INFCharacterController::jump()
{
if (!jumping && onGround_)
{
jumping = true;
RigidBody* body = GetComponent<RigidBody>();
float n = -(body->GetLinearVelocity().y_ * 0.5f);
body->ApplyImpulse(Vector3::UP * (jumpForce + n));
}
}
Also there is a bit of trick that should be mentioned : you have to take into account the current linear velocity when apply jumping , because the jump height while going downhill or uphill will be different due to physisc engine (To achieve the same jump height bigger force is needed when the character is going downwards and smaller if going upwards. Use the current linear velocity to calculate te required force).
PS : There’s a lot of variables you have to tweak to have good result the forces , timers , dimensions , mass , etc. But i suggest to have one unique scalar variable that can change everything.
calculate everything for characterScale = 1.0f; if your set thin value to 2.0f everything will be recalculated (forces masses sizes etc).So you don’t need additional code to handle different size enemies/players.
Stairs and slopes… the Phiscs Engines’ worst enemies So instead of writing some code to handle these situations i suggest to use some hidden helper meshes as i did
Wrong Geometry : you have to plan your levels wisely ! No matter how professional your character controller is , if the level geometry is wrong it will stuck in walls , narrow streets , invalid polygons etc.
Don’t hesitate to ask if you need further help
Regards