feat: add vehicle and kinematics systems

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-06 11:48:49 +08:00
parent 60e0e213a0
commit 4604a343cd
Signed by: szTom
GPG Key ID: 072D999D60C6473C
8 changed files with 302 additions and 13 deletions

View File

@ -1,7 +1,12 @@
cmake_minimum_required(VERSION 3.27) cmake_minimum_required(VERSION 3.27)
set(ISTD_CORE_SRC set(ISTD_CORE_SRC
src/devices/vehicle.cpp
src/device.cpp
src/room.cpp src/room.cpp
src/system.cpp
src/unit.cpp
src/world.cpp
) )
add_library(istd_core STATIC ${ISTD_CORE_SRC}) add_library(istd_core STATIC ${ISTD_CORE_SRC})

View File

@ -30,13 +30,14 @@ public:
struct DevicePrototype { struct DevicePrototype {
std::string_view name; std::string_view name;
RegSetStrategy *reg_set_strategy; // life cycle: static const RegSetStrategy *reg_set_strategy; // life cycle: static
std::uint32_t mass; std::uint32_t mass;
ItemPort input_n, output_n; // number of input/output ports ItemType item; // item type this device is built from
ItemPort input_n, output_n; // number of input/output ports
}; };
struct DevicePrototypeComponent { struct DevicePrototypeComponent {
DevicePrototype *prototype; // life cycle: static const DevicePrototype *prototype; // life cycle: static
}; };
struct DeviceIdComponent { struct DeviceIdComponent {
@ -49,18 +50,27 @@ struct DeviceBuilder {
World &world, entt::entity unit, DeviceId device_id World &world, entt::entity unit, DeviceId device_id
) const ) const
= 0; = 0;
// No virtual destructor: static lifetime and no member variables is the
// intended use case
}; };
struct DeviceBuilderRegistry { class DeviceBuilderRegistry {
static DeviceBuilderRegistry &instance(); public:
static DeviceBuilderRegistry &instance() noexcept;
void register_builder(ItemType item, const DeviceBuilder *builder);
void register_builder(Item item, DeviceBuilder *builder);
entt::entity build( entt::entity build(
World &world, Item item, entt::entity unit, DeviceId device_id World &world, ItemType item, entt::entity unit, DeviceId device_id
) const; ) const;
struct Registar {
Registar(ItemType item, const DeviceBuilder *builder);
};
private: private:
SmallMap<Item, DeviceBuilder *> builders_; SmallMap<ItemType, const DeviceBuilder *> builders_;
}; };
} // namespace istd } // namespace istd

View File

@ -0,0 +1,23 @@
#ifndef ISTD_CORE_DEVICE_VIHICLE_H
#define ISTD_CORE_DEVICE_VIHICLE_H
#include <entt/entt.hpp>
#include <string_view>
namespace istd {
struct VehiclePrototype {
std::string_view name;
std::uint32_t ideal_working_mass;
float max_speed; // in tile/tick
};
struct VehicleComponent {
const VehiclePrototype *prototype; // life cycle: static
float heading; // in radians, 0 is South, counter-clockwise
float speed; // percentage of max speed, 0.0 to 1.0
};
} // namespace istd
#endif

View File

@ -16,7 +16,7 @@ consteval std::uint32_t id_string(const char *str) {
return id; return id;
} }
enum class Item : std::uint32_t { enum class ItemType : std::uint32_t {
Null = 0, Null = 0,
// Materials // Materials

View File

@ -35,6 +35,16 @@ struct DeviceStackComponent {
std::vector<entt::entity> devices; std::vector<entt::entity> devices;
}; };
/**
* @brief Flag component to indicate that the unit is on the ground.
*/
struct OnGroundFlag {};
/**
* @brief Flag component to indicate that the unit is in the air.
*/
struct AirborneFlag {};
} // namespace istd } // namespace istd
#endif #endif

View File

