Refactor world-related objects | move them to :game module

This commit is contained in:
2021-02-16 18:05:27 +01:00
parent 7e89bedffc
commit a40ec1f45a
28 changed files with 222 additions and 210 deletions

View File

@@ -0,0 +1,40 @@
package com.bartlomiejpluta.base.game.image.manager;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.texture.TextureManager;
import com.bartlomiejpluta.base.core.util.math.MathUtil;
import com.bartlomiejpluta.base.core.util.mesh.MeshManager;
import com.bartlomiejpluta.base.game.image.model.Image;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultImageManager implements ImageManager {
private final MeshManager meshManager;
private final TextureManager textureManager;
@Override
public Image createImage(String imageFileName) {
var texture = textureManager.loadTexture(imageFileName);
var width = texture.getWidth();
var height = texture.getHeight();
var gcd = MathUtil.gcdEuclidean(width, height);
var initialWidth = width / gcd;
var initialHeight = height / gcd;
var mesh = meshManager.createQuad(initialWidth, initialHeight, 0, 0);
var image = new Image(mesh, Material.textured(texture), initialWidth, initialHeight);
image.setScale(gcd);
return image;
}
@Override
public void cleanUp() {
log.info("There is nothing to clean up here");
}
}

View File

@@ -0,0 +1,8 @@
package com.bartlomiejpluta.base.game.image.manager;
import com.bartlomiejpluta.base.core.gc.Cleanable;
import com.bartlomiejpluta.base.game.image.model.Image;
public interface ImageManager extends Cleanable {
Image createImage(String imageFileName);
}

View File

@@ -0,0 +1,18 @@
package com.bartlomiejpluta.base.game.image.model;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.world.object.Sprite;
import lombok.Getter;
@Getter
public class Image extends Sprite {
private final int initialWidth;
private final int initialHeight;
public Image(Mesh mesh, Material texture, int initialWidth, int initialHeight) {
super(mesh, texture);
this.initialWidth = initialWidth;
this.initialHeight = initialHeight;
}
}

View File

@@ -0,0 +1,41 @@
package com.bartlomiejpluta.base.game.world.animation;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.core.world.object.Sprite;
import lombok.EqualsAndHashCode;
import org.joml.Vector2f;
@EqualsAndHashCode(callSuper = true)
public abstract class AnimatedSprite extends Sprite {
public AnimatedSprite(Mesh mesh, Material material) {
super(mesh, material);
}
// Returns time in ms between frames
public abstract int getAnimationSpeed();
public abstract boolean shouldAnimate();
public abstract Vector2f[] getSpriteAnimationFramesPositions();
@Override
public void render(Window window, Camera camera, ShaderManager shaderManager) {
animate();
super.render(window, camera, shaderManager);
}
private void animate() {
if (shouldAnimate()) {
var positions = getSpriteAnimationFramesPositions();
var delay = getAnimationSpeed();
var currentPosition = (int) (System.currentTimeMillis() % (positions.length * delay)) / delay;
var current = positions[currentPosition];
material.setSpritePosition(current);
}
}
}

View File

@@ -1,6 +1,6 @@
package com.bartlomiejpluta.base.game.world.entity.config;
import com.bartlomiejpluta.base.core.world.movement.Direction;
import com.bartlomiejpluta.base.game.world.movement.Direction;
import lombok.Data;
import org.joml.Vector2i;
import org.springframework.boot.context.properties.ConfigurationProperties;

View File

@@ -3,12 +3,11 @@ package com.bartlomiejpluta.base.game.world.entity.manager;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.util.mesh.MeshManager;
import com.bartlomiejpluta.base.core.world.map.GameMap;
import com.bartlomiejpluta.base.game.world.map.GameMap;
import com.bartlomiejpluta.base.game.world.entity.config.EntitySpriteConfiguration;
import com.bartlomiejpluta.base.game.world.entity.model.Entity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.joml.Vector2f;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

View File

