Adapt for web, make CharacterState class
This commit is contained in:
1 parent
c891161d69
commit
0eb9d0be25
88 files changed
+810
-102277
No files matched your search
+63
-162
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
#include "CharacterState.h"
|
||||
#include "BoneAnimatedModelNew.h"
|
||||
#include "render/Renderer.h"
|
||||
#include "render/TextureManager.h"
|
||||
@@ -15,39 +16,63 @@ namespace ZL {
|
||||
|
||||
class TextRenderer;
|
||||
|
||||
enum class AnimationState {
|
||||
STAND = 0,
|
||||
WALK = 1,
|
||||
STAND_TO_ACTION = 2,
|
||||
ACTION_ATTACK = 3,
|
||||
ACTION_ATTACK_2 = 4,
|
||||
ACTION_IDLE = 5,
|
||||
ACTION_TO_STAND = 6,
|
||||
ACTION_TO_DEATH = 7,
|
||||
DEATH_IDLE = 8,
|
||||
};
|
||||
|
||||
class Character {
|
||||
public:
|
||||
// Resolves a character index to a live Character pointer.
|
||||
// kNoTarget (-1) → nullptr, kPlayerIndex (-2) → Location::player, 0..n → npcs[n].
|
||||
using CharacterResolver = std::function<Character*(int index)>;
|
||||
|
||||
using PathPlanner = std::function<std::vector<Eigen::Vector3f>(
|
||||
const Eigen::Vector3f& start,
|
||||
const Eigen::Vector3f& end)>;
|
||||
|
||||
void loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
|
||||
void loadBinaryAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = "");
|
||||
// All serializable game state (position, hp, flags, pathfinding, etc.)
|
||||
CharacterState state;
|
||||
|
||||
// Assigns the given texture to a specific mesh (by name, as defined in the animation file).
|
||||
// ---- Asset loading ----
|
||||
void loadAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile = "");
|
||||
void loadBinaryAnimation(AnimationState animState, const std::string& filename, const std::string& zipFile = "");
|
||||
|
||||
// Assigns a texture to a specific mesh by name.
|
||||
void setTexture(const std::string& meshName, std::shared_ptr<Texture> texture);
|
||||
// Assigns the given texture to every mesh in every loaded animation.
|
||||
// Call AFTER loading animations so the mesh names are known.
|
||||
// Assigns one texture to every mesh in every loaded animation. Call AFTER loading animations.
|
||||
void setTexture(std::shared_ptr<ZL::Texture> texture);
|
||||
void update(int64_t deltaMs);
|
||||
// onArrived is called once when the character reaches this target.
|
||||
// From Python you can chain further commands (walk, wait, trigger others).
|
||||
void setTarget(const Eigen::Vector3f& target,
|
||||
std::function<void()> onArrived = nullptr);
|
||||
|
||||
// Creates a fully-loaded Character from a CharacterState (including its creationInfo).
|
||||
// Loads all animations, textures, and weapon mesh/texture from the paths stored in creationInfo.
|
||||
static std::unique_ptr<Character> createFromState(CharacterState st, Renderer& renderer, const char* zipFile);
|
||||
|
||||
// ---- Per-frame update ----
|
||||
// resolver maps character indices to live pointers so attackTargetIndex / faceTargetIndex
|
||||
// can be dereferenced inside update(). Pass nullptr only if no targets are set.
|
||||
void update(int64_t deltaMs, const CharacterResolver& resolver = nullptr);
|
||||
|
||||
void setPathPlanner(PathPlanner planner);
|
||||
|
||||
// ---- Navigation (path data lives in state; pathPlanner lives here) ----
|
||||
void setTarget(const Eigen::Vector3f& target, std::function<void()> onArrived = nullptr);
|
||||
void forceReplan();
|
||||
void stopInPlace() { state.stopInPlace(); }
|
||||
|
||||
// ---- Reset ----
|
||||
void resetPlayerAfterDeath();
|
||||
|
||||
// ---- Damage ----
|
||||
void applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection = Eigen::Vector3f::Zero());
|
||||
|
||||
// ---- Spark setup ----
|
||||
void setupHitSparks(std::shared_ptr<Texture> sparkTexture);
|
||||
void prepareHitSparksForDraw(const Eigen::Matrix4f& cameraViewMatrix);
|
||||
|
||||
// ---- HUD overlays ----
|
||||
void drawName(TextRenderer& textRenderer,
|
||||
const Eigen::Matrix4f& cameraViewMatrix,
|
||||
const Eigen::Matrix4f& projectionMatrix);
|
||||
void drawHealthBar(Renderer& renderer,
|
||||
const Eigen::Matrix4f& cameraViewMatrix,
|
||||
const Eigen::Matrix4f& projectionMatrix);
|
||||
|
||||
// ---- Draw passes ----
|
||||
void draw(Renderer& renderer);
|
||||
void drawShadowDepth(Renderer& renderer);
|
||||
void drawWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
|
||||
@@ -64,167 +89,44 @@ public:
|
||||
const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex,
|
||||
const float* ambientColor, const float* fogColor);
|
||||
|
||||
// Character-to-character collision (XZ-plane). Used by Location to keep
|
||||
// player/NPCs from walking through each other.
|
||||
float collisionRadius = 0.45f;
|
||||
// ---- Query helpers (delegate to state) ----
|
||||
bool isMoving() const { return state.isMoving(); }
|
||||
const Eigen::Vector3f& getRequestedWalkTarget() const { return state.getRequestedWalkTarget(); }
|
||||
Eigen::Vector3f getCurrentNavigationTarget() const { return state.getCurrentNavigationTarget(); }
|
||||
float getHp() const { return state.getHp(); }
|
||||
void setHp(float newHp) { state.setHp(newHp); }
|
||||
|
||||
// Movement/path introspection used for dynamic replanning.
|
||||
bool isMoving() const;
|
||||
const Eigen::Vector3f& getRequestedWalkTarget() const { return requestedWalkTarget; }
|
||||
Eigen::Vector3f getCurrentNavigationTarget() const;
|
||||
void forceReplan();
|
||||
// Cancels any active walk target and clears waypoints so the character
|
||||
// stays at its current position. Used when an external push moves the
|
||||
// character and we don't want it to walk back to where it was.
|
||||
void stopInPlace();
|
||||
float getSpeedMultiplier(float transitionProgress) const;
|
||||
|
||||
void resetPlayerAfterDeath();
|
||||
|
||||
|
||||
// attackDirection is a world-space horizontal vector pointing from the
|
||||
// attacker toward this character — i.e. the direction the hit pushes
|
||||
// them. A zero vector disables directional sparks (isotropic burst).
|
||||
void applyDamage(float damageAmount, const Eigen::Vector3f& attackDirection = Eigen::Vector3f::Zero());
|
||||
|
||||
// Configures the per-character hit-spark emitter with the shared spark
|
||||
// texture. Safe to call once per character after construction.
|
||||
void setupHitSparks(std::shared_ptr<Texture> sparkTexture);
|
||||
|
||||
// Call per-frame between update() and draw(), with the active camera
|
||||
// view matrix (world → eye). The emitter billboards and sorts its
|
||||
// particles against that camera before its VBO is uploaded.
|
||||
void prepareHitSparksForDraw(const Eigen::Matrix4f& cameraViewMatrix);
|
||||
|
||||
// Draws the NPC name floating above this character in 2D screen space.
|
||||
// No-op for the player, for dead characters, or if npcName is empty.
|
||||
// The text is green for peaceful (canAttack == false) NPCs, red otherwise.
|
||||
void drawName(TextRenderer& textRenderer,
|
||||
const Eigen::Matrix4f& cameraViewMatrix,
|
||||
const Eigen::Matrix4f& projectionMatrix);
|
||||
|
||||
// Draws a horizontal health bar above this character in 2D screen space.
|
||||
// Visible only for the player and hostile NPCs (canAttack == true), and
|
||||
// only while hp > 0. The left portion is green (hp / initialHp) and the
|
||||
// remainder is red.
|
||||
void drawHealthBar(Renderer& renderer,
|
||||
const Eigen::Matrix4f& cameraViewMatrix,
|
||||
const Eigen::Matrix4f& projectionMatrix);
|
||||
|
||||
// Public: read by Game for camera tracking and ray-cast origin
|
||||
Eigen::Vector3f position = Eigen::Vector3f(0.f, 0.f, 0.f);
|
||||
float facingAngle = 0.0f;
|
||||
float targetFacingAngle = 0.0f;
|
||||
|
||||
|
||||
// Per-character tuning — set after construction, before first update
|
||||
float walkSpeed = 3.0f;
|
||||
float rotationSpeed = 4.0f;
|
||||
float modelScale = 0.12f;
|
||||
// Applied after scale, fixes model-space orientation (e.g. Blender Z-up exports)
|
||||
Eigen::Quaternionf modelCorrectionRotation = Eigen::Quaternionf::Identity();
|
||||
|
||||
std::string npcId;
|
||||
std::string npcName;
|
||||
float initialHp = 200.f;
|
||||
protected:
|
||||
float hp = 200.f;
|
||||
public:
|
||||
|
||||
float getHp() const {
|
||||
return hp;
|
||||
}
|
||||
|
||||
void setHp(float newHp) {
|
||||
hp = newHp;
|
||||
if (onHpChanged) onHpChanged(hp, initialHp);
|
||||
}
|
||||
|
||||
// The position the NPC should return to if pushed too far away.
|
||||
// Initialised to the spawn position; updated whenever a script walk command is issued.
|
||||
Eigen::Vector3f homePosition = Eigen::Vector3f::Zero();
|
||||
// Maximum allowed drift from homePosition before a return walk is triggered (metres).
|
||||
// Set to 0 to disable drift recovery for this character.
|
||||
float maxHomeDrift = 3.0f;
|
||||
|
||||
|
||||
int battle_state = 0;
|
||||
bool resetAnim = false;
|
||||
int attack = 0;
|
||||
AnimationState currentState = AnimationState::STAND;
|
||||
float attack_cooldown = 0.f;
|
||||
bool canAttack = false;
|
||||
Character* attackTarget = nullptr;
|
||||
// While standing still, smoothly rotate once to face this character.
|
||||
// Auto-cleared by update() the moment this character finishes rotating,
|
||||
// so it acts as a one-shot orient (e.g. an NPC turning to the player at
|
||||
// the start of a conversation, then holding that pose).
|
||||
Character* faceTarget = nullptr;
|
||||
bool isPlayer = false;
|
||||
bool enabled = true;
|
||||
bool useGpuSkinning = true;
|
||||
//bool useGpuSkinning = false;
|
||||
|
||||
float interactionRadius = 0.0f;
|
||||
|
||||
VertexRenderStruct weaponMesh;
|
||||
// ---- Weapon rendering data (initialized from state.creationInfo by createFromState) ----
|
||||
VertexRenderStruct weaponMesh;
|
||||
std::shared_ptr<Texture> weaponTexture;
|
||||
Eigen::Matrix3f weaponInitialRotation = Eigen::Matrix3f::Identity();
|
||||
Eigen::Vector3f weaponInitialPosition = Eigen::Vector3f::Zero();
|
||||
std::string weaponAttachBoneName = "RightHand";
|
||||
bool showWeapon = true;
|
||||
Eigen::Matrix3f weaponInitialRotation = Eigen::Matrix3f::Identity();
|
||||
Eigen::Vector3f weaponInitialPosition = Eigen::Vector3f::Zero();
|
||||
std::string weaponAttachBoneName = "RightHand";
|
||||
|
||||
SparkEmitter hitSparkEmitter;
|
||||
|
||||
float slowMoTimeRemaining = 0.0f;
|
||||
float slowMoTransitionTime = 0.5f;
|
||||
float slowMoActiveTime = 6.0f;
|
||||
float getSpeedMultiplier(float transitionProgress) const;
|
||||
|
||||
// Called once when the death animation finishes and the character enters DEATH_IDLE.
|
||||
std::function<void()> onDeathAnimComplete;
|
||||
|
||||
// Called whenever hp changes (e.g. on damage). Arguments: current hp, max hp.
|
||||
std::function<void(float hp, float maxHp)> onHpChanged;
|
||||
|
||||
private:
|
||||
|
||||
std::map<AnimationState, BoneAnimationDataNew> animations;
|
||||
VertexRenderStruct modelMutable;
|
||||
VertexRenderStruct healthBarMesh;
|
||||
std::unordered_map<std::string, std::shared_ptr<Texture>> meshTextures;
|
||||
|
||||
Eigen::Vector3f walkTarget = Eigen::Vector3f(0.f, 0.f, 0.f);
|
||||
Eigen::Vector3f requestedWalkTarget = Eigen::Vector3f(0.f, 0.f, 0.f);
|
||||
std::vector<Eigen::Vector3f> pathWaypoints;
|
||||
size_t currentWaypointIndex = 0;
|
||||
PathPlanner pathPlanner;
|
||||
std::function<void()> onArrivedCallback;
|
||||
|
||||
static constexpr float WALK_THRESHOLD = 0.05f;
|
||||
static constexpr float TARGET_REPLAN_THRESHOLD = 0.25f;
|
||||
static constexpr float TARGET_REPLAN_THRESHOLD = 0.25f;
|
||||
static constexpr float HOME_DRIFT_CHECK_INTERVAL = 5.0f;
|
||||
|
||||
float homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL;
|
||||
|
||||
// Returns the animation state to actually play/draw, falling back to IDLE
|
||||
// if the requested state has no loaded animation.
|
||||
AnimationState resolveActiveState() const;
|
||||
bool prepareGpuSkinning();
|
||||
void drawAttachedWeapon(Renderer& renderer);
|
||||
|
||||
bool prepareGpuSkinning();
|
||||
|
||||
// Draws the weapon mesh attached to weaponAttachBoneName.
|
||||
// Caller must ensure the right shader is active with matching uniforms.
|
||||
void drawAttachedWeapon(Renderer& renderer);
|
||||
|
||||
// GPU skinning: draw using shader-based skinning
|
||||
void drawGpuSkinning(Renderer& renderer);
|
||||
// Shadow: draw into depth map (no texture, no projection push)
|
||||
void drawShadowDepthGpuSkinning(Renderer& renderer);
|
||||
void drawShadowDepthCpu(Renderer& renderer);
|
||||
// Shadow: draw with shadow-aware shaders
|
||||
void drawGpuSkinningWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
|
||||
void drawCpuWithShadow(Renderer& renderer, const Eigen::Matrix4f& lightFromCamera, GLuint shadowMapTex, const Eigen::Vector3f& lightDirCamera);
|
||||
// Night: draw with spot-light shaders
|
||||
void drawNightGpuSkinning(Renderer& renderer,
|
||||
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
||||
const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse,
|
||||
@@ -233,7 +135,6 @@ private:
|
||||
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
||||
const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse,
|
||||
const float* ambientColor, const float* fogColor);
|
||||
// Night: draw with spot-light + shadow shaders
|
||||
void drawNightGpuSkinningWithShadow(Renderer& renderer,
|
||||
const float* plPosEye, const float* plDirEye, const float* plColors, int plCount,
|
||||
const float* plWorldXZ, const float* plLimitXZ, const Eigen::Matrix4f& cameraViewInverse,
|
||||
|
||||
Reference in new issue
Block a user