com.bladecoder.engine.ui.retro.RetroSceneScreen Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of blade-engine Show documentation
Show all versions of blade-engine Show documentation
Classic point and click adventure game engine
/*******************************************************************************
* Copyright 2014 Rafael Garcia Moreno.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.bladecoder.engine.ui.retro;
import java.io.IOException;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.Input.Peripheral;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.GL20;
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.TextureAtlas;
import com.badlogic.gdx.graphics.glutils.HdpiUtils;
import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Polygon;
import com.badlogic.gdx.math.Rectangle;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.scenes.scene2d.InputEvent;
import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.scenes.scene2d.ui.Button;
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.bladecoder.engine.i18n.I18N;
import com.bladecoder.engine.model.BaseActor;
import com.bladecoder.engine.model.InteractiveActor;
import com.bladecoder.engine.model.Scene;
import com.bladecoder.engine.model.Transition;
import com.bladecoder.engine.model.World;
import com.bladecoder.engine.model.World.AssetState;
import com.bladecoder.engine.ui.DialogUI;
import com.bladecoder.engine.ui.Pointer;
import com.bladecoder.engine.ui.Recorder;
import com.bladecoder.engine.ui.SceneFitViewport;
import com.bladecoder.engine.ui.SceneScreen;
import com.bladecoder.engine.ui.TesterBot;
import com.bladecoder.engine.ui.TextManagerUI;
import com.bladecoder.engine.ui.UI;
import com.bladecoder.engine.ui.UI.Screens;
import com.bladecoder.engine.util.DPIUtils;
import com.bladecoder.engine.util.EngineLogger;
import com.bladecoder.engine.util.RectangleRenderer;
public class RetroSceneScreen implements SceneScreen {
private static final float UI_SCREEN_PERCENT = 1 - 144.0f / 200.0f; // % of
// screen
// height
// of
// verbui;
private UI ui;
private Stage stage;
// we need an stage for the TextManagerUI because it runs inside the world
// viewport
private Stage worldViewportStage;
private VerbUI verbUI;
private DialogUI dialogUI;
private TextManagerUI textManagerUI;
private ShapeRenderer renderer;
private Button menuButton;
private Recorder recorder;
private TesterBot testerBot;
private final Viewport screenViewport;
private final Viewport worldViewport;
private final Vector3 unprojectTmp = new Vector3();
private final StringBuilder sbTmp = new StringBuilder();
// BaseActor under the cursor
private InteractiveActor currentActor = null;
private boolean drawHotspots = false;
private float speed = 1.0f;
private static enum UIStates {
SCENE_MODE, CUT_MODE, PLAY_MODE, PAUSE_MODE, DIALOG_MODE, TESTER_BOT_MODE
};
private UIStates state = UIStates.SCENE_MODE;
private final GlyphLayout textLayout = new GlyphLayout();
private Pointer pointer;
private final GestureDetector inputProcessor = new GestureDetector(new GestureDetector.GestureAdapter() {
@Override
public boolean touchDown(float x, float y, int pointer, int button) {
return true;
}
@Override
public boolean tap(float x, float y, int count, int button) {
EngineLogger.debug("Event TAP button: " + button);
World w = World.getInstance();
if (state == UIStates.PAUSE_MODE || state == UIStates.PLAY_MODE || state == UIStates.TESTER_BOT_MODE)
return true;
if (drawHotspots)
drawHotspots = false;
else {
if (w.inCutMode() && !recorder.isRecording()) {
w.getTextManager().next();
} else if (state == UIStates.SCENE_MODE) {
sceneClick(button);
}
}
return true;
}
@Override
public boolean longPress(float x, float y) {
EngineLogger.debug("Event LONG PRESS");
if (state == UIStates.SCENE_MODE) {
drawHotspots = true;
}
return false;
}
@Override
public boolean pan(float x, float y, float deltaX, float deltaY) {
return true;
}
@Override
public boolean panStop(float x, float y, int pointer, int button) {
return true;
}
}) {
@Override
public boolean keyUp(int keycode) {
switch (keycode) {
case Input.Keys.ESCAPE:
case Input.Keys.BACK:
case Input.Keys.MENU:
ui.setCurrentScreen(Screens.MENU_SCREEN);
break;
case Input.Keys.SPACE:
if (drawHotspots)
drawHotspots = false;
break;
}
return true;
}
@Override
public boolean keyTyped(char character) {
switch (character) {
case 'd':
EngineLogger.toggle();
break;
case '1':
EngineLogger.setDebugLevel(EngineLogger.DEBUG0);
break;
case '2':
EngineLogger.setDebugLevel(EngineLogger.DEBUG1);
break;
case '3':
EngineLogger.setDebugLevel(EngineLogger.DEBUG2);
break;
case 'f':
// ui.toggleFullScreen();
break;
case 's':
try {
World.getInstance().saveGameState();
} catch (IOException e) {
EngineLogger.error(e.getMessage());
}
break;
case 'l':
try {
World.getInstance().loadGameState();
} catch (IOException e) {
EngineLogger.error(e.getMessage());
}
break;
case 't':
testerBot.setEnabled(!testerBot.isEnabled());
break;
case '.':
if (recorder.isRecording())
recorder.setRecording(false);
else
recorder.setRecording(true);
break;
case ',':
if (recorder.isPlaying())
recorder.setPlaying(false);
else {
recorder.load();
recorder.setPlaying(true);
}
break;
case 'p':
if (World.getInstance().isPaused()) {
World.getInstance().resume();
} else {
World.getInstance().pause();
}
break;
case ' ':
if (state == UIStates.SCENE_MODE) {
drawHotspots = true;
}
break;
}
// FIXME: This is returning false even in the cases where we
// actually process the character
return false;
}
};
public RetroSceneScreen() {
screenViewport = new SceneFitViewport();
worldViewport = new Viewport() {
// This is the World Viewport. It is like a ScreenViewport but the
// camera is the same that the screenViewport;
@Override
public void apply(boolean centerCamera) {
HdpiUtils.glViewport(getScreenX(), getScreenY(), getScreenWidth(), getScreenHeight());
getCamera().viewportWidth = getScreenWidth();
getCamera().viewportHeight = getScreenHeight();
if (centerCamera)
getCamera().position.set(getScreenWidth() / 2, getScreenHeight() / 2, 0);
getCamera().update();
}
};
worldViewport.setCamera(screenViewport.getCamera());
}
public UI getUI() {
return ui;
}
private void setUIState(UIStates s) {
if (state == s)
return;
switch (s) {
case PAUSE_MODE:
case PLAY_MODE:
case TESTER_BOT_MODE:
case CUT_MODE:
dialogUI.setVisible(false);
verbUI.hide();
pointer.hide();
break;
case DIALOG_MODE:
dialogUI.setVisible(true);
verbUI.hide();
pointer.show();
break;
case SCENE_MODE:
dialogUI.setVisible(false);
verbUI.show();
pointer.show();
break;
}
state = s;
}
/**
* Sets the game speed. Can be used to fastfordward
*
* @param s
* The multiplier speed. ej. 2.0
*/
public void setSpeed(float s) {
speed = s;
}
public float getSpeed() {
return speed;
}
private void update(float delta) {
final World world = World.getInstance();
currentActor = null;
if (!world.isDisposed()) {
world.update(delta * speed);
}
AssetState assetState = world.getAssetState();
if (assetState != AssetState.LOADED) {
ui.setCurrentScreen(Screens.LOADING_SCREEN);
return;
}
// CHECK FOR STATE CHANGES
switch (state) {
case CUT_MODE:
if (!world.inCutMode())
setUIState(UIStates.SCENE_MODE);
break;
case DIALOG_MODE:
if (world.getCurrentDialog() == null)
setUIState(UIStates.SCENE_MODE);
else if (world.inCutMode())
setUIState(UIStates.CUT_MODE);
break;
case PAUSE_MODE:
if (!world.isPaused())
setUIState(UIStates.SCENE_MODE);
break;
case PLAY_MODE:
if (!recorder.isPlaying())
setUIState(UIStates.SCENE_MODE);
break;
case TESTER_BOT_MODE:
if (!testerBot.isEnabled())
setUIState(UIStates.SCENE_MODE);
break;
case SCENE_MODE:
if (world.isPaused())
setUIState(UIStates.PAUSE_MODE);
else if (world.inCutMode())
setUIState(UIStates.CUT_MODE);
else if (recorder.isPlaying())
setUIState(UIStates.PLAY_MODE);
else if (testerBot.isEnabled())
setUIState(UIStates.TESTER_BOT_MODE);
else if (world.getCurrentDialog() != null)
setUIState(UIStates.DIALOG_MODE);
break;
}
stage.act(delta);
worldViewportStage.act(delta);
if (state == UIStates.PAUSE_MODE)
return;
recorder.update(delta * speed);
testerBot.update(delta * speed);
if (state == UIStates.SCENE_MODE) {
final float tolerance;
if (Gdx.input.isPeripheralAvailable(Peripheral.MultitouchScreen))
tolerance = DPIUtils.getTouchMinSize();
else
tolerance = 0;
currentActor = world.getInteractiveActorAtInput(worldViewport, tolerance);
verbUI.setCurrentActor(currentActor);
if (world.getInventory().isVisible())
verbUI.show();
else
verbUI.hide();
}
}
@Override
public void render(float delta) {
final World world = World.getInstance();
update(delta);
// Gdx.gl.glClearColor(0, 0, 0, 1);
// Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
if (world.getAssetState() != AssetState.LOADED)
return;
SpriteBatch batch = ui.getBatch();
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
// WORLD CAMERA
if (world.getInventory().isVisible()) {
worldViewport.setScreenY(screenViewport.getScreenY() + (int) verbUI.getHeight());
worldViewport.setScreenHeight(screenViewport.getScreenHeight() - (int) verbUI.getHeight());
world.resize(world.getWidth(), world.getHeight() * (1 - UI_SCREEN_PERCENT));
} else {
worldViewport.setScreenY(screenViewport.getScreenY());
worldViewport.setScreenHeight(screenViewport.getScreenHeight());
world.resize(world.getWidth(), world.getHeight());
}
worldViewport.apply(true);
world.draw();
// DRAW DEBUG BBOXES
if (EngineLogger.debugMode() && EngineLogger.getDebugLevel() == EngineLogger.DEBUG1) {
renderer.setProjectionMatrix(world.getSceneCamera().combined);
world.getCurrentScene().drawBBoxLines(renderer);
renderer.end();
}
// SCREEN CAMERA
batch.setProjectionMatrix(worldViewport.getCamera().combined);
batch.begin();
// DRAW DEBUG STRING
if (EngineLogger.debugMode()) {
drawDebugText(batch);
}
Transition t = world.getTransition();
t.draw(batch, worldViewport.getScreenWidth(), worldViewport.getScreenHeight());
recorder.draw(batch);
testerBot.draw(batch);
if (drawHotspots)
drawHotspots(batch);
batch.end();
worldViewportStage.draw();
// STAGE CAMERA
screenViewport.apply(true);
stage.draw();
}
private void drawDebugText(SpriteBatch batch) {
World w = World.getInstance();
w.getSceneCamera().getInputUnProject(worldViewport, unprojectTmp);
Color color;
sbTmp.setLength(0);
if (EngineLogger.lastError != null) {
sbTmp.append(EngineLogger.lastError);
color = Color.RED;
} else {
sbTmp.append("( ");
sbTmp.append((int) unprojectTmp.x);
sbTmp.append(", ");
sbTmp.append((int) unprojectTmp.y);
sbTmp.append(") FPS:");
sbTmp.append(Gdx.graphics.getFramesPerSecond());
// sbTmp.append(" Density:");
// sbTmp.append(Gdx.graphics.getDensity());
// sbTmp.append(" UI Multiplier:");
// sbTmp.append(DPIUtils.getSizeMultiplier());
sbTmp.append(" UI STATE: ");
sbTmp.append(state.toString());
if (w.getCurrentScene().getPlayer() != null) {
sbTmp.append(" Depth Scl: ");
sbTmp.append(w.getCurrentScene().getFakeDepthScale(unprojectTmp.y));
}
color = Color.WHITE;
}
String strDebug = sbTmp.toString();
textLayout.setText(ui.getSkin().getFont("debug"), strDebug, color, worldViewport.getScreenWidth(), Align.left,
true);
RectangleRenderer.draw(batch, 0, worldViewport.getScreenHeight() - textLayout.height - 10, textLayout.width,
textLayout.height + 10, Color.BLACK);
ui.getSkin().getFont("debug").draw(batch, textLayout, 0, worldViewport.getScreenHeight() - 5);
// Draw actor states when debug
if (EngineLogger.getDebugLevel() == EngineLogger.DEBUG1) {
for (BaseActor a : w.getCurrentScene().getActors().values()) {
Rectangle r = a.getBBox().getBoundingRectangle();
sbTmp.setLength(0);
sbTmp.append(a.getId());
if (a instanceof InteractiveActor && ((InteractiveActor) a).getState() != null)
sbTmp.append(".").append(((InteractiveActor) a).getState());
unprojectTmp.set(r.getX(), r.getY(), 0);
w.getSceneCamera().scene2screen(worldViewport, unprojectTmp);
if (w.getInventory().isVisible()) {
// unprojectTmp.y += verbUI.getHeight();
}
ui.getSkin().getFont("debug").draw(batch, sbTmp.toString(), unprojectTmp.x, unprojectTmp.y);
}
}
}
private void drawHotspots(SpriteBatch batch) {
final World world = World.getInstance();
for (BaseActor a : world.getCurrentScene().getActors().values()) {
if (!(a instanceof InteractiveActor) || !a.isVisible() || a == world.getCurrentScene().getPlayer())
continue;
InteractiveActor ia = (InteractiveActor) a;
if (!ia.canInteract())
continue;
Polygon p = a.getBBox();
if (p == null) {
EngineLogger.error("ERROR DRAWING HOTSPOT FOR: " + a.getId());
}
Rectangle r = a.getBBox().getBoundingRectangle();
unprojectTmp.set(r.getX() + r.getWidth() / 2, r.getY() + r.getHeight() / 2, 0);
world.getSceneCamera().scene2screen(worldViewport, unprojectTmp);
if (world.getInventory().isVisible()) {
// unprojectTmp.y += verbUI.getHeight();
}
if (ia.getDesc() == null) {
float size = DPIUtils.ICON_SIZE * DPIUtils.getSizeMultiplier();
Drawable drawable = ((TextureRegionDrawable) getUI().getSkin().getDrawable("circle")).tint(Color.RED);
drawable.draw(batch, unprojectTmp.x - size / 2, unprojectTmp.y - size / 2, size, size);
} else {
BitmapFont font = getUI().getSkin().getFont("desc");
String desc = ia.getDesc();
if (desc.charAt(0) == I18N.PREFIX)
desc = I18N.getString(desc.substring(1));
textLayout.setText(font, desc);
float textX = unprojectTmp.x - textLayout.width / 2;
float textY = unprojectTmp.y + textLayout.height;
RectangleRenderer.draw(batch, textX - 8, textY - textLayout.height - 8, textLayout.width + 16,
textLayout.height + 16, Color.BLACK);
font.draw(batch, textLayout, textX, textY);
}
}
}
@Override
public void resize(int width, int height) {
final World world = World.getInstance();
if (!world.isDisposed()) {
screenViewport.setWorldSize(world.getWidth(), world.getHeight());
screenViewport.update(width, height, true);
worldViewport.setScreenBounds(screenViewport.getScreenX(), screenViewport.getScreenY(),
screenViewport.getScreenWidth(), screenViewport.getScreenHeight());
world.resize(screenViewport.getWorldWidth(), screenViewport.getWorldHeight());
}
pointer.resize();
verbUI.setSize(screenViewport.getScreenWidth(), screenViewport.getScreenHeight() * UI_SCREEN_PERCENT);
float size = DPIUtils.getPrefButtonSize();
float margin = DPIUtils.getMarginSize();
menuButton.setSize(size, size);
menuButton.setPosition(
stage.getViewport().getScreenWidth() - menuButton.getWidth()
- margin, stage.getViewport()
.getScreenHeight()
- menuButton.getHeight()
- margin);
}
public void dispose() {
renderer.dispose();
stage.dispose();
worldViewportStage.dispose();
}
private void retrieveAssets(TextureAtlas atlas) {
renderer = new ShapeRenderer();
}
private void sceneClick(int button) {
World w = World.getInstance();
w.getSceneCamera().getInputUnProject(worldViewport, unprojectTmp);
Scene s = w.getCurrentScene();
if (currentActor != null) {
if (EngineLogger.debugMode()) {
EngineLogger.debug(currentActor.toString());
}
actorClick(currentActor, button);
} else if (s.getPlayer() != null) {
if (s.getPlayer().getVerb("goto") != null) {
runVerb(s.getPlayer(), "goto", null);
} else {
Vector2 pos = new Vector2(unprojectTmp.x, unprojectTmp.y);
if (recorder.isRecording()) {
recorder.add(pos);
}
s.getPlayer().goTo(pos, null, false);
}
}
}
public void actorClick(InteractiveActor a, int button) {
runVerb(a, verbUI.getCurrentVerb(), verbUI.getTarget());
}
/**
* Run actor verb and handles recording
*
* @param a
* @param verb
* @param target
*/
public void runVerb(InteractiveActor a, String verb, String target) {
if (recorder.isRecording()) {
recorder.add(a.getId(), verb, target);
}
a.runVerb(verb, target);
}
@Override
public void show() {
retrieveAssets(ui.getUIAtlas());
stage = new Stage(screenViewport);
// stage.addActor(textManagerUI);
stage.addActor(dialogUI);
stage.addActor(menuButton);
stage.addActor(verbUI);
stage.addActor(pointer);
menuButton.addListener(new ClickListener() {
public void clicked(InputEvent event, float x, float y) {
ui.setCurrentScreen(Screens.MENU_SCREEN);
}
});
worldViewportStage = new Stage(worldViewport);
worldViewportStage.addActor(textManagerUI);
final InputMultiplexer multiplexer = new InputMultiplexer();
multiplexer.addProcessor(stage);
multiplexer.addProcessor(inputProcessor);
Gdx.input.setInputProcessor(multiplexer);
if (World.getInstance().isDisposed()) {
try {
World.getInstance().load();
} catch (Exception e) {
EngineLogger.error("ERROR LOADING GAME", e);
dispose();
Gdx.app.exit();
}
}
World.getInstance().resume();
}
@Override
public void hide() {
World.getInstance().pause();
currentActor = null;
dispose();
}
@Override
public void pause() {
World.getInstance().pause();
}
@Override
public void resume() {
World.getInstance().resume();
}
public Viewport getViewport() {
return screenViewport;
}
public InteractiveActor getCurrentActor() {
return currentActor;
}
@Override
public void setUI(UI ui) {
this.ui = ui;
recorder = ui.getRecorder();
testerBot = ui.getTesterBot();
textManagerUI = new TextManagerUI(ui.getSkin());
menuButton = new Button(ui.getSkin(), "menu");
dialogUI = new DialogUI(ui);
verbUI = new VerbUI(this);
pointer = new Pointer(ui.getSkin());
}
}