Merge pull request #7 from teppyboy/Iuno_OP_scene
feat(scene): dialogue scene implementation
This commit is contained in:
@ -29,6 +29,7 @@ public class Game extends ApplicationAdapter {
|
|||||||
public Scene mainMenuScene;
|
public Scene mainMenuScene;
|
||||||
public Scene settingsScene;
|
public Scene settingsScene;
|
||||||
public ArrayList<Scene> gameScenes;
|
public ArrayList<Scene> gameScenes;
|
||||||
|
private boolean usingCustomScene = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void create() {
|
public void create() {
|
||||||
@ -60,34 +61,42 @@ public class Game extends ApplicationAdapter {
|
|||||||
stage.draw();
|
stage.draw();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (this.state) {
|
|
||||||
case INTRO:
|
// Only use state-based scene switching if not using custom scene
|
||||||
currentScene = introScene;
|
if (!usingCustomScene) {
|
||||||
break;
|
switch (this.state) {
|
||||||
case MAIN_MENU:
|
case INTRO:
|
||||||
currentScene = mainMenuScene;
|
currentScene = introScene;
|
||||||
break;
|
break;
|
||||||
case SETTINGS:
|
case MAIN_MENU:
|
||||||
currentScene = settingsScene;
|
currentScene = mainMenuScene;
|
||||||
break;
|
break;
|
||||||
case IN_GAME:
|
case SETTINGS:
|
||||||
// Render in-game scene
|
currentScene = settingsScene;
|
||||||
break;
|
break;
|
||||||
default:
|
case IN_GAME:
|
||||||
log.warn("Unknown state: {}", state);
|
// Render in-game scene
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log.warn("Unknown state: {}", state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var scene : gameScenes) {
|
for (var scene : gameScenes) {
|
||||||
// log.trace("Checking scene visibility: {}", scene.getClass().getSimpleName());
|
// log.trace("Checking scene visibility: {}", scene.getClass().getSimpleName());
|
||||||
if (scene != currentScene && scene.root.isVisible()) {
|
if (scene != currentScene && scene.root != null && scene.root.isVisible()) {
|
||||||
log.trace("Hiding scene: {}", scene.getClass().getSimpleName());
|
log.trace("Hiding scene: {}", scene.getClass().getSimpleName());
|
||||||
scene.root.setVisible(false);
|
scene.root.setVisible(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
batch.begin();
|
// Only render if currentScene is not null
|
||||||
currentScene.render(batch);
|
if (currentScene != null) {
|
||||||
batch.end();
|
batch.begin();
|
||||||
|
currentScene.render(batch);
|
||||||
|
batch.end();
|
||||||
|
}
|
||||||
|
|
||||||
// Handle stage drawing for UI elements
|
// Handle stage drawing for UI elements
|
||||||
stage.act(Gdx.graphics.getDeltaTime());
|
stage.act(Gdx.graphics.getDeltaTime());
|
||||||
stage.draw();
|
stage.draw();
|
||||||
@ -108,4 +117,86 @@ public class Game extends ApplicationAdapter {
|
|||||||
stage.dispose();
|
stage.dispose();
|
||||||
log.debug("Resources disposed");
|
log.debug("Resources disposed");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Sets the current scene directly (for VN and other non-state-based scenes).
|
||||||
|
*
|
||||||
|
* @param scene the scene to switch to
|
||||||
|
*/
|
||||||
|
public void setScene(Scene scene) {
|
||||||
|
if (currentScene != null && currentScene.root != null) {
|
||||||
|
currentScene.root.setVisible(false);
|
||||||
|
}
|
||||||
|
currentScene = scene;
|
||||||
|
usingCustomScene = true;
|
||||||
|
if (scene.root != null) {
|
||||||
|
scene.root.setVisible(true);
|
||||||
|
if (!gameScenes.contains(scene)) {
|
||||||
|
gameScenes.add(scene);
|
||||||
|
if (scene.root.getStage() == null) {
|
||||||
|
stage.addActor(scene.root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.debug("Scene switched to: {}", scene.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the game state and returns to state-based scene switching.
|
||||||
|
*
|
||||||
|
* @param newState the new game state
|
||||||
|
*/
|
||||||
|
public void setState(State newState) {
|
||||||
|
this.state = newState;
|
||||||
|
this.usingCustomScene = false;
|
||||||
|
// Reset input processor to main stage
|
||||||
|
Gdx.input.setInputProcessor(stage);
|
||||||
|
log.debug("State changed to: {}", newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current scene and state (used by transitions).
|
||||||
|
*
|
||||||
|
* @param scene the scene to set as current
|
||||||
|
* @param newState the new state
|
||||||
|
*/
|
||||||
|
public void setCurrentSceneAndState(Scene scene, State newState) {
|
||||||
|
this.currentScene = scene;
|
||||||
|
this.state = newState;
|
||||||
|
this.usingCustomScene = false;
|
||||||
|
if (scene.root != null) {
|
||||||
|
scene.root.setVisible(true);
|
||||||
|
}
|
||||||
|
Gdx.input.setInputProcessor(stage);
|
||||||
|
log.debug("Current scene set to: {}, state: {}", scene.getClass().getSimpleName(), newState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns to main menu from a custom scene.
|
||||||
|
*/
|
||||||
|
public void returnToMainMenu() {
|
||||||
|
// Create transition from DialogueScene to MainMenu instead of direct switch
|
||||||
|
if (currentScene != null && usingCustomScene && currentScene instanceof DialogueScene) {
|
||||||
|
log.debug("Creating transition from DialogueScene to MainMenu");
|
||||||
|
DialogueScene dialogueScene = (DialogueScene) currentScene;
|
||||||
|
dialogueScene.enterTransition(); // Mark as entering transition to stop rendering
|
||||||
|
transition = new DialogueToMenuTransition(this, dialogueScene, mainMenuScene, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback for other custom scenes
|
||||||
|
if (currentScene != null && usingCustomScene) {
|
||||||
|
log.debug("Disposing custom scene: {}", currentScene.getClass().getSimpleName());
|
||||||
|
currentScene.dispose();
|
||||||
|
currentScene = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(State.MAIN_MENU);
|
||||||
|
currentScene = mainMenuScene;
|
||||||
|
if (mainMenuScene.root != null) {
|
||||||
|
mainMenuScene.root.setVisible(true);
|
||||||
|
}
|
||||||
|
Gdx.input.setInputProcessor(stage);
|
||||||
|
log.debug("Returned to main menu, state={}, usingCustomScene={}", state, usingCustomScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -126,6 +126,9 @@ public class Assets {
|
|||||||
assetManager.load("textures/ui/UI_SliderKnob.png", Texture.class);
|
assetManager.load("textures/ui/UI_SliderKnob.png", Texture.class);
|
||||||
assetManager.load("textures/ui/UI_SliderBg.png", Texture.class);
|
assetManager.load("textures/ui/UI_SliderBg.png", Texture.class);
|
||||||
assetManager.load("textures/ui/UI_SliderBg2.png", Texture.class);
|
assetManager.load("textures/ui/UI_SliderBg2.png", Texture.class);
|
||||||
|
// VN scene textures
|
||||||
|
assetManager.load("textures/vn_scene/char_base.png", Texture.class);
|
||||||
|
assetManager.load("textures/vn_scene/separator.png", Texture.class);
|
||||||
// "Load" unsupported file types as FileHandle
|
// "Load" unsupported file types as FileHandle
|
||||||
loadingThread = new Thread(() -> {
|
loadingThread = new Thread(() -> {
|
||||||
loadAny("videos/main_menu_background.webm");
|
loadAny("videos/main_menu_background.webm");
|
||||||
|
|||||||
@ -0,0 +1,183 @@
|
|||||||
|
package org.vibecoders.moongazer.scenes;
|
||||||
|
|
||||||
|
import static org.vibecoders.moongazer.Constants.*;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import com.badlogic.gdx.graphics.Pixmap;
|
||||||
|
import com.badlogic.gdx.graphics.Texture;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.InputListener;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Stage;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Image;
|
||||||
|
import com.badlogic.gdx.utils.viewport.ScreenViewport;
|
||||||
|
|
||||||
|
import org.vibecoders.moongazer.Game;
|
||||||
|
import org.vibecoders.moongazer.managers.Assets;
|
||||||
|
import org.vibecoders.moongazer.vn.CharacterActor;
|
||||||
|
import org.vibecoders.moongazer.vn.ChoiceBox;
|
||||||
|
import org.vibecoders.moongazer.vn.DialogueBoxTransparent;
|
||||||
|
|
||||||
|
public class DialogueScene extends Scene {
|
||||||
|
private Stage stage;
|
||||||
|
private final CharacterActor character;
|
||||||
|
private final DialogueBoxTransparent dialogue;
|
||||||
|
private ChoiceBox choice;
|
||||||
|
private int step = 0;
|
||||||
|
private float alpha = 1f;
|
||||||
|
private boolean isActive = true;
|
||||||
|
private boolean inTransition = false;
|
||||||
|
|
||||||
|
public DialogueScene(Game game) {
|
||||||
|
super(game);
|
||||||
|
stage = new Stage(new ScreenViewport());
|
||||||
|
Gdx.input.setInputProcessor(stage);
|
||||||
|
|
||||||
|
Texture bgTexture = Assets.getAsset("textures/main_menu/background.png", Texture.class);
|
||||||
|
Image background = new Image(bgTexture);
|
||||||
|
background.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||||
|
background.setColor(0.5f, 0.5f, 0.5f, 1f);
|
||||||
|
stage.addActor(background);
|
||||||
|
|
||||||
|
Pixmap overlayPixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
|
||||||
|
overlayPixmap.setColor(0, 0, 0, 0.4f);
|
||||||
|
overlayPixmap.fill();
|
||||||
|
Texture overlayTexture = new Texture(overlayPixmap);
|
||||||
|
overlayPixmap.dispose();
|
||||||
|
Image overlay = new Image(overlayTexture);
|
||||||
|
overlay.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||||
|
stage.addActor(overlay);
|
||||||
|
|
||||||
|
TextureRegion charBase = loadTexture("textures/vn_scene/char_base.png");
|
||||||
|
character = new CharacterActor(charBase);
|
||||||
|
float charX = (WINDOW_WIDTH - character.getWidth()) / 2f;
|
||||||
|
float charY = (WINDOW_HEIGHT - character.getHeight()) / 2f + 100;
|
||||||
|
character.setPosition(charX, charY);
|
||||||
|
stage.addActor(character);
|
||||||
|
|
||||||
|
TextureRegion dialogBg = createDialogBackground();
|
||||||
|
TextureRegion separator = loadTexture("textures/vn_scene/separator.png");
|
||||||
|
|
||||||
|
var font = Assets.getFont("ui", 20);
|
||||||
|
dialogue = new DialogueBoxTransparent(font, dialogBg, separator, WINDOW_WIDTH - 100);
|
||||||
|
dialogue.setPosition(50, 20);
|
||||||
|
stage.addActor(dialogue);
|
||||||
|
|
||||||
|
showStep(0);
|
||||||
|
|
||||||
|
stage.addListener(new InputListener() {
|
||||||
|
@Override
|
||||||
|
public boolean keyDown(InputEvent e, int keycode) {
|
||||||
|
if (choice == null) {
|
||||||
|
nextOrSkip();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean touchDown(InputEvent e, float x, float y, int pointer, int button) {
|
||||||
|
if (choice == null) {
|
||||||
|
nextOrSkip();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextureRegion loadTexture(String path) {
|
||||||
|
try {
|
||||||
|
Texture tex = Assets.getAsset(path, Texture.class);
|
||||||
|
return new TextureRegion(tex);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to load texture: {}, creating placeholder", path);
|
||||||
|
Pixmap pixmap = new Pixmap(100, 100, Pixmap.Format.RGBA8888);
|
||||||
|
pixmap.setColor(Color.GRAY);
|
||||||
|
pixmap.fill();
|
||||||
|
Texture tex = new Texture(pixmap);
|
||||||
|
pixmap.dispose();
|
||||||
|
return new TextureRegion(tex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextureRegion createDialogBackground() {
|
||||||
|
Pixmap pixmap = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
|
||||||
|
pixmap.setColor(0, 0, 0, 0.7f);
|
||||||
|
pixmap.fill();
|
||||||
|
Texture tex = new Texture(pixmap);
|
||||||
|
pixmap.dispose();
|
||||||
|
return new TextureRegion(tex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void nextOrSkip() {
|
||||||
|
if (!dialogue.isDone()) {
|
||||||
|
dialogue.skip();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showStep(++step);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showStep(int s) {
|
||||||
|
if (choice != null) {
|
||||||
|
choice.remove();
|
||||||
|
choice = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (s) {
|
||||||
|
case 0:
|
||||||
|
dialogue.setDialogue("Iuno", "Hmph. Apologies, but I need my rest...");
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
var font = Assets.getFont("ui", 18);
|
||||||
|
choice = new ChoiceBox(font, new String[]{"New game", "Back to Main Menu"}, idx -> {
|
||||||
|
if (idx == 0) {
|
||||||
|
dialogue.setDialogue("Iuno", "Toi yeu tunxd...");
|
||||||
|
step = 2;
|
||||||
|
} else {
|
||||||
|
game.returnToMainMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
choice.setPosition(WINDOW_WIDTH - 260, WINDOW_HEIGHT / 2);
|
||||||
|
stage.addActor(choice);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
game.returnToMainMenu();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlpha(float alpha) {
|
||||||
|
this.alpha = alpha;
|
||||||
|
if (stage != null && stage.getRoot() != null) {
|
||||||
|
stage.getRoot().setColor(1, 1, 1, alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void enterTransition() {
|
||||||
|
inTransition = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(SpriteBatch batch) {
|
||||||
|
if (inTransition || !isActive || stage == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
stage.act(Gdx.graphics.getDeltaTime());
|
||||||
|
stage.draw();
|
||||||
|
} catch (Exception e) {
|
||||||
|
isActive = false;
|
||||||
|
log.warn("Error rendering DialogueScene, marking as inactive", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
if (stage != null) {
|
||||||
|
stage.dispose();
|
||||||
|
stage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package org.vibecoders.moongazer.scenes;
|
||||||
|
|
||||||
|
import org.vibecoders.moongazer.Game;
|
||||||
|
import org.vibecoders.moongazer.State;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||||
|
|
||||||
|
public class DialogueToMenuTransition extends Transition {
|
||||||
|
private final DialogueScene from;
|
||||||
|
private final Scene to;
|
||||||
|
private float totalTime = 0f;
|
||||||
|
private final long duration;
|
||||||
|
|
||||||
|
public DialogueToMenuTransition(Game game, DialogueScene from, Scene to, long duration) {
|
||||||
|
super(game, from, to, State.MAIN_MENU, duration);
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(SpriteBatch batch) {
|
||||||
|
totalTime += Gdx.graphics.getDeltaTime();
|
||||||
|
float progress = totalTime / (((float) duration) / 1000);
|
||||||
|
|
||||||
|
if (progress >= 1.0f) {
|
||||||
|
game.transition = null;
|
||||||
|
from.dispose();
|
||||||
|
game.setCurrentSceneAndState(to, State.MAIN_MENU);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float fromOpacity = 1 - progress;
|
||||||
|
from.setAlpha(fromOpacity);
|
||||||
|
from.render(batch);
|
||||||
|
|
||||||
|
float toOpacity = progress;
|
||||||
|
if (to.root != null) {
|
||||||
|
to.root.setVisible(true);
|
||||||
|
to.root.setColor(1, 1, 1, toOpacity);
|
||||||
|
}
|
||||||
|
batch.setColor(1, 1, 1, toOpacity);
|
||||||
|
to.render(batch);
|
||||||
|
|
||||||
|
batch.setColor(1, 1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
package org.vibecoders.moongazer.scenes;
|
||||||
|
|
||||||
|
import org.vibecoders.moongazer.Game;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Gdx;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||||
|
|
||||||
|
public class DialogueTransition extends Transition {
|
||||||
|
private final Scene from;
|
||||||
|
private final DialogueScene to;
|
||||||
|
private float totalTime = 0f;
|
||||||
|
private final long duration;
|
||||||
|
|
||||||
|
public DialogueTransition(Game game, Scene from, DialogueScene to, long duration) {
|
||||||
|
super(game, from, to, null, duration);
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
this.duration = duration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void render(SpriteBatch batch) {
|
||||||
|
totalTime += Gdx.graphics.getDeltaTime();
|
||||||
|
float progress = totalTime / (((float) duration) / 1000);
|
||||||
|
|
||||||
|
if (progress >= 1.0f) {
|
||||||
|
game.transition = null;
|
||||||
|
to.setAlpha(1f);
|
||||||
|
game.setScene(to);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float fromOpacity = 1 - progress;
|
||||||
|
if (from.root != null) {
|
||||||
|
from.root.setVisible(true);
|
||||||
|
from.root.setColor(1, 1, 1, fromOpacity);
|
||||||
|
}
|
||||||
|
batch.setColor(1, 1, 1, fromOpacity);
|
||||||
|
from.render(batch);
|
||||||
|
|
||||||
|
to.setAlpha(progress);
|
||||||
|
batch.setColor(1, 1, 1, 1);
|
||||||
|
to.render(batch);
|
||||||
|
|
||||||
|
batch.setColor(1, 1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -93,7 +93,14 @@ public class MainMenu extends Scene {
|
|||||||
exitButton.setPosition(centerX, startY - spacing * 4);
|
exitButton.setPosition(centerX, startY - spacing * 4);
|
||||||
|
|
||||||
// Mouse click handlers
|
// Mouse click handlers
|
||||||
playButton.onClick(() -> log.debug("Play clicked"));
|
playButton.onClick(() -> {
|
||||||
|
log.debug("Play clicked");
|
||||||
|
// Create transition to DialogueScene
|
||||||
|
if (game.transition == null) {
|
||||||
|
DialogueScene dialogueScene = new DialogueScene(game);
|
||||||
|
game.transition = new DialogueTransition(game, this, dialogueScene, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
loadButton.onClick(() -> log.debug("Load clicked"));
|
loadButton.onClick(() -> log.debug("Load clicked"));
|
||||||
leaderboardButton.onClick(() -> log.debug("Leaderboard clicked"));
|
leaderboardButton.onClick(() -> log.debug("Leaderboard clicked"));
|
||||||
settingsButton.onClick(() -> {
|
settingsButton.onClick(() -> {
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
package org.vibecoders.moongazer.vn;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Image;
|
||||||
|
|
||||||
|
public class CharacterActor extends Image {
|
||||||
|
public CharacterActor(TextureRegion baseReg) {
|
||||||
|
super(baseReg);
|
||||||
|
float ox = getWidth() * 0.5f;
|
||||||
|
float oy = 0f;
|
||||||
|
setOrigin(ox, oy);
|
||||||
|
}
|
||||||
|
}
|
||||||
36
app/src/main/java/org/vibecoders/moongazer/vn/ChoiceBox.java
Normal file
36
app/src/main/java/org/vibecoders/moongazer/vn/ChoiceBox.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package org.vibecoders.moongazer.vn;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.InputEvent;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
|
||||||
|
|
||||||
|
public class ChoiceBox extends Group {
|
||||||
|
public interface Listener {
|
||||||
|
void onChoice(int idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChoiceBox(BitmapFont font, String[] options, Listener listener) {
|
||||||
|
TextButton.TextButtonStyle style = new TextButton.TextButtonStyle();
|
||||||
|
style.font = font;
|
||||||
|
|
||||||
|
VerticalGroup col = new VerticalGroup().space(8);
|
||||||
|
|
||||||
|
for (int i = 0; i < options.length; i++) {
|
||||||
|
final int idx = i;
|
||||||
|
TextButton b = new TextButton(options[i], style);
|
||||||
|
b.addListener(new ClickListener() {
|
||||||
|
@Override
|
||||||
|
public void clicked(InputEvent e, float x, float y) {
|
||||||
|
listener.onChoice(idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
col.addActor(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
addActor(col);
|
||||||
|
col.pack();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
package org.vibecoders.moongazer.vn;
|
||||||
|
|
||||||
|
import com.badlogic.gdx.graphics.Color;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.BitmapFont;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.TextureRegion;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Group;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Image;
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Label;
|
||||||
|
import com.badlogic.gdx.utils.Align;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transparent dialogue box with typing effect.
|
||||||
|
* Shows speaker name, separator, and dialogue text.
|
||||||
|
*/
|
||||||
|
public class DialogueBoxTransparent extends Group {
|
||||||
|
private final Label nameLabel;
|
||||||
|
private final Label textLabel;
|
||||||
|
private CharSequence fullText;
|
||||||
|
private float shown = 0f;
|
||||||
|
private boolean done = true;
|
||||||
|
private final float charPerSec = 45f;
|
||||||
|
|
||||||
|
private static final float BOX_HEIGHT = 200f;
|
||||||
|
private static final float TEXT_MARGIN = 20f;
|
||||||
|
|
||||||
|
public DialogueBoxTransparent(BitmapFont font, TextureRegion bgRegion, TextureRegion sepRegion, float width) {
|
||||||
|
Image background = new Image(bgRegion);
|
||||||
|
background.setSize(width, BOX_HEIGHT);
|
||||||
|
background.setColor(1f, 1f, 1f, 0.3f);
|
||||||
|
addActor(background);
|
||||||
|
|
||||||
|
Label.LabelStyle nameStyle = new Label.LabelStyle();
|
||||||
|
nameStyle.font = font;
|
||||||
|
nameStyle.fontColor = Color.GOLD;
|
||||||
|
nameLabel = new Label("", nameStyle);
|
||||||
|
nameLabel.setFontScale(1.2f);
|
||||||
|
nameLabel.setAlignment(Align.center);
|
||||||
|
nameLabel.setWidth(width);
|
||||||
|
nameLabel.setPosition(0, BOX_HEIGHT + 40);
|
||||||
|
addActor(nameLabel);
|
||||||
|
|
||||||
|
Image separator = new Image(sepRegion);
|
||||||
|
float separatorDisplayWidth = width;
|
||||||
|
float aspectRatio = (float) sepRegion.getRegionHeight() / (float) sepRegion.getRegionWidth();
|
||||||
|
float separatorDisplayHeight = separatorDisplayWidth * aspectRatio;
|
||||||
|
|
||||||
|
if (separatorDisplayHeight > 180f) {
|
||||||
|
separatorDisplayHeight = 180f;
|
||||||
|
separatorDisplayWidth = separatorDisplayHeight / aspectRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
separator.setSize(separatorDisplayWidth, separatorDisplayHeight);
|
||||||
|
float sepX = (width - separatorDisplayWidth) / 2f;
|
||||||
|
float sepY = BOX_HEIGHT - 100;
|
||||||
|
separator.setPosition(sepX, sepY);
|
||||||
|
separator.setColor(1f, 1f, 1f, 1f);
|
||||||
|
addActor(separator);
|
||||||
|
|
||||||
|
Label.LabelStyle textStyle = new Label.LabelStyle();
|
||||||
|
textStyle.font = font;
|
||||||
|
textStyle.fontColor = Color.WHITE;
|
||||||
|
textLabel = new Label("", textStyle);
|
||||||
|
textLabel.setWrap(true);
|
||||||
|
textLabel.setWidth(width - TEXT_MARGIN * 2);
|
||||||
|
textLabel.setAlignment(Align.center);
|
||||||
|
textLabel.setPosition(TEXT_MARGIN, (BOX_HEIGHT / 2) - 20);
|
||||||
|
addActor(textLabel);
|
||||||
|
|
||||||
|
setSize(width, BOX_HEIGHT + 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDialogue(String speaker, String text) {
|
||||||
|
nameLabel.setText(speaker);
|
||||||
|
fullText = text;
|
||||||
|
shown = 0f;
|
||||||
|
done = false;
|
||||||
|
textLabel.setText("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDone() {
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void skip() {
|
||||||
|
if (fullText != null) {
|
||||||
|
textLabel.setText(fullText);
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void act(float delta) {
|
||||||
|
super.act(delta);
|
||||||
|
if (done || fullText == null) return;
|
||||||
|
|
||||||
|
shown += charPerSec * delta;
|
||||||
|
int n = Math.min(fullText.length(), (int) shown);
|
||||||
|
textLabel.setText(fullText.subSequence(0, n));
|
||||||
|
|
||||||
|
if (n >= fullText.length()) {
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
app/src/main/resources/textures/vn_scene/char_base.png
Normal file
BIN
app/src/main/resources/textures/vn_scene/char_base.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 79 KiB |
BIN
app/src/main/resources/textures/vn_scene/separator.png
Normal file
BIN
app/src/main/resources/textures/vn_scene/separator.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
Reference in New Issue
Block a user