So this may seem odd. I tried to give my AI class an animation trigger for movements and such, but for some reason their animation trigger fires on my Character class. So for example my AI class has an animation sound trigger for their footsteps which is a completely different sound for testing purposes. When they move nothing happens. When my character moves it plays their sound. I think I made a mistake in my class somewhere, but not to sure.
AIMelee.h
#pragma once
#include <Urho3D/Input/Controls.h>
#include <Urho3D/Scene/LogicComponent.h>
#include <Urho3D/Audio/SoundSource3D.h>
#include <Urho3D/Audio/Sound.h>
#include <Urho3D/Audio/AudioEvents.h>
using namespace Urho3D;
/// Character component, responsible for physical movement according to controls, as well as animation.
class AIMelee : public LogicComponent
{
URHO3D_OBJECT(AIMelee, LogicComponent);
public:
/// Construct.
AIMelee(Context* context):
LogicComponent(context),
elapsedTime_(0.0f)
{
SetUpdateEventMask(USE_FIXEDUPDATE);
SetUpdateEventMask(USE_UPDATE);
}
/// Register object factory and attributes.
static void RegisterObject(Context* context);
/// Handle startup. Called by LogicComponent base class.
virtual void Start();
virtual void DelayedStart();
/// Handle physics world update. Called by LogicComponent base class.
virtual void FixedUpdate(float timestep);
virtual void Update(float timestep);
virtual void PlaySound(const String& soundName);
virtual void HandleAnimationTriggerZombie(StringHash eventType, VariantMap& eventData);
virtual void loadResources();
virtual void bigHead(StringHash eventType, VariantMap& eventData);
virtual void melee(StringHash eventType, VariantMap& eventData);
virtual void setHealth(int h)
{
health = h;
}
int getHealth()
{
return health;
}
virtual void setDeath(bool d)
{
dead_ = d;
}
bool getDeath()
{
return dead_;
}
/// Movement controls. Assigned by the main program each frame.
Controls controls_;
SoundSource3D* soundSrc;
ResourceCache* cache;
AnimationController* idle;
SharedPtr<Node> handboneNode;
WeakPtr<Scene> scene_;
DebugRenderer* debug;
AnimatedModel* swordObject;
private:
/// Handle physics collision event.
void HandleNodeCollision(StringHash eventType, VariantMap& eventData);
/// Grounded flag for movement.
bool onGround_;
/// Grounded flag for movement.
bool dead_ = false;
/// Jump flag.
bool okToJump_;
/// In air timer. Due to possible physics inaccuracy, character can be off ground for max. 1/10 second and still be allowed to move.
float inAirTimer_;
/// elapsed time
float elapsedTime_;
int health;
SharedPtr<Node> head;
RigidBody* body;
CollisionShape* shape;
};
AIMelee.cpp
#include // std::cout
#include
#include <Urho3D/Core/CoreEvents.h>
#include <Urho3D/Core/Context.h>
#include <Urho3D/Graphics/AnimationController.h>
#include <Urho3D/Graphics/AnimatedModel.h>
#include <Urho3D/Graphics/DrawableEvents.h>
#include <Urho3D/IO/MemoryBuffer.h>
#include <Urho3D/Physics/PhysicsEvents.h>
#include <Urho3D/Physics/PhysicsWorld.h>
#include <Urho3D/Physics/RigidBody.h>
#include <Urho3D/Physics/CollisionShape.h>
#include <Urho3D/Scene/Scene.h>
#include <Urho3D/Scene/SceneEvents.h>
#include <Urho3D/Input/Input.h>
#include <Urho3D/Audio/Sound.h>
#include <Urho3D/Audio/SoundSource3D.h>
#include <Urho3D/Audio/SoundListener.h>
#include <Urho3D/Audio/AudioEvents.h>
#include <Urho3D/Resource/ResourceCache.h>
#include <Urho3D/Navigation/CrowdAgent.h>
#include <Urho3D/Graphics/DebugRenderer.h>
#include <Urho3D/IO/Log.h>
#include "AIMelee.h"
void AIMelee::RegisterObject(Context* context)
{
context->RegisterFactory<AIMelee>();
}
void AIMelee::Start()
{
loadResources();
// Component has been inserted into its scene node. Subscribe to events now
SubscribeToEvent(E_NODECOLLISION, URHO3D_HANDLER(AIMelee, HandleNodeCollision));
}
void AIMelee::DelayedStart()
{
SubscribeToEvent(E_ANIMATIONTRIGGER, URHO3D_HANDLER(AIMelee, HandleAnimationTriggerZombie));
SubscribeToEvent(E_POSTRENDERUPDATE, URHO3D_HANDLER(AIMelee, bigHead));
}
void AIMelee::FixedUpdate(float timeStep)
{
}
/// Handle scene update. Called by LogicComponent base class.
void AIMelee::Update(float timeStep)
{
CrowdAgent* agent = node_->GetComponent<CrowdAgent>();
if (health <= 0 && dead_ == false)
{
PlaySound("Sounds/death_mjr.ogg");
idle->PlayExclusive("Models/combat_landing_dead.ani", 0, false, 0.5f); // Try tweaking the last value.
if (node_->HasComponent<CrowdAgent>())
{
agent->Remove();
}
shape->SetCapsule(15.0f, 5.0f, Vector3(0.0f, 5.0f, 0.0f));
body->SetMass(1000000.0f);
dead_ = true;
}
float duration_ = 2.5f;
elapsedTime_ += timeStep;
if (dead_ == true)
{
// Disappear when duration expired
if (duration_ >= 0)
{
duration_ -= timeStep;
if (duration_ <= 0)
{
node_->Remove();
}
}
}
return;
}
void AIMelee::loadResources()
{
cache = GetSubsystem<ResourceCache>();
// Create the rendering component + animation controller
AnimatedModel* object = node_->CreateComponent<AnimatedModel>();
object->SetModel(cache->GetResource<Model>("Models/masterchief.mdl"));
object->ApplyMaterialList("Materials/zombie.txt");
object->SetOccludee(true);
idle = node_->CreateComponent<AnimationController>();
handboneNode = node_->GetChild("right_hand_marker", true);
swordObject = handboneNode->CreateComponent<AnimatedModel>();
swordObject->SetModel(cache->GetResource<Model>("Models/plasma_sword.mdl"));
swordObject->ApplyMaterialList("Materials/plasma_sword.txt");
// Create rigid body, and set non-zero mass so that the body becomes dynamic
body = node_->CreateComponent<RigidBody>();
body->SetCollisionLayer(1);
body->SetMass(1.0f);
// Set zero angular factor so that physics doesn't turn the character on its own.
// Instead we will control the character yaw manually
body->SetAngularFactor(Vector3::ZERO);
// Set the rigid body to signal collision also when in rest, so that we get ground collisions properly
body->SetCollisionEventMode(COLLISION_ALWAYS);
// Set a capsule shape for collision
shape = node_->CreateComponent<CollisionShape>();
shape->SetCapsule(40.0f, 70.0f, Vector3(0.0f, 35.0f, 0.0f));
// Create a CrowdAgent component and set its height and realistic max speed/acceleration. Use default radius
auto* agent = node_->CreateComponent<CrowdAgent>();
agent->SetHeight(1.0f);
agent->SetMaxSpeed(9.0f);
agent->SetMaxAccel(9.0f);
}
void AIMelee::bigHead(StringHash eventType, VariantMap& eventData)
{
head = node_->GetChild("head", true);
head->SetScale(Vector3(2.5f, 2.5f, 2.5f));
}
void AIMelee::melee(StringHash eventType, VariantMap& eventData)
{
scene_ = node_->GetScene();
debug = scene_->GetComponent<DebugRenderer>();
CollisionShape* shape_ = handboneNode->CreateComponent<CollisionShape>();
shape_->SetCapsule(2.0f, 2.0f, Vector3::ZERO, Quaternion::IDENTITY);
PhysicsRaycastResult raycResult;
auto* physicsWorld = scene_->GetComponent<PhysicsWorld>();
const Vector3 start = handboneNode->GetWorldPosition();
const Vector3 end = start + (Vector3::FORWARD * 100.0f);
physicsWorld->ConvexCast(raycResult, shape_, start, Quaternion::IDENTITY, end, Quaternion::IDENTITY);
debug->AddCylinder(handboneNode->GetWorldPosition(), 0.025f, 0.025f, Color::RED, false);
RigidBody* resultBody{ raycResult.body_ };
AIMelee* _Node;
int damage = 100;
if (resultBody)
{
Node* resultNode{ resultBody->GetNode() };
if (_Node = resultNode->GetDerivedComponent<AIMelee>())
{
_Node->setHealth(_Node->getHealth() - damage);
resultBody->ApplyImpulse(Vector3(1.0f, 1.0f, 1.0f)* 1.0f);
}
}
}
void AIMelee::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;
RigidBody* body = node_->GetComponent<RigidBody>(true);
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 AIMelee::PlaySound(const String& soundName)
{
ResourceCache* cache = GetSubsystem<ResourceCache>();
Sound* sound = cache->GetResource<Sound>(soundName);
soundSrc = node_->CreateComponent<SoundSource3D>();
soundSrc->SetNearDistance(5.0f);
soundSrc->SetFarDistance(15.0f);
soundSrc->SetGain(1.0f);
soundSrc->SetSoundType(SOUND_EFFECT); // optional
soundSrc->Play(sound);
soundSrc->SetAutoRemoveMode(REMOVE_COMPONENT);
}
void AIMelee::HandleAnimationTriggerZombie(StringHash eventType, VariantMap& eventData)
{
using namespace AnimationTrigger;
AnimatedModel* model = node_->GetComponent<AnimatedModel>();
if (model)
{
AnimationState* state = model->GetAnimationState(eventData[P_NAME].GetString());
if (state == NULL)
return;
Node* bone = node_->GetChild(eventData[P_DATA].GetString(), true);
URHO3D_LOGDEBUG("Made it!");
if (bone != NULL)
PlaySound("Sounds/NutThrow.wav");
}
}