com.jmolina.orb.screens.Level.java Source code

Java tutorial

Introduction

Here is the source code for com.jmolina.orb.screens.Level.java

Source

/*
 * IMPULSE ORB
 * Copyright (C) 2016 Juan M. Molina
 *
 * This file is part of the IMPULSE ORB source code.
 *
 * IMPULSE ORB is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * IMPULSE ORB is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.jmolina.orb.screens;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.input.GestureDetector;
import com.badlogic.gdx.math.Interpolation;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.physics.box2d.Box2DDebugRenderer;
import com.badlogic.gdx.scenes.scene2d.actions.Actions;
import com.badlogic.gdx.utils.viewport.FitViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import com.jmolina.orb.data.GameStats;
import com.jmolina.orb.data.Tick;
import com.jmolina.orb.elements.Element;
import com.jmolina.orb.elements.Magnetic;
import com.jmolina.orb.elements.Movable;
import com.jmolina.orb.elements.Orb;
import com.jmolina.orb.interfaces.LevelManager;
import com.jmolina.orb.interfaces.PlayServices;
import com.jmolina.orb.interfaces.SuperManager;
import com.jmolina.orb.listeners.GestureHandler;
import com.jmolina.orb.managers.GameManager;
import com.jmolina.orb.managers.ScreenManager;
import com.jmolina.orb.managers.SituationManager;
import com.jmolina.orb.managers.WorldManager;
import com.jmolina.orb.situations.Situation;
import com.jmolina.orb.stages.GestureStage;
import com.jmolina.orb.stages.HUDStage;
import com.jmolina.orb.stages.ParallaxStage;
import com.jmolina.orb.var.Utils;
import com.jmolina.orb.var.Var;
import com.jmolina.orb.widgets.debug.DebugTime;

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.alpha;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.delay;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.fadeIn;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.run;
import static com.badlogic.gdx.scenes.scene2d.actions.Actions.sequence;

/**
 * Clase base para los niveles del juego. Permite su construccin a partir de {@link Situation}s.
 * Renderiza todos los elementos del juego y los sincroniza con el mundo fsico. Controla los
 * eventos del juego.
 */
public class Level extends BaseScreen implements LevelManager {

    private final boolean DEBUG_WORLD = Var.DEBUG_RENDERER;
    private final boolean DEBUG_TIME = Var.DEBUG_FRAME_TIME;
    private final boolean DEBUG_INVULNERABLE = Var.DEBUG_INVULNERABILITY;
    private Box2DDebugRenderer debugRenderer = new Box2DDebugRenderer();
    private DebugTime debugTime = new DebugTime(getAssetManager());

    private final float GESTURE_HALF_TAP_SQUARE_SIZE = 13.0f;
    private final float GESTURE_TAP_COUNT_INTERVAL = 0.4f;
    private final float GESTURE_LONG_PRESS_DURATION = 1.1f;
    private final float GESTURE_MAX_FLING_DELAY = 0.1f;
    private final float IMPULSE_FACTOR = 0.6f;
    private final float IMPULSE_MAX = 50f;

    private Tick tick;
    private float pixelsPerMeter, impulse;
    private boolean locked, ticking, achievedRobocop, achievedItsOver9000, achievedHyperdrive;
    private Viewport worldViewport, gestureViewport, hudViewport, parallaxViewport;
    private GestureStage gestureStage;
    private ParallaxStage parallaxStage;
    private HUDStage hudStage;
    private Orb orb;
    private Vector2 lastOrbPosition;
    private GameStats stats;
    private ScreenManager.Key successScreen = ScreenManager.Key.LEVEL_SELECT;
    private Runnable orbIntro, orbDestroy, reset, unlock, toSuccess;
    private SituationManager situationManager;
    private WorldManager worldManager;

