Character states, aiming and stuff

This commit is contained in:
tovjemam 2026-06-11 14:51:31 +02:00
parent 5b6e4467e9
commit 1079daa49b
24 changed files with 669 additions and 79 deletions

View File

@ -97,6 +97,10 @@ std::shared_ptr<const assets::Animation> assets::Animation::LoadFromFile(const s
{
iss >> anim->tps_;
}
else if (command == "cyclic")
{
anim->cyclic_ = true;
}
});
if (anim->channels_.empty())
@ -120,5 +124,9 @@ std::shared_ptr<const assets::Animation> assets::Animation::LoadFromFile(const s
channel.frames = &anim->frame_refs_[i * anim->num_frames_];
}
// calc duration
auto frame_range = anim->cyclic_ ? anim->num_frames_ : anim->num_frames_ - 1;
anim->duration_ = static_cast<float>(frame_range) / anim->tps_;
return anim;
}

View File

@ -24,7 +24,8 @@ public:
size_t GetNumFrames() const { return num_frames_; }
float GetTPS() const { return tps_; }
float GetDuration() const { return static_cast<float>(num_frames_) / tps_; }
float GetDuration() const { return duration_; }
bool IsCyclic() const { return cyclic_; }
size_t GetNumChannels() const { return channels_.size(); }
const AnimationChannel& GetChannel(int index) const { return channels_[index]; }
@ -32,6 +33,8 @@ public:
private:
size_t num_frames_ = 0;
float tps_ = 24.0f;
bool cyclic_ = false;
float duration_ = 0.0f;
std::vector<AnimationChannel> channels_;
std::vector<const Transform*> frame_refs_;

View File

@ -40,6 +40,8 @@ std::shared_ptr<const assets::Skeleton> assets::Skeleton::LoadFromFile(const std
}
});
skeleton->AddAimBones();
return skeleton;
}
@ -95,3 +97,24 @@ void assets::Skeleton::AddAnimation(const std::string& name, const std::shared_p
anim_idxs_[name] = anims_.size();
anims_.push_back(anim);
}
void assets::Skeleton::AddAimBones()
{
AddAimBone("DEF-spine.002", 0.5f);
AddAimBone("MCH-spine.002", 0.5f);
AddAimBone("DEF-spine.003", 0.5f);
AddAimBone("MCH-spine.003", 0.5f);
}
void assets::Skeleton::AddAimBone(const std::string& name, float weight)
{
auto idx = GetBoneIndex(name);
if (idx < 0)
return;
AimBone aimbone{};
aimbone.idx = idx;
aimbone.weight = weight;
aim_bones_.emplace_back(aimbone);
}

View File

@ -22,6 +22,12 @@ struct Bone
using AnimIdx = uint8_t;
constexpr AnimIdx NO_ANIM = 255;
struct AimBone
{
size_t idx;
float weight;
};
class Skeleton
{
public:
@ -37,16 +43,24 @@ public:
const Animation* GetAnimation(AnimIdx idx) const;
const Animation* GetAnimation(const std::string& name) const;
const std::vector<AimBone> GetAimBones() const { return aim_bones_; }
private:
void AddBone(const std::string& name, const std::string& parent_name, const Transform& transform);
void AddAnimation(const std::string& name, const std::shared_ptr<const Animation>& anim);
void AddAimBones();
void AddAimBone(const std::string& name, float weight);
private:
std::string name_;
std::vector<Bone> bones_;
std::map<std::string, int> bone_map_;
std::vector<std::shared_ptr<const Animation>> anims_;
std::map<std::string, AnimIdx> anim_idxs_;
std::vector<AimBone> aim_bones_;
};
} // namespace assets

View File

@ -196,6 +196,20 @@ static void PollEvents()
}
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
{
if (event.button.button == SDL_BUTTON_LEFT)
{
s_app->Input(game::IN_ATTACK_PRIMARY, event.button.state == SDL_PRESSED, event.button.clicks > 1);
}
else if (event.button.button == SDL_BUTTON_RIGHT)
{
s_app->Input(game::IN_ATTACK_SECONDARY, event.button.state == SDL_PRESSED, event.button.clicks > 1);
}
}
break;
}
}
@ -362,16 +376,6 @@ static void Frame()
int width, height;
SDL_GetWindowSize(s_window, &width, &height);
s_app->SetViewportSize(width, height);
game::PlayerInputFlags input = 0;
const uint8_t* kbd_state = SDL_GetKeyboardState(nullptr);
int mouse_state = SDL_GetMouseState(nullptr, nullptr);
if (mouse_state & SDL_BUTTON(SDL_BUTTON_LEFT))
input |= (1 << game::IN_ATTACK);
s_app->Frame();

