Compare commits

...

15 Commits

Author SHA1 Message Date
a65cd35cec Merge pull request #7 from teppyboy/Iuno_OP_scene
feat(scene): dialogue scene implementation
2025-10-04 00:22:31 +07:00
db253e959b vn dialogue scene implementation 2025-10-03 21:39:37 +07:00
2c77f665cb feat(audio): implement audio & main menu music 2025-10-03 08:29:17 +07:00
88dbbb4be7 chore(ui/slider): refactor 2025-10-03 07:35:26 +07:00
dc4abdd578 feat: add music slider 2025-10-02 23:27:30 +07:00
7800efcf20 feat(scene/main_menu): add leaderboard button
XD
2025-10-02 16:45:44 +07:00
d045243c7a feat(ui): add close button 2025-10-02 14:01:40 +07:00
5eb0554c62 chore(scene/settings): further simplify
Wow.
2025-10-02 11:29:15 +07:00
bc3dee458c chore(settings): "properly" implement settings 2025-10-02 10:58:56 +07:00
b42cd6d263 Merge pull request #6 from teppyboy/Settings
feat(scene): settings page
2025-10-02 02:27:53 +07:00
d358ddd042 Merge remote-tracking branch 'origin/Settings' into Settings 2025-10-02 02:08:32 +07:00
30766b9235 <feat> Add settings, keybind system, arrow_button assets 2025-10-02 02:07:09 +07:00
578ecff8c0 <feat> Add settings, keybind system, arrow_button assets 2025-10-02 01:36:58 +07:00
8ce166365a chore(scene/intro): reduce time to 4 seconds 2025-10-01 14:31:24 +07:00
57aecaf002 chore: use libGDX functions instead of System.currentTimeMillis 2025-10-01 14:30:40 +07:00
30 changed files with 1037 additions and 96 deletions

View File