    /**
     * Constructor
     *
     * @param sm SuperManager
     */
    public Level(SuperManager sm) {
        super(sm);

        achievedRobocop = false;
        achievedItsOver9000 = false;
        achievedHyperdrive = false;

        tick = new Tick();
        pixelsPerMeter = getGameManager().getPixelsPerMeter();
        impulse = IMPULSE_FACTOR / getPixelsPerMeter();
        lastOrbPosition = new Vector2();
        stats = new GameStats();

        float worldWidth = VIEWPORT_WIDTH / getPixelsPerMeter();
        float worldHeight = VIEWPORT_HEIGHT / getPixelsPerMeter();
        worldViewport = new FitViewport(worldWidth, worldHeight);
        gestureViewport = new FitViewport(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
        parallaxViewport = new FitViewport(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
        hudViewport = new FitViewport(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

        hudStage = new HUDStage(this, getAssetManager(), hudViewport);
        gestureStage = new GestureStage(getAssetManager(), gestureViewport, getPixelsPerMeter());
        parallaxStage = new ParallaxStage(getAssetManager(), parallaxViewport, getPixelsPerMeter());

        worldManager = new WorldManager();
        setOrb(new Orb(getAssetManager(), worldManager.getWorld(), getPixelsPerMeter()));
        worldManager.bindContactHandler(this, getOrb());

        GestureDetector gestureDetector = new GestureDetector(GESTURE_HALF_TAP_SQUARE_SIZE,
                GESTURE_TAP_COUNT_INTERVAL, GESTURE_LONG_PRESS_DURATION, GESTURE_MAX_FLING_DELAY,
                new GestureHandler(this));

        situationManager = new SituationManager(getAssetManager(), worldManager.getWorld(), getOrb(),
                getPixelsPerMeter(), getMainStage(), worldViewport);

        addProcessor(hudStage);
        addProcessor(gestureStage);
        addProcessor(gestureDetector);
        createRunnables();
        disableTicking();
        lock();
    }

    /**
     * Crea los runables que se utilizarn como callbacks
     */
    private void createRunnables() {
        orbIntro = new Runnable() {
            @Override
            public void run() {
                getOrb().applyIntroAction();
                getGameManager().play(GameManager.Fx.Init);
            }
        };

        reset = new Runnable() {
            @Override
            public void run() {
                getOrb().reset();
                getHUDStage().reset();
                stats.newTry();
                lastOrbPosition = getOrb().getStartPosition();
                situationManager.removeAll();
            }
        };

        unlock = new Runnable() {
            @Override
            public void run() {
                unlock();
            }
        };

        orbDestroy = new Runnable() {
            @Override
            public void run() {
                getOrb().destroy();
            }
        };

        toSuccess = new Runnable() {
            @Override
            public void run() {
                switchToScreen(getSuccessScreen(), Hierarchy.LOWER);
            }
        };
    }

    /**
     * Renderiza el nivel de juego
     *
     * @param delta Tiempo de frame
     */
    @Override
    public void render(float delta) {
        if (DEBUG_TIME)
            debugTime.start();

        clear();
        updateSituations();
        syncActors();
        act(delta);
        syncBodies();
        updateForces();
        stepPhysics(delta);
        followCamera();
        syncActors();
        updateWidgets();
        draw();
        checkAchievements();
        checkSwitching();

        if (DEBUG_TIME)
            debugTime.end();
    }

    @Override
    protected void clear() {
        Gdx.gl.glClearColor(0.19921875f, 0.19921875f, 0.19921875f, 1.0f);
        Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
    }

    /**
     * {@inheritDoc}
     * Actualiza los viewports del juego
     */
    @Override
    public void resize(int width, int height) {
        super.resize(width, height);
        worldViewport.update(width, height);
        gestureViewport.update(width, height);
        hudViewport.update(width, height);
        parallaxViewport.update(width, height);
    }

    /**
     * {@inheritDoc}
     * Libera la memoria
     */
    @Override
    public void dispose() {
        situationManager.dispose();
        getHUDStage().dispose();
        getGestureStage().dispose();
        getParallaxStage().dispose();
        worldManager.dispose();
        super.dispose();
    }

    /**
     * Ejecuta la transicin de entrada en el nivel e inicializa las estadsticas
     */
    @Override
    public void show() {
        unsetInputProcessor();
        firstGame();
        getGameManager().play(GameManager.Track.Game);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void hide() {
        super.hide();
    }

    /**
     * {@inheritDoc}
     * Inicia el men de pausa
     */
    @Override
    public void pause() {
        super.pause();
        pauseGame();
    }

    /**
     * Si el juego estaba en marcha, lo pausa y muestra el men de pausa. Si estaba pausado, lo
     * despausa y contina.
     */
    @Override
    public void back() {
        if (!isLocked())
            pauseGame();
        else
            resumeGame();
    }

    /**
     * Cambia a la pantalla indicada
     *
     * @param screen Identificador de la siguiente pantalla
     * @param hierarchy Jerarqua de la siguiente pantalla respecto de la actual
     */
    @Override
    public void switchToScreen(final ScreenManager.Key screen, final Hierarchy hierarchy) {

        Runnable flagSwitch = new Runnable() {
            @Override
            public void run() {
                flagSwitch(screen, hierarchy);
            }
        };

        getHUDStage().addAction(sequence(
                Actions.addAction(fadeIn(TRANSITION_DURATION, Interpolation.pow2), getBackgroundStage().getRoot()),
                delay(TRANSITION_DURATION), getTransitionAction(Flow.LEAVING, hierarchy), run(flagSwitch)));
    }

    private void stepPhysics(float delta) {
        if (isLocked())
            return;

        worldManager.step(delta);
    }

    /**
     * Devuelve el valor actual de {@link #pixelsPerMeter}
     *
     * @return {@link #pixelsPerMeter}
     */
    private float getPixelsPerMeter() {
        return pixelsPerMeter;
    }

    /**
     * Devuelve la Stage del fondo de scroll parallax
     *
     * @return {@link #parallaxStage}
     */
    private ParallaxStage getParallaxStage() {
        return parallaxStage;
    }

    /**
     * Devuelve la Stage de dibujado de gestos
     *
     * @return {@link #gestureStage}
     */
    private GestureStage getGestureStage() {
        return gestureStage;
    }

    /**
     * Devuelve la Stage del HUD
     *
     * @return {@link #hudStage}
     */
    private HUDStage getHUDStage() {
        return hudStage;
    }

    /**
     * Devuelve las estadsticas del juego en curso
     *
     * @return {@link #stats}
     */
    private GameStats getStats() {
        return stats;
    }

    /**
     * Guarda la pantalla de xito correspondiente a este nivel
     */
    protected void setSuccessScreen(ScreenManager.Key key) {
        successScreen = key;
    }

    /**
     * Obtiene la pantalla de xito correspondiente a este nivel
     *
     * @return {@link #successScreen}
     */
    private ScreenManager.Key getSuccessScreen() {
        return successScreen;
    }

    /**
     * Setea el {@link #orb} a la Stage principal
     *
     * @param orb {@link Orb}
     */
    private void setOrb(Orb orb) {
        addMainActor(orb.getActor());
        orb.syncActor(worldViewport);
        this.orb = orb;
    }

    /**
     * Obtiene el {@link Orb} de la pantalla de juego actual
     *
     * @return {@link #orb}
     */
    public Orb getOrb() {
        return orb;
    }

    /**
     * Setea la posicin de incio del {@link #orb} en el nivel actual
     *
     * @param x Coordenada x del mundo
     * @param y Coordenada y del mundo
     */
    protected void setOrbStartPosition(float x, float y) {
        getOrb().setPosition(x, y);
        getOrb().setStartPosition(x, y);
        lastOrbPosition.set(x, y);
    }

    /**
     * Aade una clase {@link Situation} al array de situaciones del nivel.
     *
     * @param clazz Una clase de tipo {@link Situation}
     */
    protected void addSituation(Class clazz) {
        situationManager.addSituation(clazz);
    }

    /**
     * Sincroniza la posicin y rotacin de los cuerpos con las de sus actores.
     * No es necesaria en el caso de elementos no mviles
     */
    private void syncBodies() {
        for (Situation situation : situationManager.getVisible()) {
            for (Element element : situation.getElements()) {
                if (element instanceof Movable)
                    element.syncBody(worldViewport);
            }
        }

        getOrb().syncBody(worldViewport, false, true);
    }

    /**
     * Sincroniza la posicin y rotacin de los actores con las de sus cuerpos.
     * Es necesaria en todos los casos, para que los actores se correspondan con el scroll.
     */
    private void syncActors() {
        for (Situation situation : situationManager.getVisible()) {
            for (Element element : situation.getElements()) {
                element.syncActor(worldViewport);
            }
        }

        getOrb().syncActor(worldViewport);
    }

    /**
     * Centra la cmara en el Orb
     */
    private void followCamera() {
        worldViewport.getCamera().position.x = getOrb().getBody().getPosition().x;
        worldViewport.getCamera().position.y = getOrb().getBody().getPosition().y;
        worldViewport.getCamera().update();
    }

    /**
     * Ejecuta un paso ms las Actions de cada actor
     */
    private void act(float delta) {
        if (!isLocked())
            getMainStage().act(Math.min(Gdx.graphics.getDeltaTime(), MIN_DELTA_TIME));
        else
            getOrb().getActor().act(delta);

        getGestureStage().act(Math.min(Gdx.graphics.getDeltaTime(), MIN_DELTA_TIME));
        getBackgroundStage().act(Math.min(Gdx.graphics.getDeltaTime(), MIN_DELTA_TIME));
        getHUDStage().act(Math.min(Gdx.graphics.getDeltaTime(), MIN_DELTA_TIME));
    }

    /**
     * Dibuja todos los actores
     */
    private void draw() {
        getParallaxStage().draw(worldViewport.getCamera());
        getMainStage().draw();
        getGestureStage().draw();
        getBackgroundStage().draw();
        if (DEBUG_WORLD)
            debugRenderer.render(worldManager.getWorld(), worldViewport.getCamera().combined);
        getHUDStage().draw();
    }

    /**
     * Render update
     * Actualiza las situaciones visibles. No se puede pausar la actualizacion de situaciones visibles
     * cuando se bloquee el juego, ya que eso impide que se dibujen las situaciones iniciales al empezar.
     */
    private void updateSituations() {
        situationManager.update();
    }

    /**
     * Render update
     * Computa las fuerzas sobre el orbe, si el juego no est bloqueado
     */
    private void updateForces() {
        if (isLocked())
            return;

        computeMagneticFoces();
    }

    /**
     * Render update
     * Comprueba y actualiza datos y estados, si el juego no est bloqueado
     */
    private void updateWidgets() {
        if (isLocked())
            return;

        updateHeat();
        updateFreeze();
        updateTimer();
        updateStats();
    }

    /**
     * Actualiza el calor y su indicador. El calor puede estar generado por un
     * {@link GestureHandler#tap(float, float, int, int)} o por {@link #ticking}.
     */
    private void updateHeat() {
        if (isTicking()) {
            tick.update(Gdx.graphics.getRawDeltaTime());

            if (tick.expired()) {
                tick.reset();
                tick();
            }
        }

        getOrb().updateHeat();
        getHUDStage().setGaugeLevel(getOrb().getHeat());
        getHUDStage().setGaugeOverload(getOrb().isOverloaded());
    }

    /**
     * Actualiza el tiempo de congelacin del Orb
     */
    private void updateFreeze() {
        getOrb().updateFreezeTime();
    }

    /**
     * Actualiza el cronmetro
     */
    private void updateTimer() {
        getHUDStage().updateTimer();
    }

    /**
     * Actualiza las estadsticas y su visualizacin
     */
    private void updateStats() {
        float distance = Utils.distance(getOrb().getPosition(), lastOrbPosition);
        lastOrbPosition = getOrb().getPosition();

        getStats().addTime(Gdx.graphics.getRawDeltaTime());
        getStats().addDistance(distance);
        getHUDStage().setDistanceValue(stats.getCurrentDistance());
        getHUDStage().setFullDistanceValue(stats.fullDistance());
        getHUDStage().setFullTimeValue(stats.fullTime());
        getHUDStage().setFullDestroyedValue(stats.fails());
    }

    /**
     * Calcula las fuerzas de atraccin y repulsin activas sobre el orbe y las aplica.
     */
    private void computeMagneticFoces() {
        Vector2 force = new Vector2(0, 0);

        for (Situation situation : situationManager.getVisible()) {
            for (Element element : situation.getElements()) {
                if (element instanceof Magnetic) {
                    Vector2 partial = ((Magnetic) element).getForce(getOrb().getPosition());
                    force.add(partial);
                }
            }
        }

        getOrb().getBody().applyLinearImpulse(force, getOrb().getPosition(), true);
    }

    /**
     * Comprobacin y desbloqueo de los logros que se obtienen en mitad del gameplay.
     */
    private void checkAchievements() {
        checkRobocopAchievement();
        checkOver9000Achievement();
        checkHyperdriveAchievement();
    }

    private void checkRobocopAchievement() {
        if (achievedRobocop)
            return;

        if (getStats().getCurrentTime() > GameManager.ACHIEVEMENT_ROBOCOP_TIME) {
            if (getThisKey() != ScreenManager.Key.LEVEL_1) {
                achievedRobocop = true;
                getGameManager().unlockAchievement(PlayServices.Achievement.Robocop);
            }
        }
    }

    private void checkOver9000Achievement() {
        if (achievedItsOver9000)
            return;

        if (getStats().getCurrentDistance() > GameManager.ACHIEVEMENT_OVER9000_DISTANCE) {
            achievedItsOver9000 = true;
            getGameManager().unlockAchievement(PlayServices.Achievement.ItsOver9000);
        }
    }

    private void checkHyperdriveAchievement() {
        if (achievedHyperdrive)
            return;

        if (getOrb().getBody().getLinearVelocity().len2() > GameManager.ACHIEVEMENT_HYPERDRIVE_SPEED2) {
            achievedHyperdrive = true;
            getGameManager().unlockAchievement(PlayServices.Achievement.Hyperdrive);
        }
    }

    /**
     * Desbloquea el logro de nivel correspondiente a este nivel
     */
    public void unlockLevelAchievement() {
        switch (getThisKey()) {
        case LEVEL_1:
            getGameManager().unlockAchievement(PlayServices.Achievement.KnowHow);
            break;
        case LEVEL_2:
            getGameManager().unlockAchievement(PlayServices.Achievement.TheRealDeal);
            break;
        case LEVEL_3:
            getGameManager().unlockAchievement(PlayServices.Achievement.BecomingAnExpert);
            break;
        case LEVEL_4:
            getGameManager().unlockAchievement(PlayServices.Achievement.AHeroWasBorn);
            break;
        case LEVEL_5:
            getGameManager().unlockAchievement(PlayServices.Achievement.OneAboveAll);
            break;
        default:
        }
    }

    @Override
    public void firstGame() {
        stats.newTry();
        getBackgroundStage().addAction(alpha(1));
        getOrb().getActor().addAction(alpha(0));
        getHUDStage().init(getTransitionAction(Flow.ENTERING, getHierarchy()), getBackgroundStage(), orbIntro,
                getSetAsInputProcessorRunnable(), unlock);
    }

    @Override
    public void pauseGame() {
        if (!isLocked()) {
            lock();
            getHUDStage().pause();
            getGameManager().play(GameManager.Fx.Back);
        }
    }

    @Override
    public void resumeGame() {
        if (isLocked()) {
            getHUDStage().resume(unlock);
            getGameManager().play(GameManager.Fx.Button);
        }
    }

    @Override
    public void restartGame() {
        getHUDStage().restart(reset, orbIntro, unlock);
        getGameManager().play(GameManager.Fx.Button);
    }

    @Override
    public void leaveGame() {
        unsetInputProcessor();
        getPrefsManager().saveStats(stats, getThisKey());
        getGameManager().play(GameManager.Fx.Back);
        switchToScreen(getPreviousScreen(), Hierarchy.HIGHER);
    }

    @Override
    public void successGame() {
        lock();
        getStats().setSuccessfull(true);

        if (getStats().getCurrentAttempt() != null) {
            int rank = getPrefsManager().saveStats(getStats(), getThisKey());
            getGameManager().cache(getStats().getCurrentAttempt(), rank);
            getGameManager().submitTime(getThisKey(), getStats().getCurrentTime());
        }

        unsetInputProcessor();
        getGameManager().play(GameManager.Fx.Exit);
        unlockLevelAchievement();
        getOrb().applyOutroAction(toSuccess);
    }

    @Override
    public void lock() {
        locked = true;
    }

    @Override
    public void unlock() {
        locked = false;
    }

    @Override
    public boolean isLocked() {
        return locked;
    }

    @Override
    public void collide(boolean wall) {
        getGameManager().vibrate(GameManager.Length.Short);

        if (wall)
            getGameManager().play(GameManager.Fx.WallCollision);
        else
            getGameManager().play(GameManager.Fx.ElementCollision);
    }

    @Override
    public void freeze() {
        if (!isLocked()) {
            getOrb().freeze();
            getOrb().increaseHeat();
            getGestureStage().drawTap();
            getGameManager().vibrate(GameManager.Length.Medium);
            getGameManager().play(GameManager.Fx.Tap);

            if (getOrb().isOverloaded())
                destroy();
            else if (getOrb().isHeatMaxed())
                overload();
        }
    }

    @Override
    public void impulse(float velocityX, float velocityY) {
        float impulseX = MathUtils.clamp(velocityX * impulse, -IMPULSE_MAX, IMPULSE_MAX);
        float impulseY = MathUtils.clamp(-velocityY * impulse, -IMPULSE_MAX, IMPULSE_MAX);

        if (!isLocked()) {
            getOrb().unfreeze();
            getOrb().getBody().applyLinearImpulse(impulseX, impulseY, getOrb().getBody().getPosition().x,
                    getOrb().getBody().getPosition().y, true);

            getGestureStage().drawFling();
            getGameManager().play(GameManager.Fx.Fling);
        }
    }

    @Override
    public void destroy() {
        if (DEBUG_INVULNERABLE)
            return;

        lock();
        getStats().setFailed(true);

        if (getStats().getCurrentTime() < GameManager.ACHIEVEMENT_KENNY_TIME)
            getGameManager().unlockAchievement(PlayServices.Achievement.Kenny);

        getGameManager().vibrate(GameManager.Length.Long);
        getGameManager().play(GameManager.Fx.Destroy);
        getHUDStage().destroy(orbDestroy, reset, orbIntro, unlock);
    }

    @Override
    public void enableTicking(Tick tick) {
        this.tick.amount = tick.amount;
        this.tick.period = tick.period;
        this.tick.reset();
        ticking = true;
        tick();
    }

    @Override
    public void disableTicking() {
        ticking = false;
        tick.reset();
    }

    @Override
    public boolean isTicking() {
        return ticking;
    }

    @Override
    public void tick() {
        getGameManager().play(GameManager.Fx.Tick);

        if (getOrb().isOverloaded()) {
            destroy();
            return;
        }

        getOrb().increaseHeat(tick.amount);

        if (getOrb().isHeatMaxed())
            overload();
    }

    @Override
    public void overload() {
        getOrb().setOverloaded(true);
        getHUDStage().setGaugeOverload(true);
        getGameManager().play(GameManager.Fx.Warning);
    }

}