View File

@ -9,6 +9,7 @@ game::Animal::Animal(World& world, const CharacterTuning& tuning, const glm::vec
SetPosition(position);
SetYaw(yaw);
EnablePhysics(true);
SetMovementType(CMT_TURN);
collision::AddObjectFlags(&GetController()->GetBtGhost(), collision::OF_USABLE);
}
@ -40,9 +41,9 @@ void game::Animal::SetRideableInput(PlayerInputFlags in)
SetInputs(MapPlayerInputToCharacterInput(in));
}
void game::Animal::SetRideableYaw(float yaw)
void game::Animal::SetRideableViewAngles(float yaw, float pitch)
{
SetForwardYaw(yaw);
SetViewAngles(yaw, pitch);
}
void game::Animal::OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger)

View File

@ -16,7 +16,7 @@ public:
virtual void Use(PlayerCharacter& character, uint32_t target_id) override;
virtual void SetRideableInput(PlayerInputFlags in) override;
virtual void SetRideableYaw(float yaw) override;
virtual void SetRideableViewAngles(float yaw, float pitch) override;
protected:
virtual void OnPassengerChanged(size_t seat_idx, HumanCharacter* passenger) override;

View File

@ -38,6 +38,7 @@ void game::Character::Update()
SyncTransformFromController();
UpdateMovement();
UpdateActionAnim();
root_.UpdateMatrix();
sync_current_ = 1 - sync_current_;
@ -100,6 +101,17 @@ void game::Character::SetInput(CharacterInputType type, bool enable)
in_ &= ~(1 << type);
}
void game::Character::SetMovementType(CharacterMovementType type)
{
movement_ = type;
}
void game::Character::SetViewAngles(float yaw, float pitch)
{
view_yaw_ = yaw;
view_pitch_ = pitch;
}
void game::Character::SetPosition(const glm::vec3& position)
{
root_.local.position = position;
@ -121,6 +133,30 @@ void game::Character::SetRunAnim(const std::string& anim_name)
animstate_.run_anim_idx = GetAnim(anim_name);
}
void game::Character::PlayActionAnim(assets::AnimIdx anim_idx, float speed)
{
action_anim_end_ = (anim_idx != assets::NO_ANIM) ? sk_.GetSkeleton()->GetAnimation(anim_idx)->GetDuration() : 0.0f;
if (animstate_.action_anim_idx != anim_idx)
{
// continue from current time if same anim
animstate_.action_phase = (speed > 0.0f) ? 0.0f : action_anim_end_;
}
animstate_.action_anim_idx = anim_idx;
action_anim_playback_speed_ = speed;
action_anim_done_ = anim_idx == assets::NO_ANIM;
}
void game::Character::PlayActionAnim(const std::string& anim_name, float speed)
{
PlayActionAnim(GetAnim(anim_name), speed);
}
void game::Character::ClearActionAnim()
{
PlayActionAnim(assets::NO_ANIM, 0.0f);
}
void game::Character::SyncControllerTransform()
{
if (!controller_)
@ -143,42 +179,58 @@ void game::Character::SyncTransformFromController()
root_.local.position.z -= z_offset_; // foot pos
}
static glm::vec2 GetInputDir(game::CharacterInputFlags in)
{
glm::vec2 dir(0.0f);
if (in & (1 << game::CIN_FORWARD))
dir.y += 1.0f;
if (in & (1 << game::CIN_BACKWARD))
dir.y -= 1.0f;
if (in & (1 << game::CIN_RIGHT))
dir.x -= 1.0f;
if (in & (1 << game::CIN_LEFT))
dir.x += 1.0f;
return dir;
}
void game::Character::UpdateMovement()
{
if (movement_ == CMT_DISABLED)
{
animstate_.loco_blend = 0.0f;
return;
}
constexpr float dt = 1.0f / 25.0f;
bool walking = false;
bool running = false;
glm::vec2 movedir(0.0f);
if (in_ & (1 << CIN_FORWARD))
movedir.y += 1.0f;
glm::vec3 move_dir(0.0f);
if (in_ & (1 << CIN_BACKWARD))
movedir.y -= 1.0f;
if (in_ & (1 << CIN_RIGHT))
movedir.x -= 1.0f;
if (in_ & (1 << CIN_LEFT))
movedir.x += 1.0f;
glm::vec3 walkdir(0.0f);
if (movedir.x != 0.0f || movedir.y != 0.0f)
auto input_dir = GetInputDir(in_);
if (input_dir.x != 0.0f || input_dir.y != 0.0f)
{
walking = true;
if (in_ & (1 << CIN_SPRINT))
running = true;
float target_yaw = forward_yaw_ + std::atan2(movedir.x, movedir.y);
Turn(yaw_, target_yaw, turn_speed_ * dt);
glm::vec3 forward_dir(-glm::sin(yaw_), glm::cos(yaw_), 0.0f);
walkdir = forward_dir * walk_speed_ * dt;
const bool directional = (movement_ == CMT_DIRECTIONAL);
float relative_yaw = std::atan2(input_dir.x, input_dir.y);
float turn_yaw = directional ? view_yaw_ : view_yaw_ + relative_yaw;
Turn(yaw_, turn_yaw, turn_speed_ * dt);
float move_yaw = directional ? yaw_ + relative_yaw : yaw_;
move_dir = glm::vec3(-glm::sin(move_yaw), glm::cos(move_yaw), 0.0f) * walk_speed_ * dt;
if (running)
walkdir *= run_speed_mult_;
move_dir *= run_speed_mult_;
}
@ -187,7 +239,7 @@ void game::Character::UpdateMovement()
if (controller_)
{
auto& bt_character = controller_->GetBtController();
bt_character.setWalkDirection(btVector3(walkdir.x, walkdir.y, walkdir.z));
bt_character.setWalkDirection(btVector3(move_dir.x, move_dir.y, move_dir.z));
if (in_ & (1 << CIN_JUMP) && bt_character.canJump())
{
@ -202,6 +254,9 @@ void game::Character::UpdateMovement()
if (running)
anim_speed *= run_speed_mult_;
animstate_.loco_phase = glm::mod(animstate_.loco_phase + anim_speed * dt, 1.0f);
animstate_.pitch = view_pitch_;
}
void game::Character::UpdateSyncState()
@ -220,6 +275,14 @@ void game::Character::UpdateSyncState()
state.run_anim = animstate_.run_anim_idx;
state.loco_phase.Encode(animstate_.loco_phase);
state.loco_blend.Encode(animstate_.loco_blend);
// action
state.action_anim = animstate_.action_anim_idx;
state.action_phase.Encode(animstate_.action_phase);
// aim
state.aim_yaw.Encode(animstate_.yaw);
state.aim_pitch.Encode(animstate_.pitch);
}
void game::Character::SendUpdateMsg()
@ -282,6 +345,31 @@ game::CharacterSyncFieldFlags game::Character::WriteState(net::OutMessage& msg,
net::WriteDelta(msg, curr.loco_phase, base.loco_phase);
}
// action anim
if (curr.action_anim != base.action_anim)
{
fields |= CSF_ACTION_ANIM;
msg.Write(curr.action_anim);
}
// action phase
if (curr.action_phase.value != base.action_phase.value)
{
fields |= CSF_ACTION_PHASE;
net::WriteDelta(msg, curr.action_phase, base.action_phase);
}
// aim
if (curr.aim_yaw.value != base.aim_yaw.value || curr.aim_pitch.value != base.aim_pitch.value)
{
fields |= CSF_AIM;
net::WriteDelta(msg, curr.aim_yaw.value, base.aim_yaw.value);
net::WriteDelta(msg, curr.aim_pitch.value, base.aim_pitch.value);
}
return fields;
}
@ -290,6 +378,31 @@ assets::AnimIdx game::Character::GetAnim(const std::string& name) const
return sk_.GetSkeleton()->GetAnimationIdx(name);
}
void game::Character::UpdateActionAnim()
{
if (action_anim_done_)
return;
animstate_.action_phase += action_anim_playback_speed_ * (1.0f / 25.0f);
if (action_anim_playback_speed_ > 0.0f)
{
if (animstate_.action_phase >= action_anim_end_)
{
animstate_.action_phase = action_anim_end_;
action_anim_done_ = true;
}
}
else
{
if (animstate_.action_phase <= 0.0f)
{
animstate_.action_phase = 0.0f;
action_anim_done_ = true;
}
}
}
game::CharacterPhysicsController::CharacterPhysicsController(Character& character, btDynamicsWorld& bt_world, btCapsuleShapeZ& bt_shape)
: character_(character), bt_world_(bt_world), bt_character_(&bt_ghost_, &bt_shape, 0.3f, btVector3(0, 0, 1))
{

View File

@ -45,6 +45,13 @@ private:
btKinematicCharacterController bt_character_;
};
enum CharacterMovementType
{
CMT_DISABLED,
CMT_TURN,
CMT_DIRECTIONAL,
};
class Character : public Entity
{
public:
@ -64,13 +71,17 @@ public:
void SetInput(CharacterInputType type, bool enable);
void SetInputs(CharacterInputFlags inputs) { in_ = inputs; }
CharacterInputFlags GetInputs() const { return in_; }
void SetMovementType(CharacterMovementType type);
void SetViewAngles(float yaw, float pitch);
float GetViewYaw() const { return view_yaw_; }
float GetViewPitch() const { return view_pitch_; }
void SetForwardYaw(float yaw) { forward_yaw_ = yaw; }
float GetForwardYaw() const { return forward_yaw_; }
void SetYaw(float yaw) { yaw_ = yaw; }
void SetPosition(const glm::vec3& position);
~Character() override = default;
@ -78,6 +89,10 @@ protected:
void SetIdleAnim(const std::string& anim_name);
void SetWalkAnim(const std::string& anim_name);
void SetRunAnim(const std::string& anim_name);
void PlayActionAnim(assets::AnimIdx anim_idx, float speed);
void PlayActionAnim(const std::string& anim_name, float speed = 1.0f);
void ClearActionAnim();
bool IsActionAnimDone() { return action_anim_done_; }
private:
void SyncControllerTransform();
@ -90,6 +105,8 @@ private:
assets::AnimIdx GetAnim(const std::string& name) const;
void UpdateActionAnim();
protected:
float turn_speed_ = 8.0f;
float walk_speed_ = 2.0f;
@ -108,13 +125,20 @@ private:
std::unique_ptr<CharacterPhysicsController> controller_;
float yaw_ = 0.0f;
float forward_yaw_ = 0.0f;
float view_yaw_ = 0.0f;
float view_pitch_ = 0.0f;
SkeletonInstance sk_;
CharacterAnimState animstate_;
CharacterSyncState sync_[2];
size_t sync_current_ = 0;
CharacterMovementType movement_ = CMT_DISABLED;
float action_anim_playback_speed_ = 0.0f;
float action_anim_end_ = 0.0f;
bool action_anim_done_ = true;
};
} // namespace game

