diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d632916..71affca 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,6 +6,7 @@ */ val libgdxVersion = "1.13.5" +val gdxVideoVersion = "1.3.3" plugins { // Apply the application plugin to add support for building a CLI application in Java. @@ -46,6 +47,10 @@ dependencies { // Logging implementation("org.slf4j:slf4j-api:2.1.0-alpha1") implementation("ch.qos.logback:logback-classic:1.5.18") + + // gdx-video + implementation("com.badlogicgames.gdx-video:gdx-video:${gdxVideoVersion}") + implementation("com.badlogicgames.gdx-video:gdx-video-lwjgl3:${gdxVideoVersion}") } // Apply a specific Java toolchain to ease working on different environments. diff --git a/app/src/main/java/org/vibecoders/moongazer/scenes/Intro.java b/app/src/main/java/org/vibecoders/moongazer/scenes/Intro.java index a0ca145..b0d2674 100644 --- a/app/src/main/java/org/vibecoders/moongazer/scenes/Intro.java +++ b/app/src/main/java/org/vibecoders/moongazer/scenes/Intro.java @@ -57,8 +57,13 @@ public class Intro extends Scene { } currentOpacity = 1 - ((float) (System.currentTimeMillis() - endTime) / 1000); } - batch.setColor(1, 1, 1, currentOpacity); + // Multiply with any externally applied alpha (e.g., Transition) + float externalAlpha = batch.getColor().a; // alpha set by Transition before calling render + float finalAlpha = currentOpacity * externalAlpha; + batch.setColor(1, 1, 1, finalAlpha); batch.draw(logo, WINDOW_WIDTH / 2 - logo.getWidth() / 4, WINDOW_HEIGHT / 2 - logo.getHeight() / 4, logo.getWidth() / 2, logo.getHeight() / 2); + // Reset color for safety (Transition will set again anyway) + batch.setColor(1,1,1,externalAlpha); } } diff --git a/app/src/main/java/org/vibecoders/moongazer/scenes/MainMenu.java b/app/src/main/java/org/vibecoders/moongazer/scenes/MainMenu.java index 9fc7ba6..d1a4cc3 100644 --- a/app/src/main/java/org/vibecoders/moongazer/scenes/MainMenu.java +++ b/app/src/main/java/org/vibecoders/moongazer/scenes/MainMenu.java @@ -2,62 +2,84 @@ package org.vibecoders.moongazer.scenes; 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.UITextButton; -import org.vibecoders.moongazer.Game; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.utils.TimeUtils; +import com.badlogic.gdx.video.VideoPlayer; +import com.badlogic.gdx.video.VideoPlayerCreator; public class MainMenu extends Scene { - private Texture backgroundTexture; + private final Game game; + private VideoPlayer videoPlayer; + private FileHandle videoFileHandle; private Texture titleTexture; - private float titleY; - private float titleX; - private float titleWidth; - private float titleHeight; + private float titleY, titleX, titleWidth, titleHeight; + private boolean videoPrepared = false; + private boolean firstFrameLogged = false; + private long videoStartTime; + + private static final String WEBM_PATH = "videos/main_menu_background.webm"; + private static final String OGV_PATH = "videos/main_menu_background.ogv"; public MainMenu(Game game) { super(game); - backgroundTexture = Assets.getAsset("textures/main_menu/background.png", Texture.class); + this.game = game; + initVideo(); + initUI(); + } + + private void initVideo() { + try { + videoPlayer = VideoPlayerCreator.createVideoPlayer(); + FileHandle webm = Gdx.files.internal(WEBM_PATH); + FileHandle ogv = Gdx.files.internal(OGV_PATH); + if (webm.exists()) { + videoFileHandle = webm; + log.info("Using menu background (WebM): {}", webm.path()); + } else if (ogv.exists()) { + videoFileHandle = ogv; + log.info("Using menu background (OGV): {}", ogv.path()); + } else { + log.warn("No background video found (expected {} or {}).", WEBM_PATH, OGV_PATH); + } + } catch (Exception e) { + log.error("Cannot create VideoPlayer", e); + } + } + + private void initUI() { titleTexture = Assets.getAsset("textures/main_menu/title.png", Texture.class); - // Scale and position title float targetTitleWidth = 500f; - float originalWidth = titleTexture.getWidth(); - float originalHeight = titleTexture.getHeight(); - float scale = targetTitleWidth / originalWidth; - titleWidth = originalWidth * scale; - titleHeight = originalHeight * scale; + float scale = targetTitleWidth / titleTexture.getWidth(); + titleWidth = titleTexture.getWidth() * scale; + titleHeight = titleTexture.getHeight() * scale; titleX = (WINDOW_WIDTH - titleWidth) / 2f; titleY = WINDOW_HEIGHT / 2f - titleHeight / 8f; - // Buttons var font = Assets.getFont("ui", 24); UITextButton playButton = new UITextButton("Play", font); UITextButton loadButton = new UITextButton("Load", font); UITextButton settingsButton = new UITextButton("Settings", font); UITextButton exitButton = new UITextButton("Exit", font); - int buttonWidth = 300; - int buttonHeight = 80; - playButton.setSize(buttonWidth, buttonHeight); - loadButton.setSize(buttonWidth, buttonHeight); - settingsButton.setSize(buttonWidth, buttonHeight); - exitButton.setSize(buttonWidth, buttonHeight); - + int buttonWidth = 300, buttonHeight = 80; int centerX = WINDOW_WIDTH / 2 - buttonWidth / 2; int startY = WINDOW_HEIGHT / 2 - buttonHeight / 2; - int buttonSpacing = 65; + int spacing = 65; - playButton.setPosition(centerX, startY); - loadButton.setPosition(centerX, startY - buttonSpacing); - settingsButton.setPosition(centerX, startY - buttonSpacing * 2); - exitButton.setPosition(centerX, startY - buttonSpacing * 3); + playButton.setSize(buttonWidth, buttonHeight); playButton.setPosition(centerX, startY); + loadButton.setSize(buttonWidth, buttonHeight); loadButton.setPosition(centerX, startY - spacing); + settingsButton.setSize(buttonWidth, buttonHeight); settingsButton.setPosition(centerX, startY - spacing * 2); + exitButton.setSize(buttonWidth, buttonHeight); exitButton.setPosition(centerX, startY - spacing * 3); - playButton.onClick(() -> log.debug("Play clicked")); - loadButton.onClick(() -> log.debug("Load clicked")); - settingsButton.onClick(() -> log.debug("Settings clicked")); - exitButton.onClick(() -> log.debug("Exit clicked")); + exitButton.onClick(() -> log.info("Exit clicked")); root.addActor(playButton.getActor()); root.addActor(loadButton.getActor()); @@ -66,9 +88,56 @@ public class MainMenu extends Scene { game.stage.addActor(root); } + private void ensureVideoStarted() { + if (videoPlayer == null || videoPrepared) return; + if (videoFileHandle == null || !videoFileHandle.exists()) return; + if (game.transition == null && game.state != State.MAIN_MENU) return; + startVideo(); + } + + private void startVideo() { + try { + videoPlayer.setLooping(true); + videoPlayer.play(videoFileHandle); + videoPrepared = true; + videoStartTime = TimeUtils.millis(); + } catch (Exception e) { + log.error("Failed to play video: {}", videoFileHandle.path(), e); + } + } + + public void forceStartVideo() { // used by Transition for early warm-up + if (!videoPrepared && videoFileHandle != null && videoFileHandle.exists()) startVideo(); + } + @Override public void render(SpriteBatch batch) { - batch.draw(backgroundTexture, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + ensureVideoStarted(); + if (videoPlayer != null && videoPrepared) { + videoPlayer.update(); + Texture videoTexture = videoPlayer.getTexture(); + if (videoTexture != null) { + if (!firstFrameLogged) { + firstFrameLogged = true; + log.info("Menu video first frame in {} ms", TimeUtils.timeSinceMillis(videoStartTime)); + } + batch.draw(videoTexture, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + } else { + batch.draw(Assets.getBlackTexture(), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + } + } else { + batch.draw(Assets.getBlackTexture(), 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + } batch.draw(titleTexture, titleX, titleY, titleWidth, titleHeight); } + + public void updateVideo() { + if (videoPlayer != null && videoPrepared) videoPlayer.update(); + } + + @Override + public void dispose() { + super.dispose(); + if (videoPlayer != null) videoPlayer.dispose(); + } } \ No newline at end of file diff --git a/app/src/main/java/org/vibecoders/moongazer/scenes/Transition.java b/app/src/main/java/org/vibecoders/moongazer/scenes/Transition.java index 0d74bff..e58748b 100644 --- a/app/src/main/java/org/vibecoders/moongazer/scenes/Transition.java +++ b/app/src/main/java/org/vibecoders/moongazer/scenes/Transition.java @@ -58,4 +58,4 @@ public class Transition extends Scene { to.root.setColor(1, 1, 1, toOpacity); to.render(batch); } -} +} \ No newline at end of file diff --git a/app/src/main/resources/videos/main_menu_background.webm b/app/src/main/resources/videos/main_menu_background.webm new file mode 100644 index 0000000..3a307e1 Binary files /dev/null and b/app/src/main/resources/videos/main_menu_background.webm differ