package AdventureGameViews; import AdventureGameModel.AdventureGame; import javafx.animation.PauseTransition; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.*; import javafx.scene.media.Media; import javafx.scene.media.MediaPlayer; import javafx.scene.paint.Color; import javafx.scene.layout.*; import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyCode; import javafx.scene.text.Font; import javafx.stage.Stage; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.util.Duration; import javafx.scene.AccessibleRole; import java.io.IOException; import java.util.Objects; // background colour imports import javafx.scene.control.ColorPicker; import javafx.stage.FileChooser; import java.io.File; import javafx.scene.layout.BackgroundImage; import javafx.scene.layout.BackgroundRepeat; import javafx.scene.layout.BackgroundPosition; import javafx.scene.layout.BackgroundSize; /** * Class AdventureGameView * * Visualizes model in GUI. Class handles or makes references to all UI elements */ public class AdventureGameView { AdventureGame model; // model of the game Stage stage; // stage on which everything is rendered Button saveButton, loadButton, helpButton, menuButton; // buttons Boolean helpToggle = false; // check if help is on display GridPane gridPane = new GridPane(); // to hold images and buttons Label roomDescLabel = new Label(); // to hold room description and/or instructions VBox objectsInRoom = new VBox(); // to hold room items VBox objectsInInventory = new VBox(); // to hold inventory items ImageView roomImageView; // to hold room image TextField inputTextField; // for user input public MediaPlayer mediaPlayer; // to play audio public MediaPlayer musicPlayer; // to play music public boolean mediaPlaying; // to know if the audio is playing private boolean musicPlaying; // to know if music is playing /** * Adventure Game View Constructor * __________________________ * Initializes attributes */ public AdventureGameView(AdventureGame model, Stage stage) { this.model = model; this.stage = stage; intiUI(); // initializes the UI } /** * Initialize the UI */ public void intiUI() { // setting up the stage this.stage.setTitle("Harsh Nair's Adventure Game"); // Inventory + Room nodes objectsInInventory.setSpacing(10); objectsInInventory.setAlignment(Pos.TOP_CENTER); objectsInRoom.setSpacing(10); objectsInRoom.setAlignment(Pos.TOP_CENTER); // set up the grid pane, making Node elements transparent gridPane.setPadding(new Insets(20)); Color transparentColor = Color.rgb(0, 0, 0, 0); gridPane.setBackground(new Background(new BackgroundFill( transparentColor, new CornerRadii(0), new Insets(0) ))); // three columns, three rows for the GridPane ColumnConstraints column1 = new ColumnConstraints(150); ColumnConstraints column2 = new ColumnConstraints(650); ColumnConstraints column3 = new ColumnConstraints(150); column3.setHgrow( Priority.SOMETIMES ); // let some columns grow to take any extra space column1.setHgrow( Priority.SOMETIMES ); // row constraints RowConstraints row1 = new RowConstraints(); RowConstraints row2 = new RowConstraints( 550 ); RowConstraints row3 = new RowConstraints(); row1.setVgrow( Priority.SOMETIMES ); row3.setVgrow( Priority.SOMETIMES ); gridPane.getColumnConstraints().addAll( column1 , column2 , column1 ); gridPane.getRowConstraints().addAll( row1 , row2 , row1 ); // button creation saveButton = new Button("Save"); saveButton.setId("Save"); customizeButton(saveButton, 100, 50); makeButtonAccessible(saveButton, "Save Button", "This button saves the game.", "This button saves the game. Click it in order to save your current progress, so you can play more later."); addSaveEvent(); loadButton = new Button("Load"); loadButton.setId("Load"); customizeButton(loadButton, 100, 50); makeButtonAccessible(loadButton, "Load Button", "This button loads a game from a file.", "This button loads the game from a file. Click it in order to load a game that you saved at a prior date."); addLoadEvent(); helpButton = new Button("Instructions"); helpButton.setId("Instructions"); customizeButton(helpButton, 200, 50); makeButtonAccessible(helpButton, "Help Button", "This button gives game instructions.", "This button gives instructions on the game controls. Click it to learn how to play."); addInstructionEvent(); // settings menu button menuButton = new Button(""); menuButton.setId("Settings Menu"); customizeButton(menuButton, 50, 50); makeButtonAccessible(menuButton, "Settings Button", "This button toggles the settings menu", "This button opens a settings menu that gives some additional settings options to players"); Image gear = new Image("Games/TinyGame/gear.png"); ImageView gearView = new ImageView(gear); gearView.setFitHeight(35); gearView.setFitWidth(35); menuButton.setGraphic(gearView); addSettingsEvent(); HBox topButtons = new HBox(); topButtons.getChildren().addAll(saveButton, helpButton, loadButton, menuButton); topButtons.setSpacing(10); topButtons.setAlignment(Pos.CENTER); inputTextField = new TextField(); inputTextField.setFont(new Font("Arial", 16)); inputTextField.setFocusTraversable(true); inputTextField.setAccessibleRole(AccessibleRole.TEXT_AREA); inputTextField.setAccessibleRoleDescription("Text Entry Box"); inputTextField.setAccessibleText("Enter commands in this box."); inputTextField.setAccessibleHelp("This is the area in which you can enter commands you would like to play. Enter a command and hit return to continue."); addTextHandlingEvent(); // attach an event to this input field // labels for inventory and room items Label objLabel = new Label("Objects in Room"); objLabel.setAlignment(Pos.CENTER); objLabel.setStyle("-fx-text-fill: white;"); objLabel.setFont(new Font("Arial", 16)); Label invLabel = new Label("Your Inventory"); invLabel.setAlignment(Pos.CENTER); invLabel.setStyle("-fx-text-fill: white;"); invLabel.setFont(new Font("Arial", 16)); // add all the widgets to the GridPane gridPane.add( objLabel, 0, 0, 1, 1 ); gridPane.add( topButtons, 1, 0, 1, 1 ); gridPane.add( invLabel, 2, 0, 1, 1 ); // input text field label Label commandLabel = new Label("What would you like to do?"); commandLabel.setStyle("-fx-text-fill: white;"); commandLabel.setFont(new Font("Arial", 16)); updateScene(""); // method displays an image and whatever text is supplied updateItems(); // update items shows inventory and objects in rooms updateMusic(); // adding the text area and submit button to a VBox VBox textEntry = new VBox(); // more transparency textEntry.setStyle("-fx-background-color: transparent;"); textEntry.setPadding(new Insets(20, 20, 20, 20)); textEntry.getChildren().addAll(commandLabel, inputTextField); textEntry.setSpacing(10); textEntry.setAlignment(Pos.CENTER); gridPane.add( textEntry, 0, 2, 3, 1 ); // render everything var scene = new Scene( gridPane , 1000, 800); // more transparency scene.setFill(transparentColor); this.stage.setScene(scene); this.stage.setResizable(false); this.stage.show(); // background colour functionality ColorPicker colorPicker = new ColorPicker(); colorPicker.setValue(Color.BLACK); // set the initial color to black // create a button for applying the selected color Button applyColorButton = new Button("Apply Color"); applyColorButton.setOnAction(e -> { Color selectedColor = colorPicker.getValue(); setCustomBackgroundColor(selectedColor); }); // create a button for custom image background import Button selectImageButton = new Button("Select Image"); selectImageButton.setOnAction(e -> { FileChooser fileChooser = new FileChooser(); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg", "*.jpeg", "*.gif")); File selectedFile = fileChooser.showOpenDialog(stage); if (selectedFile != null) { setCustomBackgroundImage(selectedFile.toURI().toString()); } }); // add background image elements to vbox for display on main grid pane VBox colorPickerBox = new VBox(colorPicker, applyColorButton, selectImageButton); colorPickerBox.setAlignment(Pos.CENTER); colorPickerBox.setSpacing(10); gridPane.add(colorPickerBox, 0, 3, 3, 1); } /** * setCustomBackgroundImage * Set a custom background colour * __________________________ * @param color the color of the background selected by a click event from the user */ private void setCustomBackgroundColor(Color color) { gridPane.setBackground(new Background(new BackgroundFill( color, new CornerRadii(0), new Insets(0) ))); } /** * setCustomBackgroundImage * Set a custom background image * __________________________ * @param imageUrl the url of the image the user selects from their computer */ private void setCustomBackgroundImage(String imageUrl) { Image image = new Image(imageUrl, 1000, 800, false, true); // sets the background image with reduced opacity gridPane.setBackground(new Background(new BackgroundImage( image, BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, BackgroundPosition.DEFAULT, new BackgroundSize(1.0, 1.0, true, true, false, true) ))); } /** * makeButtonAccessible * __________________________ * Renders buttons accessible for players with disabilities as per ARIA standards of accessibility: * <a href="https://www.w3.org/WAI/standards-guidelines/aria/">...</a> * * @param inputButton the button to add screenreader hooks to * @param name ARIA name * @param shortString ARIA accessible text * @param longString ARIA accessible help text */ public static void makeButtonAccessible(Button inputButton, String name, String shortString, String longString) { inputButton.setAccessibleRole(AccessibleRole.BUTTON); inputButton.setAccessibleRoleDescription(name); inputButton.setAccessibleText(shortString); inputButton.setAccessibleHelp(longString); inputButton.setFocusTraversable(true); } /** * customizeButton * __________________________ * Adjusts size, font/font size and font colours of buttons * * @param inputButton the button to customize * @param w width * @param h height */ private void customizeButton(Button inputButton, int w, int h) { inputButton.setPrefSize(w, h); inputButton.setFont(new Font("Arial", 16)); inputButton.setStyle("-fx-background-color: #17871b; -fx-text-fill: white;"); } /** * addTextHandlingEvent * __________________________ * Add an event handler to the inputTextField attribute * * Event handler responds when users * hits the ENTER or TAB KEY. If the user hits * the ENTER Key, strips white space from the * input to inputTextField and passes the stripped * string to submitEvent for processing * * If the user hits the TAB key, moves the focus * of the scene onto any other node in the scene * graph by invoking requestFocus method */ private void addTextHandlingEvent() { // if user hits the enter key this.inputTextField.setOnAction((ActionEvent event) -> { String usersInput = this.inputTextField.getText(); submitEvent(usersInput.strip()); }); // if user hits the tab key this.inputTextField.addEventHandler(KeyEvent.KEY_PRESSED, (KeyEvent event) -> { if (event.getCode() == KeyCode.TAB) { gridPane.requestFocus(); } }); } /** * submitEvent * __________________________ * Begins processing of player's inputted commands * * @param text the command that needs to be processed */ private void submitEvent(String text) { text = text.strip(); // get rid of white space stopArticulation(); if (text.equalsIgnoreCase("LOOK") || text.equalsIgnoreCase("L")) { String roomDesc = this.model.getPlayer().getCurrentRoom().getRoomDescription(); String objectString = this.model.getPlayer().getCurrentRoom().getObjectString(); if (!objectString.isEmpty()) roomDescLabel.setText(roomDesc + "\n\nObjects in this room:\n" + objectString); articulateRoomDescription(); // all we want, if we are looking, is to repeat description. return; } else if (text.equalsIgnoreCase("HELP") || text.equalsIgnoreCase("H")) { showInstructions(); return; } else if (text.equalsIgnoreCase("COMMANDS") || text.equalsIgnoreCase("C")) { showCommands(); return; } String output = this.model.interpretAction(text); // process the given command /* this condition covers all viable output options that do not transition the player to a new room, so in this case we don't want to update the music, instead we want to leave it playing */ if ((Objects.equals(output, "INVALID COMMAND.")) || (Objects.equals(output, "INVENTORY IS EMPTY")) || (Objects.equals(output, "THE TAKE COMMAND REQUIRES AN OBJECT")) || (Objects.equals(output, "THE DROP COMMAND REQUIRES AN OBJECT")) || (Objects.equals(output, "THESE OBJECTS ARE IN YOUR INVENTORY:\n" + this.model.player.getInventory().toString()))) { updateScene(output); updateItems(); } else if (output == null || (!output.equals("GAME OVER") && !output.equals("FORCED") && !output.equals("HELP"))) { updateScene(output); updateItems(); updateMusic(); } else if (output.equals("GAME OVER")) { updateScene(""); updateItems(); updateMusic(); // set a pause long enough for the victory track music to play out once PauseTransition pause = new PauseTransition(Duration.seconds(34)); pause.setOnFinished(event -> { // if the game has ended, we terminate the program Platform.exit(); }); pause.play(); } else if (output.equals("FORCED")) { /* handles "FORCED" events (rooms that do not allow users to input commands) recursively. Displays the image in the current room, pauses, then transitions to the next forced room */ updateScene(""); updateItems(); updateMusic(); PauseTransition pause = new PauseTransition(Duration.seconds(5)); inputTextField.setDisable(true); pause.setOnFinished(event -> { submitEvent("FORCED"); inputTextField.setDisable(false); }); pause.play(); } } /** * showCommands * __________________________ * Update the text in the GUI (within roomDescLabel) * to show all the moves that are possible from the * current room. * Call articulateCommands() to play a voice line * listing out the directions a user can currently go */ private void showCommands() { updateScene("You can move in these directions:\n\n" + this.model.player.getCurrentRoom().getCommands()); articulateCommands(this.model.player.getCurrentRoom().getRoomNumber()); } /** * updateMusic * __________________________ * updateMusic refreshes each time a new room is entered, * playing an ambient music file specific to that room. * When the user leaves a room, updateMusic will stop * playing the music file from the previous room, instead * switching to and playing the music file for the current * room */ public void updateMusic() { Integer currentRoomNum = this.model.player.getCurrentRoom().getRoomNumber(); if (musicPlaying) { musicPlayer.stop(); musicPlaying = false; } playAmbience(currentRoomNum); } /** * updateScene * __________________________ * Show the current room, and print some text below it. * If the input parameter is not null, it will be displayed * below the image. * Otherwise, the current room description will be displayed * below the image. * * @param textToDisplay the text to display below the image. */ public void updateScene(String textToDisplay) { getRoomImage(); // get the image of the current room formatText(textToDisplay); // format the text to display, just changes the label font and position roomDescLabel.setPrefWidth(500); roomDescLabel.setPrefHeight(500); roomDescLabel.setTextOverrun(OverrunStyle.CLIP); roomDescLabel.setWrapText(true); VBox roomPane = new VBox(roomImageView,roomDescLabel); roomPane.setPadding(new Insets(10)); roomPane.setAlignment(Pos.TOP_CENTER); roomPane.setStyle("-fx-background-color: transparent;"); gridPane.add(roomPane, 1, 1); stage.sizeToScene(); // finally, articulate the description if (textToDisplay == null || textToDisplay.isBlank()) articulateRoomDescription(); } /** * formatText * __________________________ * Format text for display. * * @param textToDisplay the text to be formatted for display. */ private void formatText(String textToDisplay) { if (textToDisplay == null || textToDisplay.isBlank()) { String roomDesc = this.model.getPlayer().getCurrentRoom().getRoomDescription() + "\n"; String objectString = this.model.getPlayer().getCurrentRoom().getObjectString(); if (objectString != null && !objectString.isEmpty()) roomDescLabel.setText(roomDesc + "\n\nObjects in this room:\n" + objectString); else roomDescLabel.setText(roomDesc); } else roomDescLabel.setText(textToDisplay); roomDescLabel.setStyle("-fx-text-fill: white;"); roomDescLabel.setFont(new Font("Arial", 16)); roomDescLabel.setAlignment(Pos.CENTER); } /** * getRoomImage * __________________________ * Get the image for the current room and place * it in the roomImageView */ private void getRoomImage() { int roomNumber = this.model.getPlayer().getCurrentRoom().getRoomNumber(); String roomImage = this.model.getDirectoryName() + "/RoomImagesAssets/" + roomNumber + ".png"; Image roomImageFile = new Image(roomImage); roomImageView = new ImageView(roomImageFile); roomImageView.setPreserveRatio(true); roomImageView.setFitWidth(400); roomImageView.setFitHeight(400); // set accessible text roomImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW); roomImageView.setAccessibleText(this.model.getPlayer().getCurrentRoom().getRoomDescription()); roomImageView.setFocusTraversable(true); roomImageView.setStyle("-fx-background-color: transparent;"); } /** * updateItems * __________________________ * This method populates the objectsInRoom and objectsInInventory VBoxes. * Each VBox contains a collection of nodes, where each node represents a different object * * Images of each object are in the assets folders of the given adventure game */ public void updateItems() { // clear new rooms of previous objects this.objectsInRoom.getChildren().clear(); // code specific to dropping/picking up BIRD object for (int i = 0; i < this.model.player.getCurrentRoom().objectsInRoom.size(); i++) { if (Objects.equals(this.model.player.getCurrentRoom().objectsInRoom.get(i).getName(), "BIRD")) { String objectImage = this.model.getDirectoryName() + "/ObjectImagesAssets/" + "BIRD" + ".jpg"; ImageView objectImageView = new ImageView(new Image(objectImage)); objectImageView.setPreserveRatio(true); objectImageView.setFitWidth(100); objectImageView.setFitHeight(100); objectImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW); objectImageView.setAccessibleText("Image of a bird"); objectImageView.setFocusTraversable(true); Button birdButton = new Button("", objectImageView); makeButtonAccessible(birdButton, "Bird Button", "Interact with Bird", "Used to pick up or drop the bird"); birdButton.setOnAction(event -> { if (this.objectsInRoom.getChildren().contains(birdButton)) { this.objectsInRoom.getChildren().remove(birdButton); this.objectsInInventory.getChildren().add(birdButton); this.model.player.takeObject("BIRD"); } else { this.objectsInInventory.getChildren().remove(birdButton); this.objectsInRoom.getChildren().add(birdButton); this.model.player.dropObject("BIRD"); } }); this.objectsInRoom.getChildren().add(birdButton); } else if (Objects.equals(this.model.player.getCurrentRoom().objectsInRoom.get(i).getName(), "CHEST")) { String objectImage = this.model.getDirectoryName() + "/ObjectImagesAssets/" + "CHEST" + ".jpg"; ImageView objectImageView = new ImageView(new Image(objectImage)); objectImageView.setPreserveRatio(true); objectImageView.setFitWidth(100); objectImageView.setFitHeight(100); objectImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW); objectImageView.setAccessibleText("Image of a chest"); objectImageView.setFocusTraversable(true); Button chestButton = new Button("", objectImageView); makeButtonAccessible(chestButton, "Chest Button", "Interact with Chest", "Used to pick up or drop the chest"); chestButton.setOnAction(event -> { if (this.objectsInRoom.getChildren().contains(chestButton)) { this.model.player.takeObject("CHEST"); this.objectsInRoom.getChildren().remove(chestButton); this.objectsInInventory.getChildren().add(chestButton); } else { this.model.player.dropObject("CHEST"); this.objectsInInventory.getChildren().remove(chestButton); this.objectsInRoom.getChildren().add(chestButton); } }); this.objectsInRoom.getChildren().add(chestButton); } else if (Objects.equals(this.model.player.getCurrentRoom().objectsInRoom.get(i).getName(), "BOOK")) { String objectImage = this.model.getDirectoryName() + "/ObjectImagesAssets/" + "BOOK" + ".jpg"; ImageView objectImageView = new ImageView(new Image(objectImage)); objectImageView.setPreserveRatio(true); objectImageView.setFitWidth(100); objectImageView.setFitHeight(100); objectImageView.setAccessibleRole(AccessibleRole.IMAGE_VIEW); objectImageView.setAccessibleText("Image of a book"); objectImageView.setFocusTraversable(true); Button bookButton = new Button("", objectImageView); makeButtonAccessible(bookButton, "Book Button", "Interact with Book", "Used to pick up or drop the book"); bookButton.setOnAction(event -> { if (this.objectsInRoom.getChildren().contains(bookButton)) { this.model.player.takeObject("BOOK"); this.objectsInRoom.getChildren().remove(bookButton); this.objectsInInventory.getChildren().add(bookButton); } else { this.model.player.dropObject("BOOK"); this.objectsInInventory.getChildren().remove(bookButton); this.objectsInRoom.getChildren().add(bookButton); } }); this.objectsInRoom.getChildren().add(bookButton); } } // add scroll tabs to GUI ScrollPane scO = new ScrollPane(objectsInRoom); scO.setPadding(new Insets(10)); scO.setFitToWidth(true); gridPane.add(scO,0,1); ScrollPane scI = new ScrollPane(objectsInInventory); scI.setFitToWidth(true); gridPane.add(scI,2,1); } /** * Show the game instructions. * * If helpToggle is False: * -- displays the help text in the CENTRE of the gridPane (i.e. within cell 1,1) * -- sets the helpToggle to TRUE * -- removes whatever nodes are within the cell beforehand * * If helpToggle is True: * -- redraws the room image in the CENTRE of the gridPane (i.e. within cell 1,1) * -- sets the helpToggle to FALSE * -- Removes whatever nodes are within the cell beforehand */ public void showInstructions() { // custom code, separate from personal // toggles help instructions, does not disappear upon entering a new room if (!helpToggle) { helpToggle = true; Label label = new Label(); label.setId("Instruction"); gridPane.add(label, 1, 1); label.setWrapText(true); label.autosize(); label.setTextFill(Color.WHITE); label.setText(model.getInstructions()); label.setStyle("-fx-background-color: black"); } else { helpToggle = false; for (Node a: gridPane.getChildren()) { if (a.getId() != null) { if (a.getId().equals("Instruction")) { gridPane.getChildren().remove(a); break; } } } } } /** * This method handles the event related to the * help button. */ public void addInstructionEvent() { helpButton.setOnAction(e -> { stopArticulation(); showInstructions(); }); } /** * This method handles the event related to the * save button. */ public void addSaveEvent() { saveButton.setOnAction(e -> { gridPane.requestFocus(); SaveView saveView = new SaveView(this); }); } /** * This method handles the event related to the * load button. */ public void addLoadEvent() { loadButton.setOnAction(e -> { gridPane.requestFocus(); try { LoadView loadView = new LoadView(this); } catch (IOException ex) { throw new RuntimeException(ex); } }); } /** * This method handles the event related to the * settings menu button */ public void addSettingsEvent() { menuButton.setOnAction(e -> { gridPane.requestFocus(); SettingsView settingsView = new SettingsView(this); }); } /** * This method articulates Room Descriptions */ public void articulateRoomDescription() { String musicFile; String adventureName = this.model.getDirectoryName(); String roomName = this.model.getPlayer().getCurrentRoom().getRoomName(); // room articulations are pulled from SoundAssets directory in TinyGame or equivalent if (!this.model.getPlayer().getCurrentRoom().getVisited()) musicFile = "./" + adventureName + "/SoundAssets/" + roomName.toLowerCase() + "-long.mp3" ; else musicFile = "./" + adventureName + "/SoundAssets/" + roomName.toLowerCase() + "-short.mp3" ; musicFile = musicFile.replace(" ","-"); Media sound = new Media(new File(musicFile).toURI().toString()); mediaPlayer = new MediaPlayer(sound); mediaPlayer.play(); mediaPlaying = true; } /** * This method is called when the user asks to see game "COMMANDS", * gathering a voice file specific to the room the user is in and playing its audio. * Audio is halted when user interrupts the voice line by entering another command */ public void articulateCommands(Integer room_number) { String musicFile; String adventureName = this.model.getDirectoryName(); // direction articulations are pulled from SoundAssets directory in TinyGame or equivalent musicFile = "./" + adventureName + "/SoundAssets/" + String.valueOf(room_number) + "-line.mp3"; musicFile = musicFile.replace(" ","-"); Media sound = new Media(new File(musicFile).toURI().toString()); mediaPlayer = new MediaPlayer(sound); mediaPlayer.play(); mediaPlaying = true; } /** * This method is called by updateMusic whenever the user enters a new room, * it searches for and plays a music file from Saved/TinyGame/SoundAssets specific * to that room, looping it */ public void playAmbience(Integer room_number) { String musicFile; String adventureName = this.model.getDirectoryName(); musicFile = "./" + adventureName + "/SoundAssets/" + String.valueOf(room_number) + "-ambient.mp3"; musicFile = musicFile.replace(" ","-"); Media sound = new Media(new File(musicFile).toURI().toString()); musicPlayer = new MediaPlayer(sound); musicPlayer.play(); musicPlayer.setOnEndOfMedia(() -> { /* loop the room music everytime a track ends so that the music keeps playing while the user is in the room */ musicPlayer.seek(Duration.ZERO); musicPlayer.play(); }); musicPlaying = true; } /** * This method stops room articulations. * Useful when transitioning to a new room or loading a new game */ public void stopArticulation() { if (mediaPlaying) { mediaPlayer.stop(); mediaPlaying = false; } } }