View File

@ -26,7 +26,6 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const
sk.ApplySkelAnim(*idle_anim, loco_phase, 1.0f);
sk.ApplySkelAnim(*walk_anim, loco_phase, UnMix(0.0f, 0.5f, loco_blend));
}
else if (loco_blend == 0.5f) // walk
{
sk.ApplySkelAnim(*walk_anim, loco_phase, 1.0f);
@ -41,4 +40,16 @@ void game::CharacterAnimState::ApplyToSkeleton(SkeletonInstance& sk) const
sk.ApplySkelAnim(*run_anim, loco_phase, 1.0f);
}
// action
auto action_anim = skeleton->GetAnimation(action_anim_idx);
if (action_anim)
{
sk.ApplySkelAnim(*action_anim, action_phase, 1.0f);
}
if (glm::abs(yaw) > 0.01f || glm::abs(pitch) > 0.01f)
{
sk.ApplyAim(yaw, pitch);
}
}

View File

@ -8,12 +8,18 @@ namespace game
struct CharacterAnimState
{
assets::AnimIdx idle_anim_idx = assets::NO_ANIM;
assets::AnimIdx walk_anim_idx = assets::NO_ANIM;
assets::AnimIdx run_anim_idx = assets::NO_ANIM;
float loco_blend = 0.0f;
float loco_phase = 0.0f;
assets::AnimIdx action_anim_idx = assets::NO_ANIM;
float action_phase = 0.0f;
float yaw = 0.0f;
float pitch = 0.0f;
void ApplyToSkeleton(SkeletonInstance& sk) const;

View File

@ -22,20 +22,30 @@ struct CharacterSyncState
assets::AnimIdx run_anim = assets::NO_ANIM;
net::AnimBlendQ loco_blend;
net::AnimTimeQ loco_phase;
// action anim
assets::AnimIdx action_anim = assets::NO_ANIM;
net::AnimTimeQ action_phase;
// aim
net::AnimAimAngleQ aim_yaw;
net::AnimAimAngleQ aim_pitch;
//assets::AnimIdx strafe_left_anim = assets::NO_ANIM;
//assets::AnimIdx strafe_right_anim = assets::NO_ANIM;
// TODO: action
};
using CharacterSyncFieldFlags = uint8_t;
enum CharacterSyncFieldFlag
{
CSF_TRANSFORM = 0x01,
CSF_IDLE_ANIM = 0x02,
CSF_LOCO_ANIMS = 0x04,
CSF_LOCO_VALS = 0x08,
CSF_TRANSFORM = 1,
CSF_IDLE_ANIM = 2,
CSF_LOCO_ANIMS = 4,
CSF_LOCO_VALS = 8,
CSF_ACTION_ANIM = 16,
CSF_ACTION_PHASE = 32,
CSF_AIM = 64,
};
} // namespace game

