From b182d5231597395dca67f5b470929af403d3e19f Mon Sep 17 00:00:00 2001 From: hudescottes Date: Wed, 5 Jun 2024 00:44:35 +0200 Subject: [PATCH] Add cutscene feature --- README.md | 2 +- .../java/com/gdx/game/map/MapManager.java | 4 + .../game/screen/CharacterSelectionScreen.java | 3 +- .../screen/cutscene/CreatorIntroScreen.java | 62 +++++++ .../screen/cutscene/CutSceneBaseScreen.java | 157 ++++++++++++++++++ .../com/gdx/game/battle/BattleHUDTest.java | 2 +- .../gdx/game/dialog/ConversationUITest.java | 25 ++- .../cutscene/CreatorIntroScreenTest.java | 63 +++++++ 8 files changed, 313 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/com/gdx/game/screen/cutscene/CreatorIntroScreen.java create mode 100644 core/src/main/java/com/gdx/game/screen/cutscene/CutSceneBaseScreen.java create mode 100644 core/src/test/java/com/gdx/game/screen/cutscene/CreatorIntroScreenTest.java diff --git a/README.md b/README.md index d9f0bef8..7a1a2bd4 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ graph TD; - [ ] crafting feature -- [ ] cutscenes feature +- [ ] credits - [ ] improve AI diff --git a/core/src/main/java/com/gdx/game/map/MapManager.java b/core/src/main/java/com/gdx/game/map/MapManager.java index 9310e125..691a70ff 100644 --- a/core/src/main/java/com/gdx/game/map/MapManager.java +++ b/core/src/main/java/com/gdx/game/map/MapManager.java @@ -236,6 +236,10 @@ public void clearCurrentSelectedMapEntity() { currentSelectedEntity = null; } + public void disableCurrentMapMusic(){ + currentMap.unloadMusic(); + } + public void setPlayer(Entity entity) { this.player = entity; } diff --git a/core/src/main/java/com/gdx/game/screen/CharacterSelectionScreen.java b/core/src/main/java/com/gdx/game/screen/CharacterSelectionScreen.java index 6e0b21da..9edb7a15 100644 --- a/core/src/main/java/com/gdx/game/screen/CharacterSelectionScreen.java +++ b/core/src/main/java/com/gdx/game/screen/CharacterSelectionScreen.java @@ -20,6 +20,7 @@ import com.gdx.game.entities.player.CharacterRecord; import com.gdx.game.manager.ResourceManager; import com.gdx.game.profile.ProfileManager; +import com.gdx.game.screen.cutscene.CreatorIntroScreen; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -99,7 +100,7 @@ public void clicked(InputEvent event, float x, float y) { gdxGame.setGameScreen(new GameScreen(gdxGame, resourceManager)); LOGGER.info("Character {} selected", playerImage.getEntity().getEntityConfig().getEntityID()); - setScreenWithTransition((BaseScreen) gdxGame.getScreen(), gdxGame.getGameScreen(), new ArrayList<>()); + setScreenWithTransition((BaseScreen) gdxGame.getScreen(), new CreatorIntroScreen(gdxGame, resourceManager), new ArrayList<>()); } }); diff --git a/core/src/main/java/com/gdx/game/screen/cutscene/CreatorIntroScreen.java b/core/src/main/java/com/gdx/game/screen/cutscene/CreatorIntroScreen.java new file mode 100644 index 00000000..cea82a4a --- /dev/null +++ b/core/src/main/java/com/gdx/game/screen/cutscene/CreatorIntroScreen.java @@ -0,0 +1,62 @@ +package com.gdx.game.screen.cutscene; + +import com.badlogic.gdx.math.Interpolation; +import com.badlogic.gdx.scenes.scene2d.Action; +import com.badlogic.gdx.scenes.scene2d.actions.Actions; +import com.badlogic.gdx.scenes.scene2d.actions.RunnableAction; +import com.gdx.game.GdxGame; +import com.gdx.game.animation.AnimatedImage; +import com.gdx.game.entities.Entity; +import com.gdx.game.entities.EntityFactory; +import com.gdx.game.manager.ResourceManager; +import com.gdx.game.map.MapFactory; + +public class CreatorIntroScreen extends CutSceneBaseScreen { + private Action setupScene01; + private AnimatedImage creator; + + public CreatorIntroScreen(GdxGame game, ResourceManager resourceManager) { + super(game, resourceManager); + + creator = getAnimatedImage(EntityFactory.EntityType.THIEF); + creator.setName("Creator"); + + setupScene01 = new RunnableAction() { + @Override + public void run() { + hideMessage(); + mapManager.loadMap(MapFactory.MapType.TOPPLE); + mapManager.disableCurrentMapMusic(); + setCameraPosition(17, 10); + + creator.setCurrentAnimation(Entity.AnimationType.WALK_UP); + creator.setVisible(true); + creator.setPosition(17, 0); + } + }; + + getStage().addActor(creator); + } + + Action getCutsceneAction() { + setupScene01.reset(); + getSwitchScreenAction().reset(); + + return Actions.sequence( + Actions.addAction(setupScene01), + Actions.delay(1), + Actions.addAction(Actions.moveTo(17, 10, 5, Interpolation.linear), creator), + Actions.delay(Float.parseFloat("2.5")), + Actions.addAction(Actions.run(() -> creator.setCurrentAnimation(Entity.AnimationType.IMMOBILE))), + Actions.run(() -> showMessage(creator, "Hello adventurer! Welcome to my game, or at least my prototype game!")), + Actions.delay(5), + Actions.run(() -> showMessage(creator, "Many thanks for your interest in my project, i hope you will like it.")), + Actions.delay(5), + Actions.run(() -> showMessage(creator, "Do not hesitate to contribute or suggest any idea that you might have. Help is always welcome!")), + Actions.delay(5), + Actions.run(() -> showMessage(creator, "Have fun :)")), + Actions.delay(3), + Actions.after(getSwitchScreenAction()) + ); + } +} diff --git a/core/src/main/java/com/gdx/game/screen/cutscene/CutSceneBaseScreen.java b/core/src/main/java/com/gdx/game/screen/cutscene/CutSceneBaseScreen.java new file mode 100644 index 00000000..4e7daf9a --- /dev/null +++ b/core/src/main/java/com/gdx/game/screen/cutscene/CutSceneBaseScreen.java @@ -0,0 +1,157 @@ +package com.gdx.game.screen.cutscene; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer; +import com.badlogic.gdx.scenes.scene2d.Action; +import com.badlogic.gdx.scenes.scene2d.Actor; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.badlogic.gdx.scenes.scene2d.actions.RunnableAction; +import com.badlogic.gdx.scenes.scene2d.ui.Dialog; +import com.badlogic.gdx.scenes.scene2d.ui.Label; +import com.badlogic.gdx.utils.viewport.ScreenViewport; +import com.badlogic.gdx.utils.viewport.Viewport; +import com.gdx.game.GdxGame; +import com.gdx.game.animation.AnimatedImage; +import com.gdx.game.entities.Entity; +import com.gdx.game.entities.EntityFactory; +import com.gdx.game.manager.ResourceManager; +import com.gdx.game.map.Map; +import com.gdx.game.profile.ProfileManager; +import com.gdx.game.screen.BaseScreen; +import com.gdx.game.screen.GameScreen; +import com.gdx.game.screen.transition.effects.FadeOutTransitionEffect; +import com.gdx.game.screen.transition.effects.TransitionEffect; + +import java.util.ArrayList; + +public abstract class CutSceneBaseScreen extends GameScreen { + private Stage stage; + private Viewport viewport; + private Stage UIStage; + private Viewport UIViewport; + private Actor followingActor; + private Dialog messageBoxUI; + private Label label; + private boolean isCameraFixed = true; + private Action switchScreenAction; + + public CutSceneBaseScreen(GdxGame game, ResourceManager resourceManager) { + super(game, resourceManager); + + viewport = new ScreenViewport(camera); + stage = new Stage(viewport); + + UIViewport = new ScreenViewport(hudCamera); + UIStage = new Stage(UIViewport); + + label = new Label("", ResourceManager.skin); + label.setWrap(true); + + messageBoxUI = new Dialog("", ResourceManager.skin); + messageBoxUI.setVisible(false); + messageBoxUI.getContentTable().add(label).width(stage.getWidth()/2).pad(10, 10, 10, 0); + messageBoxUI.pack(); + messageBoxUI.setPosition(stage.getWidth() / 2 - messageBoxUI.getWidth() / 2, stage.getHeight() - messageBoxUI.getHeight()); + + followingActor = new Actor(); + followingActor.setPosition(0, 0); + + //Actions + switchScreenAction = new RunnableAction(){ + @Override + public void run() { + ArrayList effects = new ArrayList<>(); + effects.add(new FadeOutTransitionEffect(1f)); + setScreenWithTransition((BaseScreen) gdxGame.getScreen(), gdxGame.getGameScreen(), effects); + } + }; + + UIStage.addActor(messageBoxUI); + } + + abstract Action getCutsceneAction(); + + AnimatedImage getAnimatedImage(EntityFactory.EntityType entityType){ + Entity entity = EntityFactory.getInstance().getEntity(entityType); + return setEntityAnimation(entity); + } + + private AnimatedImage setEntityAnimation(Entity entity){ + final AnimatedImage animEntity = new AnimatedImage(); + animEntity.setEntity(entity); + animEntity.setWidth(17); + animEntity.setHeight(17); + animEntity.setSize(animEntity.getWidth() * Map.UNIT_SCALE, animEntity.getHeight() * Map.UNIT_SCALE); + return animEntity; + } + + void setCameraPosition(float x, float y){ + camera.position.set(x, y, 0f); + isCameraFixed = true; + } + + void showMessage(AnimatedImage animatedImage, String message){ + label.setText(message); + messageBoxUI.getTitleLabel().setText(animatedImage.getName()); + messageBoxUI.pack(); + messageBoxUI.setVisible(true); + } + + void hideMessage(){ + messageBoxUI.setVisible(false); + } + + @Override + public void render(float delta) { + Gdx.gl.glClearColor(0, 0, 0, 1); + Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); + + mapRenderer.setView(camera); + + mapRenderer.getBatch().enableBlending(); + mapRenderer.getBatch().setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA); + + if (mapManager.hasMapChanged()) { + mapRenderer.setMap(mapManager.getCurrentTiledMap()); + mapManager.setMapChanged(false); + } + + mapRenderer.render(); + + if (!isCameraFixed) { + camera.position.set(followingActor.getX(), followingActor.getY(), 0f); + } + camera.update(); + + UIStage.act(delta); + UIStage.draw(); + + stage.act(delta); + stage.draw(); + } + + @Override + public void show() { + stage.addAction(getCutsceneAction()); + ProfileManager.getInstance().removeAllObservers(); + if (mapRenderer == null) { + mapRenderer = new OrthogonalTiledMapRenderer(mapManager.getCurrentTiledMap(), Map.UNIT_SCALE); + } + } + + @Override + public void hide() { + ProfileManager.getInstance().removeAllObservers(); + Gdx.input.setInputProcessor(null); + } + + @Override + public Stage getStage() { + return stage; + } + + public Action getSwitchScreenAction() { + return switchScreenAction; + } +} diff --git a/core/src/test/java/com/gdx/game/battle/BattleHUDTest.java b/core/src/test/java/com/gdx/game/battle/BattleHUDTest.java index 60e29cc5..ef8ea36c 100644 --- a/core/src/test/java/com/gdx/game/battle/BattleHUDTest.java +++ b/core/src/test/java/com/gdx/game/battle/BattleHUDTest.java @@ -128,7 +128,7 @@ void add_entity_battle_step(BattleObserver.BattleEvent event, int actorNumber, E private static Stream addEntitySteps() { return Stream.of( Arguments.of(BattleObserver.BattleEvent.PLAYER_ADDED, 0, Entity.AnimationType.WALK_RIGHT), - Arguments.of(BattleObserver.BattleEvent.OPPONENT_ADDED, 1, Entity.AnimationType.IDLE) + Arguments.of(BattleObserver.BattleEvent.OPPONENT_ADDED, 1, Entity.AnimationType.IMMOBILE) ); } diff --git a/core/src/test/java/com/gdx/game/dialog/ConversationUITest.java b/core/src/test/java/com/gdx/game/dialog/ConversationUITest.java index 4c2705e4..7aa318e4 100644 --- a/core/src/test/java/com/gdx/game/dialog/ConversationUITest.java +++ b/core/src/test/java/com/gdx/game/dialog/ConversationUITest.java @@ -2,7 +2,9 @@ import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.Json; +import com.badlogic.gdx.utils.JsonValue; import com.gdx.game.GdxRunner; import com.gdx.game.entities.EntityConfig; import com.gdx.game.manager.ResourceManager; @@ -10,6 +12,9 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import java.util.ArrayList; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -40,7 +45,23 @@ public void testLoadConversation_ShouldSucceed() { conversationUI.loadConversation(config); - assertThat(conversationUI.getCurrentEntityID()).isEqualTo("TOWN_FOLK1"); - assertThat(conversationUI.getCurrentConversationGraph().getCurrentConversationID()).isEqualTo("1"); + assertThat("TOWN_FOLK1").isEqualTo(conversationUI.getCurrentEntityID()); + assertThat("1").isEqualTo(conversationUI.getCurrentConversationGraph().getCurrentConversationID()); + } + + @Test + public void testLoadCourtesyConversation_ShouldSucceed() { + Json json = new Json(); + String value = "{animationConfig:[{texturePaths:[sprites/characters/Player0.png,sprites/characters/Player1.png],gridPoints:[{x:3},{x:3}]},{animationType:IMMOBILE,texturePaths:[sprites/characters/Player0.png,sprites/characters/Player1.png],gridPoints:[{x:3},{x:3}]}],state:IMMOBILE,entityID:TOWN_INNKEEPER,conversationConfigPath:\"\",questConfigPath:\"\",currentQuestID:\"\",itemTypeID:NONE}"; + EntityConfig config = json.fromJson(EntityConfig.class, value); + ConversationUI conversationUI = new ConversationUI(); + ConversationGraph graph = json.fromJson(ConversationGraph.class, Gdx.files.internal("conversations/conversation_courtesy.json")); + + conversationUI.loadConversation(config); + String conversationID = conversationUI.getCurrentConversationGraph().getCurrentConversationID(); + String dialog = graph.getConversations().get(conversationID).getDialog(); + + assertThat("TOWN_INNKEEPER").isEqualTo(conversationUI.getCurrentEntityID()); + assertThat(dialog).isEqualTo(conversationUI.getCurrentConversationGraph().displayCurrentConversation()); } } diff --git a/core/src/test/java/com/gdx/game/screen/cutscene/CreatorIntroScreenTest.java b/core/src/test/java/com/gdx/game/screen/cutscene/CreatorIntroScreenTest.java new file mode 100644 index 00000000..7911a247 --- /dev/null +++ b/core/src/test/java/com/gdx/game/screen/cutscene/CreatorIntroScreenTest.java @@ -0,0 +1,63 @@ +package com.gdx.game.screen.cutscene; + +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.graphics.GL20; +import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.scenes.scene2d.Stage; +import com.gdx.game.GdxGame; +import com.gdx.game.GdxRunner; +import com.gdx.game.entities.EntityFactory; +import com.gdx.game.entities.player.PlayerGraphicsComponent; +import com.gdx.game.manager.ResourceManager; +import com.gdx.game.profile.ProfileManager; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedConstruction; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockConstruction; + +@ExtendWith(GdxRunner.class) +public class CreatorIntroScreenTest { + + private MockedConstruction mockPlayerGraphics; + private MockedConstruction mockShapeRenderer; + private MockedConstruction mockSpriteBatch; + private MockedConstruction mockStage; + + @BeforeEach + void init() { + Gdx.gl = mock(GL20.class); + Gdx.gl20 = mock(GL20.class); + mockPlayerGraphics = mockConstruction(PlayerGraphicsComponent.class); + mockShapeRenderer = mockConstruction(ShapeRenderer.class); + mockSpriteBatch = mockConstruction(SpriteBatch.class); + mockStage = mockConstruction(Stage.class); + ProfileManager profileManager = ProfileManager.getInstance(); + profileManager.setProperty("playerCharacter", EntityFactory.EntityType.WARRIOR); + profileManager.setProperty("currentPlayerCharacterAP", 15); + profileManager.setProperty("currentPlayerCharacterDP", 15); + profileManager.setProperty("currentPlayerCharacterSPDP", 10); + } + + @AfterEach + void end() { + mockPlayerGraphics.close(); + mockShapeRenderer.close(); + mockSpriteBatch.close(); + mockStage.close(); + } + + @Test + void screen_instance() { + GdxGame gdxGame = mock(GdxGame.class); + ResourceManager resourceManager = new ResourceManager(); + CreatorIntroScreen screen = new CreatorIntroScreen(gdxGame, resourceManager); + + assertNotNull(screen); + } +}