Death & Win Screen Code Implementation - UQdeco2800/2022-studio-2 GitHub Wiki

Death & Win Screen Code Implementation - Sprint 3 & 4

Implemented and written by Elias, supported* by Isaac

[*] Supported referring to any issues with implementation I didn't understand or was stuck on Isaac helped to fix that specific issue.

Introduction

The death screen code implementation is modelled after the Main Menu, it uses DeathScreenActions, DeathScreenDisplay, and DeathScreen classes to display the visual assets and appropriately use eventlisteners.

This was iterated on a non-existent implementation in sprint 2, replacing all previous mentions of the death screen with a working, well-designed class system.

DeathScreen

The relevant death screen images are loaded in as a texture, first created:

private static final String[] deathTextures = {"images/DeathScreens/lvl_1.png", "images/DeathScreens/lvl_2.png"};

Then set out to be loaded in loadAssets()

private void loadAssets() {
    logger.debug("Loading assets");
    ResourceService resourceService = ServiceLocator.getResourceService();
    resourceService.loadTextures(deathTextures);
    resourceService.loadMusic(deathMusic);
    ServiceLocator.getResourceService().loadAll();
}

And the constructor loads them: loadAssets();

DeathScreen() also creates the UI, disposes of the loaded assets, plays music and inherent the relevant parent classes.

DeathScreenActions

Before we display DeathScreen we need to set up the actions it'll do, DeathScreenActions extends Components, it listens for the relevant actions that will occur due to button presses in DeathScreenDisplay, this holds continueGame action and exit action.

/**
 * Creates the event listeners relevant to DeathScreen
 */
@Override
public void create() {
    entity.getEvents().addListener("continueGame", this::onContinue);
    entity.getEvents().addListener("exit", this::onExit);
}

/**
 * Restarts the game by reloading Main Game screen.
 */
private void onContinue() {
    Sound sound = Gdx.audio.newSound(Gdx.files.internal("sounds/ButtonSoundtrack.wav"));
    sound.play(1.0f);
    logger.info("Continue playing game after death");
    game.setScreen(GdxGame.ScreenType.MAIN_GAME);
}


/**
 * Returns to main menu after death when exit is selected.
 */
private void onExit() {
    Sound sound = Gdx.audio.newSound(Gdx.files.internal("sounds/ButtonSoundtrack.wav"));
    sound.play(1.0f);
    logger.info("Return to main menu");
    game.setScreen(GdxGame.ScreenType.MAIN_MENU);
}

DeathScreenDisplay

DeathScreenDisplay is a UI component, it works the exact same as Main Menu, it is triggered when player health is 0. It's main different from MainMenuDisplayis it takes a level variable. If level equals 1, levelBackground() ensures that the image displayed in the addActors is the level 1 death image, same with level 2, level 1 being the default. This method also has a setButtonDisplay() class that managed the size of the transparent buttons placed over the image that is staged.

Logic: [deathBackground is the image then given to the addActor to add it to the table.]

public Image levelBackground (int level) {
    switch (level){
        case 1:
            logger.info("setting level 1 deathscreen from DeathScreenDisplay");
            return deathBackground = new Image(ServiceLocator.getResourceService()
                    .getAsset("images/DeathScreens/lvl_1.png", Texture.class));

        case 2:
            logger.info("setting level 2 deathscreen from DeathScreenDisplay");
            return deathBackground = new Image(ServiceLocator.getResourceService()
                    .getAsset("images/DeathScreens/lvl_2.png", Texture.class));
    }
    return deathBackground = new Image(ServiceLocator.getResourceService()
            .getAsset("images/DeathScreens/lvl_1.png", Texture.class));
}

addActor() displays the background image that is then staged. It then calls setButtonDisplay() to ensure the correct sized buttons are staged as well.

private void addActors() {
    // Set's background image based on the level
    Image background = levelBackground(level);
    // Makes sure the image fills the entire screen
    background.setFillParent(true);
    // Stages the image
    stage.addActor(background);
    // stages the buttons
    setButtonDisplay();
    logger.debug("DeathScreenDisplay background image and buttons has been added to the actor");
}

setButtonDisplay

This class manages the listeners related to the death and win screens. It sets up button displays and adds them to the stage. This is done by setting up textures, assigning them to buttons and assigning listeners to buttons.