View File

@ -45,7 +45,7 @@ void game::EnterableWorld::PlayerViewAnglesChanged(Player& player, float yaw, fl
auto character = it->second;
character->SetForwardYaw(yaw);
character->SetViewAngles(yaw, pitch);
}
void game::EnterableWorld::RemovePlayer(Player& player)

View File

@ -17,33 +17,27 @@ game::HumanCharacter::HumanCharacter(World& world, const HumanCharacterTuning& t
SetWalkAnim("walk");
}
void game::HumanCharacter::Update()
{
UpdateState();
UpdateActionState();
Super::Update();
}
void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx)
{
if (rideable == rideable_ && seat_idx == seat_idx_)
return;
if (rideable)
if (rideable_)
{
SetPosition(rideable->GetSeatOffset(seat_idx));
EnablePhysics(false);
Attach(rideable->GetEntity().GetEntNum());
SetIdleAnim((rideable->GetRideableType() == RIDEABLE_VEHICLE && seat_idx == 0) ? "vehicle_drive" : "vehicle_passenger");
SetYaw(0.0f);
}
else
{
EnablePhysics(true);
glm::vec3 seat_loc = rideable_->GetSeatOffset(seat_idx_);
seat_loc.x += glm::sign(seat_loc.x) * 0.5f; // to the side
glm::vec3 pos = rideable_->GetEntity().GetRoot().matrix * glm::vec4(seat_loc, 1.0f);
pos.z += 0.5f;
SetPosition(pos);
Attach(0);
SetIdleAnim("idle");
rideable_exit_pos_ = pos;
}
rideable_ = rideable;
@ -51,8 +45,10 @@ void game::HumanCharacter::SetRideable(Rideable* rideable, size_t seat_idx)
seat_idx_ = seat_idx;
is_driver_ = rideable && seat_idx_ == 0;
SetSignal(HSS_RIDEABLE_CHANGED);
OnRideableChanged();
}
void game::HumanCharacter::Ride(Rideable* rideable, size_t seat_idx)
@ -72,3 +68,238 @@ game::HumanCharacter::~HumanCharacter()
{
Ride(nullptr, 0); // exit rideable
}
void game::HumanCharacter::UpdateState()
{
struct HumanCharacterStateTableEntry
{
void (HumanCharacter::*enter)();
HumanCharacterState (HumanCharacter::*update)();
void (HumanCharacter::*exit)();
};
static const HumanCharacterStateTableEntry state_table[] =
{
// HS_INIT
{
nullptr,
&HumanCharacter::StateInitUpdate,
nullptr,
},
// HS_ON_FOOT
{
&HumanCharacter::StateOnFootEnter,
&HumanCharacter::StateOnFootUpdate,
nullptr,
},
// HS_RIDING
{
&HumanCharacter::StateRidingEnter,
&HumanCharacter::StateRidingUpdate,
&HumanCharacter::StateRidingExit,
},
// HS_KNOCKED_DOWN
{
nullptr,
&HumanCharacter::StateKnockedDownUpdate,
nullptr,
},
};
while (true)
{
auto new_state = (this->*state_table[state_].update)();
if (new_state == state_)
break;
if (auto exit_fun = state_table[state_].exit)
(this->*exit_fun)();
state_ = new_state;
if (auto enter_fun = state_table[state_].enter)
(this->*enter_fun)();
}
signals_ = 0;
}
void game::HumanCharacter::SetSignal(HumanCharacterStateSignal signal)
{
signals_ |= signal;
}
bool game::HumanCharacter::PopSignal(HumanCharacterStateSignal signal)
{
if (signals_ & signal)
{
signals_ &= ~signal;
return true;
}
return false;
}
game::HumanCharacterState game::HumanCharacter::StateInitUpdate()
{
if (GetRideable())
return HS_RIDING;
return HS_ON_FOOT;
}
void game::HumanCharacter::StateOnFootEnter()
{
SetIdleAnim("idle");
SetWalkAnim("walk");
SetMovementType(CMT_TURN);
EnablePhysics(true);
}
game::HumanCharacterState game::HumanCharacter::StateOnFootUpdate()
{
if (PopSignal(HSS_RIDEABLE_CHANGED))
return HS_INIT;
if (PopSignal(HSS_KNOCK_DOWN))
return HS_KNOCKED_DOWN;
SetMovementType(aiming_ ? CMT_DIRECTIONAL : CMT_TURN);
return HS_ON_FOOT;
}
void game::HumanCharacter::StateRidingEnter()
{
auto rideable = GetRideable();
SetPosition(rideable->GetSeatOffset(seat_idx_));
EnablePhysics(false);
Attach(rideable->GetEntity().GetEntNum());
SetIdleAnim((rideable->GetRideableType() == RIDEABLE_VEHICLE && seat_idx_ == 0) ? "vehicle_drive" : "vehicle_passenger");
SetYaw(0.0f);
SetMovementType(CMT_DISABLED);
}
game::HumanCharacterState game::HumanCharacter::StateRidingUpdate()
{
if (!GetRideable())
return HS_INIT;
if (PopSignal(HSS_RIDEABLE_CHANGED))
return HS_INIT;
return HS_RIDING;
}
void game::HumanCharacter::StateRidingExit()
{
EnablePhysics(true);
SetPosition(rideable_exit_pos_);
Attach(0);
}
game::HumanCharacterState game::HumanCharacter::StateKnockedDownUpdate()
{
return HS_INIT;
}
void game::HumanCharacter::UpdateActionState()
{
while (true)
{
auto new_state = CheckActionStateTransition();
if (new_state == actionstate_)
break;
ExitActionState();
actionstate_ = new_state;
EnterActionState();
}
}
void game::HumanCharacter::EnterActionState()
{
switch (actionstate_)
{
case ACTION_IDLE:
ClearActionAnim();
break;
case ACTION_AIM:
PlayActionAnim("rifle_aim", 3.0f);
break;
case ACTION_AIMING:
break;
case ACTION_FIRE:
PlayActionAnim("rifle_fire");
break;
case ACTION_UNAIM:
PlayActionAnim("rifle_aim", -3.0f);
break;
default:
break;
}
}
game::ActionState game::HumanCharacter::CheckActionStateTransition()
{
switch (actionstate_)
{
case ACTION_IDLE:
if (aiming_) // want aim
return ACTION_AIM;
return ACTION_IDLE;
case ACTION_AIM:
if (IsActionAnimDone())
return ACTION_AIMING;
if (!aiming_) // stop aiming immediately
return ACTION_UNAIM;
return ACTION_AIM;
case ACTION_AIMING:
if (!aiming_)
return ACTION_UNAIM; // wants aim no more
// TODO: check fire
return ACTION_AIMING;
case ACTION_FIRE:
return ACTION_FIRE;
case ACTION_UNAIM:
if (IsActionAnimDone())
return ACTION_IDLE;
if (aiming_) // start aiming again
return ACTION_AIM;
return ACTION_UNAIM;
default:
return actionstate_;
}
}
void game::HumanCharacter::ExitActionState()
{
switch (actionstate_)
{
default:
break;
}
}

