UI/HUD.razor
@using Sandbox;
@using Sandbox.UI;
@using System;
@inherits PanelComponent
@namespace Sandbox
@if(PlayerGun != null && PlayerHealth != null && HighscoreManager != null)
{
<root>
<div class="crosshair @(PlayerGun?.IsTargetingEnemy == true ? "on-target" : "")"
style="transform: translate(-50%, -50%) scale(@PlayerGun.SinceShot.Relative.Remap(0f, 0.1f, 1.3f, 1f));"></div>
<div class="hitmarker @(PlayerGun.IsHitmarkerActive ? "active" : "")"></div>
<div class="multiplier-dial @(HighscoreManager.CalculateMultiplier() > 1.4f ? "on-fire" : "")">
<div class="multiplier-needle"
style="transform: rotate( @HighscoreManager.CalculateMultiplier().Remap( 1, 2, 270, 90) deg );"></div>
</div>
<div class="ammocount @(Input.Pressed("attack2") ? "used" : "")">
<p>@($"🚀{PlayerGun.CurrentRockets}")</p>
</div>
<div class="vitals">
</div>
<div class="timer">
<div class="timer-house"> </div>
<p>
<span>@($"{(int)HighscoreManager.TimeSpanSinceGameStart.TotalMinutes:00}")</span>
<span class="separator">:</span>
<span>@($"{HighscoreManager.TimeSpanSinceGameStart.Seconds:00}")</span>
</p>
</div>
<div class="score @(PlayerHealth.CurrentHealth == 0 ? $"Center" : "")">
@foreach (var popup in activePopups)
{
<p class="gained-score"
@key="@popup.Id"
style="left: @(popup.RandomOffset)px;">
@($"+{popup.Value}")
</p>
}
<!-- Wir zeigen hier nun displayedScore an, gerundet auf eine Ganzzahl -->
<p class="multiplier">@(PlayerHealth.CurrentHealth == 0 ? $"Game Over" : "")</p>
<p class="current-score">@($"{(int)displayedScore:00000000}")</p>
</div>
<div class="health-dial-almost @(PlayerHealth.CurrentHealth < PlayerHealth.SetHealth * 0.2f ? "super-low-health" : PlayerHealth.CurrentHealth < PlayerHealth.SetHealth * 0.4f ? "low-health" : "")"></div>
<div class="health-needle"
style="transform: rotate( @PlayerHealth.CurrentHealth.Remap(0, PlayerHealth.SetHealth, -140, 138) deg );"> </div>
</root>
}
@code
{
public class ScorePopup
{
public Guid Id { get; set; } = Guid.NewGuid();
public int Value { get; set; }
public DateTime SpawnTime { get; set; }
public float RandomOffset { get; set; } // Für horizontale Streuung
}
[Property] HighscoreManager HighscoreManager { get; set; }
HealthSystem PlayerHealth { get; set; }
GunControl PlayerGun { get; set; }
private float lastHealth;
private bool isDamaged;
private TimeSince SinceDamage;
private float lastRocketCount;
private bool rocketUsed;
private TimeSince SinceRocketUsed;
private List<ScorePopup> activePopups = new();
private float scoreProcessed;
private float displayedScore = 0f; // Der Wert, der momentan angezeigt wird
protected override void OnStart()
{
HighscoreManager = Scene.Get<HighscoreManager>();
PlayerHealth = Scene.FindAllWithTag("player").First<GameObject>().GetComponent<HealthSystem>();
PlayerGun = Scene.FindAllWithTag("player").First<GameObject>().GetComponent<GunControl>();
lastHealth = PlayerHealth.CurrentHealth;
// Initialisierung, damit der Score nicht bei 0 startet, wenn man einsteigt
displayedScore = HighscoreManager.CurrentScore;
}
protected override void OnUpdate()
{
// Wir nähern displayedScore dem echten Score an.
// 5f ist die Geschwindigkeit. Höhere Werte = schnelleres Hochzählen.
displayedScore = MathX.Lerp(displayedScore, HighscoreManager.CurrentScore, Time.Delta * 5f);
// Kleiner Fix: Wenn die Differenz winzig ist, hart setzen, um Flackern zu vermeiden
if (Math.Abs(displayedScore - HighscoreManager.CurrentScore) < 0.5f)
{
displayedScore = HighscoreManager.CurrentScore;
}
// 2. Neue Punkte erkennen und Popup hinzufügen
if (HighscoreManager.CurrentScore > scoreProcessed)
{
int diff = (int)(HighscoreManager.CurrentScore - scoreProcessed);
if (diff > 0)
{
activePopups.Add(new ScorePopup
{
Value = (int)diff,
SpawnTime = DateTime.Now,
// Ein zufälliger Versatz, damit die Zahlen nicht alle exakt übereinander liegen
RandomOffset = Random.Shared.Float(150, 155)
});
}
scoreProcessed = HighscoreManager.CurrentScore;
}
// 3. Alte Popups entfernen (nach 0.6 Sekunden, passend zur CSS Animation)
activePopups.RemoveAll(p => (DateTime.Now - p.SpawnTime).TotalSeconds > 1);
// Schadens-Check
if (PlayerHealth.CurrentHealth < lastHealth)
{
// Schaden wurde erkannt!
isDamaged = true;
SinceDamage = 0; // Dauer der Animation in Sekunden
}
lastHealth = PlayerHealth.CurrentHealth;
// Timer herunterzählen, um die Klasse wieder zu entfernen
if (SinceDamage > 0.2f)
{
isDamaged = false;
}
// Rocket-Check
if (PlayerGun.CurrentRockets != lastRocketCount)
{
rocketUsed = true;
SinceRocketUsed = 0; // Dauer der Animation in Sekunden
}
if (SinceRocketUsed > 0.5f)
{
rocketUsed = false;
}
}
protected override int BuildHash() {
if (HighscoreManager == null || PlayerHealth == null || PlayerGun == null)
{
return 0;
}
var hash = new System.HashCode();
hash.Add(displayedScore);
hash.Add(HighscoreManager.LastGainedScore);
hash.Add(HighscoreManager.TimeSpanSinceGameStart.Seconds);
hash.Add(activePopups.Count);
hash.Add(HighscoreManager.CalculateMultiplier());
hash.Add(PlayerHealth.CurrentHealth);
hash.Add(isDamaged);
hash.Add(PlayerGun.CurrentRockets);
hash.Add(PlayerGun.IsTargetingEnemy);
hash.Add(PlayerGun.IsHitmarkerActive);
hash.Add(PlayerGun.SinceShot.Relative);
return hash.ToHashCode();
}
}