diff --git a/resources/start.py b/resources/start.py index 574f621..7a62639 100644 --- a/resources/start.py +++ b/resources/start.py @@ -1,4 +1,15 @@ import game_api -# Tell NPC 0 to walk to the origin (0, 0, 0) when the game starts. -game_api.npc_walk_to(0, 10.0, 0.0, 0.0) +# Chain of waypoints for NPC 0. +# Each on_arrived callback issues the next walk command. + +def step3(): + game_api.npc_walk_to(0, 0.0, 0.0, -30.0, on_arrived=step1) + +def step2(): + game_api.npc_walk_to(0, -2.0, 0.0, -2.0, on_arrived=step3) + +def step1(): + game_api.npc_walk_to(0, 2.0, 0.0, -2.0, on_arrived=step2) + +step1() \ No newline at end of file diff --git a/src/Character.cpp b/src/Character.cpp index c0f8f51..9349a49 100644 --- a/src/Character.cpp +++ b/src/Character.cpp @@ -17,8 +17,10 @@ void Character::setTexture(std::shared_ptr tex) { texture = tex; } -void Character::setTarget(const Eigen::Vector3f& target) { +void Character::setTarget(const Eigen::Vector3f& target, + std::function onArrived) { walkTarget = target; + onArrivedCallback = std::move(onArrived); } AnimationState Character::resolveActiveState() const { @@ -45,6 +47,11 @@ void Character::update(int64_t deltaMs) { currentState = AnimationState::WALK; } else { currentState = AnimationState::IDLE; + if (onArrivedCallback) { + auto cb = std::move(onArrivedCallback); + onArrivedCallback = nullptr; + cb(); + } } // Rotate toward target facing angle at constant angular speed diff --git a/src/Character.h b/src/Character.h index 5c1adee..2c66043 100644 --- a/src/Character.h +++ b/src/Character.h @@ -2,6 +2,7 @@ #include "BoneAnimatedModel.h" #include "render/Renderer.h" #include "render/TextureManager.h" +#include #include #include #include @@ -19,7 +20,10 @@ public: void loadAnimation(AnimationState state, const std::string& filename, const std::string& zipFile = ""); void setTexture(std::shared_ptr texture); void update(int64_t deltaMs); - void setTarget(const Eigen::Vector3f& target); + // 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 onArrived = nullptr); void draw(Renderer& renderer); // Public: read by Game for camera tracking and ray-cast origin @@ -48,6 +52,7 @@ private: Eigen::Vector3f walkTarget = Eigen::Vector3f(0.f, 0.f, 0.f); float targetFacingAngle = 0.0f; + std::function onArrivedCallback; static constexpr float WALK_THRESHOLD = 0.05f; diff --git a/src/Game.cpp b/src/Game.cpp index bd24540..67f4c93 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -183,12 +183,10 @@ namespace ZL npc01->loadAnimation(AnimationState::WALK, "resources/w/default_walk001.txt", CONST_ZIP_FILE); npc01->setTexture(defaultTexture); npc01->walkSpeed = 1.5f; - npc01->rotationSpeed = 2.0f; + npc01->rotationSpeed = 8.0f; npc01->modelScale = 0.01f; npc01->modelCorrectionRotation = Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitY())); - /* - Eigen::Quaternionf(Eigen::AngleAxisf(-M_PI * 0.5f, Eigen::Vector3f::UnitX())) * - Eigen::Quaternionf(Eigen::AngleAxisf(M_PI, Eigen::Vector3f::UnitZ()));*/ + npc01->position = Eigen::Vector3f(0.f, 0.f, -45.f); npc01->setTarget(npc01->position); npcs.push_back(std::move(npc01)); diff --git a/src/ScriptEngine.cpp b/src/ScriptEngine.cpp index 9dd7288..9fd699d 100644 --- a/src/ScriptEngine.cpp +++ b/src/ScriptEngine.cpp @@ -14,9 +14,11 @@ static ZL::Game* s_game = nullptr; PYBIND11_EMBEDDED_MODULE(game_api, m) { m.doc() = "Game scripting API"; - // npc_walk_to(index, x, y, z) - // Tells the NPC at `index` in Game::npcs to walk to world position (x,y,z). - m.def("npc_walk_to", [](int index, float x, float y, float z) { + // npc_walk_to(index, x, y, z, on_arrived=None) + // Tells the NPC at `index` to walk to (x,y,z). + // on_arrived is an optional Python callable invoked once when the NPC arrives. + // It can call npc_walk_to again (or anything else) to chain behaviour. + m.def("npc_walk_to", [](int index, float x, float y, float z, py::object on_arrived) { if (!s_game) { std::cerr << "[script] game_api.npc_walk_to: engine not ready\n"; return; @@ -27,10 +29,21 @@ PYBIND11_EMBEDDED_MODULE(game_api, m) { << " out of range (0.." << npcs.size() - 1 << ")\n"; return; } - npcs[index]->setTarget(Eigen::Vector3f(x, y, z)); + std::function cb; + if (!on_arrived.is_none()) { + cb = [on_arrived]() { + try { + on_arrived(); + } catch (const py::error_already_set& e) { + std::cerr << "[script] on_arrived callback error:\n" << e.what() << "\n"; + } + }; + } + npcs[index]->setTarget(Eigen::Vector3f(x, y, z), std::move(cb)); }, py::arg("index"), py::arg("x"), py::arg("y"), py::arg("z"), - "Command NPC[index] to walk to world position (x, y, z)."); + py::arg("on_arrived") = py::none(), + "Command NPC[index] to walk to (x,y,z). on_arrived is called when the NPC arrives."); } namespace ZL {