View File

@ -13,6 +13,31 @@ struct HumanCharacterTuning
class Rideable;
class DrivableVehicle;
using HumanCharacterStateSignals = uint32_t;
enum HumanCharacterStateSignal : HumanCharacterStateSignals
{
HSS_KNOCK_DOWN = 1,
HSS_RIDEABLE_CHANGED = 2,
};
enum HumanCharacterState
{
HS_INIT,
HS_ON_FOOT,
HS_RIDING,
HS_KNOCKED_DOWN,
};
enum ActionState
{
ACTION_IDLE,
ACTION_AIM,
ACTION_AIMING,
ACTION_FIRE,
ACTION_UNAIM,
};
class HumanCharacter : public Character
{
public:
@ -20,6 +45,8 @@ public:
HumanCharacter(World& world, const HumanCharacterTuning& tuning);
virtual void Update() override;
const HumanCharacterTuning& GetHumanTuning() const { return human_tuning_; }
void SetRideable(Rideable* rideable, size_t seat_idx); // called by Rideable!!
@ -31,11 +58,41 @@ public:
size_t GeatSeatIdx() const { return seat_idx_; }
bool IsDriver() const { return is_driver_; }
void SetAiming(bool aiming) { aiming_ = aiming; }
virtual ~HumanCharacter() override;
protected:
virtual void OnRideableChanged() {}
private:
void UpdateState();
void SetSignal(HumanCharacterStateSignal signal);
bool PopSignal(HumanCharacterStateSignal signal);
//void StateInitEnter();
HumanCharacterState StateInitUpdate();
//void StateInitExit();
void StateOnFootEnter();
HumanCharacterState StateOnFootUpdate();
//void StateOnFootExit();
void StateRidingEnter();
HumanCharacterState StateRidingUpdate();
void StateRidingExit();
// void StateKnockedDownEnter();
HumanCharacterState StateKnockedDownUpdate();
// void StateKnockedDownExit();
void UpdateActionState();
void EnterActionState();
ActionState CheckActionStateTransition();
void ExitActionState();
private:
HumanCharacterTuning human_tuning_;
@ -43,6 +100,16 @@ private:
DrivableVehicle* vehicle_ = nullptr;
size_t seat_idx_ = 0;
bool is_driver_ = false;
HumanCharacterState state_ = HS_INIT;
HumanCharacterStateSignals signals_ = 0;
glm::vec3 rideable_exit_pos_ = glm::vec3(0.0f);
bool aiming_ = false;
ActionState actionstate_ = ACTION_IDLE;
};
}

