package net.alcuria.umbracraft; import net.alcuria.umbracraft.engine.entities.Entity; import net.alcuria.umbracraft.engine.events.CameraTargetEvent; import net.alcuria.umbracraft.engine.events.Event; import net.alcuria.umbracraft.engine.events.EventListener; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.math.Rectangle; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.utils.viewport.FitViewport; import com.badlogic.gdx.utils.viewport.Viewport; /** A glorified wrapper for the {@link OrthographicCamera} * @author Andrew Keturi */ public class View implements EventListener { private Rectangle bounds; private final OrthographicCamera camera; private final Vector2 pan = new Vector2(); private boolean panning; private float shakePeriod, shakeCounter, shakeFrequency, shakeAmplitude; private Entity target; private final OrthographicCamera uiCamera; private final Viewport viewport; private final Viewport worldViewport; public View() { camera = new OrthographicCamera(Config.viewWidth, Config.viewHeight); uiCamera = new OrthographicCamera(Config.viewWidth, Config.viewHeight); uiCamera.translate(Config.viewWidth / 2, Config.viewHeight / 2); uiCamera.update(); viewport = new FitViewport(Config.viewWidth, Config.viewHeight); worldViewport = new FitViewport(Config.viewWidth, Config.viewHeight); worldViewport.setCamera(camera); } /** Immediately clears any bounds set by {@link View#setBounds(Rectangle)} */ public void clearBounds() { bounds = null; } /** Immediately focuses the camera to its target */ public void focus() { if (target == null) { return; } float dX = bounds != null ? (bounds.width > Config.viewWidth ? (target.position.x - camera.position.x) : bounds.x - camera.position.x) : 0; float dY = bounds != null ? (bounds.height > Config.viewHeight ? (target.position.y - camera.position.y) : bounds.y - camera.position.y) : 0; camera.translate(dX, dY); camera.update(); } /** @return The boundaries of the camera. May be null if there are no * boundaries. */ public Rectangle getBounds() { return bounds; } /** @return the camera for displaying entities, maps, etc */ public OrthographicCamera getCamera() { return camera; } /** @return the ui camera for displaying ui elements */ public OrthographicCamera getUiCamera() { return uiCamera; } /** @return the {@link Viewport} */ public Viewport getViewport() { return viewport; } /** @return the {@link Viewport} of the world */ public Viewport getWorldViewport() { return worldViewport; } @Override public void onEvent(Event event) { if (event instanceof CameraTargetEvent) { CameraTargetEvent camEvent = ((CameraTargetEvent) event); target = camEvent.gameObject; Game.log("Set camera target to " + target.getName()); } } public void pan(float x, float y) { target = null; panning = !MathUtils.isEqual(x, 0) || !MathUtils.isEqual(y, 0); pan.x = x; pan.y = y; } /** Resizes the viewport * @param width width of the viewport * @param height height of the viewport */ public void resize(int width, int height) { viewport.update(width, height); } /** Sets the boundaries of the camera. The camera will do its best to honor * the boundaries set here; however, if the boundaries are too small the * view will focus on the center of the region. When set, the camera will * not translate beyond the rectangle's x/y from the bottom/left and x+w/y+h * from the top right. * @param bounds a {@link Rectangle} describing the boundaries of the image. */ public void setBounds(Rectangle bounds) { this.bounds = bounds; if (bounds.width < Config.viewWidth) { bounds.x -= Math.abs(bounds.width - Config.viewWidth) / 2; bounds.width = Config.viewWidth; } if (bounds.height < Config.viewHeight) { bounds.y -= Math.abs(bounds.height - Config.viewHeight) / 2; bounds.height = Config.viewHeight; } this.bounds.x += Config.viewWidth / 2; this.bounds.y += Config.viewHeight / 2; update(); } /** Sets a target for the camera to follow. * @param target */ public void setTarget(Entity target) { this.target = target; } public void shake(final float duration, final float frequency, final float amplitude) { shakePeriod = duration; shakeCounter = 0; shakeFrequency = frequency; shakeAmplitude = amplitude; } /** Updates the camera, moving towards a target if it exists and honoring any * map boundaries if present. */ public void update() { boolean moved = false; if (target != null || panning) { float dX = 0; float dY = 0; if (target != null) { dX = bounds != null ? (bounds.width > Config.viewWidth ? (target.position.x - camera.position.x) / 20f : bounds.x - camera.position.x) : 0; dY = bounds != null ? (bounds.height > Config.viewHeight ? (target.position.y - camera.position.y) / 20f : bounds.y - camera.position.y) : 0; } else if (panning) { dX = pan.x; dY = pan.y; } if (dX < 0.5f && dX > -0.5f) { dX = 0; } if (dY < 0.5f && dY > -0.5f) { dY = 0; } camera.translate(dX, dY); moved = dX != 0 || dY != 0; } if (bounds != null) { if (bounds.width > Config.viewWidth) { if (camera.position.x < bounds.x) { camera.translate(bounds.x - camera.position.x, 0); moved = true; } else if (camera.position.x > bounds.width - Config.viewWidth / 2) { camera.translate(bounds.width - Config.viewWidth / 2 - camera.position.x, 0); moved = true; } } if (bounds.height > Config.viewHeight) { if (camera.position.y < bounds.y) { camera.translate(0, bounds.y - camera.position.y); moved = true; } else if (camera.position.y > bounds.height - Config.viewHeight / 2) { camera.translate(0, bounds.height - Config.viewHeight / 2 - camera.position.y); moved = true; } } } if (shakeCounter < shakePeriod) { shakeCounter += Gdx.graphics.getDeltaTime(); if (shakeCounter >= shakePeriod) { shakeCounter = shakePeriod = 0; } } if (shakeCounter > 0) { final double motion = Math.sin(shakeCounter * shakeFrequency) * shakeAmplitude; camera.translate((int) motion, 0, 0); moved = true; } if (moved) { camera.update(); } } }