3 Commits

Author SHA1 Message Date
6b1f9d7354 feat(physics): implement step-up for horizontal collisions 2026-08-02 17:14:13 +08:00
5980400e2e feat(creatures): refine pig movement physics
Add movement constants for pigs and switch deceleration to per-axis friction so creatures stop naturally. Extend wander boost duration for smoother behavior.
2026-08-02 16:36:56 +08:00
9bb1882d92 fix: correct velocity clamping and hitbox loading
Preserve sign when clamping velocity; negative velocities now clamp to zero instead of flipping direction. Support loading hitbox definitions from JSON arrays and objects. Tune pig entity movement, spawn height, and wander probabilities.
2026-08-02 16:14:56 +08:00
8 changed files with 87 additions and 45 deletions

View File

@@ -3,4 +3,11 @@
namespace Cubed { namespace Cubed {
struct PigTag {}; struct PigTag {};
namespace PigDefaults {
constexpr float MAX_SPEED = 0.1f; // tiles/tick → 2 tiles/sec
constexpr float ACCELERATION = 0.01f; // ~10 ticks (0.5s) to reach full speed
constexpr float DECELERATION = 0.02f; // ~5 ticks (0.25s) to stop
constexpr float GRAVITY = 1.0f; // ≈20 tiles/s², close to the player
} // namespace PigDefaults
} // namespace Cubed } // namespace Cubed

View File

@@ -1,14 +1,12 @@
#pragma once #pragma once
#include "Cubed/gameplay/ecs/movement.hpp" #include "Cubed/gameplay/ecs/movement.hpp"
#include "Cubed/gameplay/ecs/transform.hpp"
#include <entt/entt.hpp> #include <entt/entt.hpp>
namespace Cubed { namespace Cubed {
class ServerWorld; class ServerWorld;
class PhysicalSystem { class PhysicalSystem {
public: public:
static glm::vec3 get_move_distance(const Direction& d, static glm::vec3 get_move_distance(const TickVelocity& v);
const TickVelocity& v);
static void update(ServerWorld& world, entt::registry& registry); static void update(ServerWorld& world, entt::registry& registry);

View File

@@ -86,15 +86,19 @@ HitboxManager::Handle HitboxManager::load(std::string_view name) {
std::ifstream s{p}; std::ifstream s{p};
json j = json::parse(s); json j = json::parse(s);
if (j.contains("boxes")) { if (j.contains("boxes")) {
if (j["boxes"].contains("center")) { const json* box = &j["boxes"];
center.x = j["boxes"]["center"].at(0).get<float>(); if (box->is_array() && !box->empty()) {
center.y = j["boxes"]["center"].at(1).get<float>(); box = &(*box)[0];
center.z = j["boxes"]["center"].at(2).get<float>();
} }
if (j["boxes"].contains("half")) { if (box->contains("center")) {
half.x = j["boxes"]["half"].at(0).get<float>(); center.x = (*box)["center"].at(0).get<float>();
half.y = j["boxes"]["half"].at(1).get<float>(); center.y = (*box)["center"].at(1).get<float>();
half.z = j["boxes"]["half"].at(2).get<float>(); center.z = (*box)["center"].at(2).get<float>();
}
if (box->contains("half")) {
half.x = (*box)["half"].at(0).get<float>();
half.y = (*box)["half"].at(1).get<float>();
half.z = (*box)["half"].at(2).get<float>();
} }
} }
{ {

View File

@@ -20,6 +20,10 @@ void ServerEntityManager::init() {
m_factories.try_emplace("cubed:pig", [this]() { m_factories.try_emplace("cubed:pig", [this]() {
BaseServerCreature c; BaseServerCreature c;
c.hitbox = HitboxManager::instance().get_hitbox_id("cubed:pig"); c.hitbox = HitboxManager::instance().get_hitbox_id("cubed:pig");
c.gravity.value = PigDefaults::GRAVITY;
c.movement.acceleration = PigDefaults::ACCELERATION;
c.movement.deceleration = PigDefaults::DECELERATION;
c.velocity.max.x = c.velocity.max.z = PigDefaults::MAX_SPEED;
return create_entity_in_factory( return create_entity_in_factory(
Entity{m_next}, EntityInfo{"cubed:pig", ""}, std::move(c), PigTag{}, Entity{m_next}, EntityInfo{"cubed:pig", ""}, std::move(c), PigTag{},
AIBase{}, WanderAITag{}, MoveBoost{}); AIBase{}, WanderAITag{}, MoveBoost{});

View File

@@ -179,7 +179,7 @@ void ServerWorld::send_chunk(int task_id, const std::string& uuid,
void ServerWorld::init_world() { void ServerWorld::init_world() {
m_entity_manager.init(); m_entity_manager.init();
m_entity_manager.add_entity("cubed:pig", {0, 90, 0}); m_entity_manager.add_entity("cubed:pig", {0, 225, 0});
register_timer("player disconnect", 5, [this]() { register_timer("player disconnect", 5, [this]() {
std::vector<std::string> disconnect; std::vector<std::string> disconnect;
{ {

View File

@@ -107,36 +107,61 @@ void PhysicalSystem::update(ServerWorld& world, entt::registry& registry) {
auto view = registry.view<BaseServerCreature>(); auto view = registry.view<BaseServerCreature>();
for (auto e : view) { for (auto e : view) {
auto& creature = view.get<BaseServerCreature>(e); auto& creature = view.get<BaseServerCreature>(e);
auto distance = auto distance = get_move_distance(creature.velocity);
get_move_distance(creature.direction, creature.velocity);
auto box = HitboxManager::hitbox(creature.hitbox); auto box = HitboxManager::hitbox(creature.hitbox);
auto& pos = creature.transform.position.value; auto& pos = creature.transform.position.value;
auto& v = creature.velocity; auto& v = creature.velocity;
if (update_x(pos, distance, world, box.box)) { bool ground =
pos.x += distance.x; !update_y(pos, glm::vec3{0.0f, -0.1f, 0.0f}, world, box.box);
bool stepped = false;
} else {
v.value.x = 0.0f;
}
if (update_y(pos, distance, world, box.box)) { if (update_y(pos, distance, world, box.box)) {
pos.y += distance.y; pos.y += distance.y;
} else { } else {
v.value.y = 0.0f; v.value.y = 0.0f;
} }
if (update_x(pos, distance, world, box.box)) {
pos.x += distance.x;
} else {
if (ground && !stepped) {
glm::vec3 step_pos = pos + glm::vec3{0.0f, 1.0f, 0.0f};
if (update_x(step_pos, distance, world, box.box)) {
pos.x += distance.x;
pos.y += 1.0f;
stepped = true;
} else {
v.value.x = 0.0f;
}
} else {
v.value.x = 0.0f;
}
}
if (update_z(pos, distance, world, box.box)) { if (update_z(pos, distance, world, box.box)) {
pos.z += distance.z; pos.z += distance.z;
} else {
if (ground && !stepped) {
glm::vec3 step_pos = pos + glm::vec3{0.0f, 1.0f, 0.0f};
if (update_z(step_pos, distance, world, box.box)) {
pos.z += distance.z;
pos.y += 1.0f;
stepped = true;
} else { } else {
v.value.z = 0.0f; v.value.z = 0.0f;
} }
} else {
v.value.z = 0.0f;
}
}
} }
} }
glm::vec3 PhysicalSystem::get_move_distance(const Direction& d, glm::vec3 PhysicalSystem::get_move_distance(const TickVelocity& v) {
const TickVelocity& v) { return v.value; // Per-tick forward distance equals the velocity magnitud
return glm::vec3{d.value.x * v.value.x, v.value.y, d.value.z * v.value.z};
} }
} // namespace Cubed } // namespace Cubed

View File

@@ -10,28 +10,31 @@ void SpeedSystem::update(float dt, entt::registry& registry) {
for (auto e : view) { for (auto e : view) {
auto [creature, moveboost] = view.get<BaseServerCreature, MoveBoost>(e); auto [creature, moveboost] = view.get<BaseServerCreature, MoveBoost>(e);
auto& v = creature.velocity.value;
if (moveboost.count <= moveboost.duration) { if (moveboost.count <= moveboost.duration) {
++moveboost.count; ++moveboost.count;
creature.velocity.value += v += creature.direction.value * creature.movement.acceleration;
creature.direction.value * creature.movement.acceleration;
} else if (glm::dot(creature.velocity.value, creature.velocity.value) >=
0.001f) {
creature.velocity.value -=
creature.direction.value * creature.movement.deceleration;
} else { } else {
creature.velocity.value = glm::vec3(0.0f); // Decelerated by friction in all directions
} auto decay = [](float& c, float d) {
creature.velocity.value.y += -creature.gravity.value * dt; if (c > 0.0f)
auto v_clamp = [](float& v, float max) { c = std::max(0.0f, c - d);
auto temp = std::abs(v); else if (c < 0.0f)
max = std::abs(max); c = std::min(0.0f, c + d);
temp = std::clamp(temp, 0.0f, max);
v = temp;
}; };
v_clamp(creature.velocity.value.x, creature.velocity.max.x); decay(v.x, creature.movement.deceleration);
v_clamp(creature.velocity.value.y, creature.velocity.max.y); decay(v.z, creature.movement.deceleration);
v_clamp(creature.velocity.value.z, creature.velocity.max.z); }
v.y += -creature.gravity.value * dt;
auto v_clamp = [](float& c, float max) {
if (max < 0.0f) {
return; //-1 = unlimited
}
c = std::clamp(c, -max, max);
};
v_clamp(v.x, creature.velocity.max.x);
v_clamp(v.y, creature.velocity.max.y);
v_clamp(v.z, creature.velocity.max.z);
} }
} }

View File

@@ -5,8 +5,8 @@
#include "Cubed/tools/cubed_random.hpp" #include "Cubed/tools/cubed_random.hpp"
namespace { namespace {
constexpr double DIRECTION_PROBABILITY = 0.1; constexpr double DIRECTION_PROBABILITY = 0.01;
constexpr double MOVE_PROBABILITY = 0.1; constexpr double MOVE_PROBABILITY = 0.01;
} // namespace } // namespace
namespace Cubed { namespace Cubed {
@@ -32,7 +32,8 @@ void WanderAISystem::do_ai(BaseServerCreature& creature,
creature.direction.value = r.random_direction_horizontal(); creature.direction.value = r.random_direction_horizontal();
} }
if (r.random_bool(MOVE_PROBABILITY)) { if (r.random_bool(MOVE_PROBABILITY)) {
move_boost.duration = r.random_int(5, 8);
move_boost.duration = r.random_int(20, 40);
move_boost.count = 0; move_boost.count = 0;
} }
} }