@ -15,22 +15,30 @@ bool RegSetStrategy::write(
return false; return false;
} }
DeviceBuilderRegistry &DeviceBuilderRegistry::instance() { DeviceBuilderRegistry &DeviceBuilderRegistry::instance() noexcept {
static DeviceBuilderRegistry registry; static DeviceBuilderRegistry registry;
return registry; return registry;
} }
void DeviceBuilderRegistry::register_builder( void DeviceBuilderRegistry::register_builder(
Item item, DeviceBuilder *builder ItemType item, const DeviceBuilder *builder
) { ) {
builders_.insert(item, builder); builders_.insert(item, builder);
} }
entt::entity DeviceBuilderRegistry::build( entt::entity DeviceBuilderRegistry::build(
World &world, Item item, entt::entity unit, DeviceId device_id World &world, ItemType item, entt::entity unit, DeviceId device_id
) const { ) const {
auto builder = builders_[item]; auto builder = builders_[item];
return builder->build(world, unit, device_id); auto entity = builder->build(world, unit, device_id);
world.registry.emplace<DeviceIdComponent>(entity, unit, device_id);
return entity;
}
DeviceBuilderRegistry::Registar::Registar(
ItemType item, const DeviceBuilder *builder
) {
DeviceBuilderRegistry::instance().register_builder(item, builder);
} }
} // namespace istd } // namespace istd

View File

@ -0,0 +1,159 @@
#include "istd_core/devices/vehicle.h"
#include "istd_core/device.h"
#include "istd_core/system.h"
#include "istd_core/unit.h"
namespace istd {
namespace {
// Vehicle register allocations:
// 0: Vehicle speed (1 = 1 / 256 of max speed)
// 1: Vehicle heading (1 = 1 / 256 pi)
// 2: Vehicle x position (1 = 1 / 1024 tile) (read-only)
// 3: Vehicle y position (1 = 1 / 1024 tile) (read-only)
// 4: Device status / error code
struct VehicleRegSetStrategy : public RegSetStrategy {
virtual bool read(
World &world, entt::entity entity, std::uint8_t reg_id,
std::uint32_t &value
) const noexcept override {
auto &reg = world.registry;
switch (reg_id) {
case 0: { // Vehicle speed
auto &vehicle = reg.get<const VehicleComponent>(entity);
value = static_cast<std::uint32_t>(vehicle.speed * 256.0f);
break;
}
case 1: { // Vehicle heading
auto &vehicle = reg.get<const VehicleComponent>(entity);
value = static_cast<std::uint32_t>(
(vehicle.heading / (2.0f * M_PI)) * 256.0f
);
break;
}
case 2: { // Vehicle x position
auto owner = reg.get<const DeviceIdComponent>(entity).unit;
auto pos = reg.get<const KinematicsComponent>(owner).position;
value = static_cast<std::uint32_t>(pos.x * 1024);
break;
}
case 3: { // Vehicle y position
auto owner = reg.get<const DeviceIdComponent>(entity).unit;
auto pos = reg.get<const KinematicsComponent>(owner).position;
value = static_cast<std::uint32_t>(pos.y * 1024);
break;
}
case 4:
// No error code for now, just return 0
value = 0;
break;
default:
return false; // Invalid register ID
}
return true; // Read successful
}
virtual bool write(
World &world, entt::entity entity, std::uint8_t reg_id,
std::uint32_t value
) const noexcept override {
switch (reg_id) {
case 0: { // Vehicle speed
auto &vehicle = world.registry.get<VehicleComponent>(entity);
vehicle.speed = static_cast<float>(value) / 256.0f;
return true;
}
case 1: { // Vehicle heading
auto &vehicle = world.registry.get<VehicleComponent>(entity);
vehicle.heading = (static_cast<float>(value) / 256.0f)
* (2.0f * M_PI);
return true;
}
default:
return false; // Invalid register ID or read-only register
}
}
};
static const VehicleRegSetStrategy vehicle_reg_set_strategy;
static const VehiclePrototype basic_vihicle_prototype = {
.name = "Basic Vehicle",
.ideal_working_mass = 7440,
.max_speed = 0.3f, // 1 tile per tick
};
static const DevicePrototype basic_vehicle_device_prototype = {
.name = "Basic Vehicle Device",
.reg_set_strategy = &vehicle_reg_set_strategy,
.mass = 3270,
.item = ItemType::BasicVehicleChassis,
.input_n = 0,
.output_n = 0,
};
struct BasicVehicleBuilder : public DeviceBuilder {
entt::entity build(
World &world, entt::entity unit, DeviceId device_id
) const override {
auto entity = world.registry.create();
world.registry.emplace<DevicePrototypeComponent>(
entity, &basic_vehicle_device_prototype
);
world.registry.emplace<VehicleComponent>(
entity, &basic_vihicle_prototype, 0.0f, 0.0f
);
return entity;
}
};
static const BasicVehicleBuilder basic_vehicle_builder;
static const DeviceBuilderRegistry::Registar basic_vehicle_registrar(
ItemType::BasicVehicleChassis, &basic_vehicle_builder
);
struct VehicleVelocitySystem : public System {
void tick(World &world) const noexcept override {
auto &reg = world.registry;
reg.view<const VehicleComponent, const DeviceIdComponent>().each(
[&reg](
entt::entity entity, const VehicleComponent &vehicle,
const DeviceIdComponent &device_id
) {
// Update vehicle position based on speed and heading
auto owner = device_id.unit;
if (!reg.all_of<OnGroundFlag>(owner)) {
// If not on ground, do not update position
return;
}
auto &kinematics = reg.get<KinematicsComponent>(owner);
kinematics.velocity += Vec2::rotated(
vehicle.heading, vehicle.speed * vehicle.prototype->max_speed
);
}
);
}
std::string_view name() const noexcept override {
return "Vehicle Device System";
}
};
static const VehicleVelocitySystem vehicle_velocity_system;
static const SystemRegistry::Registar vehicle_velocity_registrar(
System::Precedence::DeviceAccumulateVelocity, &vehicle_velocity_system
);
} // namespace
} // namespace istd

