/* * Copyright 2012-2013 Ivan Gadzhega * * 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 net.ivang.axonix.main.actors.game.level; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.Input; import com.badlogic.gdx.graphics.g2d.ParticleEffect; import com.badlogic.gdx.graphics.g2d.SpriteBatch; import com.badlogic.gdx.graphics.g2d.TextureRegion; import com.badlogic.gdx.math.Circle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.scenes.scene2d.ui.Skin; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import net.ivang.axonix.main.actors.game.KinematicActor; import net.ivang.axonix.main.actors.game.level.blocks.Block; import net.ivang.axonix.main.actors.game.level.bonuses.SpeedBonus; import net.ivang.axonix.main.effects.Effect; import net.ivang.axonix.main.effects.SpeedEffect; import net.ivang.axonix.main.events.intents.game.LivesIntent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import static net.ivang.axonix.main.actors.game.level.blocks.Block.Type; /** * @author Ivan Gadzhega * @since 0.1 */ public class Protagonist extends KinematicActor { private State state; private List<Effect> effects; private float spawnX, spawnY; private float prevX, prevY; private Circle collisionCircle; private Vector2 nextDirection; boolean canChangeDirection; private Level level; private TextureRegion region; private ParticleEffect particleAlive; private ParticleEffect particleDead; private EventBus eventBus; public Protagonist(float x, float y, Level level, Skin skin, EventBus eventBus) { this.state = State.ALIVE; this.level = level; this.region = skin.getRegion("circular_flare"); this.collisionCircle = new Circle(x, y, 0.4f); setX(x); setY(y); setSpawnX(x); setSpawnY(y); setPrevX(x); setPrevY(y); setSpeed(4f); setDirection(Direction.IDLE); setWidth(1.5f); setHeight(1.5f); setOriginX(0.75f); setOriginY(0.75f); particleAlive = new ParticleEffect(); particleAlive.load(Gdx.files.internal("data/particles/protagonist/protagonist_alive.p"), skin.getAtlas()); particleDead = new ParticleEffect(); particleDead.load(Gdx.files.internal("data/particles/protagonist/protagonist_dead.p"), skin.getAtlas()); effects = new ArrayList<Effect>(); // register with the event bus this.eventBus = eventBus; eventBus.register(this); } @Override public void act(float delta) { super.act(delta); switch (state) { case ALIVE: processKeys(); updateDirection(); updatePosition(delta); particleAlive.setPosition(getX(), getY()); particleAlive.update(delta); // effects Iterator<Effect> iterator = effects.iterator(); while (iterator.hasNext()) { if (iterator.next().act(delta)) { iterator.remove(); } } break; case DYING: if (particleDead.isComplete()) { this.setState(State.DEAD); } else { particleDead.update(delta); particleAlive.update(delta); } break; } } @Override public void draw(SpriteBatch batch, float parentAlpha) { switch (state) { case ALIVE: // draw particles particleAlive.draw(batch); // draw texture batch.setColor(1, 1, 1, 1); batch.draw(region, getX() - getOriginX(), getY() - getOriginY(), getOriginX(), getOriginY(), getWidth(), getHeight(), getScaleX(), getScaleY(), getRotation()); // effects for (Effect effect : effects) { effect.draw(batch); } break; case DYING: particleDead.draw(batch); break; } } public boolean isOnNewBlock() { return ((int) getX() - (int) getPrevX() != 0) || ((int) getY() - (int) getPrevY() != 0); } public boolean hasState(State state) { return this.state == state; } public void setState(State state) { if (!hasState(state)) { this.state = state; eventBus.post(state); } } //--------------------------------------------------------------------- // Subscribers //--------------------------------------------------------------------- @Subscribe @SuppressWarnings("unused") public void onStateChange(State state) { switch (state) { case DYING: // init the "dying" particles particleDead.setPosition(getX(), getY()); particleDead.start(); // re-spawn direction = Direction.IDLE; setX(spawnX); setY(spawnY); setPrevX(spawnX); setPrevY(spawnY); particleAlive.setPosition(spawnX, spawnY); // remove all effects for (Effect effect : effects) { effect.complete(); } break; case DEAD: eventBus.post(new LivesIntent(-1)); this.setState(State.ALIVE); break; } } @Subscribe @SuppressWarnings("unused") public void onSpeedBonus(SpeedBonus bonus) { effects.add(new SpeedEffect(this, 2, 10, bonus.getParticleEffect())); } //--------------------------------------------------------------------- // Helper Methods //--------------------------------------------------------------------- private void processKeys() { boolean isDraggedLeft = false; boolean isDraggedRight = false; boolean isDraggedDown = false; boolean isDraggedUp = false; if (Gdx.input.isTouched()) { int dx = Gdx.input.getDeltaX(); int dy = Gdx.input.getDeltaY(); float diff = Math.abs(dx) - Math.abs(dy); int deadZone = Gdx.graphics.getHeight() / 240; isDraggedLeft = dx < -deadZone && diff > 0; isDraggedRight = dx > deadZone && diff >= 0; isDraggedDown = dy < -deadZone && diff < 0; isDraggedUp = dy > deadZone && diff <= 0; } Block block = level.getBlock(getX(), getY()); boolean onFilledBlock = block.hasType(Type.BLUE) || block.hasType(Type.BLUE_HARD) || block.hasType(Type.GREEN); // DOWN if (Gdx.input.isKeyPressed(Input.Keys.DOWN) || Gdx.input.isKeyPressed(Input.Keys.S) || isDraggedUp) { if (onFilledBlock || direction != Direction.UP) { nextDirection = Direction.DOWN; } // UP } else if (Gdx.input.isKeyPressed(Input.Keys.UP) || Gdx.input.isKeyPressed(Input.Keys.W) || isDraggedDown) { if(onFilledBlock || direction != Direction.DOWN) { nextDirection = Direction.UP; } // LEFT } else if (Gdx.input.isKeyPressed(Input.Keys.LEFT) || Gdx.input.isKeyPressed(Input.Keys.A) || isDraggedLeft) { if (onFilledBlock || direction != Direction.RIGHT) { nextDirection = Direction.LEFT; } // RIGHT } else if (Gdx.input.isKeyPressed(Input.Keys.RIGHT) || Gdx.input.isKeyPressed(Input.Keys.D) || isDraggedRight) { if (onFilledBlock || direction != Direction.LEFT) { nextDirection = Direction.RIGHT; } } } private void updateDirection() { if (direction == Direction.IDLE || isOnNewBlock()) { canChangeDirection = true; } if (shouldChangeDirection()) { direction = nextDirection; nextDirection = null; canChangeDirection = false; } } private boolean shouldChangeDirection() { if (canChangeDirection && nextDirection != null && direction != nextDirection) { double floorX = getX() - Math.floor(getX()); double floorY = getY() - Math.floor(getY()); if (direction == Direction.IDLE || direction.x != 0 && floorX >= 0.25f && floorX <= 0.75f || direction.y != 0 && floorY >= 0.25f && floorY <= 0.75f) { return true; } } return false; } private void updatePosition(float deltaTime) { if (direction != Direction.IDLE) { Vector2 position = new Vector2(getX(), getY()); float distance = calculateDistance(deltaTime); updatePositon(position, distance); if (position.x == getX() && position.y == getY()) { direction = Direction.IDLE; } else { // update previous coords prevX = getX(); prevY = getY(); // update current coords setX(position.x); setY(position.y); } } } private float calculateDistance(float deltaTime) { float distance = deltaTime * speed; return Math.min(distance, 1f); } private void updatePositon(Vector2 position, float distance) { position.x += distance * direction.x; position.y += distance * direction.y; // check the boundaries position.x = Math.max(0.5f, position.x); position.x = Math.min(position.x, level.getMapWidth() - 0.5f); position.y = Math.max(0.5f, position.y); position.y = Math.min(position.y, level.getMapHeight() - 0.5f); // correct for smooth turns if (direction.x != 0) { position.y = roundPosition(position.y); } else if (direction.y != 0) { position.x = roundPosition(position.x); } } private float roundPosition(float position) { float step = 0.05f; // move for 0.5f to be able to round it float nearPos = position + 0.5f; float roundPos = Math.round(nearPos); if (Math.abs(nearPos - roundPos) < step) { // return rounded value (just move back for 0.5f) return roundPos - 0.5f; } if (roundPos > nearPos) { return position + step; } else { return position - step; } } //--------------------------------------------------------------------- // Getters & Setters //--------------------------------------------------------------------- @Override public void setX(float x) { super.setX(x); collisionCircle.x = x; } @Override public void setY(float y) { super.setY(y); collisionCircle.y = y; } public float getPrevX() { return prevX; } public void setPrevX(float prevX) { this.prevX = prevX; } public float getPrevY() { return prevY; } public void setPrevY(float prevY) { this.prevY = prevY; } public void setSpawnX(float spawnX) { this.spawnX = spawnX; } public void setSpawnY(float spawnY) { this.spawnY = spawnY; } public Circle getCollisionCircle() { return collisionCircle; } public float getSpeed() { return speed; } //--------------------------------------------------------------------- // Nested Classes //--------------------------------------------------------------------- public enum State { ALIVE, DYING, DEAD } }