feat(gameplay): implement entity update packets

Add S2CEntityUpdate to sync entity positions from server to client, including update handling in the client entity manager and server-side AI updates.
This commit is contained in:
2026-07-31 17:44:33 +08:00
parent d6f304e6ca
commit 8451921161
7 changed files with 63 additions and 5 deletions

View File

@@ -10,14 +10,15 @@ namespace Cubed {
class ClientWorld; class ClientWorld;
class ClientEntityManager { class ClientEntityManager {
public: public:
enum class Command { CREATE, DESTORY }; enum class Command { CREATE, DESTORY, UPDATE };
ClientEntityManager(ClientWorld& world); ClientEntityManager(ClientWorld& world);
void update(); void update();
void init(); void init();
void receive_entity_create(S2CEntityCreate& s2c); void receive_entity_create(S2CEntityCreate& msg);
void receive_entity_destory(EntityID id); void receive_entity_destory(EntityID id);
void receive_entity_update(S2CEntityUpdate& msg);
void destory(EntityID id); void destory(EntityID id);
void create(std::string_view name, const glm::vec3& pos); void create(std::string_view name, const glm::vec3& pos);
@@ -30,11 +31,17 @@ private:
std::string name; std::string name;
glm::vec3 pos; glm::vec3 pos;
}; };
struct UpdateInfo {
EntityID id;
glm::vec3 pos;
};
using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>; using EntityMap = tbb::concurrent_hash_map<EntityID, entt::entity>;
using acc = EntityMap::accessor; using acc = EntityMap::accessor;
using cacc = EntityMap::const_accessor; using cacc = EntityMap::const_accessor;
using CreateFunc = std::function<void(EntityID id)>; using CreateFunc = std::function<void(EntityID id)>;
using TaskElement = std::variant<EntityCreateElement, EntityID>; using TaskElement = std::variant<EntityCreateElement, EntityID, UpdateInfo>;
using TaskPair = std::pair<Command, TaskElement>; using TaskPair = std::pair<Command, TaskElement>;
ClientWorld& m_world; ClientWorld& m_world;
@@ -48,6 +55,7 @@ private:
// not thread safe // not thread safe
void handle_entity_create(EntityID id, std::string_view name, void handle_entity_create(EntityID id, std::string_view name,
const glm::vec3& pos); const glm::vec3& pos);
void handle_entity_update(UpdateInfo& info);
template <typename... Args> template <typename... Args>
void create_entity_in_registry(EntityID id, Args&&... args) { void create_entity_in_registry(EntityID id, Args&&... args) {
{ {

View File

@@ -51,10 +51,12 @@ enum class PacketEnum : uint16_t {
LOGIN_RSP = 1002, LOGIN_RSP = 1002,
LOGOUT_REQ = 1003, LOGOUT_REQ = 1003,
LOGOUT_RSP = 1004, LOGOUT_RSP = 1004,
PLAYER_INFO = 2001, PLAYER_INFO = 2001,
C2S_PLAYER_INFO = 2002, C2S_PLAYER_INFO = 2002,
PLAYER_INFO_RSP = 2003, PLAYER_INFO_RSP = 2003,
PLAYER_WATER_SOUND = 2004, PLAYER_WATER_SOUND = 2004,
CHUNK_DATA_REQ = 3001, CHUNK_DATA_REQ = 3001,
CHUNK_DATA_RSP = 3002, CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003, BLOCK_CHANGE_REQ = 3003,
@@ -65,8 +67,11 @@ enum class PacketEnum : uint16_t {
S2C_ENTITY_DESTORY = 3008, S2C_ENTITY_DESTORY = 3008,
C2S_ENTITY_CREATE_REQUEST = 3009, C2S_ENTITY_CREATE_REQUEST = 3009,
C2S_ENTITY_DESTORY_REQUEST = 3010, C2S_ENTITY_DESTORY_REQUEST = 3010,
S2C_ENTITY_UPDATE = 3011,
CHAT_MSG = 4001, CHAT_MSG = 4001,
VOICE_MSG = 4002, VOICE_MSG = 4002,
PING = 9001, PING = 9001,
PONG = 9002 PONG = 9002
@@ -145,6 +150,9 @@ template <> constexpr uint16_t get_packet_id<ChatMsg>() {
template <> constexpr uint16_t get_packet_id<VoiceMsg>() { template <> constexpr uint16_t get_packet_id<VoiceMsg>() {
return std::to_underlying(PacketEnum::VOICE_MSG); return std::to_underlying(PacketEnum::VOICE_MSG);
} }
template <> constexpr uint16_t get_packet_id<S2CEntityUpdate>() {
return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE);
}
template <typename T> template <typename T>
requires std::derived_from<T, google::protobuf::Message> requires std::derived_from<T, google::protobuf::Message>

View File

@@ -44,6 +44,7 @@ private:
void handle_entity_destory(EntityID id); void handle_entity_destory(EntityID id);
void handle_task(); void handle_task();
void send_all_entities(std::shared_ptr<Session>& session); void send_all_entities(std::shared_ptr<Session>& session);
void update_ai();
template <typename... Args> template <typename... Args>
EntityID create_entity_in_factory(Args&&... args) { EntityID create_entity_in_factory(Args&&... args) {
auto entity = m_registry.create(); auto entity = m_registry.create();

View File

@@ -36,8 +36,23 @@ void ClientEntityManager::handle_entity_create(EntityID id,
c->transform.position.value = pos; c->transform.position.value = pos;
} }
void ClientEntityManager::handle_entity_update(UpdateInfo& info) {
entt::entity e;
{
cacc a;
if (m_entities.find(a, info.id)) {
e = a->second;
} else {
return;
}
}
auto creature = m_registry.try_get<BaseClientCreature>(e);
ASSERT(creature);
creature->transform.position.value = info.pos;
}
void ClientEntityManager::receive_entity_create(S2CEntityCreate& s2c) { void ClientEntityManager::receive_entity_create(S2CEntityCreate& s2c) {
EntityCreateElement c; EntityCreateElement c{};
c.id = s2c.id(); c.id = s2c.id();
c.name = s2c.name(); c.name = s2c.name();
c.pos = {s2c.pos().x(), s2c.pos().y(), s2c.pos().z()}; c.pos = {s2c.pos().x(), s2c.pos().y(), s2c.pos().z()};
@@ -48,6 +63,13 @@ void ClientEntityManager::receive_entity_destory(EntityID id) {
m_tasks.emplace(Command::DESTORY, id); m_tasks.emplace(Command::DESTORY, id);
} }
void ClientEntityManager::receive_entity_update(S2CEntityUpdate& msg) {
UpdateInfo e;
e.id = msg.id();
e.pos = Tools::get_net_pos(msg.pos());
m_tasks.emplace(Command::UPDATE, std::move(e));
}
void ClientEntityManager::destory(EntityID id) { void ClientEntityManager::destory(EntityID id) {
auto client = m_world.get_client(); auto client = m_world.get_client();
Arena arena; Arena arena;
@@ -81,6 +103,11 @@ void ClientEntityManager::handle_task() {
ASSERT(p); ASSERT(p);
handle_entity_destory(*p); handle_entity_destory(*p);
} break; } break;
case Command::UPDATE: {
auto* p = std::get_if<UpdateInfo>(&pair.second);
ASSERT(p);
handle_entity_update(*p);
}
} }
} }
} }

View File

@@ -156,6 +156,12 @@ asio::awaitable<void> NetworkClient::read_loop() {
m_world.entity_manager().receive_entity_destory(msg->id()); m_world.entity_manager().receive_entity_destory(msg->id());
} }
} break; } break;
case std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE): {
auto* msg = Arena::Create<S2CEntityUpdate>(&arena);
if (decode_packet(*msg, body_data, header)) {
m_world.entity_manager().receive_entity_update(*msg);
}
} break;
} }
} }
} catch (const asio::system_error& e) { } catch (const asio::system_error& e) {

View File

@@ -22,7 +22,10 @@ void ServerEntityManager::init() {
std::move(c), PigTag{}); std::move(c), PigTag{});
}); });
} }
void ServerEntityManager::update() { handle_task(); } void ServerEntityManager::update() {
handle_task();
update_ai();
}
void ServerEntityManager::handle_task() { void ServerEntityManager::handle_task() {
TaskPair pair; TaskPair pair;

View File

@@ -22,3 +22,8 @@ message C2SEntityCreateRequest {
string name = 2; string name = 2;
Vec3 pos = 3; Vec3 pos = 3;
} }
message S2CEntityUpdate {
uint64 id = 1;
Vec3 pos = 2;
}