View File

@ -17,7 +17,7 @@ void game::PlayerCharacter::Update()
if (GetRideable() && IsDriver())
{
GetRideable()->SetRideableYaw(GetForwardYaw());
GetRideable()->SetRideableViewAngles(GetViewYaw(), GetViewPitch());
}
}
@ -78,6 +78,8 @@ void game::PlayerCharacter::UpdateInputs()
{
SetInputs(MapPlayerInputToCharacterInput(in));
}
SetAiming(in & (1 << IN_ATTACK_SECONDARY));
}
void game::PlayerCharacter::UpdateUseTarget()

View File

@ -16,7 +16,8 @@ namespace game
IN_CROUCH,
IN_SPRINT,
IN_USE,
IN_ATTACK,
IN_ATTACK_PRIMARY,
IN_ATTACK_SECONDARY,
IN_DEBUG1,
IN_DEBUG2,
IN_DEBUG3,

View File

@ -31,7 +31,7 @@ public:
void KickAll();
virtual void SetRideableInput(PlayerInputFlags in) {}
virtual void SetRideableYaw(float yaw) {}
virtual void SetRideableViewAngles(float yaw, float pitch) {}
RideableType GetRideableType() const { return type_; }

View File

@ -52,6 +52,20 @@ void game::SkeletonInstance::ApplySkelAnim(const assets::Animation& anim, float
}
}
void game::SkeletonInstance::ApplyAim(float yaw, float pitch)
{
const auto& aim_bones = skeleton_->GetAimBones();
if (aim_bones.empty())
return;
for (const auto& aim_bone : aim_bones)
{
auto& bone_transform = bone_nodes_[aim_bone.idx].local;
auto rotation = glm::angleAxis(-pitch * aim_bone.weight, glm::vec3(1.0f, 0.0f, 0.0f));
bone_transform.rotation = rotation * bone_transform.rotation;
}
}
void game::SkeletonInstance::UpdateBoneMatrices()
{
for (TransformNode& node : bone_nodes_)

View File

@ -17,6 +17,8 @@ public:
const TransformNode& GetBoneNode(size_t index) const { return bone_nodes_[index]; }
void ApplySkelAnim(const assets::Animation& anim, float time, float weight);
void ApplyAim(float yaw, float pitch);
void UpdateBoneMatrices();
const std::vector<TransformNode> GetBoneNodes() const { return bone_nodes_; }

View File

@ -63,10 +63,13 @@ void game::view::CharacterView::Update(const UpdateInfo& info)
// interpolate states
float tps = 25.0f;
float t = (info.time - update_time_) * tps * 0.8f; // assume some jitter, interpolate for longer
float t = (info.time - update_time_) * tps * 0.8f; // assume some jitter, interpolate for longer;
t = glm::clamp(t, 0.0f, 2.0f);
float t_sane = glm::clamp(t, 0.0f, 1.0f);
root_.local = Transform::Lerp(states_[0].trans, states_[1].trans, t);
// loco
animstate_.loco_blend = glm::mix(states_[0].loco_blend, states_[1].loco_blend, t);
float loco_phase0 = states_[0].loco_phase;
@ -75,6 +78,13 @@ void game::view::CharacterView::Update(const UpdateInfo& info)
loco_phase0 -= 1.0f;
animstate_.loco_phase = glm::mod(glm::mix(loco_phase0, loco_phase1, t), 1.0f);
// action
animstate_.action_phase = glm::mix(states_[0].action_phase, states_[1].action_phase, t_sane);
// aim
animstate_.yaw = glm::mix(states_[0].aim_yaw, states_[1].aim_yaw, t_sane);
animstate_.pitch = glm::mix(states_[0].aim_pitch, states_[1].aim_pitch, t_sane);
animstate_.ApplyToSkeleton(sk_);
root_.UpdateMatrix();
@ -157,13 +167,17 @@ void game::view::CharacterView::OnAttach()
bool game::view::CharacterView::ReadState(net::InMessage* msg)
{
update_time_ = world_.GetTime();
auto& old_state = states_[0];
auto& new_state = states_[1];
// init lerp start state
states_[0].trans = root_.local;
states_[0].loco_blend = animstate_.loco_blend;
states_[0].loco_phase = animstate_.loco_phase;
auto& new_state = states_[1];
old_state.trans = root_.local;
old_state.loco_blend = animstate_.loco_blend;
old_state.loco_phase = animstate_.loco_phase;
old_state.action_phase = animstate_.action_phase;
old_state.aim_yaw = animstate_.yaw;
old_state.aim_pitch = animstate_.pitch;
if (msg)
{
@ -209,6 +223,40 @@ bool game::view::CharacterView::ReadState(net::InMessage* msg)
new_state.loco_blend = sync_.loco_blend.Decode();
new_state.loco_phase = sync_.loco_phase.Decode();
}
// action anim
if (fields & CSF_ACTION_ANIM)
{
if (!msg->Read(sync_.action_anim))
return false;
animstate_.action_anim_idx = sync_.action_anim;
}
// action phase
if (fields & CSF_ACTION_PHASE)
{
if (!net::ReadDelta(*msg, sync_.action_phase))
return false;
new_state.action_phase = sync_.action_phase.Decode();
if (fields & CSF_ACTION_ANIM)
{
// anim just changed, dont blend phase
old_state.action_phase = new_state.action_phase;
}
}
// aim
if (fields & CSF_AIM)
{
if (!net::ReadDelta(*msg, sync_.aim_yaw) || !net::ReadDelta(*msg, sync_.aim_pitch))
return false;
new_state.aim_yaw = sync_.aim_yaw.Decode();
new_state.aim_pitch = sync_.aim_pitch.Decode();
}
}
return true;

View File

@ -13,8 +13,14 @@ namespace game::view
struct CharacterViewState
{
Transform trans;
float loco_blend = 0.0f;
float loco_phase = 0.0f;
float action_phase = 0.0f;
float aim_yaw = 0.0f;
float aim_pitch = 0.0f;
};
struct CharacterViewClothes

View File

@ -139,6 +139,8 @@ using SoundPitchQ = Quantized<uint8_t, 0, 2>;
using AnimBlendQ = Quantized<uint8_t, 0, 1>;
using AnimTimeQ = Quantized<uint8_t, 0, 1>;
using AnimAimAngleQ = Quantized<uint8_t, -PI_N, PI_N, PI_D * 2>;
using NumClothes = uint8_t;
using ClothesName = FixedStr<32>;

View File

@ -147,14 +147,14 @@ inline bool ReadRGB(InMessage& msg, uint32_t& color)
// DELTA
template <std::unsigned_integral T> requires (sizeof(T) == 1)
inline void WriteDelta(OutMessage& msg, T previous, T current)
inline void WriteDelta(OutMessage& msg, T current, T previous)
{
// 1 byte => just write current
msg.Write(current);
}
template <std::unsigned_integral T> requires (sizeof(T) > 1)
inline void WriteDelta(OutMessage& msg, T previous, T current)
inline void WriteDelta(OutMessage& msg, T current, T previous)
{
static_assert(sizeof(T) <= 4);
@ -169,18 +169,18 @@ inline void WriteDelta(OutMessage& msg, T previous, T current)
template <AnyQuantized T>
inline void WriteDelta(OutMessage& msg, T current, T previous)
{
WriteDelta(msg, previous.value, current.value);
WriteDelta(msg, current.value, previous.value);
}
template <std::unsigned_integral T> requires (sizeof(T) == 1)
inline bool ReadDelta(InMessage& msg, T previous, T& current)
inline bool ReadDelta(InMessage& msg, T& current, T previous)
{
// 1 byte => just read current
return msg.Read(current);
}
template <std::unsigned_integral T> requires (sizeof(T) > 1)
inline bool ReadDelta(InMessage& msg, T previous, T& current)
inline bool ReadDelta(InMessage& msg, T& current, T previous)
{
static_assert(sizeof(T) <= 4);