If first sets up the texture, texture region and texture drawable:

    Group buttons = new Group();
    //loads in transparent image for button texture, making button transparent
    Texture btnTexture = new Texture(Gdx.files.internal
            ("images/crafting_assets_sprint2/transparent-texture-buttonClick.png"));
    // Texture Region of a given texture
    TextureRegion buttonTextureRegion = new TextureRegion(btnTexture);
    // Texture drawable of a given texture region
    TextureRegionDrawable buttonDrawable = new TextureRegionDrawable(buttonTextureRegion);

Then gets the correct dimensions of the button based on the level/screen it should be showing (level 3 always refers to the win screen). PlayAgain / continue is an example of this logic and dimensions set. Note that getLevel() is just calling a global variable, it's a more reader-friendly version.

    //playAgain
    if (getLevel() == 3) {
        playAgainBtn.setOrigin(playAgainBtn.getWidth()/3, playAgainBtn.getHeight()/3);
        playAgainBtn.setPosition(908, 370);
        playAgainBtn.setSize(88, 80);
    } else {
        playAgainBtn.setOrigin(playAgainBtn.getWidth()/3, playAgainBtn.getHeight()/3);
        playAgainBtn.setWidth(340);
        playAgainBtn.setHeight(270);
        playAgainBtn.setPosition(1565,0);
    }

Then the listener is applied, the button is added to the group, the group is staged, and the stage is drawn. This repeats for exit button.

    // play again/ restart level 1 listner
    playAgainBtn.addListener(
            new ChangeListener() {
                @Override
                public void changed(ChangeEvent changeEvent, Actor actor) {
                    logger.info("The play again button was clicked");
                    entity.getEvents().trigger("continueGame");
                }
            });
    buttons.addActor(playAgainBtn);
    stage.addActor(buttons);
    stage.draw();

Making it Work with GdxGame & MainGameScreen

To make this actually display in the game and logic statement was added to MainGameScreen's render() method:

if (dead) {
  // Could add further player cleanup functionality here
  player.getComponent(PlayerActions.class).stopWalking();
  if (gameLevel == 1) {
    game.setScreen(GdxGame.ScreenType.DEATH_SCREEN_L1);
  } else if (gameLevel == 2) {
    game.setScreen(GdxGame.ScreenType.DEATH_SCREEN_L2);
  }
}

Hence when the game first starts 'dead' is false since the player is alive, there is an event listener added to the player listening for a death, once they die dead is true, and the above logic is set in place. The enum's DEATH_SCREEN_L1 and DEATH_SCREEN_L2 come from GdxGame in which there is a switch statement under newScreen that returns a new DeathScreen for the associated enum, if it's DEATH_SCREEN_L1 then DeathScreen is given 1 as it's level and it displays level 1, same for the relevant level 2 processes for DEATH_SCREEN_L2.

Win Screen

Due to the similarities in death screen and win screen functionality, it was decided to include win screen as apart of death screen, it works as an additional screen under death screen, with the main difference being how it is triggered.

In KeyboardPlayerInputComponent.java under keyDown() there is a case that triggers a win event when the button is clicked. In sprint 4 this will be changed to be under Enter when the character has killed the level 2 boss and entered the level 2 plug, but due to level 2's lack of enemies and delays on plug location this was a quick fix.

  case Keys.N:
    entity.getEvents().trigger("win");
    return true;

Once the event is triggered, like death screen, it triggers player.getEvents().addListener("win", this::winScreenStart); in MainGameScreen.java, making the win state true:

/**

  • Sets win to true */ public void winScreenStart() { win = true;}

and thus making this logic in render() true, changing the screen type through GdxGame, and repeating the Death Screen steps.

if (win) {
  logger.info("Win screen screen type set");
  player.getComponent(PlayerActions.class).stopWalking();
  game.setScreen(GdxGame.ScreenType.WIN_SCREEN);

}

UML and Class Structure of Death Screen

deathScreenFinal

Sequence Diagram of DeathScreen:

Below visually represent how the sequence of events for DeathScreen works, this process is very similar for level 2 and win state, only difference being what is displayed and the positioning of the buttons on the screen.

Level 1 Player Die:

This sequence diagram shows the sequence of events when the player dies and DeathScreen is displayed for level 1 when the character dies:

death drawio

Level 1 Death Screen --> Exit btn pressed

The sequence diagram shows when the exit button is pressed and triggers "exit" event of DeathScreenActions, building on from the previous sequence diagram:

exit btn pressed drawio