| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- #include "AnimationController.h"
- #include "Character.h"
- #include "MemoryBuffer.h"
- #include "PhysicsEvents.h"
- #include "PhysicsWorld.h"
- #include "RigidBody.h"
- #include "Scene.h"
- #include "SceneEvents.h"
- Character::Character(Context* context) :
- Component(context),
- onGround_(false),
- okToJump_(true),
- inAirTimer_(0.0f)
- {
- }
- void Character::OnNodeSet(Node* node)
- {
- // Component has been inserted into its scene node. Subscribe to events now
- SubscribeToEvent(node, E_NODECOLLISION, HANDLER(Character, HandleNodeCollision));
- SubscribeToEvent(GetScene()->GetComponent<PhysicsWorld>(), E_PHYSICSPRESTEP, HANDLER(Character, HandleFixedUpdate));
- }
- void Character::HandleNodeCollision(StringHash eventType, VariantMap& eventData)
- {
- // Check collision contacts and see if character is standing on ground (look for a contact that has near vertical normal)
- using namespace NodeCollision;
-
- MemoryBuffer contacts(eventData[P_CONTACTS].GetBuffer());
-
- while (!contacts.IsEof())
- {
- Vector3 contactPosition = contacts.ReadVector3();
- Vector3 contactNormal = contacts.ReadVector3();
- float contactDistance = contacts.ReadFloat();
- float contactImpulse = contacts.ReadFloat();
-
- // If contact is below node center and mostly vertical, assume it's a ground contact
- if (contactPosition.y_ < (node_->GetPosition().y_ + 1.0f))
- {
- float level = Abs(contactNormal.y_);
- if (level > 0.75)
- onGround_ = true;
- }
- }
- }
- void Character::HandleFixedUpdate(StringHash eventType, VariantMap& eventData)
- {
- using namespace PhysicsPreStep;
-
- float timeStep = eventData[P_TIMESTEP].GetFloat();
-
- /// \todo Could cache the components for faster access instead of finding them each frame
- RigidBody* body = GetComponent<RigidBody>();
- AnimationController* animCtrl = GetComponent<AnimationController>();
-
- // Update the in air timer. Reset if grounded
- if (!onGround_)
- inAirTimer_ += timeStep;
- else
- inAirTimer_ = 0.0f;
- // When character has been in air less than 1/10 second, it's still interpreted as being on ground
- bool softGrounded = inAirTimer_ < INAIR_THRESHOLD_TIME;
-
- // Update movement & animation
- const Quaternion& rot = node_->GetRotation();
- Vector3 moveDir = Vector3::ZERO;
- const Vector3& velocity = body->GetLinearVelocity();
- // Velocity on the XZ plane
- Vector3 planeVelocity(velocity.x_, 0.0f, velocity.z_);
-
- if (controls_.IsDown(CTRL_UP))
- moveDir += Vector3::FORWARD;
- if (controls_.IsDown(CTRL_DOWN))
- moveDir += Vector3::BACK;
- 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();
-
- // If in air, allow control, but slower than when on ground
- body->ApplyImpulse(rot * moveDir * (softGrounded ? MOVE_FORCE : INAIR_MOVE_FORCE));
-
- if (softGrounded)
- {
- // When on ground, apply a braking force to limit maximum ground velocity
- Vector3 brakeForce = -planeVelocity * BRAKE_FORCE;
- body->ApplyImpulse(brakeForce);
-
- // Jump. Must release jump control inbetween jumps
- if (controls_.IsDown(CTRL_JUMP))
- {
- if (okToJump_)
- {
- body->ApplyImpulse(Vector3::UP * JUMP_FORCE);
- okToJump_ = false;
- }
- }
- else
- okToJump_ = true;
- }
-
- // Play walk animation if moving on ground, otherwise fade it out
- if (softGrounded && !moveDir.Equals(Vector3::ZERO))
- animCtrl->PlayExclusive("Models/Jack_Walk.ani", 0, true, 0.2f);
- else
- animCtrl->Stop("Models/Jack_Walk.ani", 0.2f);
- // Set walk animation speed proportional to velocity
- animCtrl->SetSpeed("Models/Jack_Walk.ani", planeVelocity.Length() * 0.3f);
-
- // Reset grounded flag for next frame
- onGround_ = false;
- }
|