diff --git a/configurations/demo-config-1/locations/location-2/location-2.json b/configurations/demo-config-1/locations/location-2/location-2.json new file mode 100644 index 00000000..e82af4e6 --- /dev/null +++ b/configurations/demo-config-1/locations/location-2/location-2.json @@ -0,0 +1,11 @@ +{ + "tag": "location-2", + "objects": [ + { "tag": "object-1", "position": { "row": 1, "col": 8 }, "type": "collectible", "assetPath": "assets/diamond.png" }, + { "tag": "object-2", "position": { "row": 6, "col": 3 }, "type": "collectible", "assetPath": "assets/key.png" }, + { "tag": "object-3", "position": { "row": 1, "col": 1 }, "type": "dialog", "assetPath": "assets/zombie.png" }, + { "tag": "object-4", "position": { "row": 3, "col": 2 }, "type": "dialog", "assetPath": "assets/zombie.png" }, + { "tag": "object-5", "position": { "row": 8, "col": 8 }, "type": "dialog", "assetPath": "assets/chest.png" } + ], + "backgroundPath": "assets/map10x10.png" +} diff --git a/configurations/demo-config-1/root.json b/configurations/demo-config-1/root.json index a1a906e8..d3144043 100644 --- a/configurations/demo-config-1/root.json +++ b/configurations/demo-config-1/root.json @@ -1,7 +1,8 @@ { "tag": "GameByConfig1", "locationTags": [ - "location-1" + "location-1", + "location-2" ], "rootLocation": "location-1", "player": { diff --git a/src/main/java/io/rpg/Game.java b/src/main/java/io/rpg/Game.java index 14f5dd67..268cd528 100644 --- a/src/main/java/io/rpg/Game.java +++ b/src/main/java/io/rpg/Game.java @@ -1,28 +1,28 @@ package io.rpg; import io.rpg.controller.Controller; +import io.rpg.model.actions.Action; import javafx.scene.Scene; -import org.jetbrains.annotations.NotNull; +import javafx.stage.Stage; public class Game { private Controller controller; + private Action onStart; private Game() { } - public void setLocationControllerForLocationTag(String tag) { - // tutaj przyda sie hashmapa: nazwa lokacji -> kontroller - } - - public Scene getWorldView() { - return controller.getView(); - } - public void setController(Controller controller) { this.controller = controller; } + public void start(Stage stage) { + stage.show(); + controller.setMainStage(stage); + controller.onAction(onStart); + } + public static class Builder { private final Game game; @@ -40,6 +40,11 @@ public Builder setController(Controller controller) { game.setController(controller); return this; } + + public Builder setOnStartAction(Action onStart) { + game.onStart = onStart; + return this; + } } public Controller getController() { diff --git a/src/main/java/io/rpg/Initializer.java b/src/main/java/io/rpg/Initializer.java index 2886a802..a76d14cb 100644 --- a/src/main/java/io/rpg/Initializer.java +++ b/src/main/java/io/rpg/Initializer.java @@ -5,6 +5,9 @@ import io.rpg.controller.Controller; import io.rpg.config.model.GameWorldConfig; import io.rpg.config.model.LocationConfig; +import io.rpg.controller.PlayerController; +import io.rpg.model.actions.LocationChangeAction; +import io.rpg.model.data.Position; import io.rpg.model.location.LocationModel; import io.rpg.model.object.GameObject; import io.rpg.config.model.GameObjectConfig; @@ -54,6 +57,7 @@ public Result initialize() { GameWorldConfig gameWorldConfig = gameWorldConfigLoadResult.getOkValue(); + Controller.Builder controllerBuilder = new Controller.Builder(); assert gameWorldConfig.getLocationConfigs() != null; @@ -74,15 +78,7 @@ public Result initialize() { assert view != null; - gameObjectViews.forEach(view_ -> { - view.getViewModel().addChild(view_); - }); - - if (locationConfig.getTag().equals(gameWorldConfig.getRootLocation())) { - controllerBuilder - .setModel(model) - .setView(view); - } + gameObjectViews.forEach(view::addChild); model.addOnLocationModelStateChangeObserver(view); @@ -98,18 +94,22 @@ public Result initialize() { // TODO: consider moving it to separate method Player player = (Player) GameObjectFactory.fromConfig(gameWorldConfig.getPlayerConfig()); GameObjectView playerView = GameObjectViewFactory.fromConfig(gameWorldConfig.getPlayerConfig()); - player.addGameObjectStateChangeObserver(playerView); - controllerBuilder.setPlayer(player); - player.setGameObjectView(playerView); + PlayerController playerController = new PlayerController(player, playerView); + + controllerBuilder.setPlayerController(playerController); + Controller controller = controllerBuilder.build(); - // TODO: this is a temporary solution - controller.setPlayerView(playerView); +// // TODO: this is a temporary solution +// controller.setPlayerView(playerView); Game.Builder gameBuilder = new Game.Builder(); - gameBuilder.setController(controller); + Game game = gameBuilder + .setController(controller) + .setOnStartAction(new LocationChangeAction(gameWorldConfig.getRootLocation(), player.getPosition())) + .build(); - return Result.ok(gameBuilder.build()); + return Result.ok(game); } public static List loadGameObjectsForLocation(LocationConfig config) { diff --git a/src/main/java/io/rpg/Main.java b/src/main/java/io/rpg/Main.java index eba5a09e..c497e85c 100644 --- a/src/main/java/io/rpg/Main.java +++ b/src/main/java/io/rpg/Main.java @@ -19,7 +19,7 @@ public void start(Stage stage) throws IOException { Logger logger = LogManager.getLogger(Main.class); Initializer worldInitializer = new Initializer("configurations/demo-config-1", stage); - Result initializationResult = worldInitializer.initialize(); + Result initializationResult = worldInitializer.initialize(); if (initializationResult.isError()) { logger.error("Initialization error"); @@ -37,28 +37,25 @@ public void start(Stage stage) throws IOException { return; } + // TODO: 04.05.2022 Null check for game was already made but IDE still screams Game game = initializationResult.getOkValue(); + game.start(stage); - stage.setScene(game.getWorldView()); + AnimationTimer animationTimer = new AnimationTimer() { + long lastUpdate = -1; - stage.show(); - - AnimationTimer animationTimer=new AnimationTimer() { - long lastUpdate=-1; @Override public void handle(long now) { - if(lastUpdate!=-1){ - float difference=(now-lastUpdate)/1e6f; + if (lastUpdate != -1) { + float difference = (now - lastUpdate) / 1e6f; game.getController().getCurrentModel().update(difference); -// locationModel.update(difference); - Player player=game.getController().getCurrentModel().getPlayer(); - if(player!=null){ -// game.getController().getCurrentModel().getPlayer().render(); + Player player = game.getController().getCurrentModel().getPlayer(); + if (player != null) { player.render(); } } - lastUpdate=now; + lastUpdate = now; } }; diff --git a/src/main/java/io/rpg/controller/Controller.java b/src/main/java/io/rpg/controller/Controller.java index d99f3a6a..13d258b0 100644 --- a/src/main/java/io/rpg/controller/Controller.java +++ b/src/main/java/io/rpg/controller/Controller.java @@ -1,7 +1,10 @@ package io.rpg.controller; +import io.rpg.model.actions.Action; +import io.rpg.model.actions.LocationChangeAction; import io.rpg.model.data.KeyboardEvent; import io.rpg.model.data.MouseClickedEvent; +import io.rpg.model.data.Position; import io.rpg.model.data.Vector; import io.rpg.model.location.LocationModel; import io.rpg.model.object.CollectibleGameObject; @@ -11,8 +14,11 @@ import io.rpg.util.Result; import io.rpg.view.GameObjectView; import io.rpg.view.LocationView; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import javafx.scene.Scene; import javafx.scene.input.KeyEvent; +import javafx.stage.Stage; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; @@ -27,6 +33,8 @@ public class Controller implements KeyboardEvent.Observer, MouseClickedEvent.Obs private LinkedHashMap tagToLocationViewMap; private Logger logger; private final PopupController popupController = new PopupController(); + private PlayerController playerController; + private Stage mainStage; public Controller() { @@ -49,6 +57,10 @@ public Controller(LinkedHashMap tagToLocationModelMap, this.currentView = this.tagToLocationViewMap.get(rootTag); } + public void setMainStage(Stage mainStage) { + this.mainStage = mainStage; + } + public void setModel(@NotNull LocationModel model) { this.tagToLocationModelMap.put(model.getTag(), model); currentModel = model; @@ -58,10 +70,38 @@ public void setView(Scene currentView) { this.currentView = currentView; } - public void registerToViews(List views) { - for (GameObjectView view : views) { - view.addOnClickedObserver(this); + public void onAction(Action action) { + Class[] args = {action.getClass()}; + Method onSpecificAction; + + try { + onSpecificAction = this.getClass().getDeclaredMethod("onAction", args); + } catch (NoSuchMethodException e) { + logger.error("onAction for " + action.getClass().getSimpleName() + "is not implemented"); + return; + } + + try { + onSpecificAction.invoke(this, action); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + private void onAction(LocationChangeAction action) { + if (!this.tagToLocationModelMap.containsKey(action.destinationLocationTag)) { + logger.error("Unknown location tag"); + return; } + + LocationView nextView = this.tagToLocationViewMap.get(action.destinationLocationTag); + LocationModel nextModel = this.tagToLocationModelMap.get(action.destinationLocationTag); + + playerController.teleportTo(nextModel, nextView, action.playerPosition); + + this.currentModel = nextModel; + this.currentView = nextView; + mainStage.setScene(nextView); } public Scene getView() { @@ -91,10 +131,6 @@ else if (tagToLocationViewMap.size() == 0) return Result.error(new Exception("Empty tag to location view map!")); else if (tagToLocationViewMap.size() != tagToLocationModelMap.size()) return Result.error(new Exception("Mismatched sizes of maps!")); - else if (currentView == null) - return Result.error(new Exception("No current view set!")); - else if (currentModel == null) - return Result.error(new Exception("No current model set!")); else return Result.ok(this); } @@ -107,23 +143,16 @@ public void onKeyboardEvent(KeyboardEvent event) { KeyEvent payload = event.payload(); - if (payload.getEventType() == KeyEvent.KEY_PRESSED){ + if (payload.getEventType() == KeyEvent.KEY_PRESSED) { switch (payload.getCode()) { case F -> popupController.openPointsPopup(5, getWindowCenterX(), getWindowCenterY()); case G -> popupController.openTextPopup("Hello!", getWindowCenterX(), getWindowCenterY()); - case A -> currentModel.getPlayer().setLeftPressed(true); - case D -> currentModel.getPlayer().setRightPressed(true); - case S -> currentModel.getPlayer().setDownPressed(true); - case W -> currentModel.getPlayer().setUpPressed(true); - } - } else if (payload.getEventType() == KeyEvent.KEY_RELEASED) { - switch (payload.getCode()) { - case A -> currentModel.getPlayer().setLeftPressed(false); - case D -> currentModel.getPlayer().setRightPressed(false); - case S -> currentModel.getPlayer().setDownPressed(false); - case W -> currentModel.getPlayer().setUpPressed(false); + case L -> onAction((Action) new LocationChangeAction("location-2", new Position(1, 2))); } } + // } else if (payload.getEventType() == KeyEvent.KEY_RELEASED) { + // + // } } private int getWindowCenterX() { @@ -153,58 +182,14 @@ public void onMouseClickedEvent(MouseClickedEvent event) { logger.info("Controller notified on click from " + event.source()); } - private void setPlayer(Player gameObject) { - currentModel.setPlayer(gameObject); - } - - // TODO: temporary solution - public void setPlayerView(GameObjectView playerView) { - ((LocationView) currentView).getViewModel().addChild(playerView); - } - public static class Builder { private final Controller controller; - private boolean isViewSet = false; - private boolean isModelSet = false; - - private Player player; public Builder() { controller = new Controller(); } - public Builder setTagToLocationModelMap(LinkedHashMap tagToLocationModelMap) { - controller.setTagToLocationModelMap(tagToLocationModelMap); - return this; - } - - public Builder setTagToLocationViewMap(LinkedHashMap tagToLocationViewMap) { - controller.setTagToLocationViewMap(tagToLocationViewMap); - return this; - } - - public Builder setModel(@NotNull LocationModel model) { - if (!isModelSet) { - isModelSet = true; - controller.setModel(model); - return this; - } else { - throw new IllegalStateException("Attempt to set model for the second time!"); - } - } - - public Builder setView(Scene currentView) { - if (!isViewSet) { - isViewSet = true; - controller.setView(currentView); - return this; - } else { - throw new IllegalStateException("Attempt to set view for the second time!"); - } - } - public Controller build() { - controller.setPlayer(player); Result validationResult = controller.validate(); if (validationResult.isError()) { throw new IllegalStateException(validationResult.getErrorValue()); @@ -231,9 +216,8 @@ public Builder addModelForTag(String tag, LocationModel model) { return this; } - public Builder setPlayer(Player gameObject) { - player = gameObject; - return this; + public void setPlayerController(PlayerController playerController) { + controller.playerController = playerController; } } public LocationModel getCurrentModel() { diff --git a/src/main/java/io/rpg/controller/PlayerController.java b/src/main/java/io/rpg/controller/PlayerController.java new file mode 100644 index 00000000..cab82897 --- /dev/null +++ b/src/main/java/io/rpg/controller/PlayerController.java @@ -0,0 +1,68 @@ +package io.rpg.controller; + +import io.rpg.model.data.KeyboardEvent; +import io.rpg.model.data.Position; +import io.rpg.model.location.LocationModel; +import io.rpg.model.object.Player; +import io.rpg.view.GameObjectView; +import io.rpg.view.LocationView; +import javafx.scene.input.KeyEvent; + +public class PlayerController implements KeyboardEvent.Observer { + private final Player player; + private final GameObjectView playerView; + private Runnable onChangeLocation; + + public PlayerController(Player player, GameObjectView playerView) { + this.player = player; + this.playerView = playerView; + this.onChangeLocation = () -> {}; + + player.addGameObjectStateChangeObserver(playerView); + player.setGameObjectView(playerView); + } + + @Override + public void onKeyboardEvent(KeyboardEvent event) { + KeyEvent payload = event.payload(); + if (payload.getEventType() == KeyEvent.KEY_PRESSED) { + switch (payload.getCode()) { + case A -> player.setLeftPressed(true); + case D -> player.setRightPressed(true); + case S -> player.setDownPressed(true); + case W -> player.setUpPressed(true); + } + } else if (payload.getEventType() == KeyEvent.KEY_RELEASED) { + switch (payload.getCode()) { + case A -> player.setLeftPressed(false); + case D -> player.setRightPressed(false); + case S -> player.setDownPressed(false); + case W -> player.setUpPressed(false); + } + } + } + + /** + * Changes player location. Removes listener from old location. + * + * @param model new location model. + * @param view new location view. + * @param playerPosition new player position. + */ + public void teleportTo(LocationModel model, LocationView view, Position playerPosition) { + onChangeLocation.run(); + updateOnChangeLocation(model, view); + + player.setPosition(playerPosition); + model.setPlayer(player); + view.addChild(playerView); + view.addKeyboardEventObserver(this); + } + + private void updateOnChangeLocation(LocationModel model, LocationView view) { + this.onChangeLocation = () -> { + view.removeKeyboardEventObserver(this); + view.removeChild(this.playerView); + }; + } +} diff --git a/src/main/java/io/rpg/model/actions/Action.java b/src/main/java/io/rpg/model/actions/Action.java new file mode 100644 index 00000000..3b13b7d0 --- /dev/null +++ b/src/main/java/io/rpg/model/actions/Action.java @@ -0,0 +1,7 @@ +package io.rpg.model.actions; + +/** + * A marker interface for action classes. + */ +public interface Action { +} diff --git a/src/main/java/io/rpg/model/actions/LocationChangeAction.java b/src/main/java/io/rpg/model/actions/LocationChangeAction.java new file mode 100644 index 00000000..08607743 --- /dev/null +++ b/src/main/java/io/rpg/model/actions/LocationChangeAction.java @@ -0,0 +1,17 @@ +package io.rpg.model.actions; + +import io.rpg.model.data.Position; + +/** + * Class for storing local data needed to preform a location change action. + */ + +public class LocationChangeAction implements Action { + public final String destinationLocationTag; + public final Position playerPosition; + + public LocationChangeAction(String destinationLocationTag, Position playerPosition) { + this.destinationLocationTag = destinationLocationTag; + this.playerPosition = playerPosition; + } +} diff --git a/src/main/java/io/rpg/model/location/LocationModel.java b/src/main/java/io/rpg/model/location/LocationModel.java index 97b227eb..07da8020 100644 --- a/src/main/java/io/rpg/model/location/LocationModel.java +++ b/src/main/java/io/rpg/model/location/LocationModel.java @@ -44,6 +44,7 @@ public Player getPlayer() { return player; } + public GameObject getObject(int row, int column) { GameObject object = gameObjects.stream().filter(gameObject -> gameObject.getPosition() .equals(new Position(row, column))) diff --git a/src/main/java/io/rpg/model/object/Player.java b/src/main/java/io/rpg/model/object/Player.java index 4ef5acc7..4f5b8e6e 100644 --- a/src/main/java/io/rpg/model/object/Player.java +++ b/src/main/java/io/rpg/model/object/Player.java @@ -9,25 +9,21 @@ public class Player extends GameObject { - Vector currentPosition; - int strength; - float speed; - Vector direction; - boolean rightPressed; - boolean leftPressed; - boolean upPressed; - boolean downPressed; - GameObjectView gameObjectView; + private Vector currentPosition; + private int strength; + private float speed; + private Vector direction; + private boolean rightPressed; + private boolean leftPressed; + private boolean upPressed; + private boolean downPressed; + private GameObjectView gameObjectView; private Vector pixelPosition; - // public GameObject(@NotNull String tag, @NotNull Position position, @NotNull String assetPath) { -// this.tag = tag; -// this.position = position; -// this.assetPath = assetPath; -// } + public Player(@NotNull String tag, @NotNull Position position, @NotNull String assetPath) { super(tag, position, assetPath); - this.currentPosition=new Vector(position.col, position.row); + this.currentPosition = new Vector(position.col, position.row); this.speed = 100f; this.direction = new Vector(0, 0); this.rightPressed = false; @@ -54,17 +50,21 @@ public void update(float elapsed) { float y = 0; float x = 0; // the sum tells us the direction - if (upPressed) + if (upPressed) { y += -1; + } - if (downPressed) + if (downPressed) { y += 1; + } - if (leftPressed) + if (leftPressed) { x += -1; + } - if (rightPressed) + if (rightPressed) { x += 1; + } this.pixelPosition = new Vector(this.pixelPosition.x + speed * x * elapsed / 1000, this.pixelPosition.y + speed * y * elapsed / 1000); } @@ -93,10 +93,14 @@ public void setGameObjectView(GameObjectView gameObjectView) { this.gameObjectView = gameObjectView; } - public void render(){ - if(gameObjectView!=null){ + public void render() { + if (gameObjectView != null) { gameObjectView.setX(this.pixelPosition.x); gameObjectView.setY(this.pixelPosition.y); } } + + public void setPosition(Position position) { + pixelPosition = new Vector(position); + } } diff --git a/src/main/java/io/rpg/view/LocationView.java b/src/main/java/io/rpg/view/LocationView.java index e9c4fbcc..11889df2 100644 --- a/src/main/java/io/rpg/view/LocationView.java +++ b/src/main/java/io/rpg/view/LocationView.java @@ -9,6 +9,8 @@ import io.rpg.model.object.GameObject; import io.rpg.viewmodel.LocationViewModel; import io.rpg.config.model.LocationConfig; +import java.util.Collection; +import java.util.Collections; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; @@ -89,7 +91,8 @@ public void removeKeyboardEventObserver(KeyboardEvent.Observer observer) { @Override public void emitKeyboardEvent(KeyboardEvent event) { - onKeyPressedObservers.forEach(observer -> { + List observers = new ArrayList<>(onKeyPressedObservers); + observers.forEach(observer -> { observer.onKeyboardEvent(event); }); } @@ -102,15 +105,21 @@ public void onLocationModelStateChange(LocationModelStateChange event) { } - List gameObjectViews=new ArrayList<>(); + List gameObjectViews = new ArrayList<>(); - public void createViewsForObjects(LocationModel locationModel){ - for(GameObject g: locationModel.getGameObjects()){ - GameObjectView gameObjectView=new GameObjectView(Path.of(g.getAssetPath()),g.getPosition()); + public void createViewsForObjects(LocationModel locationModel) { + for (GameObject g : locationModel.getGameObjects()) { + GameObjectView gameObjectView = new GameObjectView(Path.of(g.getAssetPath()), g.getPosition()); gameObjectViews.add(gameObjectView); -// g.view=gameObjectView; viewModel.getForegroundPane().getChildren().add(gameObjectView); } } + public void removeChild(GameObjectView view) { + viewModel.getForegroundPane().getChildren().remove(view); + } + + public void addChild(GameObjectView view) { + viewModel.getForegroundPane().getChildren().add(view); + } } diff --git a/src/main/java/io/rpg/viewmodel/LocationViewModel.java b/src/main/java/io/rpg/viewmodel/LocationViewModel.java index 451c5753..b3904feb 100644 --- a/src/main/java/io/rpg/viewmodel/LocationViewModel.java +++ b/src/main/java/io/rpg/viewmodel/LocationViewModel.java @@ -52,10 +52,6 @@ public void initialize(URL location, ResourceBundle resources) { }); } - public void addChild(ImageView child) { - contentPane.getChildren().add(child); - } - public Pane getForegroundPane() { return foregroundPane; }