mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
feat(gameplay): implement entity movement system with collision detection
- Add Velocity and HitBoxes components to entity - Introduce HitboxManager for loading per-entity collision AABBs from JSON - Create MoveSystem with per-axis collision handling - Move get_block_aabb to base World class and add virtual get_per_tick_time - Remove static get_block_aabb from ClientWorld; use member m_per_tick_time for tick duration
This commit is contained in:
@@ -96,4 +96,6 @@ target_sources(${PROJECT_NAME}
|
||||
render/model_manager.cpp
|
||||
render/model_renderer.cpp
|
||||
gameplay/chunk.cpp
|
||||
gameplay/move_system.cpp
|
||||
gameplay/hitbox_manager.cpp
|
||||
)
|
||||
@@ -505,7 +505,7 @@ void ClientWorld::start_client_thread(std::string_view uuid) {
|
||||
// Wait for 20 ticks, after the server's central chunk is generated, then
|
||||
// request chunks
|
||||
|
||||
std::this_thread::sleep_for(milliseconds(20 * DEFAULT_PER_TICK_TIME));
|
||||
std::this_thread::sleep_for(milliseconds(20 * m_per_tick_time));
|
||||
|
||||
request_chunk();
|
||||
}
|
||||
@@ -557,7 +557,7 @@ void ClientWorld::client_run(std::stop_token stoken) {
|
||||
Logger::info("Client Thread Started");
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
constexpr auto TICK = std::chrono::milliseconds(DEFAULT_PER_TICK_TIME);
|
||||
const auto TICK = std::chrono::milliseconds(m_per_tick_time);
|
||||
|
||||
auto next = Clock::now();
|
||||
while (!stoken.stop_requested()) {
|
||||
@@ -730,13 +730,6 @@ bool ClientWorld::is_receive_exit() { return m_receive_exit; }
|
||||
|
||||
int ClientWorld::chunk_size() const { return m_chunks.size(); }
|
||||
|
||||
AABB ClientWorld::get_block_aabb(const glm::ivec3& pos) {
|
||||
return {glm::vec3{static_cast<float>(pos.x) + 0.5f,
|
||||
static_cast<float>(pos.y) + 0.5f,
|
||||
static_cast<float>(pos.z) + 0.5f},
|
||||
glm::vec3{0.5f, 0.5f, 0.5f}};
|
||||
}
|
||||
|
||||
AudioEngine& ClientWorld::get_audio() { return m_audio; }
|
||||
const AudioEngine& ClientWorld::get_audio() const { return m_audio; }
|
||||
Config& ClientWorld::get_config() { return m_config; }
|
||||
@@ -755,7 +748,7 @@ void ClientWorld::request_exit() {
|
||||
if (m_client->is_connect_error() || m_exit_direct) {
|
||||
break;
|
||||
}
|
||||
std::this_thread::sleep_for(milliseconds(DEFAULT_PER_TICK_TIME));
|
||||
std::this_thread::sleep_for(milliseconds(m_per_tick_time));
|
||||
++cnt;
|
||||
if (cnt >= WORLD_EXIT_TIMEOUT) {
|
||||
Logger::warn("Can't Receive Server Exit Sign");
|
||||
@@ -780,6 +773,7 @@ void ClientWorld::receive_voice_message(VoiceMsg& msg) {
|
||||
}
|
||||
bool ClientWorld::enable_voice_chat() const { return m_voice_chat.load(); }
|
||||
const entt::registry& ClientWorld::get_registry() { return m_registry; }
|
||||
int ClientWorld::get_per_tick_time() const { return m_per_tick_time; }
|
||||
void ClientWorld::send_chat_message(ChatMessage& message) {
|
||||
Arena arena;
|
||||
auto msg = Arena::Create<ChatMsg>(&arena);
|
||||
|
||||
63
src/gameplay/hitbox_manager.cpp
Normal file
63
src/gameplay/hitbox_manager.cpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#include "Cubed/gameplay/hitbox_manager.hpp"
|
||||
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
namespace fs = std::filesystem;
|
||||
using nlohmann::json;
|
||||
namespace Cubed {
|
||||
HitboxManager::HitboxManager() {}
|
||||
HitboxManager::~HitboxManager() {}
|
||||
|
||||
HitboxManager& HitboxManager::instance() {
|
||||
static HitboxManager inst;
|
||||
return inst;
|
||||
}
|
||||
|
||||
AABB HitboxManager::aabb(const std::string& key) {
|
||||
return instance().get_aabb(key);
|
||||
}
|
||||
|
||||
AABB HitboxManager::get_aabb(const std::string& key) {
|
||||
{
|
||||
cacc c;
|
||||
if (m_hitboxes.find(c, key)) {
|
||||
return c->second;
|
||||
}
|
||||
}
|
||||
|
||||
return load(key);
|
||||
}
|
||||
|
||||
AABB HitboxManager::load(const std::string& path) {
|
||||
fs::path p = ASSETS_PATH + path;
|
||||
try {
|
||||
glm::vec3 center;
|
||||
glm::vec3 half;
|
||||
std::ifstream s{p};
|
||||
json j = json::parse(s);
|
||||
if (j.contains("boxes")) {
|
||||
if (j["boxes"].contains("center")) {
|
||||
center.x = j["boxes"]["center"].at(0).get<float>();
|
||||
center.y = j["boxes"]["center"].at(1).get<float>();
|
||||
center.z = j["boxes"]["center"].at(2).get<float>();
|
||||
}
|
||||
if (j["boxes"].contains("half")) {
|
||||
half.x = j["boxes"]["half"].at(0).get<float>();
|
||||
half.y = j["boxes"]["half"].at(1).get<float>();
|
||||
half.z = j["boxes"]["half"].at(2).get<float>();
|
||||
}
|
||||
}
|
||||
acc a;
|
||||
if (m_hitboxes.insert(a, path)) {
|
||||
a->second = AABB{center, half};
|
||||
return a->second;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Load Hitbox error {}", e.what());
|
||||
}
|
||||
Logger::error("Load hitbox {} Failed", path);
|
||||
return AABB{glm::vec3(0.0f), glm::vec3(0.0f)};
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
123
src/gameplay/move_system.cpp
Normal file
123
src/gameplay/move_system.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "Cubed/gameplay/move_system.hpp"
|
||||
|
||||
#include "Cubed/gameplay/entity.hpp"
|
||||
#include "Cubed/gameplay/hitbox_manager.hpp"
|
||||
namespace Cubed {
|
||||
void MoveSystem::update(World& world, entt::registry& registry) {
|
||||
auto view = registry.view<Transform, Velocity, EntityInfo>();
|
||||
for (auto entity : view) {
|
||||
auto [transform, v, info] =
|
||||
view.get<Transform, Velocity, EntityInfo>(entity);
|
||||
move_x(world, transform, v, info);
|
||||
move_y(world, transform, v, info);
|
||||
move_z(world, transform, v, info);
|
||||
}
|
||||
}
|
||||
|
||||
void MoveSystem::move_x(World& world, Transform& transform, Velocity& v,
|
||||
const EntityInfo& info) {
|
||||
|
||||
auto& pos = transform.pos;
|
||||
float distance = v.dx * world.get_per_tick_time() / 1000.0f;
|
||||
pos.x += distance;
|
||||
|
||||
AABB box = HitboxManager::aabb(
|
||||
std::format("model/creature/{}/collision.json", info.name));
|
||||
glm::vec3 min = box.min();
|
||||
glm::vec3 max = box.max();
|
||||
|
||||
int minx = std::floor(min.x);
|
||||
int maxx = std::floor(max.x);
|
||||
int miny = std::floor(min.y);
|
||||
int maxy = std::floor(max.y);
|
||||
int minz = std::floor(min.z);
|
||||
int maxz = std::floor(max.z);
|
||||
|
||||
for (int x = minx; x <= maxx; ++x) {
|
||||
for (int y = miny; y <= maxy; ++y) {
|
||||
for (int z = minz; z <= maxz; ++z) {
|
||||
glm::ivec3 block_pos{x, y, z};
|
||||
if (!world.can_pass_block(block_pos)) {
|
||||
AABB block_box = World::get_block_aabb(block_pos);
|
||||
if (box.intersects(block_box)) {
|
||||
pos.x -= distance;
|
||||
v.dx = 0.0f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MoveSystem::move_y(World& world, Transform& transform, Velocity& v,
|
||||
const EntityInfo& info) {
|
||||
auto& pos = transform.pos;
|
||||
float distance = v.dy * world.get_per_tick_time() / 1000.0f;
|
||||
pos.y += distance;
|
||||
|
||||
AABB box = HitboxManager::aabb(
|
||||
std::format("model/creature/{}/collision.json", info.name));
|
||||
glm::vec3 min = box.min();
|
||||
glm::vec3 max = box.max();
|
||||
|
||||
int minx = std::floor(min.x);
|
||||
int maxx = std::floor(max.x);
|
||||
int miny = std::floor(min.y);
|
||||
int maxy = std::floor(max.y);
|
||||
int minz = std::floor(min.z);
|
||||
int maxz = std::floor(max.z);
|
||||
|
||||
for (int x = minx; x <= maxx; ++x) {
|
||||
for (int y = miny; y <= maxy; ++y) {
|
||||
for (int z = minz; z <= maxz; ++z) {
|
||||
glm::ivec3 block_pos{x, y, z};
|
||||
if (!world.can_pass_block(block_pos)) {
|
||||
AABB block_box = World::get_block_aabb(block_pos);
|
||||
if (box.intersects(block_box)) {
|
||||
pos.y -= distance;
|
||||
v.dy = 0.0f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MoveSystem::move_z(World& world, Transform& transform, Velocity& v,
|
||||
const EntityInfo& info) {
|
||||
auto& pos = transform.pos;
|
||||
float distance = v.dz * world.get_per_tick_time() / 1000.0f;
|
||||
pos.z += distance;
|
||||
|
||||
AABB box = HitboxManager::aabb(
|
||||
std::format("model/creature/{}/collision.json", info.name));
|
||||
glm::vec3 min = box.min();
|
||||
glm::vec3 max = box.max();
|
||||
|
||||
int minx = std::floor(min.x);
|
||||
int maxx = std::floor(max.x);
|
||||
int miny = std::floor(min.y);
|
||||
int maxy = std::floor(max.y);
|
||||
int minz = std::floor(min.z);
|
||||
int maxz = std::floor(max.z);
|
||||
|
||||
for (int x = minx; x <= maxx; ++x) {
|
||||
for (int y = miny; y <= maxy; ++y) {
|
||||
for (int z = minz; z <= maxz; ++z) {
|
||||
glm::ivec3 block_pos{x, y, z};
|
||||
if (!world.can_pass_block(block_pos)) {
|
||||
AABB block_box = World::get_block_aabb(block_pos);
|
||||
if (box.intersects(block_box)) {
|
||||
pos.z -= distance;
|
||||
v.dz = 0.0f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -454,7 +454,7 @@ void ServerWorld::serever_run(std::stop_token stoken) {
|
||||
Logger::info("Server Thread Started!");
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
constexpr auto TICK = std::chrono::milliseconds(DEFAULT_PER_TICK_TIME);
|
||||
const auto TICK = std::chrono::milliseconds(m_per_tick_time);
|
||||
|
||||
auto next = Clock::now();
|
||||
while (!stoken.stop_requested()) {
|
||||
@@ -1074,4 +1074,6 @@ BlockType ServerWorld::get_block_tpye(const glm::ivec3& block_pos) const {
|
||||
return chunk_blocks[Chunk::index(x, y, z)];
|
||||
}
|
||||
|
||||
int ServerWorld::get_per_tick_time() const { return m_per_tick_time; }
|
||||
|
||||
} // namespace Cubed
|
||||
Reference in New Issue
Block a user