Now root motion works much better; raft/boat climbing

This commit is contained in:
2025-10-04 04:10:21 +03:00
parent 25280a9cbe
commit 19a1275a8a
14 changed files with 844 additions and 149 deletions

View File

@@ -228,8 +228,12 @@ public:
control |= 8;
else if (key == OgreBites::SDLK_LSHIFT)
control |= 16;
else if (key == 'e')
control |= 32;
else if (key == 'f')
control |= 64;
if (key == 'w' || key == 'a' || key == 's' || key == 'd' ||
key == OgreBites::SDLK_LSHIFT)
key == 'e' || key == OgreBites::SDLK_LSHIFT)
return true;
return false;
}

BIN
assets/blender/edited-normal-female.blend (Stored with Git LFS)

Binary file not shown.

BIN
assets/blender/edited-normal-male.blend (Stored with Git LFS)

Binary file not shown.

BIN
assets/blender/vehicles/raft.blend (Stored with Git LFS)

Binary file not shown.

View File

@@ -228,13 +228,14 @@ function StartGameQuest()
quest.activate = function(this)
print('activate...')
local mc_is_free = function()
this.boat_id = ecs_vehicle_set("raft", 0, 0, -20, 1.75)
this.npc_id = ecs_npc_set("normal-female.glb", 0, 2, -20, 1.75)
this.boat = true
-- ecs_set_slot(this.boat_id, this.npc_id, "captain_seat")
-- ecs_character_physics_control(this.npc_id, false)
ecs_character_params_set("player", "gravity", true)
ecs_character_params_set("player", "buoyancy", true)
this.boat_id = ecs_vehicle_set("raft", 0, 0, -10, 1.75)
this.npc_id = ecs_npc_set("normal-female.glb", 0, 2, -10, 1.75)
this.boat = true
-- ecs_set_slot(this.boat_id, this.npc_id, "captain_seat")
-- ecs_character_physics_control(this.npc_id, false)
local ent = ecs_get_player_entity()
ecs_character_params_set(ent, "gravity", true)
ecs_character_params_set(ent, "buoyancy", true)
end
this.story:bind('mc_is_free', mc_is_free)
this.base.activate(this)
@@ -248,7 +249,128 @@ function StartGameQuest()
end
return quest
end
function create_actuator()
return {
is_complete = false,
complete = function(this)
return this.is_complete
end,
finish = function(this)
this.is_complete = true
ecs_character_set_actuator(this.entity, "")
ecs_character_physics_control(this.entity, true)
print("COMPLETE")
end,
forward = function(this)
if (this.forward_animation) then
this:animation(this.forward_animation)
end
end,
animation = function(this, animation)
print("ANIMATION: ", animation)
ecs_character_set_actuator(this.entity, animation)
end,
event = function(this, event, trigger_entity, what_entity)
print("actuator events: ", event)
if event == "actuator_forward" then
this:forward()
return true
elseif event == "_in_actuator_forward" then
this:forward()
return true
elseif event == "actuator_exit" then
this:finish()
return true
end
if this.finish_events then
for i, p in ipairs(this.finish_events) do
if p == event then
this:finish()
break
end
end
if this.is_complete then
return true
end
end
return false
end,
}
end
quests = {}
local actuator = nil
function check_actuator_event(event, trigger_entity, what_entity)
print("check_actuator_event: ", event)
if event == "actuator_enter" then
if not ecs_character_is_player(what_entity) then
return
end
ecs_character_physics_control(what_entity, false)
local animation = ecs_trigger_get_animation(trigger_entity)
ecs_character_set_actuator(what_entity, animation)
ecs_trigger_set_position(trigger_entity, what_entity)
local ent = ecs_get_entity(what_entity)
if (ent.is_character()) then
print("character")
end
if (ent.is_player()) then
print("player")
end
-- crash()
actuator = create_actuator()
actuator.trigger = trigger_entity
actuator.entity = what_entity
actuator.forward = function(this)
this:animation("swimming-edge-climb")
local ent = ecs_get_player_entity()
ecs_character_params_set(ent, "gravity", true)
ecs_character_params_set(ent, "buoyancy", true)
end
actuator.base_event = actuator.event
actuator.finish_events = {"animation:swimming-edge-climb:end"}
actuator.event = function(this, event, te, we)
print("actuator events 1: ", event)
if this.base_event(this, event, te, we) then
return true
end
return false
end
elseif event == "character_enter" then
if not ecs_character_is_player(trigger_entity) then
return
end
actuator = create_actuator()
actuator.trigger = trigger_entity
actuator.entity = trigger_entity
actuator.other_entity = what_entity
actuator.forward_animation = "pass-character"
actuator.finish_events = {"animation:pass-character:end"}
actuator.base_event = actuator.event
actuator.event = function(this, event, te, we)
print("actuator events 2: ", event)
if event == "actuator_exit" then
this:animation("idle")
return true
end
if this.base_event(this, event, te, we) then
return true
end
if event == "character_enter" then
-- why?
-- ecs_character_set_actuator(this.entity, "idle")
-- ecs_character_set_actuator(this.entity, "idle")
return true
elseif event == "actuator_enter" then
-- why?
-- ecs_character_set_actuator(this.entity, "idle")
-- ecs_character_set_actuator(this.entity, "idle")
return true
end
return false
end
actuator:animation("idle")
end
end
-- ecs_set_debug_drawing(true)
setup_handler(function(event, trigger_entity, what_entity)
print(event)
@@ -268,27 +390,27 @@ setup_handler(function(event, trigger_entity, what_entity)
local answer = narration_get_answer()
print("answered:", answer)
elseif event == "new_game" then
ecs_character_params_set("player", "gravity", true)
ecs_character_params_set("player", "buoyancy", false)
local ent = ecs_get_player_entity()
ecs_character_params_set(ent, "gravity", true)
ecs_character_params_set(ent, "buoyancy", false)
local quest = StartGameQuest()
quests[quest.name] = quest
for k, v in pairs(quests) do
print(k, v.active)
end
quest:activate()
elseif event == "actuator_enter" then
ecs_character_physics_control(what_entity, false)
ecs_character_set_actuator(what_entity, "swimming-hold-edge")
ecs_trigger_set_position(trigger_entity, what_entity)
local ent = ecs_get_entity(what_entity)
if (ent.is_character()) then
print("character")
else
if not actuator then
check_actuator_event(event, trigger_entity, what_entity)
else
if not actuator:event(event, trigger_entity, what_entity) then
crash()
end
if actuator:complete() then
print("ACTUATOR COMPLETE")
print("EXIT ACTUATOR")
actuator = nil
end
end
if (ent.is_player()) then
print("player")
end
-- crash()
elseif event == "actuator_exit" then
crash()
end
end)

View File

@@ -89,7 +89,7 @@ BoatModule::BoatModule(flecs::world &ecs)
ECS::get<EngineData>()
.mWorld->addRigidBody(
0, boat.mEnt,
Ogre::Bullet::CT_TRIMESH,
Ogre::Bullet::CT_BOX,
nullptr, 2, 0x7fffffff);
b2e.entities[body.body] = e;
std::vector<Ogre::Node *> slots =

View File

@@ -1,5 +1,6 @@
#include <iostream>
#include "Components.h"
#include "EventTriggerModule.h"
#include "CharacterModule.h"
#include "CharacterAnimationModule.h"
namespace ECS
@@ -17,10 +18,18 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
ch.mSkeleton->setBlendMode(
Ogre::ANIMBLEND_CUMULATIVE);
Ogre::String animNames[] = {
"idle", "walking",
"running", "treading_water",
"swimming", "hanging-idle",
"hanging-climb", "swimming-hold-edge"
"idle",
"walking",
"running",
"treading_water",
"swimming",
"hanging-idle",
"hanging-climb",
"swimming-hold-edge",
"swimming-edge-climb",
"character-talk",
"pass-character",
"idle-act"
};
int state_count = sizeof(animNames) /
sizeof(animNames[0]);
@@ -28,15 +37,17 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
new AnimationSystem(false);
for (i = 0; i < state_count; i++) {
Animation *animation = new Animation(
ch.mSkeleton,
ch.mBodyEnt->getAnimationState(
animNames[i]),
ch.mSkeleton->getAnimation(
animNames[i])
);
animNames[i]),
e);
#ifdef VDEBUG
std::cout
<< "animation: " << animNames[i]
<< std::endl;
#endif
animation->setLoop(true);
anim.mAnimationSystem->add_animation(
animNames[i], animation);
@@ -71,16 +82,34 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
->end()
->end()
->state("actuator")
->state_machine(ANIM_FADE_SPEED, "actuator-state")
->state_machine(ANIM_FADE_SPEED * 10.0f, "actuator-state")
->state("hanging-idle")
->animation("hanging-idle")
->end()
->state("swimming-hold-edge")
->animation("swimming-hold-edge")
->end()
->state("swimming-edge-climb")
->animation("swimming-edge-climb")
->trigger(e, "end_of_climb", 0.99f, "animation:swimming-edge-climb:end")
->end()
->state("hanging-climb")
->animation("hanging-climb")
->trigger(e, "end_of_climb2", 0.99f, "animation:hanging-climb:end")
->end()
->state("idle")
->animation("idle-act")
->end()
->state("pass-character")
->animation("pass-character")
->trigger(e, "pass-character", 0.99f, "animation:pass-character:end")
->end()
->state("character-talk")
->animation("character-talk")
->end()
->transition_end("swimming-edge-climb", "idle")
->transition_end("hanging-climb", "idle")
->transition_end("pass-character", "idle")
->end()
->end()
->end();
@@ -88,32 +117,85 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
anim.mAnimationSystem
->get<AnimationNodeStateMachine>("main")
->setAnimation("locomotion");
->setAnimation("locomotion", true);
anim.mAnimationSystem
->get<AnimationNodeStateMachine>(
"locomotion-state")
->setAnimation("idle");
->setAnimation("idle", true);
anim.configured = true;
}
});
#if 0
ecs.system<CharacterBase>("RootMotionStart")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, CharacterBase &ch) {
ch.mBoneMotion = Ogre::Vector3::ZERO;
});
#endif
ecs.system<const EngineData, CharacterBase, AnimationControl>(
"HandleAnimations1")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, const EngineData &eng,
CharacterBase &ch, AnimationControl &anim) {
float delta = eng.delta;
anim.mAnimationSystem->addTime(delta);
// ch.mBoneMotion = Ogre::Vector3::ZERO;
bool result = anim.mAnimationSystem->addTime(delta);
if (!ch.mRootBone)
return;
ch.mBoneMotion += ch.mRootBone->getPosition();
// The value we get is interpolated value. When result is true it is new step
#if 0
Ogre::Vector3 offset = ch.mRootBone->getPosition();
ch.mBoneMotion = static_cast<RootMotionListener *>(
anim.mListener)
->getDeltaMotion();
ch.mRootBone->setPosition(Ogre::Vector3::ZERO);
Ogre::Vector3 d = offset - ch.mBonePrevMotion;
ch.mBonePrevMotion = offset;
std::cout << "length: " << d.length() << std::endl;
if (d.squaredLength() > 0.02f * 0.02f)
d = offset;
if (d.squaredLength() > 0.02f * 0.02f)
d = Ogre::Vector3::ZERO;
std::cout << "length2: " << d.length() << std::endl;
OgreAssert(d.length() < 0.5f, "bad offset");
ch.mBoneMotion = d;
#endif
#if 0
if (result) {
if (d.squaredLength() > 0.0f)
ch.mBoneMotion =
ch.mRootBone->getPosition() -
ch.mBoneMotion;
else
ch.mBoneMotion =
ch.mRootBone->getPosition();
} else {
ch.mBoneMotion = ch.mRootBone->getPosition() -
ch.mBoneMotion;
}
#endif
#undef VDEBUG
#ifdef VDEBUG
std::cout << "root motion: " << delta << ": "
<< ch.mBoneMotion << " - "
<< ch.mRootBone->getPosition()
<< " result: " << result << std::endl;
#endif
#undef VDEBUG
#if 0
// ch.mRootBone->setPosition(Ogre::Vector3::ZERO);
ch.mBonePrevMotion = offset;
#endif
});
ecs.system<const EngineData, const CharacterBase, CharacterVelocity>(
ecs.system<const EngineData, CharacterBase, CharacterVelocity>(
"HandleRootMotionVelocity")
.kind(flecs::OnUpdate)
.with<TerrainReady>()
.with<WaterReady>()
.each([this](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterVelocity &v) {
CharacterBase &ch, CharacterVelocity &v) {
if (eng.delta < 0.0000001f)
return;
if (!ch.mBodyNode)
@@ -121,7 +203,9 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
Ogre::Quaternion rot = ch.mBodyNode->getOrientation();
Ogre::Vector3 pos = ch.mBodyNode->getPosition();
Ogre::Vector3 boneMotion = ch.mBoneMotion;
v.velocity = rot * boneMotion / eng.delta;
v.velocity = Ogre::Math::lerp(
v.velocity, rot * boneMotion / eng.delta,
0.99f);
if (!e.has<CharacterDisablePhysics>() &&
!e.has<CharacterInActuator>()) {
if (eng.startupDelay <= 0.0f)
@@ -129,6 +213,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
v.velocity.y = Ogre::Math::Clamp(
v.velocity.y, -10.5f, 1000000.0f);
}
// ch.mBoneMotion = Ogre::Vector3::ZERO;
});
ecs.system<const EngineData, const AnimationControl,
const CharacterBase, CharacterVelocity>("HandleSwimming")
@@ -182,8 +267,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
penetration = body.mController
->isPenetrating();
if (is_on_floor)
v.gvelocity = Ogre::Vector3(
0.0f, 0.0f, 0.0f);
v.gvelocity =
Ogre::Vector3::ZERO;
btTransform from(
Ogre::Bullet::convert(
@@ -192,14 +277,21 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
Ogre::Bullet::convert(
ch.mBodyNode
->getPosition()));
ch.mBodyNode->setPosition(
ch.mBodyNode->getPosition() +
ch.mBodyNode->_setDerivedPosition(
ch.mBodyNode
->_getDerivedPosition() +
rotMotion);
ch.mBoneMotion = Ogre::Vector3(0, 0, 0);
}
}
});
ecs.system<CharacterVelocity, CharacterBase>("HandleRootMotionEnd")
.kind(flecs::OnUpdate)
.each([this](flecs::entity e, CharacterVelocity &v,
CharacterBase &ch) {
// zero the velocity;
// v.velocity = Ogre::Vector3::ZERO;
// ch.mBoneMotion = Ogre::Vector3::ZERO;
});
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandleNPCAnimations")
@@ -243,8 +335,8 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
"actuator-state");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "actuator")
main_sm->setAnimation("actuator");
actuator_sm->setAnimation(inact.animationState);
main_sm->setAnimation("actuator", true);
actuator_sm->setAnimation(inact.animationState, true);
});
ecs.system<const CharacterBase, AnimationControl>(
"HandlePlayerAnimationsNoActuator")
@@ -261,7 +353,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
"main");
Ogre::String current_state = main_sm->getCurrentState();
if (current_state != "locomotion")
main_sm->setAnimation("locomotion");
main_sm->setAnimation("locomotion", true);
});
ecs.system<const Input, const CharacterBase, AnimationControl>(
"HandlePlayerAnimations")
@@ -303,7 +395,7 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
else if (current_state != "idle" &&
!ch.is_submerged)
next_state = "idle";
state_machine->setAnimation(next_state, true);
state_machine->setAnimation(next_state);
} else if (start_motion) {
if (ch.is_submerged) {
if (input.fast)
@@ -343,5 +435,187 @@ CharacterAnimationModule::CharacterAnimationModule(flecs::world &ecs)
state_machine->setAnimation(next_state);
}
});
ecs.system<const Input, const CharacterBase, AnimationControl,
CharacterInActuator>("HandlePlayerAnimations2")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const Input &input,
const CharacterBase &ch, AnimationControl &anim,
CharacterInActuator &act) {
bool controls_idle = input.motion.zeroLength();
if (!controls_idle) {
std::cout << "motion.z: "
<< Ogre::Math::Abs(input.motion.z -
act.prevMotion.z)
<< std::endl;
bool trigger_event = false;
e.each<InTrigger>([&](flecs::entity trig) {
if (Ogre::Math::Abs(input.motion.z -
act.prevMotion.z) >
0.001f) {
if (input.motion.z < 0)
ECS::get<LuaBase>()
.mLua
->call_handler(
"actuator_forward",
trig,
e);
if (input.motion.z > 0)
ECS::get_mut<LuaBase>()
.mLua
->call_handler(
"actuator_backward",
trig,
e);
}
if (input.act_pressed)
ECS::get_mut<LuaBase>()
.mLua->call_handler(
"actuator_action",
trig, e);
// ECS::get_mut<LuaData>().call_handler(
// "actuator_update", trig, e);
trigger_event = true;
});
if (!trigger_event) {
if (Ogre::Math::Abs(input.motion.z -
act.prevMotion.z) >
0.001f) {
if (input.motion.z < 0)
ECS::get<LuaBase>()
.mLua
->call_handler(
"_in_actuator_forward",
e, e);
if (input.motion.z > 0)
ECS::get_mut<LuaBase>()
.mLua
->call_handler(
"_in_actuator_backward",
e, e);
}
if (input.act_pressed)
ECS::get_mut<LuaBase>()
.mLua->call_handler(
"_in_actuator_action",
e, e);
// ECS::get_mut<LuaData>().call_handler(
// "actuator_update", trig, e);
}
act.prevMotion.x = input.motion.x;
act.prevMotion.y = input.motion.y;
act.prevMotion.z = input.motion.z;
}
#if 0
if (!controls_idle) {
if (Ogre::Math::Abs(input.motion.z - act.prevMotion.z) > 0.001f) {
if (input.motion.z < 0)
ECS::get_mut<LuaData>().call_handler("actuator_forward", e);
}
ECS::get_mut<LuaData>().call_handler("actuator_controls_update");
}
#endif
act.prevMotion = input.motion;
});
#ifdef VDEBUG
ecs.system<const CharacterBase>("CharacterGravityStatus")
.kind(flecs::OnUpdate)
.with<Character>()
.with<Player>()
.each([](flecs::entity e, const CharacterBase &ch) {
if (e.has<CharacterGravity>())
std::cout << "gravity\n";
else
std::cout << "no gravity\n";
if (e.has<InWater>())
std::cout << "in water\n";
else
std::cout << "out of water\n";
std::cout
<< "h=" << ch.mBodyNode->_getDerivedPosition().y
<< std::endl;
});
#endif
ecs.system<const EngineData, const CharacterBase, CharacterBody>(
"UpdateBodyCast")
.kind(flecs::OnUpdate)
.without<CharacterInActuator>()
.each([](flecs::entity e, const EngineData &eng,
const CharacterBase &ch, CharacterBody &body) {
struct ResultCallback
: public btCollisionWorld::RayResultCallback {
btCollisionObject *m_me;
btVector3 m_from, m_to, m_hitNormalWorld,
m_hitPointWorld;
ResultCallback(btCollisionObject *me,
const btVector3 &from,
const btVector3 &to)
: m_me(me)
, m_from(from)
, m_to(to)
{
}
btScalar addSingleResult(
btCollisionWorld::LocalRayResult
&rayResult,
bool normalInWorldSpace) override
{
if (rayResult.m_collisionObject == m_me)
return 1.0f;
if (!btPairCachingGhostObject::upcast(
rayResult.m_collisionObject))
return 1.0f;
if (!(rayResult.m_collisionObject
->getCollisionFlags() &
btCollisionObject::
CF_CHARACTER_OBJECT))
return 1.0f;
m_closestHitFraction =
rayResult.m_hitFraction;
m_collisionObject =
rayResult.m_collisionObject;
if (normalInWorldSpace)
m_hitNormalWorld =
rayResult
.m_hitNormalLocal;
else
m_hitNormalWorld =
m_collisionObject
->getWorldTransform()
.getBasis() *
rayResult
.m_hitNormalLocal;
m_hitPointWorld.setInterpolate3(
m_from, m_to,
rayResult.m_hitFraction);
return rayResult.m_hitFraction;
}
};
Ogre::Vector3 offset(0.0f, 0.5f, 0.0f);
float dist = 0.5f;
btVector3 a = Ogre::Bullet::convert(
ch.mBodyNode->getPosition() + offset),
b(Ogre::Bullet::convert(
ch.mBodyNode->getPosition() +
ch.mBodyNode->getOrientation() *
Ogre::Vector3(0, 0, dist) +
offset));
ResultCallback result(body.mGhostObject, a, b);
// body.mGhostObject->rayTest(a, b, result);
eng.mWorld->getBtWorld()->rayTest(a, b, result);
if (result.hasHit()) {
std::cout << "Hit!!! " << result.m_hitPointWorld
<< std::endl;
e.set<CharacterInActuator>(
{ "idle", { 0, 0, 0 } });
ECS::get<LuaBase>().mLua->call_handler(
"character_enter", e,
ECS::get<Body2Entity>().entities.at(
const_cast<btCollisionObject *>(
result.m_collisionObject)));
}
});
}
}

