From 6081575eae9fafe16663b7515cd180aed27a2201 Mon Sep 17 00:00:00 2001 From: tovjemam Date: Tue, 23 Jun 2026 17:11:20 +0200 Subject: [PATCH] Sniper scope --- src/assets/item.cpp | 14 +++++++ src/assets/item.hpp | 4 +- src/game/camera_controller.cpp | 45 +++++++++++++++++++--- src/game/camera_controller.hpp | 9 +++++ src/game/camera_info.hpp | 2 + src/game/player.cpp | 1 + src/game/player_character.cpp | 18 +++++++++ src/gameview/characterview.cpp | 7 +++- src/gameview/characterview.hpp | 1 + src/gameview/client_session.cpp | 17 ++++++++- src/gameview/entityview.hpp | 4 ++ src/gameview/worldview.cpp | 9 ++++- src/gui/context.cpp | 7 ++++ src/gui/context.hpp | 1 + src/gui/player_hud.cpp | 67 ++++++++++++++++++++++++++------- src/gui/player_hud.hpp | 6 +++ 16 files changed, 187 insertions(+), 25 deletions(-) diff --git a/src/assets/item.cpp b/src/assets/item.cpp index 224b724..46e7519 100644 --- a/src/assets/item.cpp +++ b/src/assets/item.cpp @@ -154,6 +154,20 @@ std::shared_ptr assets::Item::LoadFromFile(const std::string& path { iss >> item->damage; } + else if (command == "aimtype") + { + std::string aimtype_str; + iss >> aimtype_str; + + if (aimtype_str == "none") + item->aim_type = AIMTYPE_NONE; + else if (aimtype_str == "crosshair") + item->aim_type = AIMTYPE_CROSSHAIR; + else if (aimtype_str == "scope") + item->aim_type = AIMTYPE_SCOPE; + else + throw std::runtime_error("Invalid aim type: " + aimtype_str); + } else { throw std::runtime_error("Unknown item command: " + command); diff --git a/src/assets/item.hpp b/src/assets/item.hpp index bf53d5b..604ad95 100644 --- a/src/assets/item.hpp +++ b/src/assets/item.hpp @@ -17,7 +17,7 @@ enum ItemType enum ItemAimType { AIMTYPE_NONE, - AIMTYPE_AIM, + AIMTYPE_CROSSHAIR, AIMTYPE_SCOPE, }; @@ -59,6 +59,8 @@ struct Item std::string idle_anim; std::string use_anim; // use or fire + ItemAimType aim_type = AIMTYPE_NONE; + // consumable std::string action; diff --git a/src/game/camera_controller.cpp b/src/game/camera_controller.cpp index 3e919e2..3ec6f3d 100644 --- a/src/game/camera_controller.cpp +++ b/src/game/camera_controller.cpp @@ -36,28 +36,42 @@ void game::CameraController::Recalculate(collision::DynamicsWorld* world) glm::vec3 start_noaim(0.0f); glm::vec3 start_aim(0.0f); + bool scope = ShouldDrawScope(); float distance_noaim = 5.0f; - auto aim_end_offset = right * 0.4f - forward_ * 1.8f; + glm::vec3 aim_end_offset = right * 0.4f - forward_ * 1.8f; if (character_transform_) { auto up = UpFromMatrix(*character_transform_); - start_noaim = TranslationFromMatrix(*character_transform_) + up * 2.0f; - start_aim = start_noaim - up * 0.3f; + start_noaim = TranslationFromMatrix(*character_transform_) + up * (scope ? 1.8f : 2.0f); + + if (!scope) + { + start_aim = start_noaim - up * 0.3f; + } + else + { + start_aim = start_noaim; + aim_end_offset = glm::vec3(0.0f); + } } if (rideable_transform_) { start_noaim = TranslationFromMatrix(*rideable_transform_) + glm::vec3(0.0f, 0.0f, 2.0f); distance_noaim = 8.0f; - aim_end_offset = right * 0.3f - forward_ * 4.5f + glm::vec3(0.0f, 0.0f, 0.8f); + + if (!scope) + { + aim_end_offset = right * 0.3f - forward_ * 4.5f + glm::vec3(0.0f, 0.0f, 0.8f); + } } glm::vec3 end_noaim = start_noaim - forward_ * distance_noaim; glm::vec3 end_aim = start_aim + aim_end_offset; - auto aim_factor_smooth = glm::smoothstep(0.0f, 1.0f, aim_factor_); + auto aim_factor_smooth = scope ? 1.0f : glm::smoothstep(0.0f, 1.0f, aim_factor_); auto start = glm::mix(start_noaim, start_aim, aim_factor_smooth); auto end = glm::mix(end_noaim, end_aim, aim_factor_smooth); @@ -74,3 +88,24 @@ glm::mat4 game::CameraController::GetViewMatrix() const { return glm::lookAt(eye_, eye_ + forward_, glm::vec3(0.0f, 0.0f, 1.0f)); } + +bool game::CameraController::ShouldDrawCrosshair() const +{ + return aim_crosshair_ && !aim_scope_ && aim_factor_ > 0.5f; +} + +bool game::CameraController::ShouldDrawScope() const +{ + return aim_crosshair_ && aim_scope_ && aim_factor_ > 0.7f; +} + +float game::CameraController::GetFov() const +{ + if (ShouldDrawScope()) + { + return glm::mix(25.0f, 20.0f, glm::smoothstep(0.0f, 1.0f, (aim_factor_ - 0.7f) / 0.3f)); + } + + // return glm::mix(90.0f, 80.0f, aim_factor_); + return 90.0f; +} diff --git a/src/game/camera_controller.hpp b/src/game/camera_controller.hpp index 67f4deb..5251ab0 100644 --- a/src/game/camera_controller.hpp +++ b/src/game/camera_controller.hpp @@ -18,6 +18,7 @@ public: float GetPitch() const { return pitch_; } void SetAiming(bool aiming) { aiming_ = aiming; } + void SetAimType(bool crosshair, bool scope) { aim_crosshair_ = crosshair; aim_scope_ = scope; } void Update(float time); void Recalculate(collision::DynamicsWorld* world); @@ -27,6 +28,11 @@ public: glm::mat4 GetViewMatrix() const; float GetAimFactor() const { return aim_factor_; } + bool ShouldDrawCrosshair() const; + bool ShouldDrawScope() const; + + float GetFov() const; + private: const glm::mat4* character_transform_ = nullptr; const glm::mat4* rideable_transform_ = nullptr; @@ -37,6 +43,9 @@ private: bool aiming_ = false; float aim_factor_ = 0.0f; + bool aim_crosshair_ = false; + bool aim_scope_ = false; + glm::vec3 eye_; glm::vec3 forward_; diff --git a/src/game/camera_info.hpp b/src/game/camera_info.hpp index 803a3a9..54b775c 100644 --- a/src/game/camera_info.hpp +++ b/src/game/camera_info.hpp @@ -9,6 +9,8 @@ using CameraFlags = uint8_t; enum CameraFlag : CameraFlags { CAM_AIMING = 1, + CAM_AIM_CROSSHAIR = 2, + CAM_AIM_SCOPE = 4, }; struct CameraInfo diff --git a/src/game/player.cpp b/src/game/player.cpp index 90717c3..49e488f 100644 --- a/src/game/player.cpp +++ b/src/game/player.cpp @@ -486,5 +486,6 @@ void game::Player::SendMenuMsgs() void game::Player::UpdateCamera() { camera_controller_.SetAiming(camera_info_.flags & CAM_AIMING); + camera_controller_.SetAimType(camera_info_.flags & CAM_AIM_CROSSHAIR, camera_info_.flags & CAM_AIM_SCOPE); camera_controller_.Update(1.0f / 25.0f); } diff --git a/src/game/player_character.cpp b/src/game/player_character.cpp index 99cc23b..cf7d84f 100644 --- a/src/game/player_character.cpp +++ b/src/game/player_character.cpp @@ -171,6 +171,8 @@ void game::PlayerCharacter::OnHeldItemChanged() { const auto& item = GetHeldItem(); hud_data_.held_item = item ? item->def->name : ""; + + // UpdatePlayerCamera(); // might be required? } bool game::PlayerCharacter::HaveAmmo(const std::string& ammo_name) @@ -230,6 +232,22 @@ void game::PlayerCharacter::UpdatePlayerCamera() if (GetAiming()) camera_info.flags |= CAM_AIMING; + assets::ItemAimType aim_type = assets::AIMTYPE_NONE; + const auto& held_item = GetHeldItem(); + if (held_item) + { + aim_type = held_item->def->aim_type; + } + + if (aim_type != assets::AIMTYPE_NONE) + { + camera_info.flags |= CAM_AIM_CROSSHAIR; + if (aim_type == assets::AIMTYPE_SCOPE) + { + camera_info.flags |= CAM_AIM_SCOPE; + } + } + player_->SetCamera(camera_info); } diff --git a/src/gameview/characterview.cpp b/src/gameview/characterview.cpp index 53c3c1b..756dc71 100644 --- a/src/gameview/characterview.cpp +++ b/src/gameview/characterview.cpp @@ -112,6 +112,7 @@ void game::view::CharacterView::Update(const UpdateInfo& info) if (item_) { item_node_.UpdateMatrix(); + fire_snd_node_.UpdateMatrix(); } } @@ -373,6 +374,9 @@ void game::view::CharacterView::SetItem(const std::string& item_name) item_node_.parent = bone_node ? bone_node : &root_; item_node_.local = item_->bone_offset; + fire_snd_node_.parent = &item_node_; + fire_snd_node_.local.position = glm::vec3(0.0f, 0.1f, 0.0f); + // snd if (!item_->fire_snd.empty()) { @@ -386,7 +390,6 @@ void game::view::CharacterView::SetItem(const std::string& item_name) auto loc = item_->model->GetLocation(item_->fire_fx_loc); fire_fx_offset_ = loc ? loc->position : glm::vec3(0.0f); } - } void game::view::CharacterView::DrawItem(const DrawArgs& args) @@ -411,7 +414,7 @@ void game::view::CharacterView::FireItem() if (fire_snd_) { - auto snd = audioplayer_.PlaySound(fire_snd_, &item_node_); + auto snd = audioplayer_.PlaySound(fire_snd_, &fire_snd_node_); // snd->SetPosition(item_node_.GetGlobalPosition()); } diff --git a/src/gameview/characterview.hpp b/src/gameview/characterview.hpp index 444ad60..8219f6f 100644 --- a/src/gameview/characterview.hpp +++ b/src/gameview/characterview.hpp @@ -84,6 +84,7 @@ private: std::string item_name_; TransformNode item_node_; + TransformNode fire_snd_node_; std::shared_ptr item_; std::shared_ptr fire_snd_; std::shared_ptr fire_fx_; diff --git a/src/gameview/client_session.cpp b/src/gameview/client_session.cpp index 005a9a8..90fa916 100644 --- a/src/gameview/client_session.cpp +++ b/src/gameview/client_session.cpp @@ -84,6 +84,10 @@ void game::view::ClientSession::Input(game::PlayerInputType in, bool pressed, bo void game::view::ClientSession::ProcessMouseMove(float delta_yaw, float delta_pitch) { auto sens_mult = glm::mix(1.0f, 0.3f, camera_controller_.GetAimFactor()); + if (camera_controller_.ShouldDrawScope()) + { + sens_mult = 0.1f; + } float yaw = glm::mod(camera_controller_.GetYaw() + delta_yaw * sens_mult, glm::two_pi()); @@ -303,10 +307,19 @@ void game::view::ClientSession::UpdateCamera(const UpdateInfo& info) camera_controller_.SetRideableTransform(rideable ? &rideable->GetRoot().matrix : nullptr); camera_controller_.SetAiming(camera_info_.flags & CAM_AIMING); + camera_controller_.SetAimType(camera_info_.flags & CAM_AIM_CROSSHAIR, camera_info_.flags & CAM_AIM_SCOPE); + camera_controller_.Update(info.delta_time); camera_controller_.Recalculate(world_.get()); - hud_.SetDisplayCrosshair(camera_controller_.GetAimFactor() >= 0.5f); + hud_.SetDisplayCrosshair(camera_controller_.ShouldDrawCrosshair()); + hud_.SetDisplayScope(camera_controller_.ShouldDrawScope()); + + // hide character if scope + if (character) + { + character->SetVisible(!camera_controller_.ShouldDrawScope()); + } } void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListParams& params, gui::Context& gui) @@ -317,7 +330,7 @@ void game::view::ClientSession::DrawWorld(gfx::DrawList& dlist, gfx::DrawListPar const float farplane = 3000.0f; - glm::mat4 proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, farplane); + glm::mat4 proj = glm::perspective(glm::radians(camera_controller_.GetFov() * 0.5f), aspect, 0.1f, farplane); glm::mat4 view = camera_controller_.GetViewMatrix(); params.view_proj = proj * view; diff --git a/src/gameview/entityview.hpp b/src/gameview/entityview.hpp index f7e9c01..1608fc4 100644 --- a/src/gameview/entityview.hpp +++ b/src/gameview/entityview.hpp @@ -45,6 +45,9 @@ public: Sphere GetBoundingSphere() const { return Sphere{root_.GetGlobalPosition(), radius_}; } const TransformNode& GetRoot() const { return root_; } + void SetVisible(bool visible) { visible_ = visible; } + bool IsVisible() const { return visible_; } + virtual ~EntityView() = default; protected: @@ -66,6 +69,7 @@ protected: EntityView* parent_ = nullptr; float radius_ = 1.0f; + bool visible_ = true; audio::Player audioplayer_; diff --git a/src/gameview/worldview.cpp b/src/gameview/worldview.cpp index 33c2dbe..7309f5b 100644 --- a/src/gameview/worldview.cpp +++ b/src/gameview/worldview.cpp @@ -106,8 +106,13 @@ void game::view::WorldView::Draw(const DrawArgs& args) for (const auto& [entnum, ent] : ents_) { - if (args.frustum.IsSphereVisible(ent->GetBoundingSphere())) - ent->Draw(args); + if (!ent->IsVisible()) + continue; + + if (!args.frustum.IsSphereVisible(ent->GetBoundingSphere())) + continue; + + ent->Draw(args); } DrawBeams(args); diff --git a/src/gui/context.cpp b/src/gui/context.cpp index b15917f..bd425ff 100644 --- a/src/gui/context.cpp +++ b/src/gui/context.cpp @@ -25,6 +25,13 @@ void gui::Context::DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t c PushRect(p0, glm::vec2(0.0f), p1, glm::vec2(1.0f), color); } +void gui::Context::DrawRectUV(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& uv0, const glm::vec2& uv1, + uint32_t color, const gfx::Texture* texture) +{ + BeginTexture(texture ? texture : white_tex_.get()); + PushRect(p0, uv0, p1, uv1, color); +} + static uint32_t DecodeUTF8Codepoint(const char*& p, const char* end) { if (p == end) diff --git a/src/gui/context.hpp b/src/gui/context.hpp index ecb0227..46c32b8 100644 --- a/src/gui/context.hpp +++ b/src/gui/context.hpp @@ -35,6 +35,7 @@ public: void Begin(const glm::vec2& viewport_size); void DrawRect(const glm::vec2& p0, const glm::vec2& p1, uint32_t color, const gfx::Texture* texture = nullptr); + void DrawRectUV(const glm::vec2& p0, const glm::vec2& p1, const glm::vec2& uv0, const glm::vec2& uv1, uint32_t color, const gfx::Texture* texture); glm::vec2 MeasureText(std::string_view text); void DrawText(std::string_view text, const glm::vec2& pos, uint32_t color = 0xFFFFFFFF, float scale = 1.0f); diff --git a/src/gui/player_hud.cpp b/src/gui/player_hud.cpp index e6d6056..9292c54 100644 --- a/src/gui/player_hud.cpp +++ b/src/gui/player_hud.cpp @@ -23,6 +23,7 @@ static uint32_t COLOR_ERROR = 0xFF7777FF; gui::PlayerHud::PlayerHud(const float& time) : time_(time) { crosshair_texture_ = assets::CacheManager::GetTexture("data/crosshair.png"); + scope_texture_ = assets::CacheManager::GetTexture("data/scope.png"); UpdateWeaponSlotsText(); } @@ -73,8 +74,9 @@ void gui::PlayerHud::Update(float delta_time) void gui::PlayerHud::Draw(Context& ctx) const { - DrawPain(ctx); DrawCrosshair(ctx); + DrawScope(ctx); + DrawPain(ctx); DrawHealthBar(ctx); DrawItemInfo(ctx); DrawUseTarget(ctx); @@ -102,6 +104,22 @@ void gui::PlayerHud::UpdateWeaponSlotsText() } } +uint32_t gui::PlayerHud::GetCrosshairColor() const +{ + glm::vec3 color(1.0f); + if (damage_dealt_factor_ > 0.01f) + { + color = glm::mix(color, glm::vec3(0.3f), damage_dealt_factor_); + } + + if (damage_dealt_kill_factor_ > 0.01f) + { + color = glm::mix(color, glm::vec3(1.0f, 0.1f, 0.1f), damage_dealt_kill_factor_); + } + + return glm::packUnorm4x8(glm::vec4(color, 1.0f)); +} + void gui::PlayerHud::DrawPain(Context& ctx) const { if (damage_received_factor_ <= 0.01f) @@ -116,17 +134,6 @@ void gui::PlayerHud::DrawCrosshair(Context& ctx) const if (!display_crosshair_) return; - glm::vec3 color(1.0f); - if (damage_dealt_factor_ > 0.01f) - { - color = glm::mix(color, glm::vec3(0.3f), damage_dealt_factor_); - } - - if (damage_dealt_kill_factor_ > 0.01f) - { - color = glm::mix(color, glm::vec3(1.0f, 0.1f, 0.1f), damage_dealt_kill_factor_); - } - const float crosshair_size = 32.0f; auto& viewport_size = ctx.GetViewportSize(); @@ -134,7 +141,41 @@ void gui::PlayerHud::DrawCrosshair(Context& ctx) const auto p0 = viewport_size * 0.5f - crosshair_size * 0.5f; auto p1 = p0 + crosshair_size; - ctx.DrawRect(p0, p1, glm::packUnorm4x8(glm::vec4(color, 1.0f)), crosshair_texture_.get()); + ctx.DrawRect(p0, p1, GetCrosshairColor(), crosshair_texture_.get()); +} + +void gui::PlayerHud::DrawScope(Context& ctx) const +{ + if (!display_scope_) + return; + + glm::vec2 p0(0.0f); + glm::vec2 size = ctx.GetViewportSize(); + + if (size.x < size.y) + { + p0.y += (size.y - size.x) * 0.5f; + size.y = size.x; + ctx.DrawRect(glm::vec2(0.0f), glm::vec2(p0.x + size.x, p0.y + 2.0f), 0xFF000000); + ctx.DrawRect(glm::vec2(p0.x, p0.y + size.y - 2.0f), ctx.GetViewportSize(), 0xFF000000); + } + else + { + p0.x += (size.x - size.y) * 0.5f; + size.x = size.y; + ctx.DrawRect(glm::vec2(0.0f), glm::vec2(p0.x + 2.0f, p0.y + size.y), 0xFF000000); + ctx.DrawRect(glm::vec2(p0.x + size.x - 2.0f, p0.y), ctx.GetViewportSize(), 0xFF000000); + } + + p0 = glm::floor(p0); + size = glm::floor(size); + + // glm::vec2 p1 = p0 + size * 0.5f; + glm::vec2 p2 = p0 + size; + + auto color = GetCrosshairColor(); + ctx.DrawRect(p0, p2, color, scope_texture_.get()); + } void gui::PlayerHud::DrawHealthBar(Context& ctx) const diff --git a/src/gui/player_hud.hpp b/src/gui/player_hud.hpp index 09b0bad..1e21c49 100644 --- a/src/gui/player_hud.hpp +++ b/src/gui/player_hud.hpp @@ -21,6 +21,7 @@ public: void SetUseTargetData(std::string text, std::string error_text, float delay); void SetDisplayCrosshair(bool show) { display_crosshair_ = show; } + void SetDisplayScope(bool show) { display_scope_ = show; } void ShowDamageReceived(); void ShowDamageDealt(bool kill); @@ -32,8 +33,11 @@ public: private: void UpdateWeaponSlotsText(); + uint32_t GetCrosshairColor() const; + void DrawPain(Context& ctx) const; void DrawCrosshair(Context& ctx) const; + void DrawScope(Context& ctx) const; void DrawHealthBar(Context& ctx) const; void DrawItemInfo(Context& ctx) const; void DrawUseTarget(Context& ctx) const; @@ -44,6 +48,7 @@ private: // resources std::shared_ptr crosshair_texture_; + std::shared_ptr scope_texture_; // general float health_ = 0.0f; @@ -67,6 +72,7 @@ private: // crosshair & events bool display_crosshair_ = false; + bool display_scope_ = false; float damage_received_factor_ = 0.0f; float damage_dealt_factor_ = 0.0f; float damage_dealt_kill_factor_ = 0.0f;