Replace Scene with GameMap | improve GameMap to allow variable layers

From now on, GameMap is the central container for objects
and is acting as a scene was before.
What's more, GameMap is not restricted to 4 layers anymore.
User can combine TileLayers (which define the terrain)
and ObjectLayers (which can contain movable objects) together
and is no limited to have only one ObjectLayer.
Thanks to that it would be easier to implement i.e. birds which actually
are a movable objects and are flying above the map and the highest
terrain (top TileLayers in fact).
This commit is contained in:
2021-02-02 11:05:55 +01:00
parent 1c47e66022
commit 1b87af14b2
11 changed files with 219 additions and 191 deletions

View File

@@ -1,5 +1,5 @@
package com.bartlomiejpluta.base.core.world.animation;
public interface Animator {
void animate(Iterable<? extends AnimationableObject> objects);
void animate(AnimationableObject objects);
}

View File

@@ -6,13 +6,7 @@ import org.springframework.stereotype.Component;
public class DefaultAnimator implements Animator {
@Override
public void animate(Iterable<? extends AnimationableObject> objects) {
for (var object : objects) {
animate(object);
}
}
private void animate(AnimationableObject object) {
public void animate(AnimationableObject object) {
if(object.shouldAnimate()) {
var positions = object.getSpriteAnimationFramesPositions();
var delay = object.getAnimationSpeed();

View File

@@ -1,106 +1,119 @@
package com.bartlomiejpluta.base.core.world.map;
import com.bartlomiejpluta.base.core.world.movement.Direction;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.logic.Updatable;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.animation.Animator;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.core.world.movement.MovableObject;
import com.bartlomiejpluta.base.core.world.movement.Movement;
import com.bartlomiejpluta.base.core.world.tileset.model.Tile;
import com.bartlomiejpluta.base.core.world.tileset.model.TileSet;
import lombok.Getter;
import org.joml.Vector2f;
import org.joml.Vector2i;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class GameMap {
private static final int LAYERS = 4;
public class GameMap implements Renderable, Updatable {
private final Animator animator;
private final Camera camera;
private final TileSet tileSet;
private final Tile[][] map;
private final List<Layer> layers = new ArrayList<>();
private final float scale;
private final PassageAbility[] passageMap;
@Getter
private final Vector2f stepSize;
@Getter
private final int rows;
@Getter
private final int cols;
private final int columns;
private GameMap(TileSet tileSet, int rows, int cols, float scale) {
@Getter
private final Vector2f stepSize;
public GameMap(Animator animator, Camera camera, TileSet tileSet, int rows, int columns, float scale) {
this.animator = animator;
this.camera = camera;
this.tileSet = tileSet;
this.rows = rows;
this.cols = cols;
this.scale = scale;
this.rows = rows;
this.columns = columns;
this.stepSize = new Vector2f(this.scale * this.tileSet.getTileWidth(), this.scale * this.tileSet.getTileHeight());
}
map = new Tile[LAYERS][rows * cols];
passageMap = new PassageAbility[rows * cols];
Arrays.fill(passageMap, 0, rows * cols, PassageAbility.ALLOW);
@Override
public void render(Window window, ShaderManager shaderManager) {
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, camera.getProjectionMatrix(window));
shaderManager.setUniform(UniformName.UNI_VIEW_MATRIX, camera.getViewMatrix());
for(int i=0; i<rows; ++i) {
setPassageAbility(i, 0, PassageAbility.BLOCK);
setPassageAbility(i, cols-1, PassageAbility.BLOCK);
}
for(int i=0; i<cols; ++i) {
setPassageAbility( 0, i, PassageAbility.BLOCK);
setPassageAbility( rows-1, i, PassageAbility.BLOCK);
for (var layer : layers) {
layer.render(window, shaderManager);
}
}
public void setTile(int layer, int row, int col, Tile tile) {
recalculateTileGeometry(tile, col, row);
map[layer][col * cols + row] = tile;
@Override
public void update(float dt) {
for (var layer : layers) {
layer.update(dt);
}
}
private void recalculateTileGeometry(Tile tile, int i, int j) {
tile.setScale(scale);
tile.setPosition(i * stepSize.x, j * 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(animator, new ArrayList<>(), passageMap));
return this;
}
public Tile[] getLayer(int layer) {
return map[layer];
public GameMap createTileLayer() {
layers.add(new TileLayer(new Tile[rows][columns], stepSize, scale));
return this;
}
public void setPassageAbility(int row, int col, PassageAbility passageAbility) {
passageMap[row * cols + col] = passageAbility;
public GameMap addObject(int layerIndex, MovableObject object) {
((ObjectLayer) layers.get(layerIndex)).addObject(object);
return this;
}
private PassageAbility getPassageAbility(Vector2i coordinates) {
return passageMap[coordinates.y * cols + coordinates.x];
public GameMap removeObject(int layerIndex, MovableObject object) {
((ObjectLayer) layers.get(layerIndex)).removeObject(object);
return this;
}
private PassageAbility getPassageAbility(int row, int col) {
return passageMap[row * cols + col];
public GameMap setPassageAbility(int layerIndex, int row, int column, PassageAbility passageAbility) {
((ObjectLayer) layers.get(layerIndex)).setPassageAbility(row, column, passageAbility);
return this;
}
public boolean isMovementPossible(Movement movement) {
var source = movement.getSourceCoordinate();
public GameMap setTile(int layerIndex, int row, int column, Tile tile) {
((TileLayer) layers.get(layerIndex)).setTile(row, column, tile);
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();
var isTargetReachable = switch(getPassageAbility(target)) {
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(getPassageAbility(source)) {
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;
}
public static GameMap empty(TileSet tileSet, int rows, int cols, float scale) {
return new GameMap(tileSet, rows, cols, scale);
return ((ObjectLayer) layers.get(layerIndex)).isMovementPossible(source, target, direction);
}
}

View File

@@ -0,0 +1,8 @@
package com.bartlomiejpluta.base.core.world.map;
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,73 @@
package com.bartlomiejpluta.base.core.world.map;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.animation.Animator;
import com.bartlomiejpluta.base.core.world.movement.Direction;
import com.bartlomiejpluta.base.core.world.movement.MovableObject;
import org.joml.Vector2i;
import java.util.List;
public class ObjectLayer implements Layer {
private final Animator animator;
private final List<MovableObject> objects;
private final PassageAbility[][] passageMap;
public ObjectLayer(Animator animator, List<MovableObject> objects, PassageAbility[][] passageMap) {
this.animator = animator;
this.objects = objects;
this.passageMap = passageMap;
}
public void addObject(MovableObject object) {
objects.add(object);
}
public void removeObject(MovableObject 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, ShaderManager shaderManager) {
for (var object : objects) {
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, object.getModelMatrix());
animator.animate(object);
object.render(window, shaderManager);
}
}
@Override
public void update(float dt) {
for (var object : objects) {
object.update(dt);
}
}
}

View File

@@ -0,0 +1,44 @@
package com.bartlomiejpluta.base.core.world.map;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.tileset.model.Tile;
import org.joml.Vector2f;
public class TileLayer implements Layer {
private final Tile[][] layer;
private final Vector2f stepSize;
private final float scale;
public TileLayer(Tile[][] layer, Vector2f stepSize, float scale) {
this.layer = layer;
this.stepSize = stepSize;
this.scale = scale;
}
public void setTile(int row, int column, Tile tile) {
layer[row][column] = tile;
recalculateTileGeometry(tile, row, column);
}
private void recalculateTileGeometry(Tile tile, int row, int column) {
tile.setScale(scale);
tile.setPosition(column * stepSize.x, row * stepSize.y);
}
@Override
public void render(Window window, ShaderManager shaderManager) {
for(var row : layer) {
for(var tile : row) {
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, tile.getModelMatrix());
tile.render(window, shaderManager);
}
}
}
@Override
public void update(float dt) {
}
}

View File

@@ -3,6 +3,7 @@ package com.bartlomiejpluta.base.core.world.object;
import com.bartlomiejpluta.base.core.gl.object.material.Material;
import com.bartlomiejpluta.base.core.gl.object.mesh.Mesh;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.ui.Window;
import lombok.EqualsAndHashCode;
@@ -23,7 +24,14 @@ public abstract class RenderableObject extends PositionableObject implements Ren
@Override
public void render(Window window, ShaderManager shaderManager) {
getMaterial().activateTextureIfExists();
material.activateTextureIfExists();
shaderManager.setUniform(UniformName.UNI_OBJECT_COLOR, material.getColor());
shaderManager.setUniform(UniformName.UNI_HAS_OBJECT_TEXTURE, material.hasTexture());
shaderManager.setUniform(UniformName.UNI_TEXTURE_SAMPLER, 0);
shaderManager.setUniform(UniformName.UNI_SPRITE_SIZE, material.getSpriteSize());
shaderManager.setUniform(UniformName.UNI_SPRITE_POSITION, material.getSpritePosition());
mesh.render(window, shaderManager);
}

View File

@@ -1,90 +0,0 @@
package com.bartlomiejpluta.base.core.world.scene;
import com.bartlomiejpluta.base.core.gl.render.Renderable;
import com.bartlomiejpluta.base.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.core.gl.shader.manager.ShaderManager;
import com.bartlomiejpluta.base.core.logic.Updatable;
import com.bartlomiejpluta.base.core.ui.Window;
import com.bartlomiejpluta.base.core.world.animation.AnimationableObject;
import com.bartlomiejpluta.base.core.world.animation.Animator;
import com.bartlomiejpluta.base.core.world.camera.Camera;
import com.bartlomiejpluta.base.core.world.map.GameMap;
import com.bartlomiejpluta.base.core.world.movement.MovableObject;
import com.bartlomiejpluta.base.core.world.object.RenderableObject;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@AllArgsConstructor
public class Scene implements Renderable, Updatable {
private final Animator animator;
private final Camera camera;
private final List<MovableObject> objects = new ArrayList<>();
@Setter
@Getter
private GameMap map;
public Scene addObject(MovableObject object) {
objects.add(object);
return this;
}
public Scene removeObject(MovableObject object) {
objects.remove(object);
return this;
}
@Override
public void render(Window window, ShaderManager shaderManager) {
shaderManager.setUniform(UniformName.UNI_PROJECTION_MATRIX, camera.getProjectionMatrix(window));
shaderManager.setUniform(UniformName.UNI_VIEW_MATRIX, camera.getViewMatrix());
renderArray(map.getLayer(0), window, shaderManager);
renderArray(map.getLayer(1), window, shaderManager);
// Player will be rendered here
renderList(objects, window, shaderManager);
animator.animate(objects);
renderArray(map.getLayer(2), window, shaderManager);
renderArray(map.getLayer(3), window, shaderManager);
}
@Override
public void update(float dt) {
for(var object : objects) {
object.update(dt);
}
}
private <T extends RenderableObject> void renderList(List<T> objects, Window window, ShaderManager shaderManager) {
for (var object : objects) {
if (object != null) {
renderObject(object, window, shaderManager);
}
}
}
private <T extends RenderableObject> void renderArray(T[] objects, Window window, ShaderManager shaderManager) {
for (var object : objects) {
if (object != null) {
renderObject(object, window, shaderManager);
}
}
}
private <T extends RenderableObject> void renderObject(T object, Window window, ShaderManager shaderManager) {
shaderManager.setUniform(UniformName.UNI_MODEL_MATRIX, object.getModelMatrix());
shaderManager.setUniform(UniformName.UNI_OBJECT_COLOR, object.getMaterial().getColor());
shaderManager.setUniform(UniformName.UNI_HAS_OBJECT_TEXTURE, object.getMaterial().hasTexture());
shaderManager.setUniform(UniformName.UNI_TEXTURE_SAMPLER, 0);
shaderManager.setUniform(UniformName.UNI_SPRITE_SIZE, object.getMaterial().getSpriteSize());
shaderManager.setUniform(UniformName.UNI_SPRITE_POSITION, object.getMaterial().getSpritePosition());
object.render(window, shaderManager);
}
}

View File

@@ -3,7 +3,7 @@ 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.scene.Scene;
import com.bartlomiejpluta.base.core.world.map.GameMap;
import com.bartlomiejpluta.base.game.world.entity.config.EntitySpriteConfiguration;
import com.bartlomiejpluta.base.game.world.entity.model.Entity;
import lombok.RequiredArgsConstructor;
@@ -19,8 +19,8 @@ public class DefaultEntityManager implements EntityManager {
private final EntitySpriteConfiguration configuration;
@Override
public Entity createEntity(Material material, Scene scene) {
return new Entity(scene, buildMesh(material), material, scene.getMap().getStepSize(), configuration);
public Entity createEntity(Material material, GameMap map) {
return new Entity(buildMesh(material), material, map.getStepSize(), configuration);
}
private Mesh buildMesh(Material material) {

View File

@@ -2,10 +2,9 @@ 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.scene.Scene;
import com.bartlomiejpluta.base.core.world.map.GameMap;
import com.bartlomiejpluta.base.game.world.entity.model.Entity;
import org.joml.Vector2f;
public interface EntityManager extends Cleanable {
Entity createEntity(Material material, Scene scene);
Entity createEntity(Material material, GameMap map);
}

View File

@@ -4,7 +4,6 @@ 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.core.world.scene.Scene;
import com.bartlomiejpluta.base.game.world.entity.config.EntitySpriteConfiguration;
import lombok.EqualsAndHashCode;
import lombok.Getter;
@@ -13,14 +12,10 @@ import org.joml.Vector2f;
import java.util.Map;
@EqualsAndHashCode(exclude = "scene", callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Entity extends MovableObject {
private final Map<Direction, Integer> spriteDirectionRows;
private final int defaultSpriteColumn;
private final Scene scene;
@Getter
private boolean onScene;
@Setter
private int animationSpeed = 100;
@@ -75,26 +70,10 @@ public class Entity extends MovableObject {
return framesToCrossOneTile;
}
public void pushToScene() {
if(!onScene) {
scene.addObject(this);
onScene = true;
}
}
public void removeFromScene() {
if(onScene) {
scene.removeObject(this);
onScene = false;
}
}
public Entity(Scene scene, Mesh mesh, Material material, Vector2f coordinateStepSize, EntitySpriteConfiguration configuration) {
public Entity(Mesh mesh, Material material, Vector2f coordinateStepSize, EntitySpriteConfiguration configuration) {
super(mesh, material, coordinateStepSize, configuration.getDimension().asVector());
this.scene = scene;
this.defaultSpriteColumn = configuration.getDefaultSpriteColumn();
this.spriteDirectionRows = configuration.getSpriteDirectionRows();
this.onScene = false;
this.faceDirection = Direction.DOWN;
}
}