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:
@@ -91,7 +91,7 @@ public:
|
|||||||
void request_exit();
|
void request_exit();
|
||||||
bool is_receive_exit();
|
bool is_receive_exit();
|
||||||
int chunk_size() const;
|
int chunk_size() const;
|
||||||
static AABB get_block_aabb(const glm::ivec3& pos);
|
|
||||||
AudioEngine& get_audio();
|
AudioEngine& get_audio();
|
||||||
const AudioEngine& get_audio() const;
|
const AudioEngine& get_audio() const;
|
||||||
Config& get_config();
|
Config& get_config();
|
||||||
@@ -103,6 +103,7 @@ public:
|
|||||||
void receive_voice_message(VoiceMsg& msg);
|
void receive_voice_message(VoiceMsg& msg);
|
||||||
bool enable_voice_chat() const;
|
bool enable_voice_chat() const;
|
||||||
const entt::registry& get_registry();
|
const entt::registry& get_registry();
|
||||||
|
int get_per_tick_time() const override;
|
||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) {
|
void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) {
|
||||||
m_ticktimers.emplace(
|
m_ticktimers.emplace(
|
||||||
@@ -181,6 +182,7 @@ private:
|
|||||||
std::atomic<int> m_rendering_distance{24};
|
std::atomic<int> m_rendering_distance{24};
|
||||||
std::atomic<TickType> m_game_ticks{0};
|
std::atomic<TickType> m_game_ticks{0};
|
||||||
std::atomic<TickType> m_day_tick{6000};
|
std::atomic<TickType> m_day_tick{6000};
|
||||||
|
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME;
|
||||||
std::atomic<bool> m_requesting_chunk{false};
|
std::atomic<bool> m_requesting_chunk{false};
|
||||||
std::atomic<bool> m_is_rebuilding{false};
|
std::atomic<bool> m_is_rebuilding{false};
|
||||||
std::atomic<int> m_chunk_task_id{0};
|
std::atomic<int> m_chunk_task_id{0};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Cubed/AABB.hpp"
|
||||||
#include "Cubed/gameplay/player.hpp"
|
#include "Cubed/gameplay/player.hpp"
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
@@ -34,4 +35,14 @@ struct ViewAngles {
|
|||||||
float angle = 0.0f;
|
float angle = 0.0f;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Velocity {
|
||||||
|
float dx = 0.0f;
|
||||||
|
float dy = 0.0f;
|
||||||
|
float dz = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HitBoxes {
|
||||||
|
std::vector<AABB> boxex;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
24
include/Cubed/gameplay/hitbox_manager.hpp
Normal file
24
include/Cubed/gameplay/hitbox_manager.hpp
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Cubed/AABB.hpp"
|
||||||
|
|
||||||
|
#include <tbb/concurrent_hash_map.h>
|
||||||
|
namespace Cubed {
|
||||||
|
class HitboxManager {
|
||||||
|
public:
|
||||||
|
HitboxManager();
|
||||||
|
~HitboxManager();
|
||||||
|
static HitboxManager& instance();
|
||||||
|
|
||||||
|
AABB get_aabb(const std::string& key);
|
||||||
|
|
||||||
|
static AABB aabb(const std::string& key);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using HitBoxMap = tbb::concurrent_hash_map<std::string, AABB>;
|
||||||
|
using cacc = HitBoxMap::const_accessor;
|
||||||
|
using acc = HitBoxMap::accessor;
|
||||||
|
HitBoxMap m_hitboxes;
|
||||||
|
|
||||||
|
AABB load(const std::string& path);
|
||||||
|
};
|
||||||
|
} // namespace Cubed
|
||||||
20
include/Cubed/gameplay/move_system.hpp
Normal file
20
include/Cubed/gameplay/move_system.hpp
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Cubed/gameplay/entity.hpp"
|
||||||
|
#include "Cubed/gameplay/world.hpp"
|
||||||
|
|
||||||
|
#include <entt/entt.hpp>
|
||||||
|
namespace Cubed {
|
||||||
|
|
||||||
|
class MoveSystem {
|
||||||
|
public:
|
||||||
|
static void update(World& world, entt::registry& registry);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void move_x(World& world, Transform& transform, Velocity& v,
|
||||||
|
const EntityInfo& info);
|
||||||
|
static void move_y(World& world, Transform& transform, Velocity& v,
|
||||||
|
const EntityInfo& info);
|
||||||
|
static void move_z(World& world, Transform& transform, Velocity& v,
|
||||||
|
const EntityInfo& info);
|
||||||
|
};
|
||||||
|
} // namespace Cubed
|
||||||
@@ -91,7 +91,7 @@ public:
|
|||||||
bool is_solid(const glm::ivec3& block_pos) const override;
|
bool is_solid(const glm::ivec3& block_pos) const override;
|
||||||
bool can_pass_block(const glm::ivec3& block_pos) const override;
|
bool can_pass_block(const glm::ivec3& block_pos) const override;
|
||||||
BlockType get_block_tpye(const glm::ivec3& block_pos) const override;
|
BlockType get_block_tpye(const glm::ivec3& block_pos) const override;
|
||||||
|
int get_per_tick_time() const override;
|
||||||
template <typename Fn>
|
template <typename Fn>
|
||||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||||
m_timers.emplace(std::piecewise_construct,
|
m_timers.emplace(std::piecewise_construct,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "Cubed/AABB.hpp"
|
||||||
#include "Cubed/gameplay/block.hpp"
|
#include "Cubed/gameplay/block.hpp"
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
@@ -16,5 +17,13 @@ public:
|
|||||||
virtual bool is_solid(const glm::ivec3& block_pos) const = 0;
|
virtual bool is_solid(const glm::ivec3& block_pos) const = 0;
|
||||||
virtual bool can_pass_block(const glm::ivec3& block_pos) const = 0;
|
virtual bool can_pass_block(const glm::ivec3& block_pos) const = 0;
|
||||||
virtual BlockType get_block_tpye(const glm::ivec3& block_pos) const = 0;
|
virtual BlockType get_block_tpye(const glm::ivec3& block_pos) const = 0;
|
||||||
|
virtual int get_per_tick_time() const = 0;
|
||||||
|
|
||||||
|
static AABB 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}};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
@@ -96,4 +96,6 @@ target_sources(${PROJECT_NAME}
|
|||||||
render/model_manager.cpp
|
render/model_manager.cpp
|
||||||
render/model_renderer.cpp
|
render/model_renderer.cpp
|
||||||
gameplay/chunk.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
|
// Wait for 20 ticks, after the server's central chunk is generated, then
|
||||||
// request chunks
|
// 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();
|
request_chunk();
|
||||||
}
|
}
|
||||||
@@ -557,7 +557,7 @@ void ClientWorld::client_run(std::stop_token stoken) {
|
|||||||
Logger::info("Client Thread Started");
|
Logger::info("Client Thread Started");
|
||||||
using Clock = std::chrono::steady_clock;
|
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();
|
auto next = Clock::now();
|
||||||
while (!stoken.stop_requested()) {
|
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(); }
|
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; }
|
AudioEngine& ClientWorld::get_audio() { return m_audio; }
|
||||||
const AudioEngine& ClientWorld::get_audio() const { return m_audio; }
|
const AudioEngine& ClientWorld::get_audio() const { return m_audio; }
|
||||||
Config& ClientWorld::get_config() { return m_config; }
|
Config& ClientWorld::get_config() { return m_config; }
|
||||||
@@ -755,7 +748,7 @@ void ClientWorld::request_exit() {
|
|||||||
if (m_client->is_connect_error() || m_exit_direct) {
|
if (m_client->is_connect_error() || m_exit_direct) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
std::this_thread::sleep_for(milliseconds(DEFAULT_PER_TICK_TIME));
|
std::this_thread::sleep_for(milliseconds(m_per_tick_time));
|
||||||
++cnt;
|
++cnt;
|
||||||
if (cnt >= WORLD_EXIT_TIMEOUT) {
|
if (cnt >= WORLD_EXIT_TIMEOUT) {
|
||||||
Logger::warn("Can't Receive Server Exit Sign");
|
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(); }
|
bool ClientWorld::enable_voice_chat() const { return m_voice_chat.load(); }
|
||||||
const entt::registry& ClientWorld::get_registry() { return m_registry; }
|
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) {
|
void ClientWorld::send_chat_message(ChatMessage& message) {
|
||||||
Arena arena;
|
Arena arena;
|
||||||
auto msg = Arena::Create<ChatMsg>(&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!");
|
Logger::info("Server Thread Started!");
|
||||||
|
|
||||||
using Clock = std::chrono::steady_clock;
|
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();
|
auto next = Clock::now();
|
||||||
while (!stoken.stop_requested()) {
|
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)];
|
return chunk_blocks[Chunk::index(x, y, z)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ServerWorld::get_per_tick_time() const { return m_per_tick_time; }
|
||||||
|
|
||||||
} // namespace Cubed
|
} // namespace Cubed
|
||||||
Reference in New Issue
Block a user