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: + *

    + *
  1. Unprojects the mouse's screen position to the world position using the camera.
  2. + *
  3. Calculates the tile's coordinates based on the world position and tile size.
  4. + *
  5. If there was a previously highlighted tile, it restores its original texture region.
  6. + *
  7. 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.
  8. + *
  9. Updates the reference to the last hovered tile.
  10. + *
+ *

+ * + * @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 + + } +}