refactor(gameplay): extract Entity base class from ClientPlayer

Move position, walk pose, velocity, orientation, movement, and gravity
fields and their accessors into a new Entity base class. ClientPlayer now
inherits from Entity, removing duplicated members. Also update velocity
handling to use 3D vector per axis and adjust related logic.
This commit is contained in:
2026-07-28 09:27:28 +08:00
parent 802d2ecb5a
commit a5c1c7d4aa
6 changed files with 108 additions and 72 deletions

View File

@@ -18,7 +18,7 @@
namespace Cubed { namespace Cubed {
class ClientWorld; class ClientWorld;
class ClientPlayer { class ClientPlayer : public Entity {
public: public:
static constexpr size_t HOTBAR_SUM = 10; static constexpr size_t HOTBAR_SUM = 10;
static constexpr float WALK_SOUND_INTERVAL = 0.45f; static constexpr float WALK_SOUND_INTERVAL = 0.45f;
@@ -41,7 +41,7 @@ public:
static AABB get_aabb(const glm::vec3& pos); static AABB get_aabb(const glm::vec3& pos);
const glm::vec3& get_front() const; const glm::vec3& get_front() const;
Gait get_gait() const;
const std::optional<LookBlock>& get_look_block_pos() const; const std::optional<LookBlock>& get_look_block_pos() const;
// thread safe // thread safe
glm::vec3 get_player_pos() const; glm::vec3 get_player_pos() const;
@@ -54,15 +54,10 @@ public:
float& max_walk_speed(); float& max_walk_speed();
float& max_run_speed(); float& max_run_speed();
float& max_speed();
float& acceleration();
float& deceleration();
float& g();
float& fly_y_speed(); float& fly_y_speed();
const ItemStack& get_current_itemstack() const; const ItemStack& get_current_itemstack() const;
void set_gait(Gait gait);
GameMode& game_mode(); GameMode& game_mode();
ClientWorld& get_world(); ClientWorld& get_world();
@@ -73,10 +68,6 @@ public:
void reset_key_status(); void reset_key_status();
void init(std::string_view name); void init(std::string_view name);
float yaw() const;
float pitch() const;
float& angle();
float& walk_time();
bool ray_cast(const glm::vec3& start, const glm::vec3& dir, bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
glm::ivec3& block_pos, glm::vec3& normal, glm::ivec3& block_pos, glm::vec3& normal,
float distance = 4.0f); float distance = 4.0f);
@@ -98,13 +89,10 @@ private:
Movement m_movement{}; Movement m_movement{};
Gravity m_gravity{}; Gravity m_gravity{};
float m_place_time = PLACE_BLOCK_INTERVAL; float m_place_time = PLACE_BLOCK_INTERVAL;
Orientation m_angle;
WalkPose m_walk_pos;
std::array<ItemStack, HOTBAR_SUM> m_hotbar; std::array<ItemStack, HOTBAR_SUM> m_hotbar;
float m_sensitivity = 0.15f; float m_sensitivity = 0.15f;
float m_max_speed = m_max_walk_speed;
float m_y_speed = 0.0f;
float m_fly_y_speed = 7.5f; float m_fly_y_speed = 7.5f;
bool can_up = true; bool can_up = true;
@@ -112,8 +100,6 @@ private:
bool space_on = false; bool space_on = false;
bool is_fly = false; bool is_fly = false;
float m_xz_speed = 0.0f;
int m_selected_hotbar = 0; int m_selected_hotbar = 0;
bool m_moving = false; bool m_moving = false;
@@ -123,8 +109,6 @@ private:
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f); glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 move_distance{0.0f, 0.0f, 0.0f}; glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
// player is tow block tall, the pos is the lower pos // player is tow block tall, the pos is the lower pos
glm::vec3 m_player_pos{0.0f, 255.0f, 0.0f};
ChunkPos m_last_chunk_pos{0, 0}; ChunkPos m_last_chunk_pos{0, 0};
glm::vec3 m_front{0, 0, -1}; glm::vec3 m_front{0, 0, -1};

View File

@@ -37,6 +37,7 @@ struct Orientation {
struct Velocity { struct Velocity {
glm::vec3 value{0.0f}; glm::vec3 value{0.0f};
glm::vec3 max{4.5f};
}; };
struct HitBoxes { struct HitBoxes {
@@ -46,11 +47,33 @@ struct HitBoxes {
struct Movement { struct Movement {
float acceleration = DEFAULT_ACCELERATION; float acceleration = DEFAULT_ACCELERATION;
float deceleration = DEFAULT_DECELERATION; float deceleration = DEFAULT_DECELERATION;
float jump_power = 0; float jump_power = 7.5f;
}; };
struct Gravity { struct Gravity {
float value = DEFAULT_G; float value = DEFAULT_G;
}; };
class Entity {
public:
glm::vec3& max_speed();
float& acceleration();
float& deceleration();
float& g();
void set_gait(Gait gait);
float yaw() const;
float pitch() const;
float& roll();
float& walk_time();
Gait get_gait() const;
protected:
Position m_pos;
WalkPose m_walk_pos;
Velocity m_velocity;
Orientation m_angle;
Movement m_movement;
Gravity m_gravity;
};
} // namespace Cubed } // namespace Cubed

View File

@@ -97,4 +97,5 @@ target_sources(${PROJECT_NAME}
render/model_renderer.cpp render/model_renderer.cpp
gameplay/chunk.cpp gameplay/chunk.cpp
gameplay/hitbox_manager.cpp gameplay/hitbox_manager.cpp
gameplay/entity.cpp
) )

View File

@@ -20,15 +20,13 @@ AABB ClientPlayer::get_aabb(const glm::vec3& pos) {
} }
const glm::vec3& ClientPlayer::get_front() const { return m_front; } const glm::vec3& ClientPlayer::get_front() const { return m_front; }
Gait ClientPlayer::get_gait() const { return m_walk_pos.gait; }
const std::optional<LookBlock>& ClientPlayer::get_look_block_pos() const { const std::optional<LookBlock>& ClientPlayer::get_look_block_pos() const {
return m_look_block; return m_look_block;
} }
glm::vec3 ClientPlayer::get_player_pos() const { glm::vec3 ClientPlayer::get_player_pos() const {
std::shared_lock lock(m_player_pos_mutex); std::shared_lock lock(m_player_pos_mutex);
return m_player_pos; return m_pos.value;
} }
const MoveState& ClientPlayer::get_move_state() const { return m_move_state; } const MoveState& ClientPlayer::get_move_state() const { return m_move_state; }
@@ -111,17 +109,21 @@ void ClientPlayer::change_mode(GameMode mode) {
Logger::info("Change GameMode to {}", to_str(mode)); Logger::info("Change GameMode to {}", to_str(mode));
if (mode == CREATIVE) { if (mode == CREATIVE) {
is_fly = false; is_fly = false;
m_max_speed = m_max_walk_speed; m_velocity.max = glm::vec3{m_max_walk_speed};
} else if (mode == SPECTATOR) { } else if (mode == SPECTATOR) {
is_fly = true; is_fly = true;
m_max_speed = m_max_run_speed; m_velocity.max = glm::vec3{m_max_run_speed};
} }
} }
void ClientPlayer::reload_config() { void ClientPlayer::reload_config() {
auto& config = m_world.get_config(); auto& config = m_world.get_config();
m_sensitivity = config.get("player.mouse_sensitivity", 0.15f); m_sensitivity = config.get("player.mouse_sensitivity", 0.15f);
} }
void ClientPlayer::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; } void ClientPlayer::set_player_pos(const glm::vec3& pos) {
std::lock_guard lock(m_player_pos_mutex);
m_pos.value = pos;
}
void ClientPlayer::update(float delta_time) { void ClientPlayer::update(float delta_time) {
WalkPose pos = m_walk_pos; WalkPose pos = m_walk_pos;
@@ -130,9 +132,11 @@ void ClientPlayer::update(float delta_time) {
update_move(delta_time); update_move(delta_time);
update_lookup_block(); update_lookup_block();
place_block(delta_time); place_block(delta_time);
d_rep("player_pos", "x: {:.2f} y: {:.2f} z: {:.2f}", m_player_pos.x, std::shared_lock lock(m_player_pos_mutex);
m_player_pos.y, m_player_pos.z); d_rep("player_pos", "x: {:.2f} y: {:.2f} z: {:.2f}", m_pos.value.x,
d_rep("speed", "Speed: {:.2} m/s", m_xz_speed); m_pos.value.y, m_pos.value.z);
d_rep("speed", "Speed(x, y, z): {:.2} m/s {:.2} m/s {:.2} m/s",
m_velocity.value.x, m_velocity.value.y, m_velocity.value.z);
} }
bool ClientPlayer::update_player_move_state(Key key, KeyAction action) { bool ClientPlayer::update_player_move_state(Key key, KeyAction action) {
if (key == Key::W) { if (key == Key::W) {
@@ -170,7 +174,7 @@ bool ClientPlayer::update_player_move_state(Key key, KeyAction action) {
if (space_on) { if (space_on) {
if (m_game_mode == CREATIVE) { if (m_game_mode == CREATIVE) {
is_fly = !is_fly; is_fly = !is_fly;
m_y_speed = 0.0f; m_velocity.value.y = 0.0f;
} }
space_on = false; space_on = false;
space_on_time = 0.0f; space_on_time = 0.0f;
@@ -279,8 +283,9 @@ void ClientPlayer::update_lookup_block() {
// calculate the block that is looked // calculate the block that is looked
glm::ivec3 block_pos; glm::ivec3 block_pos;
glm::vec3 block_normal; glm::vec3 block_normal;
std::shared_lock lock(m_player_pos_mutex);
if (ray_cast( if (ray_cast(
glm::vec3(m_player_pos.x, (m_player_pos.y + 1.6f), m_player_pos.z), glm::vec3(m_pos.value.x, (m_pos.value.y + 1.6f), m_pos.value.z),
m_front, block_pos, block_normal)) { m_front, block_pos, block_normal)) {
m_look_block = LookBlock{block_pos, glm::floor(block_normal)}; m_look_block = LookBlock{block_pos, glm::floor(block_normal)};
} else { } else {
@@ -333,7 +338,7 @@ void ClientPlayer::update_move(float delta_time) {
if (delta_time > 1.0f) { if (delta_time > 1.0f) {
return; return;
} }
if (m_xz_speed < 0.01f) { if (m_velocity.value.x < 0.01f || m_velocity.value.z < 0.01f) {
m_sprinting = false; m_sprinting = false;
} }
// ensure the thread safe // ensure the thread safe
@@ -341,14 +346,15 @@ void ClientPlayer::update_move(float delta_time) {
{ {
std::shared_lock lock(m_player_pos_mutex); std::shared_lock lock(m_player_pos_mutex);
player_pos = m_player_pos; player_pos = m_pos.value;
} }
if (m_game_mode != SPECTATOR) { if (m_game_mode != SPECTATOR) {
m_max_speed = m_velocity.max = (m_walk_pos.gait == Gait::RUN)
(m_walk_pos.gait == Gait::RUN) ? m_max_run_speed : m_max_walk_speed; ? glm::vec3{m_max_run_speed}
: glm::vec3{m_max_walk_speed};
} else { } else {
m_max_speed = m_max_run_speed; m_velocity.max = glm::vec3{m_max_run_speed};
} }
if (space_on) { if (space_on) {
@@ -363,45 +369,55 @@ void ClientPlayer::update_move(float delta_time) {
if (m_move_state.forward || m_move_state.back || m_move_state.left || if (m_move_state.forward || m_move_state.back || m_move_state.left ||
m_move_state.right) { m_move_state.right) {
direction = glm::vec3(0.0f, 0.0f, 0.0f); direction = glm::vec3(0.0f, 0.0f, 0.0f);
m_xz_speed += m_movement.acceleration * delta_time; m_velocity.value.x += m_movement.acceleration * delta_time;
if (m_xz_speed > m_max_speed) { m_velocity.value.z += m_movement.acceleration * delta_time;
m_xz_speed = m_max_speed; if (m_velocity.value.x > m_velocity.max.x) {
m_velocity.value.x = m_velocity.max.x;
}
if (m_velocity.value.z > m_velocity.max.z) {
m_velocity.value.z = m_velocity.max.z;
} }
} else { } else {
m_xz_speed += -m_movement.deceleration * delta_time; m_velocity.value.x += -m_movement.deceleration * delta_time;
if (m_xz_speed < 0) { m_velocity.value.z += -m_movement.deceleration * delta_time;
m_xz_speed = 0; if (m_velocity.value.z < 0.0f) {
m_velocity.value.z = 0.0f;
}
if (m_velocity.value.x < 0.0f) {
m_velocity.value.x = 0.0f;
}
if (m_velocity.value.z < 0.0f && m_velocity.value.x < 0.0f) {
direction = glm::vec3(0.0f, 0.0f, 0.0f); direction = glm::vec3(0.0f, 0.0f, 0.0f);
} }
} }
update_direction(); update_direction();
move_distance = {direction.x * m_xz_speed * delta_time, 0.0f, move_distance = {direction.x * m_velocity.value.x * delta_time, 0.0f,
direction.z * m_xz_speed * delta_time}; direction.z * m_velocity.value.z * delta_time};
if (is_fly) { if (is_fly) {
if (m_move_state.up) { if (m_move_state.up) {
m_y_speed = m_fly_y_speed; m_velocity.value.y = m_fly_y_speed;
} }
if (m_move_state.down) { if (m_move_state.down) {
m_y_speed = -m_fly_y_speed; m_velocity.value.y = -m_fly_y_speed;
} }
if (!m_move_state.down && !m_move_state.up) { if (!m_move_state.down && !m_move_state.up) {
m_y_speed = 0.0f; m_velocity.value.y = 0.0f;
} }
} else { } else {
if (m_move_state.up && can_up) { if (m_move_state.up && can_up) {
m_y_speed = 7.5; m_velocity.value.y = m_movement.jump_power;
can_up = false; can_up = false;
} }
m_y_speed += -m_gravity.value * delta_time; m_velocity.value.y += -m_gravity.value * delta_time;
} }
move_distance.y = m_y_speed * delta_time; move_distance.y = m_velocity.value.y * delta_time;
// y // y
update_y_move(player_pos); update_y_move(player_pos);
// x // x
@@ -416,7 +432,7 @@ void ClientPlayer::update_move(float delta_time) {
{ {
std::lock_guard lock(m_player_pos_mutex); std::lock_guard lock(m_player_pos_mutex);
m_player_pos = player_pos; m_pos.value = player_pos;
} }
update_player_chunk(); update_player_chunk();
auto it = m_timers.find("Player Walk Sound"); auto it = m_timers.find("Player Walk Sound");
@@ -491,7 +507,7 @@ void ClientPlayer::update_y_move(glm::vec3& player_pos) {
AABB block_box = ClientWorld::get_block_aabb(block_pos); AABB block_box = ClientWorld::get_block_aabb(block_pos);
if (player_box.intersects(block_box)) { if (player_box.intersects(block_box)) {
player_pos.y -= move_distance.y; player_pos.y -= move_distance.y;
m_y_speed = 0.0f; m_velocity.value.y = 0.0f;
if (move_distance.y < 0) { if (move_distance.y < 0) {
can_up = true; can_up = true;
is_fly = false; is_fly = false;
@@ -541,8 +557,8 @@ void ClientPlayer::update_player_chunk() {
float x, z; float x, z;
{ {
std::shared_lock lock(m_player_pos_mutex); std::shared_lock lock(m_player_pos_mutex);
x = m_player_pos.x; x = m_pos.value.x;
z = m_player_pos.z; z = m_pos.value.z;
} }
ChunkPos chunk_pos = get_chunk_pos(x, z); ChunkPos chunk_pos = get_chunk_pos(x, z);
float dist = distance2(chunk_pos, m_last_chunk_pos); float dist = distance2(chunk_pos, m_last_chunk_pos);
@@ -554,7 +570,7 @@ void ClientPlayer::update_player_chunk() {
} }
Gait ClientPlayer::compute_gait() const { Gait ClientPlayer::compute_gait() const {
if (m_xz_speed < 0.01f) if (m_velocity.value.x < 0.01f && m_velocity.value.z < 0.01f)
return Gait::STOP; return Gait::STOP;
if (m_sprinting) if (m_sprinting)
@@ -566,12 +582,12 @@ Gait ClientPlayer::compute_gait() const {
bool ClientPlayer::update_scroll(float yoffset) { bool ClientPlayer::update_scroll(float yoffset) {
if (m_game_mode == SPECTATOR) { if (m_game_mode == SPECTATOR) {
if (yoffset > 0) { if (yoffset > 0) {
if (m_max_speed < 500.0f) { if (m_velocity.max.x < 500.0f || m_velocity.max.z < 500.0f) {
m_max_speed += 1.0f; m_velocity.max += glm::vec3(1.0f, 0.0f, 1.0f);
} }
} else { } else {
if (m_max_speed > 1.0f) { if (m_velocity.max.x > 1.0f || m_velocity.max.z > 1.0f) {
m_max_speed -= 1.0f; m_velocity.max -= glm::vec3(1.0f, 0.0f, 1.0f);
} }
} }
} }
@@ -650,15 +666,11 @@ ClientPlayer::ChunkPosSet ClientPlayer::get_chunk_pos_set() {
float& ClientPlayer::max_walk_speed() { return m_max_walk_speed; } float& ClientPlayer::max_walk_speed() { return m_max_walk_speed; }
float& ClientPlayer::max_run_speed() { return m_max_run_speed; } float& ClientPlayer::max_run_speed() { return m_max_run_speed; }
float& ClientPlayer::max_speed() { return m_max_speed; }
float& ClientPlayer::acceleration() { return m_movement.acceleration; }
float& ClientPlayer::deceleration() { return m_movement.deceleration; }
float& ClientPlayer::g() { return m_gravity.value; }
float& ClientPlayer::fly_y_speed() { return m_fly_y_speed; } float& ClientPlayer::fly_y_speed() { return m_fly_y_speed; }
const ItemStack& ClientPlayer::get_current_itemstack() const { const ItemStack& ClientPlayer::get_current_itemstack() const {
return m_hotbar[m_selected_hotbar]; return m_hotbar[m_selected_hotbar];
}; };
void ClientPlayer::set_gait(Gait gait) { m_walk_pos.gait = gait; }
GameMode& ClientPlayer::game_mode() { return m_game_mode; } GameMode& ClientPlayer::game_mode() { return m_game_mode; }
ClientWorld& ClientPlayer::get_world() { return m_world; } ClientWorld& ClientPlayer::get_world() { return m_world; }
@@ -692,7 +704,14 @@ void ClientPlayer::init(std::string_view name) {
if (!m_moving || is_fly) { if (!m_moving || is_fly) {
return; return;
} }
glm::ivec3 block = glm::floor(m_player_pos); glm::ivec3 block;
glm::vec3 pos;
{
std::shared_lock lock(m_player_pos_mutex);
block = glm::floor(m_pos.value);
pos = m_pos.value;
}
block.y -= 1; block.y -= 1;
BlockType id = m_world.get_block_tpye(block); BlockType id = m_world.get_block_tpye(block);
if (id == 0) { if (id == 0) {
@@ -701,7 +720,7 @@ void ClientPlayer::init(std::string_view name) {
std::string name = BlockManager::name_form_id(id); std::string name = BlockManager::name_form_id(id);
std::string sound = "block/" + name + "/walk.ogg"; std::string sound = "block/" + name + "/walk.ogg";
auto& audio = m_world.get_audio(); auto& audio = m_world.get_audio();
audio.play_3d(sound, m_player_pos); audio.play_3d(sound, pos);
Logger::debug("Player block {} walk sound", name); Logger::debug("Player block {} walk sound", name);
}); });
@@ -712,9 +731,4 @@ void ClientPlayer::init(std::string_view name) {
bool ClientPlayer::is_underwater() const { return m_underwater; } bool ClientPlayer::is_underwater() const { return m_underwater; }
void ClientPlayer::set_underwater(bool u) { m_underwater = u; } void ClientPlayer::set_underwater(bool u) { m_underwater = u; }
float ClientPlayer::yaw() const { return m_angle.yaw; }
float ClientPlayer::pitch() const { return m_angle.pitch; }
float& ClientPlayer::angle() { return m_angle.roll; }
float& ClientPlayer::walk_time() { return m_walk_pos.walk_time; }
} // namespace Cubed } // namespace Cubed

View File

@@ -956,7 +956,7 @@ void ClientWorld::update_players(float dt) {
{ {
auto gait = m_player.get_gait(); auto gait = m_player.get_gait();
auto& walk_time = m_player.walk_time(); auto& walk_time = m_player.walk_time();
auto& angle = m_player.angle(); auto& angle = m_player.roll();
if (gait == Gait::WALK || gait == Gait::RUN) { if (gait == Gait::WALK || gait == Gait::RUN) {
walk_time += dt; walk_time += dt;

14
src/gameplay/entity.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "Cubed/gameplay/entity.hpp"
namespace Cubed {
glm::vec3& Entity::max_speed() { return m_velocity.max; }
float& Entity::acceleration() { return m_movement.acceleration; }
float& Entity::deceleration() { return m_movement.deceleration; }
float& Entity::g() { return m_gravity.value; }
void Entity::set_gait(Gait gait) { m_walk_pos.gait = gait; }
float Entity::yaw() const { return m_angle.yaw; }
float Entity::pitch() const { return m_angle.pitch; }
float& Entity::roll() { return m_angle.roll; }
float& Entity::walk_time() { return m_walk_pos.walk_time; }
Gait Entity::get_gait() const { return m_walk_pos.gait; }
} // namespace Cubed