Ga naar inhoud

Speler Health Bar

Doelstelling

De speler moet zijn/haar health kunnen zien om daarmee een beredeneerde keuze te kunnen maken. Anders heeft de speler niet genoeg informatie om het spel effectief te kunnen spelen.

Toegepaste oplossing

Health Bar

Voor de health bar heb ik een speciale class gemaakt die wat informatie bijhoud, zodat de health bar mooi geanimeerd kan zijn.

Eerst worden algemene opties voor de health bar publiek opgegeven, zoals de kleuren en hoe het moet animeren. Voor de kleuren heb ik ze samen gestopt in een subclass zodat deze als reference herbruikt kunnen worden tussen meerdere health bars, en zodat ze natuurlijk mooi samen staan.

public class HealthBar
{
    public class HealthBarColours
    {
        public Color background = Color.DimGray;
        public Color health = Color.LimeGreen;
        public Color lostHealth = Color.IndianRed;
        public Color gainedHealth = Color.ForestGreen;
    }

    public HealthBarColours colours = new();
    public TimeSpan animateAfter = TimeSpan.FromSeconds(.75);
    public TimeSpan animateDurationPerPercent = TimeSpan.FromMilliseconds(25);

    protected TimeSpan healthChangedAt;
    protected float previousPer;
    protected float previousAnimatedPer;
    protected float animateFromPer;

Hier kan de health bar reset worden, zodat het niet nodig is om een nieuwe te maken om te voorkomen dat het voor één frame flikkert.

    public HealthBar()
        => Reset();

    public void Reset()
    {
        healthChangedAt = TimeSpan.Zero;
        previousPer = 1f;
        previousAnimatedPer = 1f;
        animateFromPer = 1f;
    }

Hier gebeurt de echte logica. Eerst word een simpele afbeelding gepakt dat maar één pixel is, om simpele vierkanten mee te tekenen die geheel dezelfde kleur zijn. Hiermee word gelijk de achtergrond van de health bar getekend.

    public void UpdateAndDraw(GameTime gameTime, SpriteBatch spriteBatch, Vector2 pos, Vector2 size, float currentPer)
    {
        var pixel = App.AssetManager.GetSprite("Images/pixel");

        spriteBatch.BetterDraw(
            pixel, pos, size, origin: Vector2.Zero,
            color: colours.background
        );

Daarna word informatie over de animatie status van de health bar behandeld. Het update het beginpunt qua beide tijd en positie als de health is veranderd, zodat de animatie naadloos verder kan.

        if (!MathExtras.RoughlyEquals(currentPer, previousPer))
        {
            healthChangedAt = gameTime.TotalGameTime;

            animateFromPer =
                MathF.Sign(animateFromPer - previousPer)
                == MathF.Sign(previousPer - currentPer)
                    ? previousAnimatedPer
                    : previousPer;

            previousPer = currentPer;
        }

Hier word dan die verzamelde informatie gebruikt om de huidige positie van de animatie te berekenen.

        var animationTime = gameTime.TotalGameTime - healthChangedAt - animateAfter;
        var animationSpeed = Math.Abs(animateFromPer - currentPer) * 100 * animateDurationPerPercent.TotalSeconds;

        var animationProgress = (float)Math.Clamp(animationTime.TotalSeconds / animationSpeed, 0.0, 1.0);

        var healthAnimatedPer = MathHelper.Lerp(
            animateFromPer,
            currentPer,
            animationProgress
        );

Als laatste word dat allemaal hier gebruikt om de echte health in de health bar te tekenen.

Omdat het bewegende deel anders is als je health omhoog of omlaag gaat moet dit in een grote if, terwijl het verschil ertussen klein is. Dit kan niet anders omdat de volgorde ook belangrijk is, er word veel over elkaar getekend om de berekeningen simpel te houden.

        if (currentPer >= animateFromPer)
        {
            spriteBatch.BetterDraw(
                pixel, pos,
                size * new Vector2(currentPer, 1),
                origin: Vector2.Zero,
                color: colours.gainedHealth
            );

            spriteBatch.BetterDraw(
                pixel, pos,
                size * new Vector2(healthAnimatedPer, 1),
                origin: Vector2.Zero,
                color: colours.health
            );
        }
        else
        {
            spriteBatch.BetterDraw(
                pixel, pos,
                size * new Vector2(healthAnimatedPer, 1),
                origin: Vector2.Zero,
                color: colours.lostHealth
            );

            spriteBatch.BetterDraw(
                pixel, pos,
                size * new Vector2(currentPer, 1),
                origin: Vector2.Zero,
                color: colours.health
            );
        }

En als aller laatste word informatie van deze frame opgeslagen voor de volgende. Om het mogelijk te maken om onwaarneembaar de animatie halverwege te veranderen.

        previousAnimatedPer = healthAnimatedPer;

        if (MathExtras.RoughlyEquals(animationProgress, 1f))
            animateFromPer = currentPer;
    }
}

Speler Health Bar Systeem

De speler health bar systeem regelt er alleen voor dat er één health bar is voor de speler die bovenaan het scherm word getekend. Naast de methods van de health bar zelf oproepen doet het zelf niet echt iets speciaals.

public class PlayerHealthBarSystem : BaseSystem
{
    protected StateManager state;
    protected GameState game;
    protected Texture2D pixel;

    protected HealthBar bar = new();

    protected Vector2 padding = new(10, 10);
    protected Vector2 pos;
    protected Vector2 size;


    public PlayerHealthBarSystem() : base()
    {
        pos = padding;
        size = new(App.Screen.X - padding.X * 2, 15);
    }

    public override void Initialize(App app, SystemHandler handler)
    {
        base.Initialize(app, handler);

        state = handler.GetSystem<StateManager>();

        handler
            .GetSystem<EventBus>()
            .Subscribe((StateChangedEvent e) => {
                if (e.newState == game)
                    bar.Reset();
            });
    }

    public override void LoadContent()
    {
        game = state.GetState<GameState>();
    }

    public override void Draw(GameTime gameTime, SpriteBatch spriteBatch)
    {
        if (state.currentState != game)
            return;

        bar.UpdateAndDraw(gameTime, spriteBatch, pos, size, game.Player.Health / (float)game.Player.MaxHealth);
    }
}

Klassendiagram

classDiagram class BaseSystem { +Initialize(App, SystemHandler) +LoadContent() +Draw(GameTime, SpriteBatch) } PlayerHealthBarSystem --|> BaseSystem class PlayerHealthBarSystem { #StateManager state #GameState game #Texture2D pixel #HealthBar bar #Vector2 padding #Vector2 pos #Vector2 size } PlayerHealthBarSystem --* HealthBar class HealthBar { +HealthBarColours colours +TimeSpan animateAfter +TimeSpan animateDurationPerPercent #TimeSpan healthChangedAt #float previousPer #float previousAnimatedPer #float animateFromPer +Reset() +UpdateAndDraw(GameTime, SpriteBatch, Vector2, Vector2, float) } HealthBar --* HealthBarColours class HealthBarColours { +Color background +Color health +Color lostHealth +Color gainedHealth }

Bronnen


Laatst geüpdatet: June 9, 2024
Gecreëerd: April 25, 2024