Animation of Stats Bars - UQcsse3200/2024-studio-2 GitHub Wiki

Overview

There are multiple UIComponents throughout the game (PlayerStatsDisplay, CombatStatsDisplay) that make use of the Stats Bars. The following describes how these Stats Bars are initialised and animated. These bars reflect only some of the statistics associated with player and enemy entities, mainly the ones believed to directly affect the gameplay.

Pixel Arts and Frames:

  • Health Bar: The health bar used to describe the health of the player and/or enemy

Image of Health Bar Pixel Art Image of Health Bar Spritesheets

  • Hunger Bar: The hunger bar used to describe the stamina of the player and/or enemy

Image of Hunger Bar Image of Hunger Bar Spritesheet

  • Experience Bar: The experience bar used to describe the amount of experience the player has.

Image of Experience Bar Art Image of Experience Bar Spritesheet

Usage

The following outline the key steps involved in animating the stats bars. Majority of this code is contained in both the PlayerStatsDisplay and the CombatStatsDisplay, with there being slight differences in the code as the combat screen contains more bars (addition of enemy health bar on the screen) and has had to implement event listening differently due to the overall structure of the entity that its attached to. The code that is displayed below will be from CombatStatsDisplay.java.

1. Initialisation of variables to be used across methods in CombatStatsDisplay. These are global variables within CombatStatsDisplay as these components are used acrossed multiple methods throughout. The variable naming matches their purpose, with many of these values being initialised in further methods.

public class CombatStatsDisplay extends UIComponent {
  private CombatStatsComponent playerStats;
  private CombatStatsComponent enemyStats;
  private Image playerHealthImage;
  private Image playerHungerImage;
  private Image enemyHealthImage;
  private  Image xpImage;
  private Label playerHealthLabel;
  private Label playerHungerLabel;
  private Label enemyHealthLabel;
  private Label experienceLabel;
  private TextureAtlas[] textureAtlas;
  private static Animation<TextureRegion> playerHealthBarAnimation;
  private static Animation<TextureRegion> enemyHealthBarAnimation;
  private static Animation<TextureRegion> playerHungerBarAnimation;
  private static Animation<TextureRegion> xpBarAnimation;
  private float barImageWidth;
  private float barImageHeight;
  private static final int totalFrames = 11;
  ...
}

2. Initialise the CombatStatsDisplay with the CombatStatsComponents of the enemy and player. These are important as they are used to calculate the frame at which each stats bar should be showing.

  public CombatStatsDisplay(CombatStatsComponent playerStats, CombatStatsComponent enemyStats) {
    this.playerStats = playerStats;
    this.enemyStats = enemyStats;
  }

3. Once initialised, the create method is called and used to add actors onto the stage of the game, as well as add listeners for any stats changes in the CombatStatsComponents of the player or enemy (the listeners listen for triggers sent in CombatActions)

  @Override
  public void create() {
    super.create();
    addActors();
    entity.getEvents().addListener("onAttack", this::updateHealthUI);
    entity.getEvents().addListener("onAttack", this::updatePlayerHungerUI);
    entity.getEvents().addListener("onGuard", this::updateHealthUI);
    entity.getEvents().addListener("onGuard", this::updatePlayerHungerUI);
    entity.getEvents().addListener("onSleep", this::updateHealthUI);
    entity.getEvents().addListener("onSleep", this::updatePlayerHungerUI);
    entity.getEvents().addListener("onCombatWin", this::updatePlayerExperienceUI);
  }

4a. Inside addActors, method calls to initialiser methods are made. initialisePlayerStatBars(), initialiseEnemyStatBars() initialise and fill Tables containing the initial Image and Labels for each of the stats bars of the enemy and player. initBarAnimations() will initialise each of the Animation for each stat bar located on the stage.

  private void addActors() {
    Table playerTable = initialisePlayerStatBars();
    Table enemyTable = initialiseEnemyStatBars();

    stage.addActor(playerTable);
    stage.addActor(enemyTable);

    initBarAnimations();
  }

4b. The listener functions above each correspond to an update method for each statistic. These update functions are triggered whenever a method call such as "onSleep" or "onAttack" is called in the CombatActions component that is also attached to the same UI entity as the CombatStatsDisplay component. When triggered, the CombatActions component will send through the updated CombatStatsComponent of the player and sometimes the enemy as well, which the update method calls will use to calculate the correct frame that they are going to set in the sprite png of the respective bar.

  public void updateHealthUI(CombatStatsComponent playerStats, CombatStatsComponent enemyStats) {
    int playerCurHealth = playerStats.getHealth();
    int playerMaxHealth = playerStats.getMaxHealth();
    int enemyCurHealth = enemyStats.getHealth();
    int enemyMaxHealth = enemyStats.getMaxHealth();

    CharSequence playerText = String.format("HP: %d", playerCurHealth);
    CharSequence enemyText = String.format("HP: %d", enemyCurHealth);

    // Adjusts position as lists start at index 0
    int indexAdjustment = totalFrames - 1;
    int playerFrameIndex = indexAdjustment - (int) ((float) playerCurHealth / playerMaxHealth * (totalFrames - 1));
    playerFrameIndex = Math.max(0, Math.min(playerFrameIndex, totalFrames - 1));

    int enemyFrameIndex = indexAdjustment - (int) ((float) enemyCurHealth / enemyMaxHealth * (totalFrames - 1));
    enemyFrameIndex = Math.max(0, Math.min(enemyFrameIndex, totalFrames - 1));

    // Update player stats
    playerHealthLabel.setText(playerText);
    setNewFrame(playerFrameIndex, playerHealthBarAnimation, playerHealthImage);

    // Update enemy stats
    enemyHealthLabel.setText(enemyText);
    setNewFrame(enemyFrameIndex, enemyHealthBarAnimation, enemyHealthImage);
  }

  public void updatePlayerExperienceUI(CombatStatsComponent playerStats) {
    int experience = playerStats.getExperience();
    int maxExperience = playerStats.getMaxExperience();
    CharSequence text = String.format("EXP: %d", experience);
    experienceLabel.setText(text);

    int frameIndex = totalFrames - 1 - (int) ((float) experience / maxExperience * (totalFrames - 1));
    frameIndex = Math.max(0, Math.min(frameIndex, totalFrames - 1));
    // Set the current frame of the health bar animation
    setNewFrame(frameIndex, xpBarAnimation, xpImage);
  }

  public void updatePlayerHungerUI(CombatStatsComponent playerStats, CombatStatsComponent enemyStats) {
    int hunger = playerStats.getStamina();
    int maxHunger = playerStats.getMaxStamina();
    CharSequence text = String.format("Stamina: %d", hunger);
    playerHungerLabel.setText(text);

    int frameIndex = totalFrames - 1 - (int) ((float) hunger / maxHunger * (totalFrames - 1));
    frameIndex = Math.max(0, Math.min(frameIndex, totalFrames - 1));
    // Set the current frame of the health bar animation
    setNewFrame(frameIndex, playerHungerBarAnimation, playerHungerImage);

  }