Java tutorial
/* * This file is part of OpenSpaceBox. * Copyright (C) 2018 by Maik Becker <hi@maik.codes> * * OpenSpaceBox 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. * * OpenSpaceBox 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 OpenSpaceBox. If not, see <http://www.gnu.org/licenses/>. */ package de.cubicvoxel.openspacebox.ingame.object; import com.badlogic.gdx.graphics.g2d.Sprite; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Polygon; import com.badlogic.gdx.math.Vector2; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; import de.cubicvoxel.openspacebox.OpenSpaceBox; import de.cubicvoxel.openspacebox.definition.type.ShipType; import de.cubicvoxel.openspacebox.ingame.controller.DebugValues; import de.cubicvoxel.openspacebox.ingame.controller.GameEventBus; import de.cubicvoxel.openspacebox.ingame.controller.Sector; import de.cubicvoxel.openspacebox.ingame.ui.hud.IngameHud; import de.cubicvoxel.openspacebox.input.AbstractInputHandler; import de.cubicvoxel.openspacebox.input.OsbInput; import java8.util.Optional; import lombok.Getter; import lombok.Setter; import lombok.Value; import lombok.val; /** * @author Maik Becker {@literal <hi@maik.codes>} */ public class Ship extends CollidableIngameObject<Polygon> implements HasVelocity, TradeEntity { // Properties private ShipType shipType; private CargoHold cargoHold; // Physics private Vector2 velocity = new Vector2(0, 0); /** * The cap for the velocity's len2(). Has the purpose of cancelling minor movement of the ship to prevent * costly calculations with nearly no effect. * <p> * TODO: If the cap is too high, and the update's deltaTime too low, initial acceleration is cancelled out and * the ship does not move at all. Implement a limit for the UpdateThread (also pretty useful for saving the * phone's battery) and then set the proper value for the cap. */ private static final float VELOCITY_CAP_SQUARED = 5.0f; // Logic @Getter @Setter private boolean isPlayer; @Getter private Optional<Station> stationHoveringOver = Optional.empty(); @Getter private Optional<SectorBeacon> sectorBeaconHoveringOver = Optional.empty(); // Events @Getter private EventBus objectHoveringOverChangedEventBus = new EventBus(); public Ship(GameEventBus gameEventBus, ShipType shipType) { super(gameEventBus, createSprite(shipType), createShape()); this.shipType = shipType; this.cargoHold = new CargoHold(shipType.getCargoSpace()); OsbInput.getInputEventBus().register(this); addEnteredOtherObjectListener((sender, other) -> handleEnteredOtherObject(other)); addLeftOtherObjectListener((sender, other) -> handleLeftOtherObject(other)); } private static Sprite createSprite(ShipType shipType) { Sprite sprite = OpenSpaceBox.createSprite(shipType.getSpriteAtlas()); // TODO: 25.12.16 Define size in ShipType sprite.setOriginCenter(); return sprite; } private static Polygon createShape() { val shape = new Polygon(); shape.setVertices(new float[] { 0, 50, -50, -50, 0, -30, 50, -50, 0, 50 }); return shape; } @Override public Layer getLayer() { return Layer.SMALL_SHIPS_AND_PROPS; } @Override public void update(float delta) { super.update(delta); decelerate(delta); applyVelocity(delta); if (isPlayer()) { DebugValues.getInstance().updateValue("playerPos", getPosition().toString()); DebugValues.getInstance().updateValue("playerVelo^2", String.valueOf(getVelocity().len2())); } } private void applyVelocity(float delta) { move(velocity.cpy().scl(delta)); rotateTowardsVelocity(delta); } private void rotateTowardsVelocity(float delta) { if (!velocity.isZero()) { float angleDelta = ((velocity.angle() - getRotation() + 360 + 180) % 360) - 180; val maxDelta = shipType.getPhysics().getRotationSpeed() * delta; angleDelta = MathUtils.clamp(angleDelta, -maxDelta, maxDelta); setRotation((getRotation() + angleDelta) % 360); } } private void moveTowards(Vector2 direction, float delta) { Vector2 movement = new Vector2(direction); movement.scl(shipType.getPhysics().getAcceleration() * delta); Vector2 totalVelocity = new Vector2(velocity).add(movement); totalVelocity.limit(getMaxSpeed()); velocity = totalVelocity; } private void decelerate(float delta) { velocity.scl(1 - (shipType.getPhysics().getDeceleration() * delta)); if (!velocity.isZero() && velocity.len2() < VELOCITY_CAP_SQUARED) velocity.setZero(); } private void handleEnteredOtherObject(AbstractIngameObject other) { if (other instanceof Station) setStationHoveringOver(Optional.of((Station) other)); else if (other instanceof SectorBeacon) setSectorBeaconHoveringOver(Optional.of((SectorBeacon) other)); } private void handleLeftOtherObject(AbstractIngameObject other) { if (stationHoveringOver.isPresent() && other.equals(stationHoveringOver.get())) setStationHoveringOver(Optional.empty()); else if (sectorBeaconHoveringOver.isPresent() && other.equals(sectorBeaconHoveringOver.get())) setSectorBeaconHoveringOver(Optional.empty()); } @Subscribe @SuppressWarnings("unused") public void handleMoveInputEvent(AbstractInputHandler.MoveInputEvent event) { if (isPlayer()) moveTowards(event.getInput(), event.getDeltaTime()); } @Subscribe @SuppressWarnings("unused") public void handleEnterStationButtonPressedEvent(IngameHud.EnterStationButtonPressedEvent event) { if (isPlayer() && stationHoveringOver.isPresent()) { ShowStationInspectorRequestEvent stationInspectorRequestEvent = new ShowStationInspectorRequestEvent( stationHoveringOver.get(), this); getGameEventBus().post(stationInspectorRequestEvent); } } @Subscribe @SuppressWarnings("unused") public void handleSwitchSectorButtonPressedEvent(IngameHud.SwitchSectorButtonPressedEvent event) { if (isPlayer() && sectorBeaconHoveringOver.isPresent()) { ShowSectorBeaconDialogRequestEvent showSectorBeaconDialogRequestEvent = new ShowSectorBeaconDialogRequestEvent( this); getGameEventBus().post(showSectorBeaconDialogRequestEvent); } } @Override public Vector2 getVelocity() { return velocity; } @Override public float getMaxSpeed() { return shipType.getPhysics().getMaxSpeed(); } @Override public AbstractIngameObject getObject() { return this; } @Override public CargoHold getCargoHold() { return cargoHold; } private void setStationHoveringOver(Optional<Station> stationHoveringOver) { this.stationHoveringOver = stationHoveringOver; getObjectHoveringOverChangedEventBus().post(new StationHoveringOverChangedEvent(stationHoveringOver)); } private void setSectorBeaconHoveringOver(Optional<SectorBeacon> sectorBeaconHoveringOver) { this.sectorBeaconHoveringOver = sectorBeaconHoveringOver; getObjectHoveringOverChangedEventBus() .post(new SectorBeaconHoveringOverChangedEvent(sectorBeaconHoveringOver)); } public void goToSector(Sector sector) { sector.addObject(this); setPosition(sector.getSectorBeacon().getPosition().add(sector.getSectorBeacon().getSize().scl(.5f))); } @Value public static class StationHoveringOverChangedEvent { private Optional<Station> stationHoveringOver; } @Value public static class SectorBeaconHoveringOverChangedEvent { private Optional<SectorBeacon> sectorBeaconHoveringOver; } @Value public static class ShowStationInspectorRequestEvent { private Station station; private Ship ship; } @Value public class ShowSectorBeaconDialogRequestEvent { private Ship ship; } }