From 8920f55a40bf255a8b235b6b6cee714bb15dbbc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Przemys=C5=82aw=20Pluta?= Date: Mon, 29 Aug 2022 16:27:20 +0200 Subject: [PATCH] Add full support for auto tiles both regular ones and animated --- .../api/map/layer/autotile/AutoTileLayer.java | 7 + .../manager/DefaultContextManager.java | 3 + .../base/engine/project/model/Project.java | 4 + .../serial/ProtobufProjectDeserializer.java | 6 + .../autotile/asset/AutoTileSetAsset.java | 17 +++ .../autotile/manager/AutoTileManager.java | 9 ++ .../manager/DefaultAutoTileSetManager.java | 65 ++++++++ .../world/autotile/model/AutoSubTile.java | 63 ++++++++ .../engine/world/autotile/model/AutoTile.java | 73 +++++++++ .../world/autotile/model/AutoTileSet.java | 136 +++++++++++++++++ .../layer/autotile/DefaultAutoTileLayer.java | 144 ++++++++++++++++++ .../world/map/model/DefaultGameMap.java | 9 ++ .../map/serial/ProtobufMapDeserializer.java | 26 ++++ 13 files changed, 562 insertions(+) create mode 100644 api/src/main/java/com/bartlomiejpluta/base/api/map/layer/autotile/AutoTileLayer.java create mode 100644 engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/asset/AutoTileSetAsset.java create mode 100644 engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/AutoTileManager.java create mode 100644 engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java create mode 100644 engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoSubTile.java create mode 100644 engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTile.java create mode 100644 engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java create mode 100644 engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/DefaultAutoTileLayer.java diff --git a/api/src/main/java/com/bartlomiejpluta/base/api/map/layer/autotile/AutoTileLayer.java b/api/src/main/java/com/bartlomiejpluta/base/api/map/layer/autotile/AutoTileLayer.java new file mode 100644 index 00000000..b8a261f0 --- /dev/null +++ b/api/src/main/java/com/bartlomiejpluta/base/api/map/layer/autotile/AutoTileLayer.java @@ -0,0 +1,7 @@ +package com.bartlomiejpluta.base.api.map.layer.autotile; + +import com.bartlomiejpluta.base.api.map.layer.base.Layer; + +public interface AutoTileLayer extends Layer { + +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/context/manager/DefaultContextManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/context/manager/DefaultContextManager.java index d683448c..d8b8985a 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/context/manager/DefaultContextManager.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/context/manager/DefaultContextManager.java @@ -14,6 +14,7 @@ import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration; import com.bartlomiejpluta.base.engine.project.serial.ProjectDeserializer; import com.bartlomiejpluta.base.engine.util.reflection.ClassLoader; import com.bartlomiejpluta.base.engine.world.animation.manager.AnimationManager; +import com.bartlomiejpluta.base.engine.world.autotile.manager.AutoTileManager; import com.bartlomiejpluta.base.engine.world.character.manager.CharacterManager; import com.bartlomiejpluta.base.engine.world.character.manager.CharacterSetManager; import com.bartlomiejpluta.base.engine.world.icon.manager.IconManager; @@ -35,6 +36,7 @@ public class DefaultContextManager implements ContextManager { private final ProjectConfiguration configuration; private final ProjectDeserializer projectDeserializer; private final TileSetManager tileSetManager; + private final AutoTileManager autoTileManager; private final MapManager mapManager; private final ImageManager imageManager; private final CharacterSetManager characterSetManager; @@ -58,6 +60,7 @@ public class DefaultContextManager implements ContextManager { log.info("Registering project assets"); project.getTileSetAssets().forEach(tileSetManager::registerAsset); + project.getAutoTileSetAssets().forEach(autoTileManager::registerAsset); project.getMapAssets().forEach(mapManager::registerAsset); project.getImageAssets().forEach(imageManager::registerAsset); project.getCharacterSetAssets().forEach(characterSetManager::registerAsset); diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/project/model/Project.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/project/model/Project.java index c58d07d3..2dfe30af 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/project/model/Project.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/project/model/Project.java @@ -4,6 +4,7 @@ import com.bartlomiejpluta.base.engine.audio.asset.SoundAsset; import com.bartlomiejpluta.base.engine.gui.asset.FontAsset; import com.bartlomiejpluta.base.engine.gui.asset.WidgetDefinitionAsset; import com.bartlomiejpluta.base.engine.world.animation.asset.AnimationAsset; +import com.bartlomiejpluta.base.engine.world.autotile.asset.AutoTileSetAsset; import com.bartlomiejpluta.base.engine.world.character.asset.CharacterSetAsset; import com.bartlomiejpluta.base.engine.world.icon.asset.IconSetAsset; import com.bartlomiejpluta.base.engine.world.image.asset.ImageAsset; @@ -28,6 +29,9 @@ public class Project { @NonNull private final List tileSetAssets; + @NonNull + private final List autoTileSetAssets; + @NonNull private final List mapAssets; diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/project/serial/ProtobufProjectDeserializer.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/project/serial/ProtobufProjectDeserializer.java index aabf4851..c706ca1d 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/project/serial/ProtobufProjectDeserializer.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/project/serial/ProtobufProjectDeserializer.java @@ -5,6 +5,7 @@ import com.bartlomiejpluta.base.engine.gui.asset.FontAsset; import com.bartlomiejpluta.base.engine.gui.asset.WidgetDefinitionAsset; import com.bartlomiejpluta.base.engine.project.model.Project; import com.bartlomiejpluta.base.engine.world.animation.asset.AnimationAsset; +import com.bartlomiejpluta.base.engine.world.autotile.asset.AutoTileSetAsset; import com.bartlomiejpluta.base.engine.world.character.asset.CharacterSetAsset; import com.bartlomiejpluta.base.engine.world.icon.asset.IconSetAsset; import com.bartlomiejpluta.base.engine.world.image.asset.ImageAsset; @@ -28,6 +29,7 @@ public class ProtobufProjectDeserializer extends ProjectDeserializer { .name(proto.getName()) .runner(proto.getRunner()) .tileSetAssets(proto.getTileSetsList().stream().map(this::parseTileSetAsset).collect(toList())) + .autoTileSetAssets(proto.getAutoTilesList().stream().map(this::parseAutoTileSetAsset).collect(toList())) .mapAssets(proto.getMapsList().stream().map(this::parseGameMapAsset).collect(toList())) .imageAssets(proto.getImagesList().stream().map(this::parseImageAsset).collect(toList())) .characterSetAssets(proto.getCharacterSetsList().stream().map(this::parseCharacterSetAsset).collect(toList())) @@ -39,6 +41,10 @@ public class ProtobufProjectDeserializer extends ProjectDeserializer { .build(); } + private AutoTileSetAsset parseAutoTileSetAsset(ProjectProto.AutoTileSetAsset proto) { + return new AutoTileSetAsset(proto.getUid(), proto.getSource(), proto.getRows(), proto.getColumns()); + } + private TileSetAsset parseTileSetAsset(ProjectProto.TileSetAsset proto) { return new TileSetAsset(proto.getUid(), proto.getSource(), proto.getRows(), proto.getColumns()); } diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/asset/AutoTileSetAsset.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/asset/AutoTileSetAsset.java new file mode 100644 index 00000000..4dfd4e93 --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/asset/AutoTileSetAsset.java @@ -0,0 +1,17 @@ +package com.bartlomiejpluta.base.engine.world.autotile.asset; + +import com.bartlomiejpluta.base.engine.common.asset.Asset; +import lombok.Getter; +import lombok.NonNull; + +@Getter +public class AutoTileSetAsset extends Asset { + private final int rows; + private final int columns; + + public AutoTileSetAsset(@NonNull String uid, @NonNull String source, int rows, int columns) { + super(uid, source); + this.rows = rows; + this.columns = columns; + } +} \ No newline at end of file diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/AutoTileManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/AutoTileManager.java new file mode 100644 index 00000000..af8675d6 --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/AutoTileManager.java @@ -0,0 +1,9 @@ +package com.bartlomiejpluta.base.engine.world.autotile.manager; + +import com.bartlomiejpluta.base.engine.common.init.Initializable; +import com.bartlomiejpluta.base.engine.common.manager.AssetManager; +import com.bartlomiejpluta.base.engine.world.autotile.asset.AutoTileSetAsset; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; + +public interface AutoTileManager extends Initializable, AssetManager { +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java new file mode 100644 index 00000000..1db96e03 --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/manager/DefaultAutoTileSetManager.java @@ -0,0 +1,65 @@ +package com.bartlomiejpluta.base.engine.world.autotile.manager; + +import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh; +import com.bartlomiejpluta.base.engine.core.gl.object.texture.TextureManager; +import com.bartlomiejpluta.base.engine.error.AppException; +import com.bartlomiejpluta.base.engine.project.config.ProjectConfiguration; +import com.bartlomiejpluta.base.engine.util.mesh.MeshManager; +import com.bartlomiejpluta.base.engine.world.autotile.asset.AutoTileSetAsset; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; +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; + +@Slf4j +@Component +@RequiredArgsConstructor(onConstructor = @__(@Autowired)) +public class DefaultAutoTileSetManager implements AutoTileManager { + private final TextureManager textureManager; + private final MeshManager meshManager; + private final Map autoTiles = new HashMap<>(); + private final Map assets = new HashMap<>(); + private final ProjectConfiguration configuration; + private Mesh mesh; + + @Override + public void init() { + this.mesh = meshManager.createQuad(1, 1, 0, 0); + } + + @Override + public void registerAsset(AutoTileSetAsset asset) { + log.info("Registering [{}] auto tile set asset under UID: [{}]", asset.getSource(), asset.getUid()); + assets.put(asset.getUid(), asset); + } + + @Override + public AutoTileSetAsset getAsset(String uid) { + return assets.get(uid); + } + + @Override + public AutoTileSet loadObject(String uid) { + var autoTile = autoTiles.get(uid); + + if (autoTile == null) { + var asset = assets.get(uid); + + if (asset == null) { + throw new AppException("The auto tile set asset with UID: [%s] does not exist", uid); + } + + var source = configuration.projectFile("autotiles", asset.getSource()); + var texture = textureManager.loadTexture(source, asset.getRows() * AutoTileSet.ROWS, asset.getColumns() * AutoTileSet.COLUMNS); + autoTile = new AutoTileSet(texture, mesh, asset.getRows(), asset.getColumns()); + log.info("Loading auto tile set from assets to cache under the key: [{}]", uid); + autoTiles.put(uid, autoTile); + } + + return autoTile; + } +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoSubTile.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoSubTile.java new file mode 100644 index 00000000..83a87b06 --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoSubTile.java @@ -0,0 +1,63 @@ +package com.bartlomiejpluta.base.engine.world.autotile.model; + +import com.bartlomiejpluta.base.engine.core.gl.object.material.Material; +import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh; +import com.bartlomiejpluta.base.engine.world.object.Sprite; +import lombok.Getter; +import lombok.NonNull; +import org.joml.Vector2f; +import org.joml.Vector2fc; +import org.joml.Vector2ic; + +@Getter +public class AutoSubTile extends Sprite { + private final Vector2fc tileSpriteSize; + private final Vector2f tileScale = new Vector2f(1, 1); + + public AutoSubTile(Mesh mesh, AutoTileSet autoTileSet) { + super(mesh, Material.textured(autoTileSet.getTexture())); + + tileSpriteSize = material.getTexture().getSpriteSize(); + + super.setScale(tileSpriteSize.x() * tileScale.x, tileSpriteSize.y() * tileScale.y); + } + + public void recalculate(@NonNull Vector2ic spritePosition) { + material.setSpritePosition(spritePosition.y(), spritePosition.x()); + } + + @Override + public void setScale(float scale) { + this.tileScale.x = scale; + this.tileScale.y = scale; + super.setScale(tileSpriteSize.x() * scale, tileSpriteSize.y() * scale); + } + + public void setScale(float scaleX, float scaleY) { + this.tileScale.x = scaleX; + this.tileScale.y = scaleY; + super.setScale(tileSpriteSize.x() * scaleX, tileSpriteSize.y() * scaleY); + } + + @Override + public float getScaleX() { + return tileScale.x; + } + + @Override + public void setScaleX(float scaleX) { + this.tileScale.x = scaleX; + super.setScaleX(tileSpriteSize.x() * scaleX); + } + + @Override + public float getScaleY() { + return tileScale.y; + } + + @Override + public void setScaleY(float scaleY) { + this.tileScale.y = scaleY; + super.setScaleY(tileSpriteSize.x() * scaleY); + } +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTile.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTile.java new file mode 100644 index 00000000..ac044f67 --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTile.java @@ -0,0 +1,73 @@ +package com.bartlomiejpluta.base.engine.world.autotile.model; + +import com.bartlomiejpluta.base.api.camera.Camera; +import com.bartlomiejpluta.base.api.screen.Screen; +import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh; +import com.bartlomiejpluta.base.internal.render.Renderable; +import com.bartlomiejpluta.base.internal.render.ShaderManager; +import lombok.Getter; +import lombok.NonNull; + +public class AutoTile implements Renderable { + private final AutoTileSet autoTileSet; + private final AutoSubTile topLeftSubTile; + private final AutoSubTile topRightSubTile; + private final AutoSubTile bottomLeftSubTile; + private final AutoSubTile bottomRightSubTile; + + @Getter + private int setId; + + public AutoTile(@NonNull Mesh mesh, @NonNull AutoTileSet autoTileSet, int setId) { + this.topLeftSubTile = new AutoSubTile(mesh, autoTileSet); + this.topRightSubTile = new AutoSubTile(mesh, autoTileSet); + this.bottomLeftSubTile = new AutoSubTile(mesh, autoTileSet); + this.bottomRightSubTile = new AutoSubTile(mesh, autoTileSet); + this.autoTileSet = autoTileSet; + this.setId = setId; + } + + public void setCoordinates(int column, int row) { + var x = column * autoTileSet.getTileSize().x() * 2; + var y = row * autoTileSet.getTileSize().y() * 2; + + topLeftSubTile.setPosition(x, y); + topRightSubTile.setPosition(x + autoTileSet.getTileSize().x(), y); + bottomLeftSubTile.setPosition(x, y + autoTileSet.getTileSize().y()); + bottomRightSubTile.setPosition(x + autoTileSet.getTileSize().x(), y + autoTileSet.getTileSize().y()); + } + + public void regularTile(Integer setId, int topLeft, int topRight, int bottomLeft, int bottomRight) { + if (setId == null) { + setId = this.setId; + } + + topLeftSubTile.recalculate(autoTileSet.getTopLeftSubTiles()[setId][topLeft]); + topRightSubTile.recalculate(autoTileSet.getTopRightSubTiles()[setId][topRight]); + bottomLeftSubTile.recalculate(autoTileSet.getBottomLeftSubTiles()[setId][bottomLeft]); + bottomRightSubTile.recalculate(autoTileSet.getBottomRightSubTiles()[setId][bottomRight]); + } + + public void islandTile(Integer setId) { + if (setId == null) { + setId = this.setId; + } + + topLeftSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][0]); + topRightSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][1]); + bottomLeftSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][2]); + bottomRightSubTile.recalculate(autoTileSet.getIslandSubTiles()[setId][3]); + } + + public void shiftTileSet() { + this.setId = (setId + 1) % autoTileSet.getSetsCount(); + } + + @Override + public void render(Screen screen, Camera camera, ShaderManager shaderManager) { + topLeftSubTile.render(screen, camera, shaderManager); + topRightSubTile.render(screen, camera, shaderManager); + bottomLeftSubTile.render(screen, camera, shaderManager); + bottomRightSubTile.render(screen, camera, shaderManager); + } +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java new file mode 100644 index 00000000..4f520420 --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/autotile/model/AutoTileSet.java @@ -0,0 +1,136 @@ +package com.bartlomiejpluta.base.engine.world.autotile.model; + +import com.bartlomiejpluta.base.engine.core.gl.object.mesh.Mesh; +import com.bartlomiejpluta.base.engine.core.gl.object.texture.Texture; +import lombok.Getter; +import lombok.NonNull; +import org.joml.Vector2fc; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.LinkedList; + +// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826 +public class AutoTileSet { + public static final int ROWS = 6; + public static final int COLUMNS = 4; + + private static final Vector2ic ISLAND_TILE = new Vector2i(0, 0); + private static final Vector2ic CROSS_TILE = new Vector2i(1, 0); + private static final Vector2ic TOP_LEFT_TILE = new Vector2i(0, 1); + private static final Vector2ic TOP_RIGHT_TILE = new Vector2i(1, 1); + private static final Vector2ic BOTTOM_LEFT_TILE = new Vector2i(0, 2); + private static final Vector2ic BOTTOM_RIGHT_TILE = new Vector2i(1, 2); + + @Getter + private final Vector2ic[][] islandSubTiles; + + @Getter + private final Vector2ic[][] topLeftSubTiles; + + @Getter + private final Vector2ic[][] topRightSubTiles; + + @Getter + private final Vector2ic[][] bottomLeftSubTiles; + + @Getter + private final Vector2ic[][] bottomRightSubTiles; + + @Getter + private final Texture texture; + private final Mesh mesh; + + @Getter + private final Vector2fc tileSize; + private final int rows; + private final int columns; + + @Getter + private final int setsCount; + + public AutoTileSet(@NonNull Texture texture, @NonNull Mesh mesh, int rows, int columns) { + this.texture = texture; + this.mesh = mesh; + this.rows = rows; + this.columns = columns; + this.tileSize = texture.getSpriteSize(); + this.setsCount = rows * columns; + + var islandSubTiles = new LinkedList(); + var topLeftSubTiles = new LinkedList(); + var topRightSubTiles = new LinkedList(); + var bottomLeftSubTiles = new LinkedList(); + var bottomRightSubTiles = new LinkedList(); + + for (int setId = 0; setId < setsCount; ++setId) { + var crossSubTiles = cutSubTiles(setId, CROSS_TILE); + var topLeftTileSubTiles = cutSubTiles(setId, TOP_LEFT_TILE); + var topRightTileSubTiles = cutSubTiles(setId, TOP_RIGHT_TILE); + var bottomLeftTileSubTiles = cutSubTiles(setId, BOTTOM_LEFT_TILE); + var bottomRightTileSubTiles = cutSubTiles(setId, BOTTOM_RIGHT_TILE); + + /* + * Indexes: + * 0 - No connected tiles + * 1 - Left tile is connected + * 2 - Right tile is connected + * 3 - Left and Right tiles are connected + * 4 - Left, Right, and Center tiles are connected. + */ + var tl3 = crossSubTiles[0]; + var tr3 = crossSubTiles[1]; + var bl3 = crossSubTiles[2]; + var br3 = crossSubTiles[3]; + + var tl0 = topLeftTileSubTiles[0]; + var tr2 = topLeftTileSubTiles[1]; + var bl1 = topLeftTileSubTiles[2]; + var br4 = topLeftTileSubTiles[3]; + + var tl1 = topRightTileSubTiles[0]; + var tr0 = topRightTileSubTiles[1]; + var bl4 = topRightTileSubTiles[2]; + var br2 = topRightTileSubTiles[3]; + + var tl2 = bottomLeftTileSubTiles[0]; + var tr4 = bottomLeftTileSubTiles[1]; + var bl0 = bottomLeftTileSubTiles[2]; + var br1 = bottomLeftTileSubTiles[3]; + + var tl4 = bottomRightTileSubTiles[0]; + var tr1 = bottomRightTileSubTiles[1]; + var bl2 = bottomRightTileSubTiles[2]; + var br0 = bottomRightTileSubTiles[3]; + + islandSubTiles.add(cutSubTiles(setId, ISLAND_TILE)); + topLeftSubTiles.add(new Vector2ic[]{tl0, tl1, tl2, tl3, tl4}); + topRightSubTiles.add(new Vector2ic[]{tr0, tr1, tr2, tr3, tr4}); + bottomLeftSubTiles.add(new Vector2ic[]{bl0, bl1, bl2, bl3, bl4}); + bottomRightSubTiles.add(new Vector2ic[]{br0, br1, br2, br3, br4}); + } + + this.islandSubTiles = islandSubTiles.toArray(new Vector2ic[islandSubTiles.size()][]); + this.topLeftSubTiles = topLeftSubTiles.toArray(new Vector2ic[topLeftSubTiles.size()][]); + this.topRightSubTiles = topRightSubTiles.toArray(new Vector2ic[topRightSubTiles.size()][]); + this.bottomLeftSubTiles = bottomLeftSubTiles.toArray(new Vector2ic[bottomLeftSubTiles.size()][]); + this.bottomRightSubTiles = bottomRightSubTiles.toArray(new Vector2ic[bottomRightSubTiles.size()][]); + } + + private Vector2ic[] cutSubTiles(int setId, Vector2ic tile) { + var topLeft = getTile(setId, tile.y() * 2, tile.x() * 2); + var topRight = getTile(setId, tile.y() * 2, tile.x() * 2 + 1); + var bottomLeft = getTile(setId, tile.y() * 2 + 1, tile.x() * 2); + var bottomRight = getTile(setId, tile.y() * 2 + 1, tile.x() * 2 + 1); + + return new Vector2ic[]{topLeft, topRight, bottomLeft, bottomRight}; + } + + private Vector2ic getTile(int setId, int row, int column) { + return new Vector2i(((setId / columns) * ROWS) + row, ((setId % columns) * COLUMNS) + column); + } + + public AutoTile createTile(int setId) { + return new AutoTile(mesh, this, setId); + } +} diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/DefaultAutoTileLayer.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/DefaultAutoTileLayer.java new file mode 100644 index 00000000..564b9e5b --- /dev/null +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/layer/autotile/DefaultAutoTileLayer.java @@ -0,0 +1,144 @@ +package com.bartlomiejpluta.base.engine.world.map.layer.autotile; + +import com.bartlomiejpluta.base.api.camera.Camera; +import com.bartlomiejpluta.base.api.map.layer.autotile.AutoTileLayer; +import com.bartlomiejpluta.base.api.map.model.GameMap; +import com.bartlomiejpluta.base.api.screen.Screen; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTile; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; +import com.bartlomiejpluta.base.engine.world.map.layer.base.BaseLayer; +import com.bartlomiejpluta.base.internal.render.ShaderManager; +import lombok.NonNull; + +import java.util.Arrays; + +// Algorithm source: https://love2d.org/forums/viewtopic.php?t=7826 +public class DefaultAutoTileLayer extends BaseLayer implements AutoTileLayer { + private final AutoTileSet autoTileSet; + private final AutoTile[][] layer; + private boolean animated; + private double animationDuration; + private double accumulator; + + public DefaultAutoTileLayer(@NonNull GameMap map, @NonNull AutoTileSet autoTileSet, int rows, int columns, boolean animated, double animationDuration) { + super(map); + this.autoTileSet = autoTileSet; + this.animated = animated; + this.animationDuration = animationDuration; + layer = new AutoTile[rows][columns]; + Arrays.stream(layer).forEach(tiles -> Arrays.fill(tiles, null)); + } + + public void setTile(int row, int column, int setId) { + var tile = autoTileSet.createTile(setId); + tile.setCoordinates(column, row); + layer[row][column] = tile; + } + + public void clearTile(int row, int column) { + layer[row][column] = null; + } + + public void recalculate(Integer setId) { + for (int row = 0; row < map.getRows(); ++row) { + for (int column = 0; column < map.getColumns(); ++column) { + recalculateTile(setId, row, column); + } + } + } + + private void recalculateTile(Integer setId, int row, int column) { + if (layer[row][column] == null) { + return; + } + + var topLeft = 0; + var topRight = 0; + var bottomLeft = 0; + var bottomRight = 0; + + var tile = layer[row][column]; + + if (animated) { + tile.shiftTileSet(); + } + + // Top + if (row > 0 && layer[row - 1][column] != null) { + topLeft += 2; + topRight += 1; + } + + // Bottom + if (row < map.getRows() - 1 && layer[row + 1][column] != null) { + bottomLeft += 1; + bottomRight += 2; + } + + // Left + if (column > 0 && layer[row][column - 1] != null) { + topLeft += 1; + bottomLeft += 2; + } + + // Right + if (column < map.getColumns() - 1 && layer[row][column + 1] != null) { + topRight += 2; + bottomRight += 1; + } + + // Top left + if (row > 0 && column > 0 && layer[row - 1][column - 1] != null && topLeft == 3) { + topLeft = 4; + } + + // Top right + if (row > 0 && column < map.getColumns() - 1 && layer[row - 1][column + 1] != null && topRight == 3) { + topRight = 4; + } + + // Bottom left + if (row < map.getRows() - 1 && column > 0 && layer[row + 1][column - 1] != null && bottomLeft == 3) { + bottomLeft = 4; + } + + // Bottom right + if (row < map.getRows() - 1 && column < map.getColumns() - 1 && layer[row + 1][column + 1] != null && bottomRight == 3) { + bottomRight = 4; + } + + if (topLeft == 0 && topRight == 0 && bottomLeft == 0 && bottomRight == 0) { + tile.islandTile(setId); + return; + } + + tile.regularTile(setId, topLeft, topRight, bottomLeft, bottomRight); + } + + @Override + public void update(float dt) { + super.update(dt); + + if (animated) { + accumulator += dt; + + if (accumulator > animationDuration) { + accumulator = 0; + recalculate(null); + } + } + } + + @Override + public void render(Screen screen, Camera camera, ShaderManager shaderManager) { + for (var row : layer) { + for (var tile : row) { + if (tile != null) { + tile.render(screen, camera, shaderManager); + } + } + } + + super.render(screen, camera, shaderManager); + } +} \ No newline at end of file diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java index 02995d01..110ca010 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/model/DefaultGameMap.java @@ -13,6 +13,8 @@ import com.bartlomiejpluta.base.api.map.layer.tile.TileLayer; import com.bartlomiejpluta.base.api.map.model.GameMap; import com.bartlomiejpluta.base.api.screen.Screen; import com.bartlomiejpluta.base.engine.util.mesh.MeshManager; +import com.bartlomiejpluta.base.engine.world.autotile.model.AutoTileSet; +import com.bartlomiejpluta.base.engine.world.map.layer.autotile.DefaultAutoTileLayer; import com.bartlomiejpluta.base.engine.world.map.layer.color.DefaultColorLayer; import com.bartlomiejpluta.base.engine.world.map.layer.image.DefaultImageLayer; import com.bartlomiejpluta.base.engine.world.map.layer.object.DefaultObjectLayer; @@ -118,6 +120,13 @@ public class DefaultGameMap implements Renderable, Updatable, GameMap { return layer; } + public DefaultAutoTileLayer createAutoTileLayer(@NonNull AutoTileSet autoTileSet, boolean animated, double animationDuration) { + var layer = new DefaultAutoTileLayer(this, autoTileSet, rows, columns, animated, animationDuration); + layers.add(layer); + + return layer; + } + public ImageLayer createImageLayer(Image image, float opacity, float x, float y, float scaleX, float scaleY, ImageLayerMode mode, boolean parallax) { var layer = new DefaultImageLayer(this, image, opacity, x, y, scaleX, scaleY, mode, parallax); layers.add(layer); diff --git a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java index d15c12c3..b2300653 100644 --- a/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java +++ b/engine/src/main/java/com/bartlomiejpluta/base/engine/world/map/serial/ProtobufMapDeserializer.java @@ -4,6 +4,7 @@ import com.bartlomiejpluta.base.api.map.layer.image.ImageLayerMode; import com.bartlomiejpluta.base.api.map.layer.object.PassageAbility; import com.bartlomiejpluta.base.engine.error.AppException; import com.bartlomiejpluta.base.engine.util.mesh.MeshManager; +import com.bartlomiejpluta.base.engine.world.autotile.manager.AutoTileManager; import com.bartlomiejpluta.base.engine.world.image.manager.ImageManager; import com.bartlomiejpluta.base.engine.world.map.model.DefaultGameMap; import com.bartlomiejpluta.base.engine.world.tileset.manager.TileSetManager; @@ -21,6 +22,7 @@ import java.io.InputStream; public class ProtobufMapDeserializer extends MapDeserializer { private final MeshManager meshManager; private final TileSetManager tileSetManager; + private final AutoTileManager autoTileManager; private final ImageManager imageManager; @Override @@ -36,6 +38,8 @@ public class ProtobufMapDeserializer extends MapDeserializer { private void deserializeLayer(DefaultGameMap map, GameMapProto.Layer proto) { if (proto.hasTileLayer()) { deserializeTileLayer(map, proto); + } else if (proto.hasAutoTileLayer()) { + deserializeAutoTileLayer(map, proto); } else if (proto.hasObjectLayer()) { deserializeObjectLayer(map, proto); } else if (proto.hasColorLayer()) { @@ -63,6 +67,28 @@ public class ProtobufMapDeserializer extends MapDeserializer { } } + private void deserializeAutoTileLayer(DefaultGameMap map, GameMapProto.Layer proto) { + var autoTileSet = autoTileManager.loadObject(proto.getAutoTileLayer().getAutotileUID()); + var animated = proto.getAutoTileLayer().getAnimated(); + var animationDuration = proto.getAutoTileLayer().getAnimationDuration(); + + var layer = map.createAutoTileLayer(autoTileSet, animated, animationDuration); + var columns = map.getColumns(); + var tiles = proto.getAutoTileLayer().getTilesList(); + + for (var i = 0; i < tiles.size(); ++i) { + var tile = tiles.get(i); + + if (tile == 0) { + layer.clearTile(i / columns, i % columns); + } else { + layer.setTile(i / columns, i % columns, tile - 1); + } + } + + layer.recalculate(null); + } + private void deserializeObjectLayer(DefaultGameMap map, GameMapProto.Layer proto) { var layer = map.createObjectLayer(); var columns = map.getColumns();