add main menu keyboard controls

This commit is contained in:
Quillraven
2025-06-01 17:52:08 +02:00
parent 7e7ae56b31
commit 878eb924a5
10 changed files with 173 additions and 22 deletions

View File

@@ -4,5 +4,6 @@ public enum Command {
LEFT,
RIGHT,
DOWN,
UP
UP,
SELECT
}

View File

@@ -3,5 +3,6 @@ package io.github.com.quillraven.input;
public interface ControllerState {
void keyDown(Command command);
void keyUp(Command command);
default void keyUp(Command command) {
}
}

View File

@@ -1,15 +1,18 @@
package io.github.com.quillraven.input;
import com.badlogic.ashley.core.Engine;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.utils.ImmutableArray;
import io.github.com.quillraven.component.Controller;
import io.github.com.quillraven.component.Move;
public class GameControllerState implements ControllerState {
private final ImmutableArray<Entity> controllerEntities;
public GameControllerState(ImmutableArray<Entity> controllerEntities) {
this.controllerEntities = controllerEntities;
public GameControllerState(Engine engine) {
this.controllerEntities = engine.getEntitiesFor(Family.all(Controller.class).get());
}
private void moveEntities(float dx, float dy) {

View File

@@ -1,9 +1,9 @@
package io.github.com.quillraven.input;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.utils.ImmutableArray;
import com.badlogic.ashley.core.Engine;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.InputAdapter;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.GdxRuntimeException;
import java.util.HashMap;
@@ -14,20 +14,28 @@ public class KeyboardController extends InputAdapter {
Map.entry(Input.Keys.W, Command.UP),
Map.entry(Input.Keys.S, Command.DOWN),
Map.entry(Input.Keys.A, Command.LEFT),
Map.entry(Input.Keys.D, Command.RIGHT)
Map.entry(Input.Keys.D, Command.RIGHT),
Map.entry(Input.Keys.SPACE, Command.SELECT)
);
private final boolean[] commandState;
private final Map<Class<? extends ControllerState>, ControllerState> stateCache;
private ControllerState activeState;
public KeyboardController(Class<? extends ControllerState> initialState, ImmutableArray<Entity> controllerEntities) {
public KeyboardController(Class<? extends ControllerState> initialState,
Engine engine,
Stage stage) {
this.commandState = new boolean[Command.values().length];
this.stateCache = new HashMap<>();
this.activeState = null;
this.stateCache.put(IdleControllerState.class, new IdleControllerState());
this.stateCache.put(GameControllerState.class, new GameControllerState(controllerEntities));
if (engine != null) {
this.stateCache.put(GameControllerState.class, new GameControllerState(engine));
}
if (stage != null) {
this.stateCache.put(UiControllerState.class, new UiControllerState(stage));
}
setActiveState(initialState);
}

View File

@@ -0,0 +1,17 @@
package io.github.com.quillraven.input;
import com.badlogic.gdx.scenes.scene2d.Stage;
public class UiControllerState implements ControllerState {
private final Stage stage;
public UiControllerState(Stage stage) {
this.stage = stage;
}
@Override
public void keyDown(Command command) {
this.stage.getRoot().fire(new UiEvent(command));
}
}

View File

@@ -0,0 +1,15 @@
package io.github.com.quillraven.input;
import com.badlogic.gdx.scenes.scene2d.Event;
public class UiEvent extends Event {
private final Command command;
public UiEvent(Command command) {
this.command = command;
}
public Command getCommand() {
return command;
}
}

View File

@@ -1,10 +1,7 @@
package io.github.com.quillraven.screen;
import com.badlogic.ashley.core.Engine;
import com.badlogic.ashley.core.Entity;
import com.badlogic.ashley.core.EntitySystem;
import com.badlogic.ashley.core.Family;
import com.badlogic.ashley.utils.ImmutableArray;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.ScreenAdapter;
@@ -15,7 +12,6 @@ import com.badlogic.gdx.utils.Disposable;
import io.github.com.quillraven.GdxGame;
import io.github.com.quillraven.asset.MapAsset;
import io.github.com.quillraven.audio.AudioService;
import io.github.com.quillraven.component.Controller;
import io.github.com.quillraven.input.GameControllerState;
import io.github.com.quillraven.input.KeyboardController;
import io.github.com.quillraven.system.AnimationSystem;
@@ -48,8 +44,7 @@ public class GameScreen extends ScreenAdapter {
this.physicWorld.setAutoClearForces(false);
this.engine = new Engine();
this.tiledAshleySpawner = new TiledAshleySpawner(this.engine, this.physicWorld, this.game.getAssetService());
ImmutableArray<Entity> controllerEntities = this.engine.getEntitiesFor(Family.all(Controller.class).get());
this.keyboardController = new KeyboardController(GameControllerState.class, controllerEntities);
this.keyboardController = new KeyboardController(GameControllerState.class, engine, null);
// add ECS systems
this.engine.addSystem(new PhysicMoveSystem());

View File

@@ -9,6 +9,8 @@ import com.badlogic.gdx.utils.viewport.Viewport;
import io.github.com.quillraven.GdxGame;
import io.github.com.quillraven.asset.MusicAsset;
import io.github.com.quillraven.asset.SkinAsset;
import io.github.com.quillraven.input.KeyboardController;
import io.github.com.quillraven.input.UiControllerState;
import io.github.com.quillraven.ui.model.MenuViewModel;
import io.github.com.quillraven.ui.view.MenuView;
@@ -19,12 +21,14 @@ public class MenuScreen extends ScreenAdapter {
private final Skin skin;
private final Viewport uiViewport;
private final InputMultiplexer inputMultiplexer;
private final KeyboardController keyboardController;
public MenuScreen(GdxGame game) {
this.game = game;
this.uiViewport = new FitViewport(800f, 450f);
this.stage = new Stage(uiViewport, game.getBatch());
this.skin = game.getAssetService().get(SkinAsset.DEFAULT);
this.keyboardController = new KeyboardController(UiControllerState.class, null, stage);
this.inputMultiplexer = game.getInputMultiplexer();
}
@@ -37,6 +41,7 @@ public class MenuScreen extends ScreenAdapter {
public void show() {
this.inputMultiplexer.clear();
this.inputMultiplexer.addProcessor(stage);
this.inputMultiplexer.addProcessor(keyboardController);
this.stage.addActor(new MenuView(stage, skin, new MenuViewModel(game)));
this.game.getAudioService().playMusic(MusicAsset.MENU);

View File

@@ -12,23 +12,27 @@ import com.badlogic.gdx.scenes.scene2d.ui.Slider;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.GdxRuntimeException;
import io.github.com.quillraven.ui.model.MenuViewModel;
public class MenuView extends View<MenuViewModel> {
private final Image selectionImg;
private Group selectedItem;
public MenuView(Stage stage, Skin skin, MenuViewModel viewModel) {
super(stage, skin, viewModel);
this.selectionImg = new Image(skin, "selection");
this.selectionImg.setTouchable(Touchable.disabled);
selectMenuItem(this.findActor("startGameBtn"));
this.selectedItem = findActor(MenuOption.START_GAME.name());
selectMenuItem(this.selectedItem);
}
private void selectMenuItem(Group menuItem) {
if (selectionImg.getParent() != null) {
selectionImg.getParent().removeActor(selectionImg);
}
this.selectedItem = menuItem;
float extraSize = 7f;
float halfExtraSize = extraSize * 0.5f;
@@ -73,18 +77,21 @@ public class MenuView extends View<MenuViewModel> {
contentTable.padBottom(20.0f);
TextButton textButton = new TextButton("Start Game", skin);
textButton.setName("startGameBtn");
textButton.setName(MenuOption.START_GAME.name());
onClick(textButton, viewModel::startGame);
onEnter(textButton, this::selectMenuItem);
contentTable.add(textButton).row();
Slider musicSlider = setupVolumeSlider(contentTable, "Music Volume", viewModel.getMusicVolume());
Slider musicSlider = setupVolumeSlider(contentTable, "Music Volume", MenuOption.MUSIC_VOLUME);
musicSlider.setValue(viewModel.getMusicVolume());
onChange(musicSlider, (slider) -> viewModel.setMusicVolume(slider.getValue()));
Slider soundSlider = setupVolumeSlider(contentTable, "Sound Volume", viewModel.getSoundVolume());
Slider soundSlider = setupVolumeSlider(contentTable, "Sound Volume", MenuOption.SOUND_VOLUME);
soundSlider.setValue(viewModel.getSoundVolume());
onChange(soundSlider, (slider) -> viewModel.setSoundVolume(slider.getValue()));
textButton = new TextButton("Quit Game", skin);
textButton.setName(MenuOption.QUIT_GAME.name());
onClick(textButton, viewModel::quitGame);
onEnter(textButton, this::selectMenuItem);
contentTable.add(textButton).padTop(10.0f);
@@ -92,18 +99,82 @@ public class MenuView extends View<MenuViewModel> {
add(contentTable).align(Align.top).expandY().padTop(20f).row();
}
private Slider setupVolumeSlider(Table contentTable, String title, float initialValue) {
private Slider setupVolumeSlider(Table contentTable, String title, MenuOption menuOption) {
Table table = new Table();
table.setName(menuOption.name());
Label label = new Label(title, skin);
label.setColor(skin.getColor("sand"));
table.add(label).row();
Slider slider = new Slider(0.0f, 1f, 0.05f, false, skin);
slider.setValue(initialValue);
table.add(slider);
contentTable.add(table).padTop(10.0f).row();
onEnter(table, this::selectMenuItem);
return slider;
}
@Override
public void onDown() {
Group menuContentTable = this.selectedItem.getParent();
int currentIdx = menuContentTable.getChildren().indexOf(this.selectedItem, true);
if (currentIdx == -1) {
throw new GdxRuntimeException("'selectedItem' is not a child of 'menuContentTable'");
}
int numOptions = menuContentTable.getChildren().size;
currentIdx = (currentIdx + 1) % numOptions;
selectMenuItem((Group) menuContentTable.getChild(currentIdx));
}
@Override
public void onUp() {
Group menuContentTable = this.selectedItem.getParent();
int currentIdx = menuContentTable.getChildren().indexOf(this.selectedItem, true);
if (currentIdx == -1) {
throw new GdxRuntimeException("'selectedItem' is not a child of 'menuContentTable'");
}
int numOptions = menuContentTable.getChildren().size;
currentIdx = currentIdx == 0 ? numOptions - 1 : currentIdx - 1;
selectMenuItem((Group) menuContentTable.getChild(currentIdx));
}
@Override
public void onRight() {
MenuOption menuOption = MenuOption.valueOf(this.selectedItem.getName());
switch (menuOption) {
case MUSIC_VOLUME, SOUND_VOLUME -> {
Slider slider = (Slider) this.selectedItem.getChild(1);
slider.setValue(slider.getValue() + slider.getStepSize());
}
}
}
@Override
public void onLeft() {
MenuOption menuOption = MenuOption.valueOf(this.selectedItem.getName());
switch (menuOption) {
case MUSIC_VOLUME, SOUND_VOLUME -> {
Slider slider = (Slider) this.selectedItem.getChild(1);
slider.setValue(slider.getValue() - slider.getStepSize());
}
}
}
@Override
public void onSelect() {
MenuOption menuOption = MenuOption.valueOf(this.selectedItem.getName());
switch (menuOption) {
case START_GAME -> viewModel.startGame();
case QUIT_GAME -> viewModel.quitGame();
}
}
private enum MenuOption {
START_GAME,
MUSIC_VOLUME,
SOUND_VOLUME,
QUIT_GAME
}
}

View File

@@ -1,6 +1,8 @@
package io.github.com.quillraven.ui.view;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Event;
import com.badlogic.gdx.scenes.scene2d.EventListener;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.InputListener;
import com.badlogic.gdx.scenes.scene2d.Stage;
@@ -8,9 +10,10 @@ import com.badlogic.gdx.scenes.scene2d.ui.Skin;
import com.badlogic.gdx.scenes.scene2d.ui.Table;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import io.github.com.quillraven.input.UiEvent;
import io.github.com.quillraven.ui.model.ViewModel;
public abstract class View<T extends ViewModel> extends Table {
public abstract class View<T extends ViewModel> extends Table implements EventListener {
protected final Stage stage;
protected final Skin skin;
@@ -21,11 +24,43 @@ public abstract class View<T extends ViewModel> extends Table {
this.stage = stage;
this.skin = skin;
this.viewModel = viewModel;
this.stage.addListener(this);
setupUI();
}
protected abstract void setupUI();
public void onLeft() {
}
public void onRight() {
}
public void onUp() {
}
public void onDown() {
}
public void onSelect() {
}
@Override
public boolean handle(Event event) {
if (event instanceof UiEvent uiEvent) {
switch (uiEvent.getCommand()) {
case LEFT -> onLeft();
case RIGHT -> onRight();
case UP -> onUp();
case DOWN -> onDown();
case SELECT -> onSelect();
}
return true;
}
return false;
}
public static void onClick(Actor actor, OnEventConsumer consumer) {
actor.addListener(new ClickListener() {
@Override