com.github.fauu.helix.system.PlayerMovementSystem.java Source code

Java tutorial

Introduction

Here is the source code for com.github.fauu.helix.system.PlayerMovementSystem.java

Source

/*
 * Copyright (C) 2014, 2015 Helix Engine Developers
 * (http://github.com/fauu/HelixEngine)
 *
 * This software is licensed under the GNU General Public License
 * (version 3 or later). See the COPYING file in this distribution.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this software. If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by: Piotr Grabowski <fau999@gmail.com>
 */

package com.github.fauu.helix.system;

import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.annotations.Wire;
import com.artemis.managers.TagManager;
import com.artemis.systems.VoidEntitySystem;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Input;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Timer;
import com.github.fauu.helix.AreaType;
import com.github.fauu.helix.Direction;
import com.github.fauu.helix.PassageAction;
import com.github.fauu.helix.TilePermission;
import com.github.fauu.helix.component.*;
import com.github.fauu.helix.datum.Ambience;
import com.github.fauu.helix.datum.Move;
import com.github.fauu.helix.datum.Tile;
import com.github.fauu.helix.datum.TileAreaPassage;
import com.github.fauu.helix.displayable.DecalDisplayable;
import com.github.fauu.helix.displayable.ModelDisplayable;
import com.github.fauu.helix.graphics.AnimationType;
import com.github.fauu.helix.graphics.HelixCamera;
import com.github.fauu.helix.manager.AreaManager;
import com.github.fauu.helix.manager.LocalAmbienceManager;
import com.github.fauu.helix.manager.PlayerManager;
import com.github.fauu.helix.manager.WeatherMan;
import com.github.fauu.helix.util.IntVector2;
import com.github.fauu.helix.util.IntVector3;
import com.google.common.collect.Range;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

public class PlayerMovementSystem extends VoidEntitySystem {

    private static final float PASSAGE_BLACKOUT_DURATION = .5f;

    @Wire
    private AreaManager areaManager;

    @Wire
    private TagManager tagManager;

    @Wire
    private PlayerManager playerManager;

    @Wire
    private ComponentMapper<AreaTypeComponent> areaTypeMapper;

    @Wire
    private ComponentMapper<DimensionsComponent> dimensionsMapper;

    @Wire
    private ComponentMapper<DisplayableComponent> displayableMapper;

    @Wire
    private ComponentMapper<MovementSpeedComponent> movementSpeedMapper;

    @Wire
    private ComponentMapper<OrientationComponent> orientationMapper;

    @Wire
    private ComponentMapper<PositionComponent> positionMapper;

    @Wire
    private ComponentMapper<TilesComponent> tilesMapper;

    @Wire
    private HelixCamera camera;

    private static final float START_DELAY;

    private static final HashMap<Direction, Integer> DIRECTION_KEYS;

    private static final AnimationType[] WALK_ANIMATION_CYCLE;

    private boolean enabled;

    private Move currentMove;

    private float startDelayCounter;

    private int walkAnimationCycleCounter;

    private LinkedList<Move> queue;

    private Move lastMove;

    private Entity player;

    private DecalDisplayable displayable;

    static {
        START_DELAY = 0.1f;

        DIRECTION_KEYS = new HashMap<Direction, Integer>();
        DIRECTION_KEYS.put(Direction.NORTH, Input.Keys.W);
        DIRECTION_KEYS.put(Direction.EAST, Input.Keys.D);
        DIRECTION_KEYS.put(Direction.SOUTH, Input.Keys.S);
        DIRECTION_KEYS.put(Direction.WEST, Input.Keys.A);

        WALK_ANIMATION_CYCLE = new AnimationType[2];
        WALK_ANIMATION_CYCLE[0] = AnimationType.STEP_RIGHT_LEG;
        WALK_ANIMATION_CYCLE[1] = AnimationType.STEP_LEFT_LEG;
    }

    @Override
    public void initialize() {
        enabled = true;
        queue = new LinkedList<Move>();
    }

    @Override
    protected boolean checkProcessing() {
        return enabled;
    }

