feat(multiplayer): add rendering of other players as colored cubes

This commit is contained in:
2026-06-26 15:52:22 +08:00
parent 3c14a4ffe1
commit 48300b5fe2
12 changed files with 204 additions and 16 deletions

View File

@@ -0,0 +1,8 @@
#version 460
out vec4 color;
in vec2 tc;
void main() {
color = vec4(0.0, 0.0, 1.0, 1.0);
}

View File

@@ -0,0 +1,15 @@
#version 460
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
out vec2 tc;
void main() {
vec4 viewPos = mv_matrix * vec4(pos, 1.0);
tc = texCoord;
gl_Position = proj_matrix * viewPos;
}

View File

@@ -10,6 +10,16 @@
#include <tbb/concurrent_unordered_map.h>
namespace Cubed {
struct OtherPlayerInfo {
std::string name;
glm::vec3 pos;
};
struct RenderPlayerData {
std::string name;
glm::vec3 pos;
};
class ClientWorld {
public:
ClientWorld();
@@ -34,6 +44,8 @@ public:
void receive_block_change(const BlockChangeRsp& rsp);
void receive_time(const UpdateTime& rsp);
void receive_other_player(const PlayerInfoRsp& rsp);
int rendering_distance() const;
void rendering_distance(int rendering_distance);
void start_client_thread(std::string_view uuid);
@@ -42,7 +54,8 @@ public:
void hot_reload();
void request_chunk();
std::vector<glm::vec4>& planes();
std::vector<ChunkRenderSnapshot>& render_snapshots();
const std::vector<ChunkRenderSnapshot>& render_snapshots() const;
const std::vector<RenderPlayerData>& render_player_data() const;
glm::vec3 sunlight_dir() const;
void receive_chunk(const ChunkDataRsp& data);
template <typename Fn>
@@ -58,7 +71,9 @@ private:
tbb::concurrent_unordered_map<ChunkPos, ClientChunk, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkPosVector = std::vector<ChunkPos>;
using OtherPlayerHashMap = std::unordered_map<std::string, OtherPlayerInfo>;
ClientPlayer m_player;
OtherPlayerHashMap m_other_players;
ChunkHashMap m_chunks;
std::vector<glm::vec4> m_planes;
std::jthread m_client_thread;
@@ -68,12 +83,17 @@ private:
std::mutex m_delete_vao_mutex;
std::mutex m_pending_upload_queue_mutex;
std::mutex m_pending_chunk_data_queue_mutex;
std::mutex m_other_players_mutex;
std::deque<ClientChunk> m_pending_upload_queue;
std::deque<ChunkDataRsp> m_pending_chunk_data_queue;
std::vector<GLuint> m_pending_delete_vbo;
std::vector<GLuint> m_pending_delete_vao;
std::deque<ChunkPos> m_dirty_queue;
std::vector<ChunkRenderSnapshot> m_render_snapshots;
std::vector<RenderPlayerData> m_render_player_data;
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
std::atomic<bool> m_game_running{false};
std::atomic<int> m_rendering_distance{24};

View File

@@ -13,6 +13,7 @@ enum class PacketEnum {
LOGIN_RSP = 1002,
PLAYER_INFO = 2001,
PLAYER_POS = 2002,
PLAYER_INFO_RSP = 2003,
CHUNK_DATA_REQ = 3001,
CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003,
@@ -38,6 +39,8 @@ template <typename T> constexpr uint16_t get_packet_id() {
return to_num(PLAYER_INFO);
} else if constexpr (is_same_v<U, PlayerPos>) {
return to_num(PLAYER_POS);
} else if constexpr (is_same_v<U, PlayerInfoRsp>) {
return to_num(PLAYER_INFO_RSP);
} else if constexpr (is_same_v<U, ChunkDataReq>) {
return to_num(CHUNK_DATA_REQ);
} else if constexpr (is_same_v<U, ChunkDataRsp>) {

View File

@@ -272,6 +272,55 @@ constexpr float CROSS_TANGENTS[2][6][3] = {
{-0.7071f, 0.0f, 0.7071f}}};
#pragma endregion
#pragma region Player
constexpr float VERTICES_PLAYER[6][6][3] = {
// ===== front (z = +1) =====
{{0.0f, 0.0f, 1.0f}, // bottom left
{0.0f, 2.0f, 1.0f}, // top left
{1.0f, 2.0f, 1.0f}, // top right
{1.0f, 2.0f, 1.0f}, // top right
{1.0f, 0.0f, 1.0f}, // bottom right
{0.0f, 0.0f, 1.0f}}, // bottom left
// ===== right (x = +1) =====
{{1.0f, 0.0f, 1.0f}, // bottom front
{1.0f, 0.0f, 0.0f}, // bottom back
{1.0f, 2.0f, 0.0f}, // top back
{1.0f, 2.0f, 0.0f}, // top back
{1.0f, 2.0f, 1.0f}, // top front
{1.0f, 0.0f, 1.0f}}, // bottom front
// ===== back (z = -1) =====
{{0.0f, 0.0f, 0.0f}, // bottom left
{1.0f, 0.0f, 0.0f}, // bottom right
{1.0f, 2.0f, 0.0f}, // top right
{1.0f, 2.0f, 0.0f}, // top right
{0.0f, 2.0f, 0.0f}, // top left
{0.0f, 0.0f, 0.0f}}, // bottom left
// ===== left (x = -1) =====
{{0.0f, 0.0f, 0.0f}, // bottom back
{0.0f, 0.0f, 1.0f}, // bottom front
{0.0f, 2.0f, 1.0f}, // top front
{0.0f, 2.0f, 1.0f}, // top front
{0.0f, 2.0f, 0.0f}, // top back
{0.0f, 0.0f, 0.0f}}, // bottom back
// ===== top (y = +2) =====
{{0.0f, 2.0f, 0.0f}, // back left
{1.0f, 2.0f, 0.0f}, // back right
{1.0f, 2.0f, 1.0f}, // front right
{1.0f, 2.0f, 1.0f}, // front right
{0.0f, 2.0f, 1.0f}, // front left
{0.0f, 2.0f, 0.0f}}, // back left
// ===== bottom (y = -1) =====
{{0.0f, 0.0f, 1.0f}, // front left
{1.0f, 0.0f, 1.0f}, // front right
{1.0f, 0.0f, 0.0f}, // back right
{1.0f, 0.0f, 0.0f}, // back right
{0.0f, 0.0f, 0.0f}, // back left
{0.0f, 0.0f, 1.0f}} // front left
};
#pragma endregion
// [-1, 1]
constexpr float QUAD_VERTICES[] = {
// postion // texcoorlds

View File

@@ -122,7 +122,7 @@ private:
GLuint m_outline_indices_vbo = 0;
GLuint m_outline_vbo = 0;
GLuint m_ui_vbo = 0;
GLuint m_player_vbo = 0;
GLuint m_fbo = 0;
GLuint m_screen_texture = 0;
GLuint m_screen_depth_texture = 0;
@@ -174,7 +174,7 @@ private:
2 - outline vao
3 - ui vao
4 - text vao
5 - player vao
*/
std::vector<GLuint> m_vao;
std::vector<Vertex2D> m_ui;
@@ -189,6 +189,7 @@ private:
void render_text();
void render_ui();
void render_world();
void render_player();
void render_underwater();
void render_dev_panel();

View File

@@ -190,13 +190,23 @@ void ClientWorld::receive_time(const UpdateTime& rsp) {
m_day_tick = rsp.day_tick();
}
void ClientWorld::receive_other_player(const PlayerInfoRsp& rsp) {
{
std::lock_guard lock(m_other_players_mutex);
m_other_players[rsp.uuid()].name = rsp.name();
m_other_players[rsp.uuid()].pos =
glm::vec3{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
// Logger::info("Player {} pos Update", rsp.name());
}
}
void ClientWorld::init(std::string_view player_name,
std::shared_ptr<NetworkClient> client) {
m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
m_player.init(player_name);
m_client = client;
// timer
register_timer("player_pos", 2, [this]() { report_player_pos(); });
register_timer("player_pos", 1, [this]() { report_player_pos(); });
LoginReq req;
req.set_name(m_player.get_name());
while (!client->is_connected()) {
@@ -450,6 +460,13 @@ void ClientWorld::update(float delta_time) {
}
}
}
m_render_player_data.clear();
{
std::lock_guard lock(m_other_players_mutex);
for (auto& [uuid, player] : m_other_players) {
m_render_player_data.emplace_back(player.name, player.pos);
}
}
}
glm::vec3 ClientWorld::sunlight_dir() const {
@@ -476,8 +493,11 @@ int ClientWorld::rendering_distance() const {
void ClientWorld::rendering_distance(int rendering_distance) {
m_rendering_distance = rendering_distance;
}
std::vector<ChunkRenderSnapshot>& ClientWorld::render_snapshots() {
const std::vector<ChunkRenderSnapshot>& ClientWorld::render_snapshots() const {
return m_render_snapshots;
};
const std::vector<RenderPlayerData>& ClientWorld::render_player_data() const {
return m_render_player_data;
}
std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; }
} // namespace Cubed

View File

@@ -81,8 +81,8 @@ asio::awaitable<void> NetworkClient::read_loop() {
} break;
case to_num(PacketEnum::CHUNK_DATA_RSP): {
ChunkDataRsp rsp;
Logger::info("Client: Receive Chunk Data rsp, size {}mb",
body_data.size() / 1024.0f / 1024);
// Logger::info("Client: Receive Chunk Data rsp, size {}mb",
// body_data.size() / 1024.0f / 1024);
if (rsp.ParseFromArray(body_data.data(), body_data.size())) {
m_world.receive_chunk(rsp);
}
@@ -100,6 +100,12 @@ asio::awaitable<void> NetworkClient::read_loop() {
m_world.receive_time(rsp);
}
}
case to_num(PacketEnum::PLAYER_INFO_RSP): {
PlayerInfoRsp rsp;
if (rsp.ParseFromArray(body_data.data(), body_data.size())) {
m_world.receive_other_player(rsp);
}
} break;
}
}
} catch (const asio::system_error& e) {

View File

@@ -400,13 +400,40 @@ void ServerWorld::update() {
void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y,
float z) {
std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
Logger::warn("Player {} is not in this Server", uuid);
return;
std::string name;
{
std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
Logger::warn("Player {} is not in this Server", uuid);
return;
}
it->second.update_pos(x, y, z);
name = it->second.get_name();
}
// update other player pos;
std::vector<std::shared_ptr<Session>> other;
{
std::shared_lock lock(m_player_mutex);
for (auto& [o_uuid, player] : m_players) {
if (o_uuid == uuid) {
continue;
}
other.emplace_back(player.get_session());
}
}
for (auto& session : other) {
PlayerInfoRsp rsp;
rsp.set_uuid(uuid);
rsp.set_name(name);
auto* pos = rsp.mutable_pos();
pos->set_x(x);
pos->set_y(y);
pos->set_z(z);
session->send(make_packet(rsp));
}
it->second.update_pos(x, y, z);
}
void ServerWorld::handle_player_login(const std::string& name,

View File

@@ -77,7 +77,7 @@ asio::awaitable<void> Session::read_loop() {
}
if (cmd_id == to_num(PacketEnum::CHUNK_DATA_REQ)) {
ChunkDataReq req;
Logger::info("Session: Receive Chunk Data req");
// Logger::info("Session: Receive Chunk Data req");
if (req.ParseFromArray(body_data.data(), body_data.size())) {
m_server_world.handle_chunk_req(
req.uuid(), ChunkPos(req.pos().x(), req.pos().z()));

View File

@@ -5,4 +5,10 @@ import "common/vector3.proto";
message PlayerPos {
string uuid = 1;
Vec3 pos = 2;
}
message PlayerInfoRsp {
string uuid = 1;
string name = 2;
Vec3 pos = 3;
}

View File

@@ -34,6 +34,7 @@ Renderer::~Renderer() {
glDeleteBuffers(1, &m_sky_vbo);
glDeleteBuffers(1, &m_ui_vbo);
glDeleteBuffers(1, &m_text_vbo);
glDeleteBuffers(1, &m_player_vbo);
glBindVertexArray(0);
glDeleteVertexArrays(NUM_VAO, m_vao.data());
glDeleteFramebuffers(1, &m_fbo);
@@ -88,7 +89,9 @@ void Renderer::init() {
"shaders/billboard_f_shader.glsl"};
Shader water_shader{"water", "shaders/water_v_shader.glsl",
"shaders/water_f_shader.glsl"};
Shader player_shader{"player", "shaders/player_v_shader.glsl",
"shaders/player_f_shader.glsl"};
m_shaders.insert({player_shader.hash(), std::move(player_shader)});
m_shaders.insert({world_shader.hash(), std::move(world_shader)});
m_shaders.insert({outline_shader.hash(), std::move(outline_shader)});
m_shaders.insert({sky_shdaer.hash(), std::move(sky_shdaer)});
@@ -102,6 +105,7 @@ void Renderer::init() {
m_shaders.insert({depth_shader.hash(), std::move(depth_shader)});
m_shaders.insert({billboard.hash(), std::move(billboard)});
m_shaders.insert({water_shader.hash(), std::move(water_shader)});
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
@@ -167,6 +171,15 @@ void Renderer::init() {
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glBindVertexArray(m_vao[5]);
glGenBuffers(1, &m_player_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_player_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_PLAYER), VERTICES_PLAYER,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
init_quad();
init_text();
hot_reload();
@@ -217,7 +230,7 @@ void Renderer::render() {
render_sky();
render_world();
render_outline();
render_player();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_DEPTH_TEST);
@@ -927,6 +940,26 @@ void Renderer::render_world() {
DebugCollector::get().report(
"rendered_chunk", "Rendered Chunk: " + std::to_string(rendered_sum));
}
void Renderer::render_player() {
auto& shader = get_shader("player");
shader.use();
m_v_mat = m_camera.get_camera_lookat();
auto& players = m_world.render_player_data();
for (auto& player : players) {
m_m_mat = glm::translate(glm::mat4(1.0f),
player.pos + glm::vec3(-0.5f, 0.0f, -0.5f));
m_mv_mat = m_v_mat * m_m_mat;
shader.set_loc("mv_matrix", m_mv_mat);
shader.set_loc("proj_matrix", m_p_mat);
glBindVertexArray(m_vao[5]);
glEnable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
#pragma endregion
void Renderer::render_dev_panel() {
glDisable(GL_DEPTH_TEST);