#include <Urho3D/Core/Context.h>
#include <Urho3D/Graphics/AnimationController.h>
#include <Urho3D/IO/MemoryBuffer.h>
#include <Urho3D/Physics/PhysicsEvents.h>
#include <Urho3D/Physics/PhysicsWorld.h>
#include <Urho3D/Physics/RigidBody.h>
#include <Urho3D/Scene/Scene.h>
#include <Urho3D/Scene/SceneEvents.h>
#include <Urho3D/Input/Input.h>
#include <Urho3D/Graphics/DrawableEvents.h>
#include <Urho3D/Graphics/AnimatedModel.h>
#include <Urho3D/Audio/SoundSource3D.h>
#include <Urho3D/Audio/Sound.h>
#include <Urho3D/Audio/AudioEvents.h>
#include <Urho3D/Resource/ResourceCache.h>
#include <Urho3D/Graphics/ParticleEffect.h>
#include <Urho3D/Graphics/ParticleEmitter.h>
#include "Character.h"
#include "GameObject.h"
Character::Character(Context* context) :
LogicComponent(context),
onGround_(false),
okToJump_(true),
inAirTimer_(0.0f)
{
// Only the physics update event is needed: un-subscribe from the rest for optimization
SetUpdateEventMask(USE_FIXEDUPDATE);
}
void Character::RegisterObject(Context* context)
{
context->RegisterFactory<Character>();
// These macros register the class attributes to the Context for automatic load / save handling.
// We specify the Default attribute mode which means it will be used both for saving into file, and network replication
URHO3D_ATTRIBUTE("Controls Yaw", float, controls_.yaw_, 0.0f, AM_DEFAULT);
URHO3D_ATTRIBUTE("Controls Pitch", float, controls_.pitch_, 0.0f, AM_DEFAULT);
URHO3D_ATTRIBUTE("On Ground", bool, onGround_, false, AM_DEFAULT);
URHO3D_ATTRIBUTE("OK To Jump", bool, okToJump_, true, AM_DEFAULT);
URHO3D_ATTRIBUTE("In Air Timer", float, inAirTimer_, 0.0f, AM_DEFAULT);
}
void Character::Start()
{
// Component has been inserted into its scene node. Subscribe to events now
SubscribeToEvent(GetNode(), E_NODECOLLISION, URHO3D_HANDLER(Character, HandleNodeCollision));
SubscribeToEvent(GetNode()->GetChild("Jack",true), E_ANIMATIONTRIGGER, URHO3D_HANDLER(Character, HandleAnimationTrigger));
}
void Character::FixedUpdate(float timeStep)
{
/// \todo Could cache the components for faster access instead of finding them each frame
RigidBody* body = GetComponent<RigidBody>();
AnimationController* animCtrl = node_->GetComponent<AnimationController>(true);
Input* input = GetSubsystem<Input>();
// 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.5f, 0.0f, velocity.z_ * 0.5f);
if (controls_.IsDown(CTRL_FORWARD))
moveDir += Vector3::FORWARD;
if (controls_.IsDown(CTRL_BACK))
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 between jumps
if (controls_.IsDown(CTRL_JUMP))
{
if (okToJump_)
{
body->ApplyImpulse(Vector3::UP * JUMP_FORCE);
okToJump_ = false;
animCtrl->PlayExclusive("Models/stand_sword_airborne.ani", 0, false, 0.2f);
}
}
else
okToJump_ = true;
}
// On air
if (!onGround_) {
// Falling a lot
if (inAirTimer_ > 2.75f) {
animCtrl->PlayExclusive("Models/Falling.ani", 0, true, 0.2f);
// Falling a bit
}
else {
animCtrl->PlayExclusive("Models/stand_support_high_airborne.ani", 0, true, 0.2f);
}
// On ground
}
else {
// On ground with movement
if (softGrounded && !moveDir.Equals(Vector3::ZERO)) {
// Moving forward
if (softGrounded && moveDir.Equals(Vector3::FORWARD)) {
animCtrl->PlayExclusive("Models/bogeyman_movefront.ani", 0, true, 0.2f);
animCtrl->SetSpeed("Models/bogeyman_movefront.ani", planeVelocity.Length() * 0.3f);
}
// Moving back
if (softGrounded && moveDir.Equals(Vector3::BACK)) {
animCtrl->PlayExclusive("Models/walk_back.ani", 0, true, 0.2f);
animCtrl->SetSpeed("Models/walk_back.ani", planeVelocity.Length() * 0.3f);
}
// Moving left
if (softGrounded && moveDir.Equals(Vector3::LEFT)) {
animCtrl->PlayExclusive("Models/walk_left.ani", 0, true, 0.2f);
animCtrl->SetSpeed("Models/walk_left.ani", planeVelocity.Length() * 0.3f);
}
// Moving right
if (softGrounded && moveDir.Equals(Vector3::RIGHT)) {
animCtrl->PlayExclusive("Models/walk_right.ani", 0, true, 0.2f);
animCtrl->SetSpeed("Models/walk_right.ani", planeVelocity.Length() * 0.3f);
}
// On ground idle
}
else {
animCtrl->PlayExclusive("Models/bogeyman_idle.ani", 0, true, 0.2f);
}
}
if (controls_.IsDown(CTRL_MELEE)) {
animCtrl->PlayExclusive("Models/hammer_slam.ani", 0, false, 1.0f); // Try tweaking the last value.
animCtrl->SetSpeed("Models/hammer_slam.ani", 1.0f); // Try tweaking the last value.
}
if (controls_.IsDown(CTRL_CROUCH) && softGrounded) {
animCtrl->PlayExclusive("Models/crouch_sword_idle.ani", 0, false, 0.25f); // Try tweaking the last value.
animCtrl->SetSpeed("Models/crouch_sword_idle.ani", 0.5f); // Try tweaking the last value.
}
// Reset grounded flag for next frame
onGround_ = false;
}
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 pointing up, assume it's a ground contact
if (contactPosition.y_ < (node_->GetPosition().y_ + 1.0f))
{
float level = contactNormal.y_;
if (level > 0.75)
onGround_ = true;
}
}
}
void Character::HandleAnimationTrigger(StringHash eventType, VariantMap& eventData)
{
DelayedStart();
using namespace AnimationTrigger;
AnimatedModel* model = node_->GetComponent<AnimatedModel>();
if (model)
{
exit(0);
AnimationState* state = model->GetAnimationState(eventData[P_NAME].GetString());
if (state == NULL)
return;
Node* bone = node_->GetChild(eventData[P_DATA].GetString(), true);
if (bone != NULL)
GameObject::SpawnParticleEffect(bone, node_->GetWorldPosition(), "Particle/Smoke.xml", 1, LOCAL);
GameObject::SpawnSound(bone, node_->GetWorldPosition(), "Sounds/BigExplosion.wav", 3);
}
}
Here is my Character.cpp. I have tried quite a few things. The XML file is fine. I can’t get the animation trigger to work. I believe is may be what @Sinoid said. I’m not sure how to fix it though.