implement basic pause menu
This commit is contained in:
@@ -16,6 +16,7 @@ import com.badlogic.gdx.graphics.g3d.utils.DepthShaderProvider;
|
|||||||
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
|
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
|
||||||
import wtf.beatrice.retrorender.engine.DebugHud;
|
import wtf.beatrice.retrorender.engine.DebugHud;
|
||||||
import wtf.beatrice.retrorender.engine.FpsCameraController;
|
import wtf.beatrice.retrorender.engine.FpsCameraController;
|
||||||
|
import wtf.beatrice.retrorender.engine.PauseMenu;
|
||||||
import wtf.beatrice.retrorender.engine.World3D;
|
import wtf.beatrice.retrorender.engine.World3D;
|
||||||
|
|
||||||
public class GameScreen implements Screen {
|
public class GameScreen implements Screen {
|
||||||
@@ -30,6 +31,11 @@ public class GameScreen implements Screen {
|
|||||||
private World3D world;
|
private World3D world;
|
||||||
private DebugHud hud;
|
private DebugHud hud;
|
||||||
|
|
||||||
|
private boolean showHud = false;
|
||||||
|
private boolean paused = false;
|
||||||
|
|
||||||
|
private PauseMenu pauseMenu;
|
||||||
|
|
||||||
// Shadow Mapping
|
// Shadow Mapping
|
||||||
private ModelBatch shadowBatch;
|
private ModelBatch shadowBatch;
|
||||||
private DirectionalShadowLight shadowLight;
|
private DirectionalShadowLight shadowLight;
|
||||||
@@ -42,8 +48,6 @@ public class GameScreen implements Screen {
|
|||||||
private static final int RETRO_WIDTH = 320;
|
private static final int RETRO_WIDTH = 320;
|
||||||
private static final int RETRO_HEIGHT = 240;
|
private static final int RETRO_HEIGHT = 240;
|
||||||
|
|
||||||
private boolean showHud = false;
|
|
||||||
|
|
||||||
public GameScreen(Main game) {
|
public GameScreen(Main game) {
|
||||||
this.game = game;
|
this.game = game;
|
||||||
}
|
}
|
||||||
@@ -89,8 +93,9 @@ public class GameScreen implements Screen {
|
|||||||
world = new World3D();
|
world = new World3D();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initHud() {
|
private void initMenus() {
|
||||||
hud = new DebugHud();
|
hud = new DebugHud();
|
||||||
|
pauseMenu = new PauseMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- screen methods
|
// --- screen methods
|
||||||
@@ -118,25 +123,35 @@ public class GameScreen implements Screen {
|
|||||||
initCamera();
|
initCamera();
|
||||||
initEnvironment();
|
initEnvironment();
|
||||||
initWorld();
|
initWorld();
|
||||||
initHud();
|
initMenus();
|
||||||
initRetroBuffer();
|
initRetroBuffer();
|
||||||
|
|
||||||
cameraController.onShow();
|
cameraController.onShow(); // captures mouse with warmup
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void render(float delta) {
|
public void render(float delta) {
|
||||||
|
|
||||||
cameraController.update(delta, world);
|
// ESC: toggle pause and mouse capture
|
||||||
world.update(delta);
|
if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
|
||||||
|
paused = !paused;
|
||||||
|
if (paused) {
|
||||||
|
cameraController.releaseMouse();
|
||||||
|
} else {
|
||||||
|
cameraController.captureMouse();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!paused) {
|
||||||
|
cameraController.update(delta, world);
|
||||||
|
world.update(delta);
|
||||||
|
}
|
||||||
|
|
||||||
if (Gdx.input.isKeyJustPressed(Input.Keys.TAB)) {
|
if (Gdx.input.isKeyJustPressed(Input.Keys.TAB)) {
|
||||||
showHud = !showHud;
|
showHud = !showHud;
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- shadow pass: render depth from light's point of view
|
// --- shadow pass: render depth from light's point of view
|
||||||
// point to center the shadow camera on;
|
|
||||||
// can be adjusted this later (e.g. follow player).
|
|
||||||
shadowLight.begin(new com.badlogic.gdx.math.Vector3(0f, 0f, 0f), camera.direction);
|
shadowLight.begin(new com.badlogic.gdx.math.Vector3(0f, 0f, 0f), camera.direction);
|
||||||
|
|
||||||
shadowBatch.begin(shadowLight.getCamera());
|
shadowBatch.begin(shadowLight.getCamera());
|
||||||
@@ -165,6 +180,11 @@ public class GameScreen implements Screen {
|
|||||||
cameraController.getYaw(),
|
cameraController.getYaw(),
|
||||||
cameraController.getPitch());
|
cameraController.getPitch());
|
||||||
|
|
||||||
|
// pause menu
|
||||||
|
if (paused && pauseMenu != null) {
|
||||||
|
pauseMenu.render(RETRO_WIDTH, RETRO_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
frameBuffer.end();
|
frameBuffer.end();
|
||||||
|
|
||||||
// -- scale framebuffer to screen, preserve aspect ratio --
|
// -- scale framebuffer to screen, preserve aspect ratio --
|
||||||
@@ -180,9 +200,6 @@ public class GameScreen implements Screen {
|
|||||||
windowH / (float) RETRO_HEIGHT
|
windowH / (float) RETRO_HEIGHT
|
||||||
);
|
);
|
||||||
|
|
||||||
// strict integer scaling
|
|
||||||
// scale = (float)Math.max(1, Math.floor(scale));
|
|
||||||
|
|
||||||
int drawW = Math.round(RETRO_WIDTH * scale);
|
int drawW = Math.round(RETRO_WIDTH * scale);
|
||||||
int drawH = Math.round(RETRO_HEIGHT * scale);
|
int drawH = Math.round(RETRO_HEIGHT * scale);
|
||||||
|
|
||||||
@@ -190,6 +207,36 @@ public class GameScreen implements Screen {
|
|||||||
int offsetX = (windowW - drawW) / 2;
|
int offsetX = (windowW - drawW) / 2;
|
||||||
int offsetY = (windowH - drawH) / 2;
|
int offsetY = (windowH - drawH) / 2;
|
||||||
|
|
||||||
|
// --- handle mouse click on pause button (window -> retro coords) ---
|
||||||
|
if (paused && pauseMenu != null && Gdx.input.justTouched()) {
|
||||||
|
int mouseX = Gdx.input.getX();
|
||||||
|
int mouseY = Gdx.graphics.getHeight() - Gdx.input.getY(); // to bottom-left origin
|
||||||
|
|
||||||
|
// is the click inside the scaled game area?
|
||||||
|
if (mouseX >= offsetX && mouseX <= offsetX + drawW &&
|
||||||
|
mouseY >= offsetY && mouseY <= offsetY + drawH) {
|
||||||
|
|
||||||
|
float relX = (mouseX - offsetX) / (float) drawW;
|
||||||
|
float relY = (mouseY - offsetY) / (float) drawH;
|
||||||
|
|
||||||
|
float retroX = relX * RETRO_WIDTH;
|
||||||
|
float retroY = relY * RETRO_HEIGHT;
|
||||||
|
|
||||||
|
PauseMenu.MenuAction action = pauseMenu.getActionAt(retroX, retroY);
|
||||||
|
switch (action) {
|
||||||
|
case RESUME:
|
||||||
|
paused = false;
|
||||||
|
cameraController.onShow();
|
||||||
|
break;
|
||||||
|
case QUIT:
|
||||||
|
Gdx.app.exit();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// viewport in backbuffer coordinates
|
// viewport in backbuffer coordinates
|
||||||
Gdx.gl.glViewport(0, 0, backW, backH);
|
Gdx.gl.glViewport(0, 0, backW, backH);
|
||||||
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
|
Gdx.gl.glClearColor(0f, 0f, 0f, 1f);
|
||||||
@@ -229,5 +276,8 @@ public class GameScreen implements Screen {
|
|||||||
|
|
||||||
shadowBatch.dispose();
|
shadowBatch.dispose();
|
||||||
shadowLight.dispose();
|
shadowLight.dispose();
|
||||||
|
if (pauseMenu != null) pauseMenu.dispose();
|
||||||
|
if (frameBuffer != null) frameBuffer.dispose();
|
||||||
|
if (screenBatch != null) screenBatch.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,16 @@ public class FpsCameraController {
|
|||||||
|
|
||||||
/** Call from Screen.show() */
|
/** Call from Screen.show() */
|
||||||
public void onShow() {
|
public void onShow() {
|
||||||
|
captureMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Call from Screen.hide() */
|
||||||
|
public void onHide() {
|
||||||
|
releaseMouse();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Explicitly capture mouse (used when resuming from pause). */
|
||||||
|
public void captureMouse() {
|
||||||
mouseCaptured = true;
|
mouseCaptured = true;
|
||||||
centerX = Gdx.graphics.getWidth() / 2;
|
centerX = Gdx.graphics.getWidth() / 2;
|
||||||
centerY = Gdx.graphics.getHeight() / 2;
|
centerY = Gdx.graphics.getHeight() / 2;
|
||||||
@@ -76,8 +86,8 @@ public class FpsCameraController {
|
|||||||
captureWarmupFrames = WARMUP_FRAMES;
|
captureWarmupFrames = WARMUP_FRAMES;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Call from Screen.hide() */
|
/** Explicitly release mouse (used when pausing). */
|
||||||
public void onHide() {
|
public void releaseMouse() {
|
||||||
mouseCaptured = false;
|
mouseCaptured = false;
|
||||||
Gdx.graphics.setSystemCursor(Cursor.SystemCursor.Arrow);
|
Gdx.graphics.setSystemCursor(Cursor.SystemCursor.Arrow);
|
||||||
}
|
}
|
||||||
@@ -95,23 +105,8 @@ public class FpsCameraController {
|
|||||||
|
|
||||||
/** Update each frame with delta time */
|
/** Update each frame with delta time */
|
||||||
public void update(float delta, World3D world) {
|
public void update(float delta, World3D world) {
|
||||||
// --- ESC: release mouse, stop movement / camera
|
// if we don't own the mouse, just don't move camera / player
|
||||||
if (Gdx.input.isKeyJustPressed(Input.Keys.ESCAPE)) {
|
|
||||||
mouseCaptured = false;
|
|
||||||
Gdx.graphics.setSystemCursor(Cursor.SystemCursor.Arrow);
|
|
||||||
}
|
|
||||||
|
|
||||||
// only capture inputs if focused/captured
|
|
||||||
if (!mouseCaptured) {
|
if (!mouseCaptured) {
|
||||||
if (Gdx.input.justTouched()) { // if clicked
|
|
||||||
mouseCaptured = true;
|
|
||||||
centerX = Gdx.graphics.getWidth() / 2;
|
|
||||||
centerY = Gdx.graphics.getHeight() / 2;
|
|
||||||
Gdx.input.setCursorPosition(centerX, centerY);
|
|
||||||
ensureInvisibleCursor();
|
|
||||||
Gdx.graphics.setCursor(invisibleCursor);
|
|
||||||
captureWarmupFrames = WARMUP_FRAMES;
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package wtf.beatrice.retrorender.engine;
|
||||||
|
|
||||||
|
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.BitmapFont;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.GlyphLayout;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
|
||||||
|
import com.badlogic.gdx.graphics.g2d.freetype.FreeTypeFontGenerator;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PauseMenu {
|
||||||
|
|
||||||
|
private final SpriteBatch batch;
|
||||||
|
private final BitmapFont font;
|
||||||
|
private final Texture pixel;
|
||||||
|
|
||||||
|
// simple action enum for the menu
|
||||||
|
public enum MenuAction {
|
||||||
|
NONE,
|
||||||
|
RESUME,
|
||||||
|
QUIT
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple button model
|
||||||
|
private static class Button {
|
||||||
|
String label;
|
||||||
|
MenuAction action;
|
||||||
|
float x, y, w, h;
|
||||||
|
|
||||||
|
Button(String label, MenuAction action) {
|
||||||
|
this.label = label;
|
||||||
|
this.action = action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<Button> buttons = new ArrayList<>();
|
||||||
|
|
||||||
|
public PauseMenu() {
|
||||||
|
batch = new SpriteBatch();
|
||||||
|
|
||||||
|
// Same style as DebugHud
|
||||||
|
FreeTypeFontGenerator generator =
|
||||||
|
new FreeTypeFontGenerator(Gdx.files.internal("fonts/red-hat-mono.ttf"));
|
||||||
|
FreeTypeFontGenerator.FreeTypeFontParameter param =
|
||||||
|
new FreeTypeFontGenerator.FreeTypeFontParameter();
|
||||||
|
|
||||||
|
param.size = 12;
|
||||||
|
param.color = Color.WHITE;
|
||||||
|
param.mono = true;
|
||||||
|
param.hinting = FreeTypeFontGenerator.Hinting.None;
|
||||||
|
param.borderWidth = 0.2f;
|
||||||
|
param.borderColor = Color.WHITE;
|
||||||
|
param.shadowOffsetX = 0;
|
||||||
|
param.shadowOffsetY = 0;
|
||||||
|
param.minFilter = Texture.TextureFilter.Nearest;
|
||||||
|
param.magFilter = Texture.TextureFilter.Nearest;
|
||||||
|
|
||||||
|
font = generator.generateFont(param);
|
||||||
|
generator.dispose();
|
||||||
|
|
||||||
|
// create buttons
|
||||||
|
buttons.add(new Button(" Resume ", MenuAction.RESUME));
|
||||||
|
buttons.add(new Button(" Quit ", MenuAction.QUIT));
|
||||||
|
|
||||||
|
// 1x1 pixel texture (white, we tint it)
|
||||||
|
Pixmap pm = new Pixmap(1, 1, Pixmap.Format.RGBA8888);
|
||||||
|
pm.setColor(1f, 1f, 1f, 1f);
|
||||||
|
pm.fill();
|
||||||
|
pixel = new Texture(pm);
|
||||||
|
pm.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void render(int width, int height) {
|
||||||
|
batch.getProjectionMatrix().setToOrtho2D(0, 0, width, height);
|
||||||
|
batch.begin();
|
||||||
|
|
||||||
|
// --- black bar at bottom ---
|
||||||
|
float barH = 40f; // in retro pixels
|
||||||
|
float barY = 0f;
|
||||||
|
|
||||||
|
batch.setColor(0f, 0f, 0f, 0.9f);
|
||||||
|
batch.draw(pixel, 0f, barY, width, barH);
|
||||||
|
|
||||||
|
// --- buttons ---
|
||||||
|
float spacing = 8f; // horizontal space between buttons
|
||||||
|
|
||||||
|
// measure each button label
|
||||||
|
GlyphLayout layout = new GlyphLayout();
|
||||||
|
float totalWidth = 0f;
|
||||||
|
float maxButtonH = 0f;
|
||||||
|
|
||||||
|
for (Button b : buttons) {
|
||||||
|
layout.setText(font, b.label);
|
||||||
|
float bw = layout.width + 16f; // padding
|
||||||
|
float bh = layout.height + 8f;
|
||||||
|
b.w = bw;
|
||||||
|
b.h = bh;
|
||||||
|
totalWidth += bw;
|
||||||
|
if (bh > maxButtonH) maxButtonH = bh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add spacing between buttons
|
||||||
|
if (!buttons.isEmpty()) {
|
||||||
|
totalWidth += spacing * (buttons.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
float startX = (width - totalWidth) / 2f;
|
||||||
|
float centerY = barY + (barH - maxButtonH) / 2f;
|
||||||
|
|
||||||
|
// draw each button
|
||||||
|
float currentX = startX;
|
||||||
|
for (Button b : buttons) {
|
||||||
|
b.x = currentX;
|
||||||
|
b.y = centerY;
|
||||||
|
|
||||||
|
// button background
|
||||||
|
batch.setColor(0.1f, 0.1f, 0.1f, 1f);
|
||||||
|
batch.draw(pixel, b.x, b.y, b.w, b.h);
|
||||||
|
|
||||||
|
// simple 1px white border
|
||||||
|
batch.setColor(1f, 1f, 1f, 1f);
|
||||||
|
float border = 1f;
|
||||||
|
// top
|
||||||
|
batch.draw(pixel, b.x, b.y + b.h - border, b.w, border);
|
||||||
|
// bottom
|
||||||
|
batch.draw(pixel, b.x, b.y, b.w, border);
|
||||||
|
// left
|
||||||
|
batch.draw(pixel, b.x, b.y, border, b.h);
|
||||||
|
// right
|
||||||
|
batch.draw(pixel, b.x + b.w - border, b.y, border, b.h);
|
||||||
|
|
||||||
|
// text
|
||||||
|
layout.setText(font, b.label);
|
||||||
|
float textX = b.x + (b.w - layout.width) / 2f;
|
||||||
|
float textY = b.y + b.h - (b.h - layout.height) / 2f - 2f;
|
||||||
|
font.draw(batch, layout, textX, textY);
|
||||||
|
|
||||||
|
currentX += b.w + spacing;
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
public MenuAction getActionAt(float x, float y) {
|
||||||
|
for (Button b : buttons) {
|
||||||
|
if (x >= b.x && x <= b.x + b.w &&
|
||||||
|
y >= b.y && y <= b.y + b.h) {
|
||||||
|
return b.action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MenuAction.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void dispose() {
|
||||||
|
batch.dispose();
|
||||||
|
font.dispose();
|
||||||
|
pixel.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user