    @Override
    protected void processSystem() {
        player = playerManager.getPlayer();
        displayable = (DecalDisplayable) displayableMapper.get(player).get();

        if (!isMoving()) {
            Direction pressedDirection = pollPressedDirection();
            if (pressedDirection != null) {
                if (startDelayCounter == 0) {
                    orientationMapper.get(player).set(pressedDirection);
                    displayable.orientate(pressedDirection);
                }

                if (queue.size() == 0 && startDelayCounter >= START_DELAY) {
                    tryMoving(pressedDirection);
                } else {
                    startDelayCounter += Gdx.graphics.getDeltaTime();
                }
            } else {
                startDelayCounter = 0;
            }

            Move nextMove = queue.poll();
            if (nextMove != null) {
                currentMove = nextMove;
            }
        }

        if (isMoving()) {
            advanceMove();
        }
    }

    private void tryMoving(Direction direction) {
        orientationMapper.get(player).set(direction);
        displayable.orientate(direction);

        IntVector3 position = positionMapper.get(player).get();
        IntVector2 targetCoords = position.toIntVector2().add(direction.getVector());

        Entity area = areaManager.getArea();

        if (!coordsInsideArea(targetCoords, area)) {
            return;
        }

        Tile[][] tiles = tilesMapper.get(area).get();

        Tile currentTile = Tile.get(tiles, position.toIntVector2());
        Tile targetTile = Tile.get(tiles, targetCoords);
        switch (targetTile.getPermissions()) {
        case OBSTACLE:
            break;
        case PASSAGE:
            moveThroughAreaPassage(targetTile.getAreaPassage(), targetCoords, direction);
            break;
        case RAMP:
            moveOnRamp(currentTile, findRampEnd(tiles, targetCoords, direction), direction);
            break;
        default:
            Move move = new Move();

            move.setDirection(direction);
            if (currentTile.getPermissions() == TilePermission.RAMP) {
                float moveVectorZSgn = direction == lastMove.getDirection() ? 1 : -1;

                move.setVectorZ(moveVectorZSgn * lastMove.getVector().z);
            }
            move.setSpeed(movementSpeedMapper.get(player).get());

            queue.push(move);
        }
    }

    private void moveOnRamp(Tile currentTile, Tile rampEnd, Direction direction) {
        float moveVectorZ;
        if (currentTile.getPermissions() == TilePermission.RAMP) {
            moveVectorZ = lastMove.getVector().z;
        } else {
            int currentElevation = currentTile.getPermissions().getElevation();
            int finalElevation = rampEnd.getPermissions().getElevation();

            moveVectorZ = currentElevation < finalElevation ? .5f : -.5f;
        }

        Move move = new Move();

        move.setDirection(direction);
        move.setVectorZ(moveVectorZ);
        move.setSpeed(movementSpeedMapper.get(player).get());

        queue.push(move);
    }

    private Tile findRampEnd(Tile[][] tiles, IntVector2 startCoords, Direction direction) {
        IntVector2 coords = startCoords.cpy();

        Tile end;
        do {
            end = Tile.get(tiles, coords);

            coords.add(direction.getVector());
        } while (end.getPermissions() == TilePermission.RAMP);

        return end;
    }

    private void moveThroughAreaPassage(final TileAreaPassage passage, final IntVector2 passageCoords,
            final Direction direction) {
        float entryDuration = moveIntoAreaPassage(passageCoords, direction);

        Timer.schedule(new Timer.Task() {
            @Override
            public void run() {
                areaManager.unloadCurrent();
                areaManager.load(passage.getTargetAreaName());

                // TODO: Get this out of here
                if (areaTypeMapper.get(areaManager.getArea()).get() == AreaType.OUTDOOR) {
                    world.getManager(LocalAmbienceManager.class)
                            .setAmbience(world.getManager(WeatherMan.class).getWeather());
                } else {
                    world.getManager(LocalAmbienceManager.class).setAmbience(new Ambience());
                }

                IntVector3 exitPosition = new IntVector3(passage.getTargetCoords());

                positionMapper.get(player).set(exitPosition);
                displayable.updatePosition(exitPosition);
                // TODO: Camera stuff out of here?
                camera.updateTargetPosition(exitPosition);

                moveOutOfAreaPassage(passage, direction);
            }
        }, entryDuration + PASSAGE_BLACKOUT_DURATION);
    }