@ -12,6 +12,7 @@ import java.util.ArrayList;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.vibecoders.moongazer.managers.Assets; import org.vibecoders.moongazer.managers.Assets;
import org.vibecoders.moongazer.managers.Audio;
import org.vibecoders.moongazer.scenes.*; import org.vibecoders.moongazer.scenes.*;
public class Game extends ApplicationAdapter { public class Game extends ApplicationAdapter {
@ -28,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() {
@ -59,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();
@ -94,11 +104,99 @@ public class Game extends ApplicationAdapter {
@Override @Override
public void dispose() { public void dispose() {
// Save settings
Settings.saveSettings();
// Dispose scenes
introScene.dispose(); introScene.dispose();
mainMenuScene.dispose(); mainMenuScene.dispose();
// Dispose resources
Audio.dispose();
Assets.dispose(); Assets.dispose();
// Dispose batch and stage
batch.dispose(); batch.dispose();
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);
}
}

View File

@ -0,0 +1,65 @@
package org.vibecoders.moongazer;
import com.badlogic.gdx.Input;
import java.util.HashMap;
import org.slf4j.Logger;
public class Settings {
private static final Logger log = org.slf4j.LoggerFactory.getLogger(Settings.class);
private static float masterVolume = 1.0f;
private static float musicVolume = 1.0f;
private static float sfxVolume = 1.0f;
public static HashMap<String, Integer> keybinds = new HashMap<>() {
{
put("p1_left", Input.Keys.LEFT);
put("p1_right", Input.Keys.RIGHT);
put("p2_left", Input.Keys.A);
put("p2_right", Input.Keys.D);
}
};
public static int getKeybind(String action) {
return keybinds.getOrDefault(action, Input.Keys.UNKNOWN);
}
public static float getMasterVolume() {
return masterVolume;
}
public static float getMusicVolume() {
return musicVolume;
}
public static float getSfxVolume() {
return sfxVolume;
}
public static void setKeybind(String action, int keycode) {
keybinds.put(action, keycode);
}
public static void setMasterVolume(float volume) {
masterVolume = Math.max(0, Math.min(1, volume));
}
public static void setMusicVolume(float volume) {
musicVolume = Math.max(0, Math.min(1, volume));
}
public static void setSfxVolume(float volume) {
sfxVolume = Math.max(0, Math.min(1, volume));
}
public static void saveSettings() {
log.debug("Saving settings: Master Volume = {}, Music Volume = {}, SFX Volume = {}", masterVolume, musicVolume, sfxVolume);
for (java.util.Map.Entry<String, Integer> entry : keybinds.entrySet()) {
log.debug("Keybind: {} = {}", entry.getKey(), Input.Keys.toString(entry.getValue()));
}
}
public static void loadSettings() {
}
}

View File

@ -25,6 +25,7 @@ public class Assets {
private static final HashMap<String, FileHandle> loadedFiles = new HashMap<>(); private static final HashMap<String, FileHandle> loadedFiles = new HashMap<>();
private static boolean startLoadAll = false; private static boolean startLoadAll = false;
private static boolean loadedAll = false; private static boolean loadedAll = false;
private static Thread loadingThread = null;
private static Texture textureWhite; private static Texture textureWhite;
private static Texture textureBlack; private static Texture textureBlack;
@ -117,8 +118,23 @@ public class Assets {
assetManager.load("textures/ui/UI_Icon_Setting.png", Texture.class); assetManager.load("textures/ui/UI_Icon_Setting.png", Texture.class);
assetManager.load("textures/ui/ImgReShaSoundOn.png", Texture.class); assetManager.load("textures/ui/ImgReShaSoundOn.png", Texture.class);
assetManager.load("textures/ui/UI_Gcg_Icon_Close.png", Texture.class); assetManager.load("textures/ui/UI_Gcg_Icon_Close.png", Texture.class);
assetManager.load("textures/ui/arrow-button-left.png", Texture.class);
assetManager.load("textures/ui/arrow-button-right.png", Texture.class);
assetManager.load("textures/ui/close.png", Texture.class);
assetManager.load("textures/ui/close_hover.png", Texture.class);
assetManager.load("textures/ui/close_clicked.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_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
loadAny("videos/main_menu_background.webm"); loadingThread = new Thread(() -> {
loadAny("videos/main_menu_background.webm");
loadAny("audio/I Once Praised the Day.mp3");
});
loadingThread.start();
} }
public static boolean isLoadedAll() { public static boolean isLoadedAll() {
@ -131,6 +147,14 @@ public class Assets {
public static void waitUntilLoaded() { public static void waitUntilLoaded() {
assetManager.finishLoading(); assetManager.finishLoading();
if (loadingThread != null) {
try {
loadingThread.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
loadingThread = null;
}
if (startLoadAll) { if (startLoadAll) {
loadedAll = true; loadedAll = true;
} }

View File

@ -0,0 +1,53 @@
package org.vibecoders.moongazer.managers;
import org.slf4j.Logger;
import org.vibecoders.moongazer.Settings;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.audio.Music;
import com.badlogic.gdx.files.FileHandle;
public class Audio {
private static final Logger log = org.slf4j.LoggerFactory.getLogger(Audio.class);
private static boolean initialized = false;
private static Music menuMusic = null;
public static void init() {
if (initialized) {
return;
}
initialized = true;
menuMusic = Gdx.audio.newMusic(Assets.getAsset("audio/I Once Praised the Day.mp3", FileHandle.class));
log.info("Audio manager initialized");
}
public static void menuMusicSetVolume() {
menuMusic.setVolume(Settings.getMusicVolume() * Settings.getMasterVolume());
}
public static void musicSetVolume() {
menuMusicSetVolume();
}
public static void menuMusicPlay() {
if (!menuMusic.isPlaying()) {
menuMusic.setLooping(true);
menuMusic.play();
}
}
public static void menuMusicStop() {
if (menuMusic.isPlaying()) {
menuMusic.stop();
}
}
public static void dispose() {
if (menuMusic != null) {
menuMusic.dispose();
menuMusic = null;
}
initialized = false;
log.info("Audio manager disposed");
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -5,7 +5,9 @@ import static org.vibecoders.moongazer.Constants.*;
import org.vibecoders.moongazer.Game; import org.vibecoders.moongazer.Game;
import org.vibecoders.moongazer.State; import org.vibecoders.moongazer.State;
import org.vibecoders.moongazer.managers.Assets; import org.vibecoders.moongazer.managers.Assets;
import org.vibecoders.moongazer.managers.Audio;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
@ -17,8 +19,8 @@ import com.badlogic.gdx.utils.ScreenUtils;
public class Intro extends Scene { public class Intro extends Scene {
private Texture logo; private Texture logo;
private Game game; private Game game;
private long startTime; private float totalTime = 0;
private long endTime = 0; private boolean end = false;
/** /**
* Initializes the intro scene, starts loading assets. * Initializes the intro scene, starts loading assets.
@ -26,23 +28,27 @@ public class Intro extends Scene {
public Intro(Game game) { public Intro(Game game) {
this.game = game; this.game = game;
logo = Assets.getAsset("icons/logo.png", Texture.class); logo = Assets.getAsset("icons/logo.png", Texture.class);
startTime = System.currentTimeMillis() + 500;
log.info("Starting to load all remaining assets..."); log.info("Starting to load all remaining assets...");
Assets.loadAll(); Assets.loadAll();
Audio.init();
Audio.musicSetVolume();
// Create scenes // Create scenes
game.mainMenuScene = new MainMenu(game); game.mainMenuScene = new MainMenu(game);
game.settingsScene = new Settings(game); game.settingsScene = new SettingsScene(game);
game.gameScenes.add(game.mainMenuScene); game.gameScenes.add(game.mainMenuScene);
game.gameScenes.add(game.settingsScene); game.gameScenes.add(game.settingsScene);
} }
/** /**
* Renders the intro scene. * Renders the intro scene.
*
* @param batch The SpriteBatch to draw with. * @param batch The SpriteBatch to draw with.
*/ */
@Override @Override
public void render(SpriteBatch batch) { public void render(SpriteBatch batch) {
if (System.currentTimeMillis() > endTime + 2000 && endTime != 0) { totalTime += Gdx.graphics.getDeltaTime();
log.trace("Intro total time: {}", totalTime);
if (totalTime > 4f) {
if (game.transition == null) { if (game.transition == null) {
Assets.waitUntilLoaded(); Assets.waitUntilLoaded();
log.info("All assets loaded successfully."); log.info("All assets loaded successfully.");
@ -52,13 +58,14 @@ public class Intro extends Scene {
return; return;
} }
ScreenUtils.clear(Color.BLACK); ScreenUtils.clear(Color.BLACK);
// log.debug("Rendering logo at position: ({}, {})", WINDOW_WIDTH / 2 - logo.getWidth() / 4, WINDOW_HEIGHT / 2 - logo.getHeight() / 4); // log.debug("Rendering logo at position: ({}, {})", WINDOW_WIDTH / 2 -
var currentOpacity = (float) (System.currentTimeMillis() - startTime) / 1000; // logo.getWidth() / 4, WINDOW_HEIGHT / 2 - logo.getHeight() / 4);
var currentOpacity = totalTime;
if (currentOpacity > 1) { if (currentOpacity > 1) {
if (endTime == 0) { if (!end) {
endTime = System.currentTimeMillis() + 2000; end = true;
} }
currentOpacity = 1 - ((float) (System.currentTimeMillis() - endTime) / 1000); currentOpacity = 4f - totalTime;
} }
// Multiply with any externally applied alpha (e.g., Transition) // Multiply with any externally applied alpha (e.g., Transition)
float externalAlpha = batch.getColor().a; float externalAlpha = batch.getColor().a;

View File

@ -9,6 +9,7 @@ import java.util.Map;
import org.vibecoders.moongazer.Game; import org.vibecoders.moongazer.Game;
import org.vibecoders.moongazer.State; import org.vibecoders.moongazer.State;
import org.vibecoders.moongazer.managers.Assets; import org.vibecoders.moongazer.managers.Assets;
import org.vibecoders.moongazer.managers.Audio;
import org.vibecoders.moongazer.ui.UITextButton; import org.vibecoders.moongazer.ui.UITextButton;
import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Gdx;
@ -18,6 +19,7 @@ import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.InputEvent; import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener; import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.utils.TimeUtils;
import com.badlogic.gdx.video.VideoPlayer; import com.badlogic.gdx.video.VideoPlayer;
import com.badlogic.gdx.video.VideoPlayerCreator; import com.badlogic.gdx.video.VideoPlayerCreator;
@ -61,15 +63,17 @@ public class MainMenu extends Scene {
var font = Assets.getFont("ui", 24); var font = Assets.getFont("ui", 24);
UITextButton playButton = new UITextButton("Play", font); UITextButton playButton = new UITextButton("Play", font);
UITextButton loadButton = new UITextButton("Load", font); UITextButton loadButton = new UITextButton("Load", font);
UITextButton leaderboardButton = new UITextButton("Leaderboard", font);
UITextButton settingsButton = new UITextButton("Settings", font); UITextButton settingsButton = new UITextButton("Settings", font);
UITextButton exitButton = new UITextButton("Exit", font); UITextButton exitButton = new UITextButton("Exit", font);
buttons = new UITextButton[] { playButton, loadButton, settingsButton, exitButton }; buttons = new UITextButton[] { playButton, loadButton, leaderboardButton, settingsButton, exitButton };
int buttonWidth = 300; int buttonWidth = 300;
int buttonHeight = 80; int buttonHeight = 80;
playButton.setSize(buttonWidth, buttonHeight); playButton.setSize(buttonWidth, buttonHeight);
loadButton.setSize(buttonWidth, buttonHeight); loadButton.setSize(buttonWidth, buttonHeight);
leaderboardButton.setSize(buttonWidth, buttonHeight);
settingsButton.setSize(buttonWidth, buttonHeight); settingsButton.setSize(buttonWidth, buttonHeight);
exitButton.setSize(buttonWidth, buttonHeight); exitButton.setSize(buttonWidth, buttonHeight);
@ -81,14 +85,24 @@ public class MainMenu extends Scene {
playButton.setPosition(centerX, startY); playButton.setPosition(centerX, startY);
loadButton.setSize(buttonWidth, buttonHeight); loadButton.setSize(buttonWidth, buttonHeight);
loadButton.setPosition(centerX, startY - spacing); loadButton.setPosition(centerX, startY - spacing);
leaderboardButton.setSize(buttonWidth, buttonHeight);
leaderboardButton.setPosition(centerX, startY - spacing * 2);
settingsButton.setSize(buttonWidth, buttonHeight); settingsButton.setSize(buttonWidth, buttonHeight);
settingsButton.setPosition(centerX, startY - spacing * 2); settingsButton.setPosition(centerX, startY - spacing * 3);
exitButton.setSize(buttonWidth, buttonHeight); exitButton.setSize(buttonWidth, buttonHeight);
exitButton.setPosition(centerX, startY - spacing * 3); 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"));
settingsButton.onClick(() -> { settingsButton.onClick(() -> {
log.debug("Settings clicked"); log.debug("Settings clicked");
if (game.transition == null) { if (game.transition == null) {
@ -102,6 +116,7 @@ public class MainMenu extends Scene {
root.addActor(playButton.getActor()); root.addActor(playButton.getActor());
root.addActor(loadButton.getActor()); root.addActor(loadButton.getActor());
root.addActor(leaderboardButton.getActor());
root.addActor(settingsButton.getActor()); root.addActor(settingsButton.getActor());
root.addActor(exitButton.getActor()); root.addActor(exitButton.getActor());
@ -133,7 +148,7 @@ public class MainMenu extends Scene {
@Override @Override
public boolean keyDown(InputEvent event, int keycode) { public boolean keyDown(InputEvent event, int keycode) {
sKeyDown(event, keycode); sKeyDown(event, keycode);
currentKeyDown.put(keycode, System.currentTimeMillis()); currentKeyDown.put(keycode, TimeUtils.millis());
return true; return true;
} }
}); });
@ -156,6 +171,7 @@ public class MainMenu extends Scene {
return; return;
videoPlayer.setLooping(true); videoPlayer.setLooping(true);
videoPlayer.play(); videoPlayer.play();
Audio.menuMusicPlay();
videoPrepared = true; videoPrepared = true;
} }
@ -169,10 +185,10 @@ public class MainMenu extends Scene {
log.trace("Key pressed: {}", keycode); log.trace("Key pressed: {}", keycode);
switch (keycode) { switch (keycode) {
case Input.Keys.UP: case Input.Keys.UP:
currentChoice = (currentChoice - 1 + 4) % 4; currentChoice = (currentChoice - 1 + buttons.length) % buttons.length;
break; break;
case Input.Keys.DOWN: case Input.Keys.DOWN:
currentChoice = (currentChoice + 1) % 4; currentChoice = (currentChoice + 1) % buttons.length;
break; break;
case Input.Keys.RIGHT: case Input.Keys.RIGHT:
case Input.Keys.ENTER: case Input.Keys.ENTER:
@ -201,9 +217,9 @@ public class MainMenu extends Scene {
for (Map.Entry<Integer, Long> entry : currentKeyDown.entrySet()) { for (Map.Entry<Integer, Long> entry : currentKeyDown.entrySet()) {
Integer keyCode = entry.getKey(); Integer keyCode = entry.getKey();
Long timeStamp = entry.getValue(); Long timeStamp = entry.getValue();
if (System.currentTimeMillis() - timeStamp > 100) { if (TimeUtils.millis() - timeStamp > 100) {
sKeyDown(null, keyCode); sKeyDown(null, keyCode);
currentKeyDown.put(keyCode, System.currentTimeMillis()); currentKeyDown.put(keyCode, TimeUtils.millis());
} }
} }
startVideoOnce(); startVideoOnce();

View File

@ -1,50 +0,0 @@
package org.vibecoders.moongazer.scenes;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import static org.vibecoders.moongazer.Constants.*;
import org.vibecoders.moongazer.Game;
import org.vibecoders.moongazer.State;
import org.vibecoders.moongazer.managers.Assets;
import org.vibecoders.moongazer.ui.UIImageButton;
public class Settings extends Scene {
public Settings(Game game) {
super(game);
// WIP
UIImageButton settingButton = new UIImageButton("textures/ui/UI_Icon_Setting.png");
UIImageButton exitImgButton = new UIImageButton("textures/ui/IconExitGame.png");
UIImageButton soundButton = new UIImageButton("textures/ui/ImgReShaSoundOn.png");
UIImageButton closeButton = new UIImageButton("textures/ui/UI_Gcg_Icon_Close.png");
settingButton.setSize(50, 50);
exitImgButton.setSize(50, 50);
soundButton.setSize(50, 50);
closeButton.setSize(50, 50);
settingButton.setPosition(20, WINDOW_HEIGHT - 70);
exitImgButton.setPosition(WINDOW_WIDTH - 70, WINDOW_HEIGHT - 70);
soundButton.setPosition(20, 20);
closeButton.setPosition(WINDOW_WIDTH - 70, 20);
root.addActor(settingButton.getActor());
root.addActor(exitImgButton.getActor());
root.addActor(soundButton.getActor());
root.addActor(closeButton.getActor());
settingButton.onClick(() -> log.debug("Settings clicked"));
exitImgButton.onClick(() -> {
log.debug("Exit clicked");
if (game.transition == null) {
game.transition = new Transition(game, this, game.mainMenuScene, State.MAIN_MENU, 350);
}
});
soundButton.onClick(() -> log.debug("Sound clicked"));
closeButton.onClick(() -> log.debug("Close clicked"));
game.stage.addActor(root);
}
@Override
public void render(SpriteBatch batch) {
batch.draw(Assets.getWhiteTexture(), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
}
}

View File

@ -0,0 +1,209 @@
package org.vibecoders.moongazer.scenes;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import org.vibecoders.moongazer.Game;
import org.vibecoders.moongazer.State;
import org.vibecoders.moongazer.Settings;
import org.vibecoders.moongazer.managers.Assets;
import org.vibecoders.moongazer.managers.Audio;
import org.vibecoders.moongazer.ui.UICloseButton;
import org.vibecoders.moongazer.ui.UITextButton;
import org.vibecoders.moongazer.ui.UISlider;
import java.util.HashMap;
import static org.vibecoders.moongazer.Constants.*;
public class SettingsScene extends Scene {
private boolean isEditingKeybind = false;
private UITextButton currentEditingButton = null;
private String currentKeybindAction = "";
private HashMap<String, UITextButton> keybindButtons = new HashMap<>();
private UISlider masterVolSlider;
private UISlider musicSlider;
private UISlider sfxSlider;
public SettingsScene(Game game) {
super(game);
root.setFillParent(true);
BitmapFont font = Assets.getFont("ui", 24);
Label.LabelStyle labelStyle = new Label.LabelStyle(font, Color.WHITE);
// Main panel
Table mainPanel = new Table();
mainPanel.setSize(800, 600);
mainPanel.setPosition((Gdx.graphics.getWidth() - 800) / 2f,
(Gdx.graphics.getHeight() - 600) / 2f);
Label title = new Label("Settings", labelStyle);
mainPanel.add(title).colspan(2).padTop(60).padBottom(40);
mainPanel.row();
// Volume settings
TextureRegionDrawable bg = new TextureRegionDrawable(Assets.getWhiteTexture());
var tintedBg = bg.tint(new Color(0.2f, 0.2f, 0.2f, 0.3f));
String[] volumes = { "Master Volume", "Music Volume", "SFX Volume" };
// TODO: implement audio manager
for (String volume : volumes) {
Table row = new Table();
row.setBackground(tintedBg);
row.add(new Label(volume, labelStyle)).expandX().left().padLeft(40).pad(15);
if (volume.equals("Master Volume")) {
masterVolSlider = new UISlider();
masterVolSlider.onChanged(() -> {
Settings.setMasterVolume(masterVolSlider.getValue());
Audio.musicSetVolume();
});
row.add(masterVolSlider.slider).width(300).right().padRight(40);
} else if (volume.equals("Music Volume")) {
musicSlider = new UISlider();
musicSlider.onChanged(() -> {
Settings.setMusicVolume(musicSlider.getValue());
Audio.musicSetVolume();
});
row.add(musicSlider.slider).width(300).right().padRight(40);
} else if (volume.equals("SFX Volume")) {
sfxSlider = new UISlider();
sfxSlider.onChanged(() -> {
Settings.setSfxVolume(sfxSlider.getValue());
});
row.add(sfxSlider.slider).width(300).right().padRight(40);
}
mainPanel.add(row).width(700).height(60).padBottom(5);
mainPanel.row();
}
// Keybind settings
Table section = new Table();
section.setBackground(tintedBg);
section.add(new Label("Keybinds", labelStyle)).colspan(2).expandX().left()
.padLeft(20).padTop(15).padBottom(10);
section.row();
// Player keybinds
String[] players = { "Player 1", "Player 2" };
String[] prefixes = { "p1", "p2" };
String[] actions = { "_left", "_right" };
String[] labels = { " Move Left", " Move Right" };
for (int p = 0; p < players.length; p++) {
section.add(new Label(" " + players[p], labelStyle)).colspan(2).left()
.padLeft(60);
section.row();
for (int a = 0; a < actions.length; a++) {
section.add(new Label(labels[a], labelStyle)).left().padLeft(80);
String action = prefixes[p] + actions[a];
UITextButton button = new UITextButton(getKeyName(Settings.getKeybind(action)), font);
button.button.addListener(new ClickListener() {
@Override
public void clicked(InputEvent event, float x, float y) {
if (!isEditingKeybind) {
isEditingKeybind = true;
currentEditingButton = button;
currentKeybindAction = action;
((TextButton) (button.button)).setText("Press Key...");
}
}
});
keybindButtons.put(action, button);
section.add(button.button).width(240).height(60).right().padRight(40);
section.row();
}
}
mainPanel.add(section).width(700).padBottom(20);
mainPanel.row();
// Back button
UICloseButton backButton = new UICloseButton();
backButton.setSize(40, 40);
backButton.setPosition(Gdx.graphics.getWidth() - 80, Gdx.graphics.getHeight() - 80);
backButton.onClick(() -> {
if (game.transition == null && !isEditingKeybind) {
game.transition = new Transition(game, this, game.mainMenuScene,
State.MAIN_MENU, 350);
}
});
root.addActor(mainPanel);
root.addActor(backButton.getActor());
game.stage.addActor(root);
// Key listener
root.addListener(new InputListener() {
@Override
public boolean keyDown(InputEvent event, int keycode) {
if (isEditingKeybind && currentEditingButton != null) {
if (keycode == Input.Keys.ESCAPE) {
if (currentEditingButton != null) {
((TextButton) (currentEditingButton.button))
.setText(getKeyName(Settings.getKeybind(currentKeybindAction)));
}
isEditingKeybind = false;
currentEditingButton = null;
currentKeybindAction = "";
return true;
}
if (isKeybindAlreadyUsed(keycode, currentKeybindAction)) {
((TextButton) (currentEditingButton.button)).setText("Key in use!");
new Thread(() -> {
try {
Thread.sleep(1000);
Gdx.app.postRunnable(() -> {
if (isEditingKeybind && currentEditingButton != null) {
((TextButton) (currentEditingButton.button)).setText("Press Key...");
}
});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
return true;
}
Settings.keybinds.put(currentKeybindAction, keycode);
((TextButton) (currentEditingButton.button)).setText(getKeyName(keycode));
isEditingKeybind = false;
currentEditingButton = null;
currentKeybindAction = "";
return true;
} else if (keycode == Input.Keys.ESCAPE) {
backButton.click();
return true;
}
return false;
}
});
}
private String getKeyName(int keycode) {
String keyName = Input.Keys.toString(keycode);
return keyName != null ? keyName.toUpperCase() : "Unknown";
}
private boolean isKeybindAlreadyUsed(int keycode, String currentAction) {
return Settings.keybinds.entrySet().stream()
.anyMatch(entry -> !entry.getKey().equals(currentAction) &&
entry.getValue().equals(keycode));
}
@Override
public void render(SpriteBatch batch) {
game.mainMenuScene.render(batch);
batch.setColor(0, 0, 0, 0.8f);
batch.draw(Assets.getWhiteTexture(), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
batch.setColor(Color.WHITE);
}
}

View File

@ -3,6 +3,7 @@ package org.vibecoders.moongazer.scenes;
import org.vibecoders.moongazer.Game; import org.vibecoders.moongazer.Game;
import org.vibecoders.moongazer.State; import org.vibecoders.moongazer.State;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.SpriteBatch;
/** /**
@ -12,15 +13,16 @@ public class Transition extends Scene {
private Scene from; private Scene from;
private Scene to; private Scene to;
private State targetState; private State targetState;
private long startTime; private float totalTime = 0f;
private long duration; private long duration;
/** /**
* Creates a new transition between two scenes. * Creates a new transition between two scenes.
* @param from The scene to transition from. *
* @param to The scene to transition to. * @param from The scene to transition from.
* @param to The scene to transition to.
* @param targetState The target state of the game after the transition. * @param targetState The target state of the game after the transition.
* @param duration The duration of the transition in milliseconds. * @param duration The duration of the transition in milliseconds.
*/ */
public Transition(Game game, Scene from, Scene to, State targetState, long duration) { public Transition(Game game, Scene from, Scene to, State targetState, long duration) {
// Transition does not need to render UI elements // Transition does not need to render UI elements
@ -30,16 +32,17 @@ public class Transition extends Scene {
this.to = to; this.to = to;
this.targetState = targetState; this.targetState = targetState;
this.duration = duration; this.duration = duration;
startTime = System.currentTimeMillis();
} }
/** /**
* Renders the transition effect. * Renders the transition effect.
*
* @param batch The SpriteBatch to draw with. * @param batch The SpriteBatch to draw with.
*/ */
@Override @Override
public void render(SpriteBatch batch) { public void render(SpriteBatch batch) {
var toOpacity = ((float) (System.currentTimeMillis() - startTime)) / duration; totalTime += Gdx.graphics.getDeltaTime();
var toOpacity = totalTime / (((float) duration) / 1000);
if (toOpacity >= 0.99) { if (toOpacity >= 0.99) {
log.trace("Transition complete to state: {}", targetState); log.trace("Transition complete to state: {}", targetState);
game.state = targetState; game.state = targetState;

View File

@ -0,0 +1,24 @@
package org.vibecoders.moongazer.ui;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.ui.ImageButton;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import org.vibecoders.moongazer.managers.Assets;
public class UICloseButton extends UIButton {
public UICloseButton() {
Texture closeTexture = Assets.getAsset("textures/ui/close.png", Texture.class);
Texture closeHoverTexture = Assets.getAsset("textures/ui/close_hover.png", Texture.class);
Texture closeClickTexture = Assets.getAsset("textures/ui/close_clicked.png", Texture.class);
TextureRegionDrawable drawable = new TextureRegionDrawable(new TextureRegion(closeTexture));
TextureRegionDrawable hoverDrawable = new TextureRegionDrawable(new TextureRegion(closeHoverTexture));
TextureRegionDrawable clickDrawable = new TextureRegionDrawable(new TextureRegion(closeClickTexture));
ImageButton.ImageButtonStyle style = new ImageButton.ImageButtonStyle();
style.imageUp = drawable;
style.imageDown = clickDrawable;
style.imageOver = hoverDrawable;
this.button = new ImageButton(style);
this.actor = button;
}
}

View File

@ -0,0 +1,61 @@
package org.vibecoders.moongazer.ui;
import org.vibecoders.moongazer.managers.Assets;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.EventListener;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.scenes.scene2d.ui.Slider;
import com.badlogic.gdx.scenes.scene2d.ui.Slider.SliderStyle;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
public class UISlider {
public Slider slider;
public UISlider() {
Texture sliderBgTexture = Assets.getAsset("textures/ui/UI_SliderBg2.png", Texture.class);
Texture sliderKnobTexture = Assets.getAsset("textures/ui/UI_SliderKnob.png", Texture.class);
Texture sliderKnobOverTexture = Assets.getAsset("textures/ui/UI_SliderBg.png", Texture.class);
SliderStyle sliderStyle = new SliderStyle();
sliderStyle.background = new TextureRegionDrawable(new TextureRegion(sliderBgTexture));
sliderStyle.knob = new TextureRegionDrawable(new TextureRegion(sliderKnobTexture));
sliderStyle.knobBefore = new TextureRegionDrawable(new TextureRegion(sliderKnobOverTexture));
sliderStyle.knobAfter = new TextureRegionDrawable(new TextureRegion(sliderBgTexture));
slider = new Slider(0f, 1f, 0.01f, false, sliderStyle);
slider.setValue(1f);
slider.setProgrammaticChangeEvents(true);
}
public void setValue(float value) {
slider.setValue(value);
}
public float getValue() {
return slider.getValue();
}
public void setSize(float width, float height) {
slider.setSize(width, height);
}
public void setPosition(float x, float y) {
slider.setPosition(x, y);
}
public void addListener(EventListener listener) {
slider.addListener(listener);
}
public void onChanged(Runnable action) {
slider.addListener(new ChangeListener() {
@Override
public void changed(ChangeEvent event, Actor actor) {
action.run();
}
});
}
}

View File

@ -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);
}
}

View 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();
}
}

View File

@ -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;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB