Sniper scope

This commit is contained in:
tovjemam 2026-06-23 17:11:20 +02:00
parent fc59fe11e3
commit 6081575eae
16 changed files with 187 additions and 25 deletions

View File

@ -154,6 +154,20 @@ std::shared_ptr<assets::Item> 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);

View File

@ -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;

View File

@ -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;
}

View File

@ -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_;

View File

@ -9,6 +9,8 @@ using CameraFlags = uint8_t;
enum CameraFlag : CameraFlags
{
CAM_AIMING = 1,
CAM_AIM_CROSSHAIR = 2,
CAM_AIM_SCOPE = 4,
};
struct CameraInfo

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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());
}

View File

@ -84,6 +84,7 @@ private:
std::string item_name_;
TransformNode item_node_;
TransformNode fire_snd_node_;
std::shared_ptr<const assets::Item> item_;
std::shared_ptr<const audio::Sound> fire_snd_;
std::shared_ptr<const assets::Effect> fire_fx_;

View File

@ -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<float>());
@ -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;

View File

@ -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_;

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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

View File

@ -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<const gfx::Texture> crosshair_texture_;
std::shared_ptr<const gfx::Texture> 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;