World.java
package io.github.unisim.world;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TmxMapLoader;
import com.badlogic.gdx.maps.tiled.renderers.IsometricTiledMapRenderer;
import com.badlogic.gdx.math.Matrix4;
import com.badlogic.gdx.math.Vector2;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.ScreenUtils;
import com.badlogic.gdx.utils.viewport.ScreenViewport;
import com.badlogic.gdx.utils.viewport.Viewport;
import io.github.unisim.Point;
import io.github.unisim.building.Building;
import io.github.unisim.building.BuildingManager;
import io.github.unisim.building.BuildingType;
/**
* A class that holds all the gameplay elements of the game UniSim.
* It has the ablity to render the game and update the state of the game
*/
public class World {
private final OrthographicCamera camera = new OrthographicCamera();
private final Viewport viewport = new ScreenViewport(camera);
private TiledMap map;
private IsometricTiledMapRenderer renderer;
private PeopleManager peopleManager;
private final Vector2 camPosition = new Vector2(150f, 0f);
private final Vector2 panVelocity = new Vector2(0f, 0f);
private float zoomVelocity = 0f;
private final float timeStepSize = 0.001f;
private float panDt = 0f;
private float zoomDt = 0f;
private float minZoom;
private float maxZoom;
private SpriteBatch tileHighlightBatch;
private SpriteBatch buildingBatch;
private Texture tileHighlight;
private Texture errTileHighlight;
private Matrix4 isoTransform;
private Matrix4 invIsoTransform;
private final BuildingManager buildingManager;
private boolean canBuild;
private Point mousePosInWorld;
private Point btmLeft;
private Point topRight;
public Building selectedBuilding;
public boolean selectedBuildingUpdated;
/**
* Create a new World.
*/
public World() {
camera.zoom = 0.05f;
initIsometricTransform();
buildingManager = new BuildingManager(isoTransform);
selectedBuilding = null;
}
public void loadMap(String name) {
map = new TmxMapLoader().load(name);
renderer = new IsometricTiledMapRenderer(map, 1.0f / 16.0f);
tileHighlight = new Texture(Gdx.files.internal("map/tileHighlight.png"));
errTileHighlight = new Texture(Gdx.files.internal("map/errTileHighlight.png"));
tileHighlightBatch = new SpriteBatch();
buildingBatch = new SpriteBatch();
peopleManager = new PeopleManager(buildingManager, isoTransform);
}
/**
* Releases all resources of this object.
* Should be called when the World object is no longer needed
*/
public void dispose() {
if (map != null) {
map.dispose();
}
}
/**
* Steps the world forward by delta time and renders the world.
*/
public void render(float deltaTime) {
viewport.apply();
ScreenUtils.clear(0.59f, 0.72f, 1f, 1f);
updatePan();
updateZoom();
// Render the map tiles
// Render the map 0.0624 units lower than the rest of the world to account for
// the extra pixel at the bottom of each tile. (The pixel is used to prevent
// tiny gaps between the tiles caused by floating point errors)
camera.position.set(camPosition.x, camPosition.y + 0.0624f, 0);
camera.update();
renderer.setView((OrthographicCamera) viewport.getCamera());
renderer.render();
// Reset the camera position to the correct value for the rest of the world
camera.position.set(camPosition.x, camPosition.y, 0);
camera.update();
// Update the mouse grid pos and the buildable flag
Point mouseGridPos = getCursorGridPos();
if (!mouseGridPos.equals(mousePosInWorld) || selectedBuildingUpdated) {
mousePosInWorld = mouseGridPos;
btmLeft = mousePosInWorld;
Point buildingSize = selectedBuilding == null ? new Point(1, 1) : selectedBuilding.size;
btmLeft.x -= buildingSize.x / 2;
btmLeft.y -= buildingSize.y / 2;
topRight = new Point(btmLeft.x + buildingSize.x - 1, btmLeft.y + buildingSize.y - 1);
canBuild = buildingManager.isBuildable(btmLeft, topRight, getMapTiles());
if (selectedBuilding != null) {
selectedBuilding.location = btmLeft;
}
buildingManager.setPreviewBuilding(selectedBuilding);
}
// Render the tile highlight
if (selectedBuilding != null) {
tileHighlightBatch.setProjectionMatrix(camera.combined);
tileHighlightBatch.begin();
highlightRegion(btmLeft, topRight, canBuild ? tileHighlight : errTileHighlight);
tileHighlightBatch.end();
}
// render buildings after all map related rendering
buildingBatch.setProjectionMatrix(camera.combined);
buildingBatch.begin();
peopleManager.render(deltaTime, buildingBatch);
buildingManager.render(buildingBatch);
buildingBatch.end();
}
/**
* Resizes the gameplay (usually to fit the size of the window)
* This is mostly done by resizing the relevant viewports.
*
* @param width - The new width of the window
* @param height - The new height of the window
*/
public void resize(int width, int height) {
if (camera.viewportHeight > 0) {
camera.zoom *= (float) camera.viewportHeight / height;
}
viewport.update(width, height);
minZoom = 10f / camera.viewportHeight;
maxZoom = 100f / camera.viewportHeight;
}
/**
* Pans the view of the game by translating the camera by a multiple of the
* vector (x, y).
* The view will continue to move in the same direction for a short period
* afterwards
*
* @param x - The distance to pan horizontally
* @param y - The distance to pan vertically
*/
public void pan(float x, float y) {
camPosition.add(x * camera.zoom, y * camera.zoom);
if (Gdx.input.isButtonPressed(0) || Gdx.input.isButtonPressed(1)
|| Gdx.input.isButtonPressed(2)) {
panVelocity.set(x * timeStepSize / Gdx.graphics.getDeltaTime(),
y * timeStepSize / Gdx.graphics.getDeltaTime());
}
}
/**
* Pans the view of the game by translating the camera by a multiple of the
* vector (x, y).
*
* @param x - The distance to pan horizontally
* @param y - The distance to pan vertically
*/
public void panWithoutInertia(float x, float y) {
camPosition.add(x * camera.zoom, y * camera.zoom);
}
/**
* Tell the game to zoom in or out by a certain amount.
*
* @param amount - The speed to zoom at; negative to zoom in and positive to
* zoom out
*/
public void zoom(float amount) {
final float zoomAcceleration = 0.0003f;
zoomVelocity += amount * zoomAcceleration;
}
/**
* Adjusts the zoom of the camera based on the zoomVelocity.
* Also slightly reduces the zoomVelocity to prevent infinite zooming
* Limits the zoom of the camera to be between minZoom and maxZoom
*/
private void updateZoom() {
zoomDt += Gdx.graphics.getDeltaTime();
while (zoomDt > timeStepSize) {
zoomDt -= timeStepSize;
zoomVelocity *= 0.987f;
float scaleFactor = (1f + zoomVelocity * (float) Math.sqrt(camera.zoom) / camera.zoom);
if (camera.zoom * scaleFactor < minZoom) {
scaleFactor = minZoom / camera.zoom;
}
if (camera.zoom * scaleFactor > maxZoom) {
scaleFactor = maxZoom / camera.zoom;
}
panWithoutInertia(
Gdx.input.getX() - camera.viewportWidth / 2, camera.viewportHeight / 2 - Gdx.input.getY()
);
camera.zoom *= scaleFactor;
panWithoutInertia(
camera.viewportWidth / 2 - Gdx.input.getX(), Gdx.input.getY() - camera.viewportHeight / 2
);
}
}
/**
* Adjusts the panning of the camera based on the panVelocity.
* Also slightly reduces the panVelocity to prevent infinite panning
*/
private void updatePan() {
panDt += Gdx.graphics.getDeltaTime();
while (panDt > timeStepSize) {
panDt -= timeStepSize;
panVelocity.scl(0.987f);
if (!(Gdx.input.isButtonPressed(0) || Gdx.input.isButtonPressed(1)
|| Gdx.input.isButtonPressed(2))) {
panWithoutInertia(panVelocity.x, panVelocity.y);
}
}
}
/**
* Returns the maximum allowed zoom level.
*
* @return - A float holding the mazimum allowed zoom level
*/
public float getMaxZoom() {
return maxZoom;
}
/**
* Returns the current zoom level.
*
* @return - A float holding the current zoom level
*/
public float getZoom() {
return camera.zoom;
}
/**
* Return the (x, y) co-ordinates of the grid cell that the mouse pointer
* is currently within.
*
* @return - A Vector2 containing the position of the cursor in grid space
*/
public Point getCursorGridPos() {
Vector3 unprojected = camera.unproject(
new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0)).mul(invIsoTransform);
unprojected.add(0.45f, -0.45f, 0f);
return new Point((int) Math.floor(unprojected.x), (int) Math.floor(unprojected.y));
}
/**
* Highlight a rectangular region about the cursor with a given highlight texture.
*
* @param btmLeft - The bottom left edge of the region
* @param topRight - The top right edge of the region
* @param highlightTexture - The texture to highlight the squares with
*/
public void highlightRegion(Point btmLeft, Point topRight, Texture highlightTexture) {
Point tilePos = new Point();
for (tilePos.x = btmLeft.x; tilePos.x <= topRight.x; tilePos.x++) {
for (tilePos.y = btmLeft.y; tilePos.y <= topRight.y; tilePos.y++) {
Vector3 worldPos = gridPosToWorldPos(tilePos);
tileHighlightBatch.draw(highlightTexture, worldPos.x, worldPos.y, 1, 1);
}
}
}
/**
* Gets the camera position as a 2D vector.
*
* @return a Vector2 holding the position of the camera
*/
public Vector2 getCameraPos() {
return new Vector2(camera.position.x, camera.position.y);
}
/**
* Transforms a point from grid space to world space.
*
* @param gridPos - The coordinates of the point in grid space
* @return - The coordinates of the point in world space
*/
private Vector3 gridPosToWorldPos(Point gridPos) {
return new Vector3(
(float) Math.floor(gridPos.x), (float) Math.floor(gridPos.y), 0f).mul(isoTransform);
}
/**
* Calculates the matrices needed to transform a point into and outof isometric
* world space.
*/
private void initIsometricTransform() {
// create the isometric transform
isoTransform = new Matrix4();
isoTransform.idt();
// isoTransform.translate(0, 32, 0);
isoTransform.scale((float) (Math.sqrt(2.0) / 2.0), (float) (Math.sqrt(2.0) / 4.0), 1.0f);
isoTransform.rotate(0.0f, 0.0f, 1.0f, -45);
// ... and the inverse matrix
invIsoTransform = new Matrix4(isoTransform);
invIsoTransform.inv();
}
public TiledMapTileLayer getMapTiles() {
return (TiledMapTileLayer) map.getLayers().get(0);
}
/**
* Place a building onto the map, called when a tile is clicked and building mode is enabled.
*
* @return - True if building could be done successfully, false otherwise.
*/
public boolean placeBuilding() {
if (!canBuild) {
return false;
}
buildingManager.placeBuilding(
new Building(
selectedBuilding.texture, selectedBuilding.textureScale, selectedBuilding.textureOffset,
selectedBuilding.location.getNewPoint(), selectedBuilding.size.getNewPoint(),
selectedBuilding.flipped, selectedBuilding.type, selectedBuilding.name, selectedBuilding.price,
selectedBuilding.passiveIncome
)
);
selectedBuilding = null;
return true;
}
/**
* Returns the number of buildings of a certain type that have been placed
* in the world.
*
* @param type - The type of building
* @return - An int holding the number of that building that have been placed
*/
public int getBuildingCount(BuildingType type) {
return buildingManager.getBuildingCount(type);
}
public boolean isZoomingIn() {
return zoomVelocity < -0.0001f;
}
public boolean isZoomingOut() {
return zoomVelocity > 0.0001f;
}
public BuildingManager getBuildingManager() {
return buildingManager;
}
public PeopleManager getPeopleManager() {
return peopleManager;
}
}