#ifndef CHARACTER_CLASS #define CHARACTER_CLASS #include #include #include #include #include #include "t.h" #include "Rig.h" #include "Object.h" #include "Animator.h" #include "CharacterAction.h" class Character; class CharacterManager; class CharacterMind; class Ragdoll; class Physics; class p; // The physical side of a character: where it is, its physics body, its animation, // and carrying out whatever actions its mind sends it. class CharacterBody : public t_package { public: Rig* rig = nullptr; p* physics = nullptr; // made by Character::CreatePhysicsBody, its box is t's scale std::vector animators; Animator* animator = nullptr; // the base layer, always fully on: standing idle Animator* walkAnimator = nullptr; // on top of the idle, faded in while walking Animator* actionAnimator = nullptr; // one-shots on top of both, see PlayAction float walkSpeed = 2.34f; // m/s on flat ground float turnSpeed = 12.0f; // how quickly it turns to face where it's walking float walkFadeSpeed = 8.0f; // how quickly it fades between idle and walking CharacterBody() { animator = AddAnimator(0); animator->blendWeight = 1.0f; // base always fully visible } Animator* AddAnimator(int weight) { Animator* a = new Animator(); a->weight = weight; if (rig) a->SetRig(rig); animators.push_back(a); std::sort(animators.begin(), animators.end(), [](Animator* a, Animator* b) { return a->weight < b->weight; }); return a; } // The skinning matrices for this frame, from every animator layered together. Worked out in Step. // Base (weight 0) is the foundation; each higher-weight animator blends in by its blendWeight, // but only on the bones its animation has tracks for, so an arm-only clip leaves the legs walking. const std::vector& GetBlendedBoneMatrices() const { return blendedBones.empty() ? animator->GetFinalBoneMatrices() : blendedBones; } void SetRig(Rig* r) { rig = r; for (Animator* a : animators) a->SetRig(r); } void SetAnimation(Animation* anim) { animator->SetAnimation(anim); animator->PlayAnimation(); animator->blendWeight = 1.0f; // base never blends, always instant } // Plays an animation once on top of everything else. Starts over if it's already playing. void PlayAction(Animation* anim) { if (!anim) return; if (!actionAnimator) { actionAnimator = AddAnimator(2); actionAnimator->blendDuration = 0.1f; } actionAnimator->loops = false; actionAnimator->SetAnimation(anim); actionAnimator->PlayAnimation(); } // The loop played while walking. It keeps running underneath so it picks up mid-stride. void SetWalkAnimation(Animation* anim) { if (!walkAnimator) walkAnimator = AddAnimator(1); walkAnimator->loops = true; walkAnimator->SetAnimation(anim); walkAnimator->PlayAnimation(); walkAnimator->blendWeight = walkBlend; } // Carries out one action. Movement actions only count for the frame they arrive in. void Perform(const CharacterAction& action); // Pushes the physics body toward walking speed along wherever it faces, from this frame's Move action. // speedFactor scales it, 0 stops it. void ApplyMovement(bool grounded, float speedFactor, float dt, float referenceDt); // Turning and animation void Step(float dt); void SetWalking(bool walking); glm::vec3 GetForwardDirection() const; private: glm::vec3 wishDirection = glm::vec3(0.0f); // from this frame's Move action glm::vec3 turnDirection = glm::vec3(0.0f); // where it's turning to face, kept after it stops bool walking = false; float walkBlend = 0.0f; // 0 = idle, 1 = walking std::vector blendedBones; // Per (base clip, layer clip): bone name -> what turns the layer's track into one relative to the bone's // real parent. Only bones whose parent differs between the two files have an entry. std::map, std::unordered_map> parentCorrections; void BlendBones(const AssimpNodeData& node, const glm::mat4& parentTransform); const std::unordered_map& GetParentCorrections(const Animation* layer); }; class Character : public Object, public Renderable { public: CharacterMind* mind = nullptr; // what decides its actions, nullptr does nothing CharacterBody* body = nullptr; Ragdoll* ragdoll = nullptr; // when set, physics drives the rig instead of the animators SurfaceTexture surface; glm::vec4 colorOverride = glm::vec4(0.0f); // rgb + strength (0 = keep the rig's own texture) float visualScale = 1.4f; // rig is ~1.8 tall natively; its physics box is 2.5 float metallic = 0.0f; float roughness = 0.7f; float ao = 1.0f; // Asks the mind for this frame's actions and hands them to the body void Think(); glm::vec3 GetForwardDirection() const { return body->GetForwardDirection(); } void EnableRagdoll(Physics& physics); // The box physics moves the character around with, sized by body->t's scale void CreatePhysicsBody(Physics& physics, float massKg); // Its physics body's group, so it never collides with anything else of its own int GetCollisionGroup(); Character(); void Render(Shader& shader) override; bool GetWorldBounds(BoundingBox& bounds) const override; void Step(float dt) { if (ragdoll) return; body->Step(dt); } private: glm::mat4 GetRigMatrix() const; int collisionGroup = 0; std::vector pendingActions; }; #endif