@@ -2,7 +2,7 @@ package com.bartlomiejpluta.base.game.world.entity.manager;
import com.bartlomiejpluta.base.core.gc.Cleanable;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.world.map.GameMap;
import com.bartlomiejpluta.base.game.world.map.GameMap;
import com.bartlomiejpluta.base.game.world.entity.model.Entity;
public interface EntityManager extends Cleanable {

View File

@@ -2,8 +2,8 @@ package com.bartlomiejpluta.base.game.world.entity.model;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.world.movement.Direction;
import com.bartlomiejpluta.base.core.world.movement.MovableObject;
import com.bartlomiejpluta.base.game.world.movement.Direction;
import com.bartlomiejpluta.base.game.world.movement.MovableSprite;
import com.bartlomiejpluta.base.game.world.entity.config.EntitySpriteConfiguration;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -13,7 +13,7 @@ import org.joml.Vector2f;
import java.util.Map;
@EqualsAndHashCode(callSuper = true)
public class Entity extends MovableObject {
public class Entity extends MovableSprite {
private final Map<Direction, Integer> spriteDirectionRows;
private final int defaultSpriteColumn;

View File

@@ -0,0 +1,8 @@
package com.bartlomiejpluta.base.game.world.layer.base;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.logic.Updatable;
public interface Layer extends Renderable, Updatable {
}

View File

@@ -0,0 +1,61 @@
package com.bartlomiejpluta.base.game.world.layer.image;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.game.image.model.Image;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.game.world.layer.base.Layer;
import com.bartlomiejpluta.base.game.world.map.GameMap;
public class ImageLayer implements Layer {
public enum Mode {
NORMAL,
FIT_SCREEN,
FIT_MAP
}
private final float mapWidth;
private final float mapHeight;
private Image image;
private float imageInitialWidth;
private float imageInitialHeight;
private final Mode mode;
public ImageLayer(GameMap map, Image image, Mode mode) {
var stepSize = map.getStepSize();
this.mapWidth = map.getColumns() * stepSize.x;
this.mapHeight = map.getRows() * stepSize.y;
this.mode = mode;
setImage(image);
}
public void setImage(Image image) {
this.image = image;
this.imageInitialWidth = image.getInitialWidth();
this.imageInitialHeight = image.getInitialHeight();
}
@Override
public void render(Window window, Camera camera, ShaderManager shaderManager) {
if (image == null) {
return;
}
shaderManager.setUniform(UniformName.UNI_VIEW_MODEL_MATRIX, camera.computeViewModelMatrix(image.getModelMatrix()));
switch (mode) {
case FIT_SCREEN -> image.setScale(window.getWidth() / imageInitialWidth, window.getHeight() / imageInitialHeight);
case FIT_MAP -> image.setScale(mapWidth / imageInitialWidth, mapHeight / imageInitialHeight);
}
image.render(window, camera, shaderManager);
}
@Override
public void update(float dt) {
}
}

View File

@@ -0,0 +1,69 @@
package com.bartlomiejpluta.base.game.world.layer.object;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.game.world.layer.base.Layer;
import com.bartlomiejpluta.base.game.world.movement.Direction;
import com.bartlomiejpluta.base.game.world.movement.MovableSprite;
import org.joml.Vector2i;
import java.util.List;
public class ObjectLayer implements Layer {
private final List<MovableSprite> objects;
private final PassageAbility[][] passageMap;
public ObjectLayer(List<MovableSprite> objects, PassageAbility[][] passageMap) {
this.objects = objects;
this.passageMap = passageMap;
}
public void addObject(MovableSprite object) {
objects.add(object);
}
public void removeObject(MovableSprite object) {
objects.remove(object);
}
public void setPassageAbility(int row, int column, PassageAbility passageAbility) {
passageMap[row][column] = passageAbility;
}
public boolean isMovementPossible(Vector2i source, Vector2i target, Direction direction) {
var isTargetReachable = switch (passageMap[target.y][target.x]) {
case UP_ONLY -> direction != Direction.DOWN;
case DOWN_ONLY -> direction != Direction.UP;
case LEFT_ONLY -> direction != Direction.RIGHT;
case RIGHT_ONLY -> direction != Direction.LEFT;
case BLOCK -> false;
case ALLOW -> true;
};
var canMoveFromCurrentTile = switch (passageMap[source.y][source.x]) {
case UP_ONLY -> direction == Direction.UP;
case DOWN_ONLY -> direction == Direction.DOWN;
case LEFT_ONLY -> direction == Direction.LEFT;
case RIGHT_ONLY -> direction == Direction.RIGHT;
default -> true;
};
return isTargetReachable && canMoveFromCurrentTile;
}
@Override
public void render(Window window, Camera camera, ShaderManager shaderManager) {
for (var object : objects) {
object.render(window, camera, shaderManager);
}
}
@Override
public void update(float dt) {
for (var object : objects) {
object.update(dt);
}
}
}

View File

@@ -0,0 +1,10 @@
package com.bartlomiejpluta.base.game.world.layer.object;
public enum PassageAbility {
BLOCK,
ALLOW,
UP_ONLY,
DOWN_ONLY,
LEFT_ONLY,
RIGHT_ONLY
}

View File

@@ -0,0 +1,46 @@
package com.bartlomiejpluta.base.game.world.layer.tile;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.game.world.layer.base.Layer;
import com.bartlomiejpluta.base.game.world.tileset.model.Tile;
import java.util.Arrays;
public class TileLayer implements Layer {
private final Tile[][] layer;
private final int rows;
private final int columns;
public TileLayer(int rows, int columns) {
this.rows = rows;
this.columns = columns;
layer = new Tile[rows][columns];
Arrays.stream(layer).forEach(tiles -> Arrays.fill(tiles, null));
}
public void setTile(int row, int column, Tile tile) {
layer[row][column] = tile;
if(tile != null) {
tile.setCoordinates(row, column);
}
}
@Override
public void render(Window window, Camera camera, ShaderManager shaderManager) {
for (var row : layer) {
for (var tile : row) {
if (tile != null) {
tile.render(window, camera, shaderManager);
}
}
}
}
@Override
public void update(float dt) {
}
}

View File

@@ -0,0 +1,143 @@
package com.bartlomiejpluta.base.game.world.map;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.game.image.model.Image;
import com.bartlomiejpluta.base.core.logic.Updatable;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.game.world.layer.base.Layer;
import com.bartlomiejpluta.base.game.world.layer.image.ImageLayer;
import com.bartlomiejpluta.base.game.world.layer.object.ObjectLayer;
import com.bartlomiejpluta.base.game.world.layer.object.PassageAbility;
import com.bartlomiejpluta.base.game.world.layer.tile.TileLayer;
import com.bartlomiejpluta.base.game.world.movement.MovableSprite;
import com.bartlomiejpluta.base.game.world.movement.Movement;
import com.bartlomiejpluta.base.game.world.tileset.model.TileSet;
import lombok.Getter;
import lombok.NonNull;
import org.joml.Vector2f;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class GameMap implements Renderable, Updatable {
private final List<Layer> layers = new ArrayList<>();
@NonNull
private final TileSet tileSet;
@Getter
private final int rows;
@Getter
private final int columns;
@Getter
private final Vector2f stepSize;
public GameMap(TileSet tileSet, int rows, int columns) {
this.tileSet = tileSet;
this.rows = rows;
this.columns = columns;
this.stepSize = new Vector2f(tileSet.getTileSet().getSpriteSize());
}
@Override
public void render(Window window, Camera camera, ShaderManager shaderManager) {
for (var layer : layers) {
layer.render(window, camera, shaderManager);
}
}
@Override
public void update(float dt) {
for (var layer : layers) {
layer.update(dt);
}
}
public Vector2f getSize() {
return new Vector2f(columns * stepSize.x, rows * stepSize.y);
}
public GameMap createObjectLayer() {
var passageMap = new PassageAbility[rows][columns];
for (int i = 0; i < rows; ++i) {
Arrays.fill(passageMap[i], 0, columns, PassageAbility.ALLOW);
}
layers.add(new ObjectLayer(new ArrayList<>(), passageMap));
return this;
}
public GameMap createTileLayer() {
layers.add(new TileLayer(rows, columns));
return this;
}
public GameMap createImageLayer(Image image, ImageLayer.Mode imageDisplayMode) {
layers.add(new ImageLayer(this, image, imageDisplayMode));
return this;
}
public GameMap addObject(int layerIndex, MovableSprite object) {
((ObjectLayer) layers.get(layerIndex)).addObject(object);
return this;
}
public GameMap removeObject(int layerIndex, MovableSprite object) {
((ObjectLayer) layers.get(layerIndex)).removeObject(object);
return this;
}
public GameMap setPassageAbility(int layerIndex, int row, int column, PassageAbility passageAbility) {
((ObjectLayer) layers.get(layerIndex)).setPassageAbility(row, column, passageAbility);
return this;
}
public GameMap setTile(int layerIndex, int row, int column, int tileId) {
((TileLayer) layers.get(layerIndex)).setTile(row, column, tileSet.tileById(tileId));
return this;
}
public GameMap setTile(int layerIndex, int row, int column, int tileSetRow, int tileSetColumn) {
((TileLayer) layers.get(layerIndex)).setTile(row, column, tileSet.tileAt(tileSetRow, tileSetColumn));
return this;
}
public GameMap clearTile(int layerIndex, int row, int column) {
((TileLayer) layers.get(layerIndex)).setTile(row, column, null);
return this;
}
public GameMap setImage(int layerIndex, Image image) {
((ImageLayer) layers.get(layerIndex)).setImage(image);
return this;
}
public boolean isMovementPossible(int layerIndex, Movement movement) {
var target = movement.getTargetCoordinate();
// Is trying to go beyond the map
if (target.x < 0 || target.y < 0 || target.x >= columns || target.y >= rows) {
return false;
}
var source = movement.getSourceCoordinate();
var direction = movement.getDirection();
return ((ObjectLayer) layers.get(layerIndex)).isMovementPossible(source, target, direction);
}
}

View File

@@ -0,0 +1,24 @@
package com.bartlomiejpluta.base.game.world.movement;
import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import org.joml.Vector2f;
import org.joml.Vector2i;
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public enum Direction {
UP(new Vector2i(0, -1)),
DOWN(new Vector2i(0, 1)),
LEFT(new Vector2i(-1, 0)),
RIGHT(new Vector2i(1, 0));
private final Vector2i vector;
public Vector2i asIntVector() {
return new Vector2i(vector);
}
public Vector2f asFloatVector() {
return new Vector2f(vector);
}
}

View File

@@ -0,0 +1,81 @@
package com.bartlomiejpluta.base.game.world.movement;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.logic.Updatable;
import com.bartlomiejpluta.base.game.world.animation.AnimatedSprite;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.joml.Vector2f;
import org.joml.Vector2i;
@EqualsAndHashCode(callSuper = true)
public abstract class MovableSprite extends AnimatedSprite implements Updatable {
private final Vector2f coordinateStepSize;
private int moveTime = 0;
private Vector2f movementVector;
@Getter
private final Vector2i coordinates = new Vector2i(0, 0);
protected int framesToCrossOneTile = 1;
public boolean isMoving() {
return movementVector != null;
}
@Override
public void update(float dt) {
if(movementVector != null) {
if(moveTime > 0) {
--moveTime;
movePosition(movementVector);
} else {
adjustCoordinates();
setDefaultAnimationFrame();
movementVector = null;
}
}
}
protected abstract void setDefaultAnimationFrame();
private void adjustCoordinates() {
var position = new Vector2f(getPosition());
setCoordinates(new Vector2i((int) (position.x / coordinateStepSize.x), (int) (position.y / coordinateStepSize.y)));
}
public Movement prepareMovement(Direction direction) {
return new Movement(this, direction);
}
protected boolean move(Direction direction) {
if (this.movementVector != null) {
return false;
}
var speed = new Vector2f(coordinateStepSize).div(framesToCrossOneTile);
movementVector = direction.asFloatVector().mul(speed);
moveTime = framesToCrossOneTile;
return true;
}
public MovableSprite setCoordinates(int x, int y) {
coordinates.x = x;
coordinates.y = y;
setPosition((x + 0.5f) * coordinateStepSize.x, (y + 0.5f) * coordinateStepSize.y);
return this;
}
public MovableSprite setCoordinates(Vector2i coordinates) {
return setCoordinates(coordinates.x, coordinates.y);
}
public MovableSprite(Mesh mesh, Material material, Vector2f coordinateStepSize) {
super(mesh, material);
this.coordinateStepSize = coordinateStepSize;
setCoordinates(0, 0);
}
}

View File

@@ -0,0 +1,31 @@
package com.bartlomiejpluta.base.game.world.movement;
import lombok.AccessLevel;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.joml.Vector2i;
@Data
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public class Movement {
private final MovableSprite object;
private final Direction direction;
private boolean performed = false;
public boolean perform() {
performed = object.move(direction);
return performed;
}
public Vector2i getSourceCoordinate() {
return new Vector2i(object.getCoordinates());
}
public Vector2i getTargetCoordinate() {
return direction.asIntVector().add(object.getCoordinates());
}
public Movement another() {
return object.prepareMovement(direction);
}
}

View File

@@ -0,0 +1,45 @@
package com.bartlomiejpluta.base.game.world.tileset.manager;
import com.bartlomiejpluta.base.core.gl.object.texture.TextureManager;
import com.bartlomiejpluta.base.core.util.mesh.MeshManager;
import com.bartlomiejpluta.base.game.world.tileset.model.TileSet;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
import static java.lang.String.format;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class DefaultTileSetManager implements TileSetManager {
private final TextureManager textureManager;
private final MeshManager meshManager;
private final Map<String, TileSet> tileSets = new HashMap<>();
@Override
public TileSet createTileSet(String tileSetFileName, int rows, int columns) {
var key = format("%dx%d__%s", rows, columns, tileSetFileName);
var tileset = tileSets.get(key);
if (tileset == null) {
var texture = textureManager.loadTexture(tileSetFileName, rows, columns);
var size = texture.getSpriteSize();
var mesh = meshManager.createQuad(size.x, size.y, 0, 0);
tileset = new TileSet(texture, mesh);
log.info("Loading [{}] tileset to cache under the key: [{}]", tileSetFileName, key);
tileSets.put(key, tileset);
}
return tileset;
}
@Override
public void cleanUp() {
log.info("There is nothing to clean up here");
}
}

View File

@@ -0,0 +1,8 @@
package com.bartlomiejpluta.base.game.world.tileset.manager;
import com.bartlomiejpluta.base.core.gc.Cleanable;
import com.bartlomiejpluta.base.game.world.tileset.model.TileSet;
public interface TileSetManager extends Cleanable {
TileSet createTileSet(String tileSetFileName, int rows, int columns);
}

View File

@@ -0,0 +1,36 @@
package com.bartlomiejpluta.base.game.world.tileset.model;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.gl.object.texture.Texture;
import com.bartlomiejpluta.base.core.world.object.Sprite;
import lombok.Getter;
@Getter
public class Tile extends Sprite {
private final int id;
private final int tileSetRow;
private final int tileSetColumn;
public Tile setCoordinates(int row, int column) {
var stepSize = material.getTexture().getSpriteSize();
setPosition(column * stepSize.x, row * stepSize.y);
return this;
}
public Tile(Mesh mesh, Texture tileSet, int id) {
super(mesh, Material.textured(tileSet));
this.id = id;
this.tileSetRow = id / tileSet.getColumns();
this.tileSetColumn = id % tileSet.getColumns();
material.setSpritePosition(tileSetColumn, tileSetRow);
}
public Tile(Mesh mesh, Texture tileSet, int row, int column) {
super(mesh, Material.textured(tileSet));
this.tileSetRow = row;
this.tileSetColumn = column;
this.id = row * tileSet.getColumns() + column;
material.setSpritePosition(tileSetColumn, tileSetRow);
}
}

View File

@@ -0,0 +1,21 @@
package com.bartlomiejpluta.base.game.world.tileset.model;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.gl.object.texture.Texture;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class TileSet {
private final Texture tileSet;
private final Mesh mesh;
public Tile tileById(int id) {
return new Tile(mesh, tileSet, id);
}
public Tile tileAt(int row, int column) {
return new Tile(mesh, tileSet, row, column);
}
}