74
core/src/unit.cpp Normal file
View File

@ -0,0 +1,74 @@
#include "istd_core/unit.h"
#include "istd_core/system.h"
#include "istd_util/tile_geometry.h"
#include "tilemap/tile.h"
namespace istd {
namespace {
struct ResetVelocitySystem : public System {
void tick(World &world) const noexcept override {
auto &reg = world.registry;
reg.view<KinematicsComponent>().each(
[](entt::entity, KinematicsComponent &kinematics) {
kinematics.velocity = Vec2::zero();
}
);
}
std::string_view name() const noexcept override {
return "Reset Velocity System";
}
};
static const ResetVelocitySystem reset_velocity_system;
static const SystemRegistry::Registar reset_velocity_registrar(
System::Precedence::ResetVelocity, &reset_velocity_system
);
bool is_passible_tile(Tile tile) {
return tile.base != BaseTileType::Mountain;
}
void update_pos(const TileMap &tilemap, KinematicsComponent &kinematics) {
auto next_pos = kinematics.position + kinematics.velocity;
auto ray_cast = tiles_on_segment(kinematics.position, next_pos);
for (auto [i, j] : ray_cast) {
auto tile = tilemap.get_tile(TilePos::from_global(i, j));
if (!is_passible_tile(tile)) {
// Hit an impassable tile
// Calculate intersection point
auto inter = tile_segment_intersection(
kinematics.position, next_pos, {i, j}
);
next_pos = inter;
break;
}
}
kinematics.position = next_pos;
}
struct KinematicsSystem : public System {
void tick(World &world) const noexcept override {
auto &reg = world.registry;
reg.view<KinematicsComponent>().each(
std::bind(
update_pos, std::ref(world.tilemap), std::placeholders::_1
)
);
}
std::string_view name() const noexcept override {
return "Kinematics System";
}
};
static const KinematicsSystem kinematics_system;
static const SystemRegistry::Registar kinematics_registrar(
System::Precedence::UpdateKinematics, &kinematics_system
);
} // namespace
} // namespace istd