    private float moveIntoAreaPassage(IntVector2 passageCoords, Direction direction) {
        final ModelDisplayable areaDisplayable = (ModelDisplayable) displayableMapper.get(areaManager.getArea())
                .get();

        String entryAnimationId = AreaManager.constructPassageAnimationId(passageCoords, PassageAction.ENTRY);

        float entryDelay = areaDisplayable.animateIfAnimationExists(entryAnimationId) ? 1 : 0;

        enabled = false;

        final Move entryMove = new Move();
        entryMove.setDirection(direction);
        entryMove.setSpeed(movementSpeedMapper.get(player).get());
        Timer.schedule(new Timer.Task() {
            @Override
            public void run() {
                queue.push(entryMove);

                world.getSystem(ScreenFadingSystem.class).fade(ScreenFadingSystem.FadeType.FADE_OUT, .2f);

                enabled = true;
            }
        }, entryDelay);

        return entryDelay + entryMove.getDuration();
    }

    private void moveOutOfAreaPassage(TileAreaPassage passage, Direction direction) {
        float exitAnimationWaitTime = 0;

        String exitAnimationId = AreaManager.constructPassageAnimationId(passage.getTargetCoords(),
                PassageAction.EXIT);

        ModelDisplayable areaDisplayable = (ModelDisplayable) displayableMapper.get(areaManager.getArea()).get();

        areaDisplayable.animateIfAnimationExists(exitAnimationId);

        final Move exitMove = new Move();
        exitMove.setDirection(direction);
        exitMove.setSpeed(movementSpeedMapper.get(player).get());
        Timer.schedule(new Timer.Task() {
            @Override
            public void run() {
                world.getSystem(ScreenFadingSystem.class).fade(ScreenFadingSystem.FadeType.FADE_IN, .2f);

                queue.push(exitMove);
            }
        }, exitAnimationWaitTime);
    }

    private void advanceMove() {
        if (!currentMove.hasFinished()) {
            if (!currentMove.hasStarted()) {
                IntVector3 position = positionMapper.get(player).get();
                IntVector2 targetPosition = position.toIntVector2().add(currentMove.getDirection().getVector());

                Tile[][] tiles = tilesMapper.get(areaManager.getArea()).get();
                Tile targetTile = tiles[targetPosition.y][targetPosition.x];

                displayable.animate(WALK_ANIMATION_CYCLE[walkAnimationCycleCounter++], currentMove.getDirection(),
                        currentMove.getDuration());

                walkAnimationCycleCounter %= 2;

                PositionComponent positionComponent = positionMapper.get(player);
                IntVector3 newPosition = new IntVector3(targetPosition.x, targetPosition.y,
                        targetTile.getPermissions().getElevation());
                positionComponent.set(newPosition);
            }

            // TODO: Find a way for the movement to be less choppy while remaining accurate
            float delta = currentMove.willHaveBeenFinishedIn(Gdx.graphics.getDeltaTime())
                    ? currentMove.getRemaining()
                    : Gdx.graphics.getDeltaTime();

            Vector3 translation = currentMove.getVector().cpy().scl(delta * currentMove.getSpeed());

            displayable.move(translation);
            camera.translateTargetPosition(translation);

            currentMove.setElapsed(currentMove.getElapsed() + delta);
        } else {
            lastMove = currentMove;
            currentMove = null;
        }
    }

    private Direction pollPressedDirection() {
        Direction pressedDirection = null;

        for (Map.Entry<Direction, Integer> dk : DIRECTION_KEYS.entrySet()) {
            if (Gdx.input.isKeyPressed(dk.getValue())) {
                pressedDirection = dk.getKey();

                break;
            }
        }

        return pressedDirection;
    }

    private boolean coordsInsideArea(IntVector2 coords, Entity area) {
        IntVector2 areaDimensions = dimensionsMapper.get(area).get();

        return (Range.closedOpen(0, areaDimensions.x).contains(coords.x)
                && Range.closedOpen(0, areaDimensions.y).contains(coords.y));
    }

    private boolean isMoving() {
        return currentMove != null;
    }

}