View File

@@ -3,9 +3,74 @@
#include <Ogre.h>
#include <flecs.h>
#include "GameData.h"
#include "CharacterModule.h"
#include "LuaData.h"
namespace ECS
{
class RootMotionListener : public Ogre::NodeAnimationTrack::Listener {
Ogre::Vector3 prevTranslation;
mutable Ogre::Vector3 deltaMotion;
flecs::entity e;
public:
RootMotionListener(flecs::entity e)
: Ogre::NodeAnimationTrack::Listener()
, e(e)
, prevTranslation(Ogre::Vector3::ZERO)
, deltaMotion(Ogre::Vector3::ZERO)
{
}
bool getInterpolatedKeyFrame(const Ogre::AnimationTrack *t,
const Ogre::TimeIndex &timeIndex,
Ogre::KeyFrame *kf) override
{
Ogre::TransformKeyFrame *vkf =
static_cast<Ogre::TransformKeyFrame *>(kf);
Ogre::KeyFrame *kf1, *kf2;
Ogre::TransformKeyFrame *k1, *k2;
unsigned short firstKeyIndex;
float tm = t->getKeyFramesAtTime(timeIndex, &kf1, &kf2,
&firstKeyIndex);
k1 = static_cast<Ogre::TransformKeyFrame *>(kf1);
k2 = static_cast<Ogre::TransformKeyFrame *>(kf2);
Ogre::Vector3 translation;
Ogre::Quaternion rotation;
if (tm == 0.0f) {
rotation = k1->getRotation();
translation = k1->getTranslate();
deltaMotion = translation;
// vkf->setRotation(k1->getRotation());
// vkf->setTranslate(k1->getTranslate());
// vkf->setScale(k1->getScale());
} else {
rotation = Ogre::Quaternion::nlerp(
tm, k1->getRotation(), k2->getRotation(), true);
translation =
k1->getTranslate() +
(k2->getTranslate() - k1->getTranslate()) * tm;
deltaMotion = translation - prevTranslation;
if (deltaMotion.squaredLength() >
translation.squaredLength())
deltaMotion = translation;
}
#if 0
std::cout << "time: " << tm
<< " Position: " << deltaMotion;
std::cout << " Quaternion: " << rotation;
std::cout << std::endl;
#endif
vkf->setTranslate(deltaMotion);
// vkf->setTranslate(translation);
vkf->setRotation(rotation);
vkf->setScale(Ogre::Vector3(1, 1, 1));
prevTranslation = translation;
e.get_mut<CharacterBase>().mBoneMotion = deltaMotion;
e.get_mut<CharacterBase>().mBonePrevMotion = prevTranslation;
e.modified<CharacterBase>();
return true;
}
};
struct AnimationTrigger;
struct AnimationTriggerSubscriber {
virtual void operator()(const AnimationTrigger *trigger) = 0;
@@ -65,17 +130,20 @@ struct Animation {
Ogre::Animation *mSkelAnimation;
Ogre::NodeAnimationTrack *mHipsTrack;
Ogre::NodeAnimationTrack *mRootTrack;
RootMotionListener *mListener;
float m_weight;
float m_accWeight;
std::multimap<float, AnimationTrigger *> trigger_list;
Animation(Ogre::AnimationState *animState,
Ogre::Animation *skelAnimation)
Animation(Ogre::Skeleton *skeleton, Ogre::AnimationState *animState,
Ogre::Animation *skelAnimation, flecs::entity e)
: mAnimationState(animState)
, mSkelAnimation(skelAnimation)
, mListener(OGRE_NEW RootMotionListener(e))
, m_weight(0)
, m_accWeight(0)
{
int j;
mRootTrack = nullptr;
mHipsTrack = nullptr;
for (const auto &it : mSkelAnimation->_getNodeTrackList()) {
Ogre::NodeAnimationTrack *track = it.second;
Ogre::String trackName =
@@ -87,10 +155,21 @@ struct Animation {
// mRootTracks[i]->removeAllKeyFrames();
}
}
if (!mRootTrack) {
Ogre::Bone *bone = skeleton->getBone("Root");
mRootTrack = mSkelAnimation->createNodeTrack(
bone->getHandle(), bone);
Ogre::TransformKeyFrame *kf =
mRootTrack->createNodeKeyFrame(0.0f);
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(Ogre::Quaternion::IDENTITY);
}
mRootTrack->setListener(mListener);
#if 0
OgreAssert(mHipsTrack, "no hips track");
OgreAssert(mRootTrack, "no Root track");
#endif
#if 0
if (mRootTrack) {
Ogre::Vector3 delta = Ogre::Vector3::ZERO;
Ogre::Vector3 motion = Ogre::Vector3::ZERO;
@@ -107,6 +186,55 @@ struct Animation {
motion = trans;
}
}
#endif
#if 0
if (!mMetaRootTrack) {
Ogre::Bone *bone = nullptr;
OgreAssert(skeleton->hasBone("MetaRoot"),
"no bone MetaRoot");
if (skeleton->hasBone("MetaRoot"))
bone = skeleton->getBone("MetaRoot");
#if 0
else
bone = skeleton->createBone("MetaRoot");
#endif
bone->setPosition(Ogre::Vector3::ZERO);
bone->setOrientation(Ogre::Quaternion::IDENTITY);
std::vector<
std::pair<float, std::pair<Ogre::Vector3,
Ogre::Quaternion> > >
keyframes;
for (j = 0; j < mRootTrack->getNumKeyFrames(); j++) {
Ogre::TransformKeyFrame *kf =
mRootTrack->getNodeKeyFrame(j);
const Ogre::Vector3 &pt = kf->getTranslate();
const Ogre::Quaternion &rt = kf->getRotation();
float tp = kf->getTime();
keyframes.push_back({ tp, { pt, rt } });
#if 0
new_kf->setTranslate(pt);
new_kf->setTranslate(kf->getTranslate());
new_kf->setRotation(kf->getRotation());
#endif
kf->setTranslate(Ogre::Vector3::ZERO);
kf->setRotation(Ogre::Quaternion::IDENTITY);
}
mMetaRootTrack = mSkelAnimation->createNodeTrack(
bone->getHandle());
OgreAssert(mMetaRootTrack,
"failed to create node track");
for (j = 0; j < keyframes.size(); j++) {
Ogre::TransformKeyFrame *new_kf =
mMetaRootTrack->createNodeKeyFrame(
keyframes[j].first);
new_kf->setTranslate(keyframes[j].second.first);
new_kf->setRotation(keyframes[j].second.second);
}
}
#if 0
mRootTrack = track;
#endif
#endif
}
Ogre::String getName()
{
@@ -139,11 +267,22 @@ struct Animation {
{
return m_weight;
}
void addTime(float time)
bool addTime(float time)
{
preUpdateTriggers();
bool result = mAnimationState->getEnabled();
if (!result)
return result;
Ogre::TimeIndex index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
Ogre::KeyFrame *kf1, *kf2;
unsigned short prev_index, next_index;
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &prev_index);
unsigned int previous_frame = index.getKeyIndex();
mAnimationState->addTime(time);
postUpdateTriggers(time);
index = mSkelAnimation->_getTimeIndex(
mAnimationState->getTimePosition());
mRootTrack->getKeyFramesAtTime(index, &kf1, &kf2, &next_index);
return prev_index != next_index;
}
void reset()
{
@@ -161,55 +300,9 @@ struct Animation {
{
return m_accWeight;
}
void addTrigger(AnimationTrigger *trigger)
{
trigger_list.insert(std::pair<float, AnimationTrigger *>(
trigger->getTriggerTime(), trigger));
}
void clearTriggers()
{
auto it = trigger_list.begin();
while (it != trigger_list.end()) {
delete it->second;
it++;
}
trigger_list.clear();
}
float mpreUpdateTime;
void preUpdateTriggers()
{
mpreUpdateTime = mAnimationState->getTimePosition() /
mAnimationState->getLength();
}
void postUpdateTriggers(float delta)
{
float postUpdateTime = mAnimationState->getTimePosition() /
mAnimationState->getLength();
bool positive = delta >= 0.0f;
if (positive)
updateTriggers(mpreUpdateTime, postUpdateTime);
else
updateTriggers(postUpdateTime, mpreUpdateTime);
}
void updateTriggers(float currentTime, float nextTime)
{
int i;
float weight = getWeight();
if (currentTime <= nextTime) {
auto it = trigger_list.lower_bound(currentTime);
while (it != trigger_list.end()) {
if (nextTime <=
it->second->getTriggerTime()) // in future, sorrted by time
return;
it->second->notify(weight);
}
} else {
updateTriggers(currentTime, 1);
updateTriggers(0, nextTime);
}
}
float getLength() const
{
return mAnimationState->getLength();
if (getEnabled())
return mAnimationState->getLength();
else
@@ -217,6 +310,7 @@ struct Animation {
}
float getTimePosition() const
{
return mAnimationState->getTimePosition();
if (getEnabled())
return mAnimationState->getTimePosition();
else
@@ -228,11 +322,12 @@ struct AnimationNode {
std::vector<AnimationNode *> children;
float m_weight;
Ogre::String m_name;
std::multimap<float, AnimationTrigger *> trigger_list;
AnimationNode()
: m_weight(0)
{
}
virtual void addTime(float time) = 0;
virtual bool addTime(float time) = 0;
virtual void setWeight(float weight) = 0;
virtual void reset() = 0;
virtual float getLength() const = 0;
@@ -256,6 +351,52 @@ struct AnimationNode {
return getTimePosition() / l;
return 0.0f;
}
void addTrigger(AnimationTrigger *trigger)
{
trigger_list.insert(std::pair<float, AnimationTrigger *>(
trigger->getTriggerTime(), trigger));
}
void clearTriggers()
{
auto it = trigger_list.begin();
while (it != trigger_list.end()) {
delete it->second;
it++;
}
trigger_list.clear();
}
float mpreUpdateTime;
void preUpdateTriggers()
{
mpreUpdateTime = getTime();
}
void postUpdateTriggers(float delta)
{
float postUpdateTime = getTime();
bool positive = delta >= 0.0f;
if (positive)
updateTriggers(mpreUpdateTime, postUpdateTime);
else
updateTriggers(postUpdateTime, mpreUpdateTime);
}
void updateTriggers(float currentTime, float nextTime)
{
int i;
float weight = getWeight();
if (currentTime <= nextTime) {
auto it = trigger_list.lower_bound(currentTime);
while (it != trigger_list.end()) {
if (nextTime <=
it->second->getTriggerTime()) // in future, sorrted by time
return;
it->second->notify(weight);
it++;
}
} else {
updateTriggers(currentTime, 1);
updateTriggers(0, nextTime);
}
}
};
struct AnimationNodeAnimation : AnimationNode {
@@ -266,9 +407,13 @@ struct AnimationNodeAnimation : AnimationNode {
, mAnimation(animation)
{
}
void addTime(float time)
bool addTime(float time)
{
mAnimation->addTime(time);
bool ret;
preUpdateTriggers();
ret = mAnimation->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
@@ -281,6 +426,7 @@ struct AnimationNodeAnimation : AnimationNode {
}
float getLength() const
{
return mAnimation->getLength();
if (enabled)
return mAnimation->getLength();
else
@@ -288,6 +434,7 @@ struct AnimationNodeAnimation : AnimationNode {
}
float getTimePosition() const
{
return mAnimation->getTimePosition();
if (enabled)
return mAnimation->getTimePosition();
else
@@ -299,9 +446,13 @@ struct AnimationNodeStateMachineState : AnimationNode {
: AnimationNode()
{
}
void addTime(float time)
bool addTime(float time)
{
children[0]->addTime(time);
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
@@ -319,7 +470,7 @@ struct AnimationNodeStateMachineState : AnimationNode {
}
float getTimePosition() const
{
return children[0]->getLength();
return children[0]->getTimePosition();
}
};
struct AnimationNodeSpeed : AnimationNode {
@@ -331,9 +482,13 @@ struct AnimationNodeSpeed : AnimationNode {
, enabled(false)
{
}
void addTime(float time)
bool addTime(float time)
{
children[0]->addTime(time * m_speed);
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time * m_speed);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
@@ -383,36 +538,43 @@ struct AnimationNodeStateMachine : AnimationNode {
{
m_weight = 1.0f;
}
void addTime(float time)
bool addTime(float time)
{
int i;
preUpdateTriggers();
if (!configured) {
configure();
configured = true;
}
#ifdef VDEBUG
if (debug) {
std::cout << "state machine addTime" << std::endl;
std::cout
<< "state machine children: " << children.size()
<< std::endl;
}
#endif
for (i = 0; i < children.size(); i++) {
#ifdef VDEBUG
if (debug)
std::cout << "child weight: " << i << " "
<< children[i]->getWeight()
<< std::endl;
#endif
AnimationNode *child = children[i];
if (fade_in.find(child) != fade_in.end()) {
Ogre::Real newWeight =
child->getWeight() + time * fade_speed;
child->setWeight(Ogre::Math::Clamp<Ogre::Real>(
newWeight * m_weight, 0, m_weight));
#ifdef VDEBUG
if (debug) {
std::cout << "fade in: " << newWeight
<< std::endl;
std::cout << "m_weight: " << m_weight
<< std::endl;
}
#endif
if (newWeight >= 1)
fade_in.erase(child);
}
@@ -426,8 +588,11 @@ struct AnimationNodeStateMachine : AnimationNode {
}
}
OgreAssert(currentAnim, "bad current anim");
bool ret = false;
if (currentAnim)
currentAnim->addTime(time);
ret = currentAnim->addTime(time);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
@@ -522,9 +687,13 @@ struct AnimationNodeOutput : AnimationNode {
, m_speed(1.0f)
{
}
void addTime(float time)
bool addTime(float time)
{
children[0]->addTime(time * m_speed);
bool ret;
preUpdateTriggers();
ret = children[0]->addTime(time * m_speed);
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{
@@ -614,8 +783,9 @@ struct AnimationSystem : AnimationNode {
Ogre::String event;
void operator()(const AnimationTrigger *trigger)
{
ECS::get_mut<LuaData>().call_handler(
event, ent, ent);
ECS::get_mut<LuaBase>()
.mLua->call_handler(event, ent,
ent);
}
EventSubscriber(flecs::entity e,
const Ogre::String &event)
@@ -625,15 +795,57 @@ struct AnimationSystem : AnimationNode {
}
};
OgreAssert(parent, "bad parent");
Animation *animation =
static_cast<AnimationNodeAnimation *>(parent)
->mAnimation;
OgreAssert(animation, "bad animation");
AnimationTrigger *trigger =
new AnimationTrigger(name, time, 0.1f);
EventSubscriber *sub = new EventSubscriber(e, event);
trigger->addSubscriber(sub);
animation->addTrigger(trigger);
parent->addTrigger(trigger);
return this;
} // leaf too...
AnimationSystemBuilder *
transition_end(const Ogre::String &state_from,
const Ogre::String &state_to)
{
struct EndTransitionSubscriber
: AnimationTriggerSubscriber {
AnimationNodeStateMachine *sm;
Ogre::String next_state;
bool reset;
void operator()(const AnimationTrigger *trigger)
{
sm->setAnimation(next_state, reset);
}
EndTransitionSubscriber(
AnimationNodeStateMachine *sm,
const Ogre::String &next_state,
bool reset = true)
: sm(sm)
, next_state(next_state)
, reset(reset)
{
}
};
OgreAssert(parent, "no parent");
AnimationNodeStateMachine *sm =
static_cast<AnimationNodeStateMachine *>(
parent);
OgreAssert(sm, "no state machine");
AnimationTrigger *trigger = new AnimationTrigger(
"transition:" + state_from + "_" + state_to,
0.99f, 0.1f);
EndTransitionSubscriber *sub =
new EndTransitionSubscriber(sm, state_to);
int i;
bool ok = false;
for (i = 0; i < sm->children.size(); i++) {
if (sm->children[i]->getName() == state_from) {
trigger->addSubscriber(sub);
sm->children[i]->addTrigger(trigger);
ok = true;
break;
}
}
OgreAssert(ok, "Failed to set transition");
return this;
}
AnimationSystemBuilder *speed(float speed,
@@ -685,10 +897,11 @@ struct AnimationSystem : AnimationNode {
}
};
AnimationSystemBuilder m_builder;
void addTime(float time)
bool addTime(float time)
{
int i;
m_builder.animation_nodes[0]->addTime(time);
preUpdateTriggers();
bool ret = m_builder.animation_nodes[0]->addTime(time);
for (i = 0; i < m_builder.animationNodeList.size(); i++) {
AnimationNodeAnimation *anim =
m_builder.animationNodeList[i];
@@ -706,13 +919,17 @@ struct AnimationSystem : AnimationNode {
float weight = vanimation_list[i]->getAccWeight();
vanimation_list[i]->setWeight(weight);
vanimation_list[i]->resetAccWeight();
#define VDEBUG
#ifdef VDEBUG
if (debug)
if (debug && vanimation_list[i]->getEnabled())
std::cout << i << " animation: "
<< vanimation_list[i]->getName()
<< " " << weight << std::endl;
#endif
#undef VDEBUG
}
postUpdateTriggers(time);
return ret;
}
void setWeight(float weight)
{

View File

@@ -51,6 +51,22 @@ CharacterModule::CharacterModule(flecs::world &ecs)
zaxis = 0.0f;
else
zaxis = Ogre::Math::Sign(zaxis);
if (active & 32)
input.act = true;
else
input.act = false;
if (pressed & 32)
input.act_pressed = true;
else
input.act_pressed = false;
if (active & 64)
input.act2 = true;
else
input.act2 = false;
if (pressed & 64)
input.act2_pressed = true;
else
input.act2_pressed = false;
input.motion.z = zaxis;
float xaxis = input.motion.x;
xaxis *= 0.9f;
@@ -246,11 +262,15 @@ CharacterModule::CharacterModule(flecs::world &ecs)
ch.mBodyNode->setPosition(loc.position);
ch.mBodyNode->attachObject(ch.mBodyEnt);
ch.mSkeleton = ch.mBodyEnt->getSkeleton();
OgreAssert(ch.mBodyEnt->getSkeleton()->hasBone("Root"),
"No root bone");
OgreAssert(ch.mSkeleton->hasBone("Root"),
"No root bone");
ch.mRootBone = ch.mSkeleton->getBone("Root");
OgreAssert(ch.mRootBone, "No root bone");
body.mController = nullptr;
ch.mBoneMotion = Ogre::Vector3::ZERO;
ch.mBonePrevMotion = Ogre::Vector3::ZERO;
e.set<CharacterVelocity>(
{ { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } });
body.checkGround = false;
@@ -283,8 +303,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
->calculatePrincipalAxisTransform(
masses, principal, inertia);
}
body.mGhostObject->setCollisionFlags(
btCollisionObject::CF_KINEMATIC_OBJECT /*|
body.mGhostObject->setCollisionFlags(body.mGhostObject->getCollisionFlags() | btCollisionObject::CF_CHARACTER_OBJECT | btCollisionObject::CF_KINEMATIC_OBJECT
/*btCollisionObject::CF_KINEMATIC_OBJECT |
btCollisionObject::CF_NO_CONTACT_RESPONSE */);
body.mGhostObject->setActivationState(
DISABLE_DEACTIVATION);
@@ -295,6 +315,8 @@ CharacterModule::CharacterModule(flecs::world &ecs)
e.add<CharacterGravity>();
e.add<CharacterBuoyancy>();
anim.configured = false;
OgreAssert(body.mGhostObject->hasContactResponse(),
"need contact response");
});
ecs.system<const EngineData, CharacterBase, CharacterBody>(
"UpdateCharacterPhysics")
@@ -434,8 +456,11 @@ CharacterModule::CharacterModule(flecs::world &ecs)
.with<InWater>()
.each([](flecs::entity e, const WaterBody &waterb,
const CharacterBase &ch, CharacterBody &body) {
if (waterb.isInWater(body.mGhostObject) &&
ch.mBodyNode->_getDerivedPosition().y > 0.05f)
float h = ch.mBodyNode->_getDerivedPosition().y;
if (waterb.isInWater(body.mGhostObject) && h > 0.05f)
e.remove<InWater>();
else if (!waterb.isInWater(body.mGhostObject) &&
h > 0.05f)
e.remove<InWater>();
});
#if 0

View File

@@ -19,6 +19,7 @@ struct CharacterBase {
Ogre::Skeleton *mSkeleton;
Ogre::Node *mRootBone;
Ogre::Vector3 mBoneMotion;
Ogre::Vector3 mBonePrevMotion;
Ogre::Vector3 mGoalDirection; // actual intended direction in world-space
bool is_submerged;
};
@@ -42,6 +43,7 @@ struct CharacterVelocity {
};
struct CharacterInActuator {
Ogre::String animationState;
Vector3 prevMotion;
};
struct CharacterModule {
CharacterModule(flecs::world &ecs);

View File

@@ -45,6 +45,10 @@ struct Input {
bool mouse_moved;
bool wheel_moved;
bool fast;
bool act;
bool act_pressed;
bool act2;
bool act2_pressed;
Input()
: control(0)
, control_prev(0)

View File

@@ -258,6 +258,14 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
.entities
.end(),
"No body to entity mapping");
flecs::entity obj_e =
ECS::get<
Body2Entity>()
.entities
.at(const_cast<
btCollisionObject
*>(
other));
ECS::get<
LuaBase>()
.mLua
@@ -265,13 +273,12 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
evt.event +
"_enter",
e,
ECS::get<
Body2Entity>()
.entities
.at(const_cast<
btCollisionObject
*>(
other)));
obj_e);
obj_e.add<
InTrigger>(
e);
e.add<TriggeredBy>(
obj_e);
}
}
}
@@ -283,14 +290,17 @@ ECS::EventTriggerModule::EventTriggerModule(flecs::world &ecs)
while (it != body.contactBodies.end()) {
if (currentContactBodies.find(*it) ==
currentContactBodies.end()) {
ECS::get<LuaBase>().mLua->call_handler(
evt.event + "_exit", e,
flecs::entity obj_e =
ECS::get<Body2Entity>()
.entities
.at(const_cast<
btCollisionObject
*>(
*it)));
*it));
ECS::get<LuaBase>().mLua->call_handler(
evt.event + "_exit", e, obj_e);
obj_e.remove<InTrigger>(e);
e.remove<TriggeredBy>(obj_e);
std::cout << "body exited" << std::endl;
it = body.contactBodies.erase(it);
if (it == body.contactBodies.end())

View File

@@ -16,6 +16,8 @@ struct EventTrigger {
struct EventTriggerData {
std::set<flecs::entity_t> entities;
};
struct InTrigger {};
struct TriggeredBy {};
struct EventTriggerModule {
EventTriggerModule(flecs::world &ecs);
};

View File

@@ -91,7 +91,7 @@ int LuaData::call_handler(const Ogre::String &event, flecs::entity e,
return 0;
}
int luaLibraryLoader(lua_State *L)
static int luaLibraryLoader(lua_State *L)
{
int i;
if (!lua_isstring(L, 1)) {
@@ -260,11 +260,11 @@ LuaData::LuaData()
lua_setglobal(L, "main_menu");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 3, "Invalid parameters");
luaL_checktype(L, 1, LUA_TSTRING);
luaL_checktype(L, 1, LUA_TNUMBER);
luaL_checktype(L, 2, LUA_TSTRING);
luaL_checktype(L, 3, LUA_TBOOLEAN);
bool enable = lua_toboolean(L, 3);
flecs::entity e = ECS::get().lookup(lua_tostring(L, 1));
flecs::entity e = idmap.get_entity(lua_tointeger(L, 1));
Ogre::String what = lua_tostring(L, 2);
OgreAssert(e.is_valid(), "Invalid character");
OgreAssert(e.has<Character>(), "Not a character");
@@ -401,6 +401,24 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "ecs_trigger_set_position");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // trigger
int trigger = lua_tointeger(L, 1);
flecs::entity trigger_e = idmap.get_entity(trigger);
Ogre::SceneNode *node = trigger_e.get<EventTrigger>().node;
Ogre::Any animationAny =
node->getUserObjectBindings().getUserAny(
"trigger_animation");
if (animationAny.has_value()) {
Ogre::String animation =
Ogre::any_cast<Ogre::String>(animationAny);
lua_pushstring(L, animation.c_str());
return 1;
}
lua_pushnil(L);
return 1;
});
lua_setglobal(L, "ecs_trigger_get_animation");
lua_pushcfunction(L, [](lua_State *L) -> int {
OgreAssert(lua_gettop(L) == 5, "Invalid parameters");
luaL_checktype(L, 1, LUA_TSTRING); // type
@@ -472,6 +490,14 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "ecs_character_physics_control");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // object
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
lua_pushboolean(L, object_e.has<Player>());
return 1;
});
lua_setglobal(L, "ecs_character_is_player");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // object
luaL_checktype(L, 2, LUA_TSTRING); // animation
@@ -479,8 +505,10 @@ LuaData::LuaData()
int object = lua_tointeger(L, 1);
flecs::entity object_e = idmap.get_entity(object);
if (animation.length())
object_e.set<CharacterInActuator>({ animation });
object_e.set<CharacterVelocity>({ { 0, 0, 0 }, { 0, 0, 0 } });
if (animation.length() > 0)
object_e.set<CharacterInActuator>(
{ animation, { 0, 0, 0 } });
else
object_e.remove<CharacterInActuator>();
return 0;
@@ -500,6 +528,13 @@ LuaData::LuaData()
return 0;
});
lua_setglobal(L, "ecs_set_slot");
lua_pushcfunction(L, [](lua_State *L) -> int {
flecs::entity e = ECS::get().lookup("player");
int result = idmap.add_entity(e);
lua_pushinteger(L, result);
return result;
});
lua_setglobal(L, "ecs_get_player_entity");
lua_pushcfunction(L, [](lua_State *L) -> int {
luaL_checktype(L, 1, LUA_TNUMBER); // entity id
int id = lua_tointeger(L, 1);