diff --git a/source/core/assets/images/GrassTile/grass_tile_1.png b/source/core/assets/images/GrassTile/grass_tile_1.png
new file mode 100644
index 000000000..770193053
Binary files /dev/null and b/source/core/assets/images/GrassTile/grass_tile_1.png differ
diff --git a/source/core/assets/images/GrassTile/grass_tile_2.png b/source/core/assets/images/GrassTile/grass_tile_2.png
new file mode 100644
index 000000000..261cd8ad8
Binary files /dev/null and b/source/core/assets/images/GrassTile/grass_tile_2.png differ
diff --git a/source/core/assets/images/GrassTile/grass_tile_3.png b/source/core/assets/images/GrassTile/grass_tile_3.png
new file mode 100644
index 000000000..3c18bce32
Binary files /dev/null and b/source/core/assets/images/GrassTile/grass_tile_3.png differ
diff --git a/source/core/assets/images/GrassTile/grass_tile_4.png b/source/core/assets/images/GrassTile/grass_tile_4.png
new file mode 100644
index 000000000..846fbc184
Binary files /dev/null and b/source/core/assets/images/GrassTile/grass_tile_4.png differ
diff --git a/source/core/assets/images/GrassTile/grass_tile_5.png b/source/core/assets/images/GrassTile/grass_tile_5.png
new file mode 100644
index 000000000..7efc7788c
Binary files /dev/null and b/source/core/assets/images/GrassTile/grass_tile_5.png differ
diff --git a/source/core/assets/images/GrassTile/grass_tile_6.png b/source/core/assets/images/GrassTile/grass_tile_6.png
new file mode 100644
index 000000000..ac27e6981
Binary files /dev/null and b/source/core/assets/images/GrassTile/grass_tile_6.png differ
diff --git a/source/core/assets/images/GrassTile/grass_tile_7.png b/source/core/assets/images/GrassTile/grass_tile_7.png
new file mode 100644
index 000000000..8f7fb2f76
Binary files /dev/null and b/source/core/assets/images/GrassTile/grass_tile_7.png differ
diff --git a/source/core/assets/images/grass_2.png b/source/core/assets/images/grass_2.png
index 81dbc6ef7..bc1ab9447 100644
Binary files a/source/core/assets/images/grass_2.png and b/source/core/assets/images/grass_2.png differ
diff --git a/source/core/assets/images/highlight_tile.png b/source/core/assets/images/highlight_tile.png
new file mode 100644
index 000000000..4c6883788
Binary files /dev/null and b/source/core/assets/images/highlight_tile.png differ
diff --git a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java
index ee6540f1b..ed515b455 100644
--- a/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java
+++ b/source/core/src/main/com/csse3200/game/areas/ForestGameArea.java
@@ -5,6 +5,7 @@
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.math.Vector2;
+import com.csse3200.game.areas.terrain.TerrainComponent;
import com.csse3200.game.components.ProjectileEffects;
import com.csse3200.game.areas.terrain.TerrainFactory;
import com.csse3200.game.areas.terrain.TerrainFactory.TerrainType;
@@ -114,7 +115,15 @@ public class ForestGameArea extends GameArea {
"images/mobboss/demon.png",
"images/mobboss/demon2.png",
"images/mobs/fire_worm.png",
- "images/mobboss/patrick.png"
+ "images/mobboss/patrick.png",
+ "images/GrassTile/grass_tile_1.png",
+ "images/GrassTile/grass_tile_2.png",
+ "images/GrassTile/grass_tile_3.png",
+ "images/GrassTile/grass_tile_4.png",
+ "images/GrassTile/grass_tile_5.png",
+ "images/GrassTile/grass_tile_6.png",
+ "images/GrassTile/grass_tile_7.png",
+ "images/highlight_tile.png"
};
private static final String[] forestTextureAtlases = {
"images/economy/econ-tower.atlas",
@@ -285,7 +294,9 @@ private void displayUI() {
private void spawnTerrain() {
terrain = terrainFactory.createTerrain(TerrainType.ALL_DEMO);
- spawnEntity(new Entity().addComponent(terrain));
+ // TODO: We might need a MapService
+ Entity entity = new Entity().addComponent(terrain);
+ spawnEntity(entity);
// Terrain walls
float tileSize = terrain.getTileSize();
diff --git a/source/core/src/main/com/csse3200/game/areas/terrain/TerrainComponent.java b/source/core/src/main/com/csse3200/game/areas/terrain/TerrainComponent.java
index 4e9f2f8ed..3b56b4ab5 100644
--- a/source/core/src/main/com/csse3200/game/areas/terrain/TerrainComponent.java
+++ b/source/core/src/main/com/csse3200/game/areas/terrain/TerrainComponent.java
@@ -1,19 +1,29 @@
package com.csse3200.game.areas.terrain;
-import com.badlogic.gdx.graphics.OrthographicCamera;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.*;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
+import com.badlogic.gdx.maps.tiled.TiledMapTile;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.math.GridPoint2;
import com.badlogic.gdx.math.Vector2;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.utils.Timer;
import com.csse3200.game.rendering.RenderComponent;
+import com.csse3200.game.services.ResourceService;
+import com.csse3200.game.services.ServiceLocator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Render a tiled terrain for a given tiled map and orientation. A terrain is a map of tiles that
* shows the 'ground' in the game. Enabling/disabling this component will show/hide the terrain.
*/
public class TerrainComponent extends RenderComponent {
+ private static final Logger logger = LoggerFactory.getLogger(TerrainComponent.class);
private static final int TERRAIN_LAYER = 0;
private final TiledMap tiledMap;
@@ -21,6 +31,11 @@ public class TerrainComponent extends RenderComponent {
private final OrthographicCamera camera;
private final TerrainOrientation orientation;
private final float tileSize;
+ private TiledMapTileLayer.Cell lastHoveredCell = null;
+ private TiledMapTile originalTile = null;
+ private TextureRegion originalRegion = null;
+
+
public TerrainComponent(
OrthographicCamera camera,
@@ -33,6 +48,7 @@ public TerrainComponent(
this.orientation = orientation;
this.tileSize = tileSize;
this.tiledMapRenderer = renderer;
+
}
public Vector2 tileToWorldPosition(GridPoint2 tilePos) {
@@ -70,6 +86,7 @@ public TiledMap getMap() {
@Override
public void draw(SpriteBatch batch) {
tiledMapRenderer.setView(camera);
+ hoverHighlight();
tiledMapRenderer.render();
}
@@ -89,6 +106,88 @@ public int getLayer() {
return TERRAIN_LAYER;
}
+ // TODO : This is just a visual effect that we might not need in the end but just keeping it here for now
+ public void colorTile(final int x, final int y) {
+ final TiledMapTileLayer tileLayer = (TiledMapTileLayer) tiledMap.getLayers().get(0);
+ final TiledMapTile originalTile = tileLayer.getCell(x, y).getTile();
+
+ ResourceService resourceService = ServiceLocator.getResourceService();
+
+ // Load all the tiles into an array
+ final TerrainTile[] terrainTiles = new TerrainTile[7];
+ for (int i = 0; i < 7; i++) {
+ Texture texture = resourceService.getAsset("images/GrassTile/grass_tile_" + (i + 1) + ".png", Texture.class);
+ terrainTiles[i] = new TerrainTile(new TextureRegion(texture));
+ }
+
+ final float interval = 0.2f; // Switch every 0.2 seconds
+ final float duration = 1.4f; // 7 images * 0.2 seconds each
+
+ Timer.schedule(new Timer.Task() {
+ float timeElapsed = 0.0f;
+
+ @Override
+ public void run() {
+ timeElapsed += interval;
+
+ if (timeElapsed >= duration) {
+ tileLayer.getCell(x, y).setTile(originalTile); // Reset to original tile after the total duration
+ this.cancel(); // End the timer task
+ } else {
+ int index = (int) (timeElapsed / interval);
+ tileLayer.getCell(x, y).setTile(terrainTiles[index]);
+ }
+ }
+ }, 0, interval, (int) (duration / interval) - 1); // Scheduling the task
+ }
+
+ /**
+ * Highlights the tile under the mouse cursor by changing its texture region.
+ *
+ *
When hovering over a tile on the terrain, this method performs the following:
+ *
+ * - Unprojects the mouse's screen position to the world position using the camera.
+ * - Calculates the tile's coordinates based on the world position and tile size.
+ * - If there was a previously highlighted tile, it restores its original texture region.
+ * - If the current tile under the mouse is different from the last hovered tile, it updates
+ * the tile's texture region to a highlight texture.
+ * - Updates the reference to the last hovered tile.
+ *
+ *
+ *
+ * @see TiledMapTileLayer
+ * @see TiledMapTileLayer.Cell
+ * @see TextureRegion
+ */
+
+ public void hoverHighlight() {
+ Vector3 mousePos = new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0);
+ camera.unproject(mousePos);
+
+
+ int tileX = (int) (mousePos.x / tileSize);
+ int tileY = (int) (mousePos.y / tileSize);
+
+ final TiledMapTileLayer tileLayer = (TiledMapTileLayer) tiledMap.getLayers().get(0);
+ TiledMapTileLayer.Cell currentCell = tileLayer.getCell(tileX, tileY);
+
+
+ if (lastHoveredCell != null && lastHoveredCell != currentCell && originalRegion != null) {
+ lastHoveredCell.getTile().setTextureRegion(originalRegion);
+ }
+
+
+ if (currentCell != null && currentCell != lastHoveredCell) {
+ originalRegion = currentCell.getTile().getTextureRegion();
+
+ ResourceService resourceService = ServiceLocator.getResourceService();
+ Texture texture = resourceService.getAsset("images/highlight_tile.png", Texture.class);
+ currentCell.getTile().setTextureRegion(new TextureRegion(texture));
+ }
+
+ lastHoveredCell = currentCell;
+ }
+
public enum TerrainOrientation {
ORTHOGONAL,
ISOMETRIC,
diff --git a/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java b/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java
index d6377e4a1..dfbd4f782 100644
--- a/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java
+++ b/source/core/src/main/com/csse3200/game/areas/terrain/TerrainFactory.java
@@ -1,6 +1,7 @@
package com.csse3200.game.areas.terrain;
import com.badlogic.gdx.graphics.OrthographicCamera;
+import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.maps.tiled.TiledMap;
@@ -8,6 +9,7 @@
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.maps.tiled.TiledMapTileLayer.Cell;
import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
+import com.badlogic.gdx.maps.tiled.tiles.StaticTiledMapTile;
import com.badlogic.gdx.math.GridPoint2;
import com.csse3200.game.components.CameraComponent;
import com.csse3200.game.services.ResourceService;
@@ -81,7 +83,7 @@ private TerrainComponent createTerrain(float tileWorldSize, TextureRegion terrai
* @return A TiledMapRenderer instance suitable for the given map and scale.
*/
- private TiledMapRenderer createRenderer(TiledMap tiledMap, float tileScale) {
+ public TiledMapRenderer createRenderer(TiledMap tiledMap, float tileScale) {
switch (orientation) {
case ORTHOGONAL:
return new OrthogonalTiledMapRenderer(tiledMap, tileScale);
@@ -100,9 +102,8 @@ private TiledMapRenderer createRenderer(TiledMap tiledMap, float tileScale) {
private TiledMap createTiles(GridPoint2 tileSize, TextureRegion terrain) {
TiledMap tiledMap = new TiledMap();
- TerrainTile Tile = new TerrainTile(terrain);
- TiledMapTileLayer Layer = new TiledMapTileLayer(20, 8, tileSize.x, tileSize.y);
- fillInvisibleTiles(Layer, new GridPoint2(20, 8), Tile);
+ TiledMapTileLayer Layer = new TiledMapTileLayer(20, 6, tileSize.x, tileSize.y);
+ fillInvisibleTiles(Layer, new GridPoint2(20, 6), terrain);
tiledMap.getLayers().add(Layer);
return tiledMap;
@@ -114,11 +115,12 @@ private TiledMap createTiles(GridPoint2 tileSize, TextureRegion terrain) {
*
* @param layer The tile layer to fill.
* @param mapSize The size of the map in tiles.
- * @param tile The tile used to fill the layer.
+ * @param terrain The tile used to fill the layer.
*/
- private void fillInvisibleTiles(TiledMapTileLayer layer, GridPoint2 mapSize, TerrainTile tile) {
+ private void fillInvisibleTiles(TiledMapTileLayer layer, GridPoint2 mapSize, TextureRegion terrain) {
for (int x = 0; x < mapSize.x; x++) {
- for (int y = 2; y < mapSize.y; y++) {
+ for (int y = 0; y < mapSize.y; y++) {
+ TerrainTile tile = new TerrainTile(terrain);
Cell cell = new Cell();
cell.setTile(tile);
layer.setCell(x, y, cell);
diff --git a/source/core/src/test/com/csse3200/game/areas/terrain/TerrainComponentTest.java b/source/core/src/test/com/csse3200/game/areas/terrain/TerrainComponentTest.java
index 6761b39b9..72961fb8d 100644
--- a/source/core/src/test/com/csse3200/game/areas/terrain/TerrainComponentTest.java
+++ b/source/core/src/test/com/csse3200/game/areas/terrain/TerrainComponentTest.java
@@ -3,12 +3,23 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.Input;
import com.badlogic.gdx.graphics.OrthographicCamera;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.maps.MapLayers;
import com.badlogic.gdx.maps.tiled.TiledMap;
import com.badlogic.gdx.maps.tiled.TiledMapRenderer;
+import com.badlogic.gdx.maps.tiled.TiledMapTile;
+import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
import com.badlogic.gdx.math.Vector2;
import com.csse3200.game.areas.terrain.TerrainComponent.TerrainOrientation;
import com.csse3200.game.extensions.GameExtension;
+import static org.mockito.Mockito.*;
+
+import com.csse3200.game.services.ResourceService;
+import com.csse3200.game.services.ServiceLocator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -35,6 +46,42 @@ void shouldConvertPositionHexagonal() {
TerrainComponent component = makeComponent(TerrainOrientation.HEXAGONAL, 3f);
}
+ @Test
+ void shouldHighlightTileOnHover1() {
+
+ TerrainComponent component = makeComponent(TerrainOrientation.ORTHOGONAL, 1f);
+
+ // Mock Gdx input to return specific mouse position
+ Gdx.input = mock(Input.class);
+ when(Gdx.input.getX()).thenReturn(2);
+ when(Gdx.input.getY()).thenReturn(4);
+
+
+ MapLayers mockLayers = mock(MapLayers.class);
+ when(component.getMap().getLayers()).thenReturn(mockLayers);
+
+ TiledMapTileLayer mockTileLayer = mock(TiledMapTileLayer.class);
+ when(mockLayers.get(0)).thenReturn(mockTileLayer);
+
+ TiledMapTileLayer.Cell mockCell = mock(TiledMapTileLayer.Cell.class);
+ when(mockTileLayer.getCell(2, 4)).thenReturn(mockCell);
+
+ TiledMapTile mockTile = mock(TiledMapTile.class);
+ when(mockCell.getTile()).thenReturn(mockTile);
+
+
+ Texture mockTexture = mock(Texture.class);
+ ServiceLocator.registerResourceService(mock(ResourceService.class));
+ when(ServiceLocator.getResourceService().getAsset("images/highlight_tile.png", Texture.class))
+ .thenReturn(mockTexture);
+
+
+ component.hoverHighlight();
+
+ // Verify that the tile's texture region was changed
+ verify(mockTile).setTextureRegion(any(TextureRegion.class));
+ }
+
private static TerrainComponent makeComponent(TerrainOrientation orientation, float tileSize) {
OrthographicCamera camera = mock(OrthographicCamera.class);
TiledMap map = mock(TiledMap.class);
diff --git a/source/core/src/test/com/csse3200/game/areas/terrain/TerrainFactoryTest.java b/source/core/src/test/com/csse3200/game/areas/terrain/TerrainFactoryTest.java
new file mode 100644
index 000000000..c70237fd5
--- /dev/null
+++ b/source/core/src/test/com/csse3200/game/areas/terrain/TerrainFactoryTest.java
@@ -0,0 +1,63 @@
+package com.csse3200.game.areas.terrain;
+
+import com.badlogic.gdx.graphics.OrthographicCamera;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.maps.tiled.TiledMap;
+import com.badlogic.gdx.maps.tiled.TiledMapTileLayer;
+import com.badlogic.gdx.maps.tiled.renderers.OrthogonalTiledMapRenderer;
+import com.csse3200.game.components.CameraComponent;
+import com.csse3200.game.services.ResourceService;
+import com.csse3200.game.services.ServiceLocator;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+public class TerrainFactoryTest {
+
+ private TerrainFactory terrainFactory;
+ private CameraComponent mockedCameraComponent;
+ private OrthogonalTiledMapRenderer mockedRenderer;
+ private ResourceService mockResourceService;
+ private Texture mockTexture;
+
+ @BeforeEach
+ public void setUp() {
+ // Create mocks
+ mockedCameraComponent = mock(CameraComponent.class);
+ mockedRenderer = mock(OrthogonalTiledMapRenderer.class);
+ mockResourceService = mock(ResourceService.class);
+ mockTexture = mock(Texture.class);
+
+
+ ServiceLocator.registerResourceService(mockResourceService);
+
+
+ OrthographicCamera mockedCamera = mock(OrthographicCamera.class);
+ when(mockedCameraComponent.getCamera()).thenReturn(mockedCamera);
+
+
+ terrainFactory = spy(new TerrainFactory(mockedCameraComponent));
+
+
+ // When createRenderer is called on terrainFactory return the mockedRenderer
+ doReturn(mockedRenderer).when(terrainFactory).createRenderer(any(TiledMap.class), anyFloat());
+ }
+
+ @Test
+ public void testCreateTerrainGeneral() {
+ // Given the texture is taken from the ResourceService
+ when(mockResourceService.getAsset("images/terrain_use.png", Texture.class)).thenReturn(mockTexture);
+
+
+ TerrainComponent terrainComponent = terrainFactory.createTerrain(TerrainFactory.TerrainType.ALL_DEMO);
+ TiledMapTileLayer layer = (TiledMapTileLayer) terrainComponent.getMap().getLayers().get(0);
+
+ // the terrainComponent should not be null
+ assertNotNull(terrainComponent, "TerrainComponent should not be null");
+ assertEquals(6,layer.getHeight()); // 6 lanes
+ assertEquals(20,layer.getWidth()); // 20 tiles per lane
+
+ }
+}