Forum begins after the advertisement:
[Part 7] spawning the waves on a randomly generating map
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 7] spawning the waves on a randomly generating map
- This topic has 2 replies, 2 voices, and was last updated 4 months, 3 weeks ago by C Vagdalt.
-
AuthorPosts
-
July 2, 2024 at 9:47 pm #15176::
hello,
along with this tutorial i have a scene where a map gets randomly generated when the scene loads, how can i spawn the enemies on this mapphoto of the hierarchy from the walkergenerator script:
code for the map generation:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Tilemaps; public class WalkerGenerator : MonoBehaviour { public enum TileType { Empty, Floor, Wall } public int mapWidth = 60; public int mapHeight = 60; public int roomCount = 10; public int minRoomSize = 5; public int maxRoomSize = 15; public Tilemap tileMap; public Tilemap coldiertile; public Tile floorTile; public Tile wallTile; public Tile topBotRight; public Tile topBotLeft; public Tile botLeftRight; public Tile topLeftRight; public Tile botWallTile; public Tile topWallTile; public Tile rightWallTile; public Tile leftWallTile; public Tile topBotTile; public Tile leftRightTile; public Tile rightBot; public Tile rightTop; public Tile leftBot; public Tile leftTop; public GameObject player; public GameObject chestPrefab; public GameObject portalPrefab; public GameObject[] decorationPrefabs; public CameraController cameraController; // Updated to CameraController private TileType[,] map; private List<RectInt> rooms; private bool portalSpawned = false; private bool playerSpawned = false; // Add a flag for player spawning void Start() { InitializeGrid(); } public void InitializeGrid() { map = new TileType[mapWidth, mapHeight]; rooms = new List<RectInt>(); CreateRooms(); CreateCorridors(); CreateWalls(); ClearExistingObjects(); StartCoroutine(SpawnObjectsCoroutine()); } private void CreateRooms() { for (int i = 0; i < roomCount; i++) { int width = Random.Range(minRoomSize, maxRoomSize); int height = Random.Range(minRoomSize, maxRoomSize); int x = Random.Range(1, mapWidth - width - 1); int y = Random.Range(1, mapHeight - height - 1); RectInt newRoom = new RectInt(x, y, width, height); bool roomIntersects = false; foreach (RectInt room in rooms) { if (newRoom.Overlaps(room)) { roomIntersects = true; break; } } if (!roomIntersects) { rooms.Add(newRoom); for (int j = x; j < x + width; j++) { for (int k = y; k < y + height; k++) { map[j, k] = TileType.Floor; } } } } } private void CreateCorridors() { for (int i = 0; i < rooms.Count - 1; i++) { Vector2Int pointA = new Vector2Int(rooms[i].x + rooms[i].width / 2, rooms[i].y + rooms[i].height / 2); Vector2Int pointB = new Vector2Int(rooms[i + 1].x + rooms[i + 1].width / 2, rooms[i + 1].y + rooms[i + 1].height / 2); while (pointA != pointB) { if (pointA.x != pointB.x) { if (pointA.x < pointB.x) pointA.x++; else pointA.x--; } else if (pointA.y != pointB.y) { if (pointA.y < pointB.y) pointA.y++; else pointA.y--; } map[pointA.x, pointA.y] = TileType.Floor; } } } private void CreateWalls() { coldiertile.ClearAllTiles(); tileMap.ClearAllTiles(); for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { if (map[x, y] == TileType.Floor) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int newX = x + dx; int newY = y + dy; if (newX >= 0 && newY >= 0 && newX < mapWidth && newY < mapHeight && map[newX, newY] == TileType.Empty) { map[newX, newY] = TileType.Wall; } } } } } } for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { if (map[x, y] == TileType.Wall) { Tile tileToPlace = DetermineWallTile(x, y); coldiertile.SetTile(new Vector3Int(x, y, 0), tileToPlace); } else if (map[x, y] == TileType.Floor) { tileMap.SetTile(new Vector3Int(x, y, 0), floorTile); } } } } private Tile DetermineWallTile(int x, int y) { bool left = x > 0 && map[x - 1, y] == TileType.Floor; bool right = x < mapWidth - 1 && map[x + 1, y] == TileType.Floor; bool top = y < mapHeight - 1 && map[x, y + 1] == TileType.Floor; bool bottom = y > 0 && map[x, y - 1] == TileType.Floor; bool leftWall = x > 0 && map[x - 1, y] == TileType.Wall; bool rightWall = x < mapWidth - 1 && map[x + 1, y] == TileType.Wall; bool bottomWall = y > 0 && map[x, y - 1] == TileType.Wall; bool topWall = y < mapHeight - 1 && map[x, y + 1] == TileType.Wall; if (left && top && bottom) return topBotRight; if (left && top && right) return topBotLeft; if (bottom && left && right) return botLeftRight; if (top && left && right) return topLeftRight; if (right && bottom) return rightTop; if (right && top) return rightBot; if (left && bottom) return leftTop; if (left && top) return leftBot; if (right && left) return leftRightTile; if (top && bottom) return topBotTile; if (top) return botWallTile; if (bottom) return topWallTile; if (right) return rightWallTile; if (left) return leftWallTile; if (rightWall && bottomWall) return null; if (leftWall && bottomWall) return null; if (leftWall && topWall) return null; if (rightWall && topWall) return null; return wallTile; } private void ClearExistingObjects() { Debug.Log("Clearing existing objects..."); foreach (GameObject obj in GameObject.FindGameObjectsWithTag("Chest")) { Destroy(obj); } foreach (GameObject obj in GameObject.FindGameObjectsWithTag("Portal")) { Destroy(obj); } foreach (GameObject obj in GameObject.FindGameObjectsWithTag("Decoration")) { Destroy(obj); } // Only clear player if explicitly needed if (playerSpawned) { foreach (GameObject obj in GameObject.FindGameObjectsWithTag("Player")) { Destroy(obj); } playerSpawned = false; } } private IEnumerator SpawnObjectsCoroutine() { Debug.Log("Starting SpawnObjectsCoroutine..."); // Removed SpawnPlayer from WalkerGenerator yield return new WaitForSeconds(0.5f); yield return StartCoroutine(SpawnPortal()); yield return new WaitForSeconds(0.5f); yield return StartCoroutine(SpawnChests()); yield return new WaitForSeconds(0.5f); yield return StartCoroutine(SpawnDecorations()); } private IEnumerator SpawnPortal() { Debug.Log("Spawning portal..."); if (portalPrefab != null && !portalSpawned) { List<Vector3Int> floorTiles = new List<Vector3Int>(); for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { if (map[x, y] == TileType.Floor) { floorTiles.Add(new Vector3Int(x, y, 0)); } } } if (floorTiles.Count == 0) { Debug.LogError("No floor tiles available for spawning objects."); yield break; } Vector3Int playerSpawnPos = floorTiles[Random.Range(0, floorTiles.Count)]; Vector3Int portalPos = GetValidPositionInRoom(playerSpawnPos, 5); if (portalPos != Vector3Int.zero && IsWithinMapBounds(portalPos)) { Debug.Log($"Portal spawn position: {portalPos}"); Instantiate(portalPrefab, new Vector3(portalPos.x + 0.5f, portalPos.y + 0.5f, 0), Quaternion.identity); portalSpawned = true; } else { Debug.LogWarning("No valid position found for the portal."); } } yield return null; } private IEnumerator SpawnChests() { Debug.Log("Spawning chests..."); int chestCount = Random.Range(1, 5); // Spawns between 1 and 4 chests List<Vector3Int> floorTiles = new List<Vector3Int>(); for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { if (map[x, y] == TileType.Floor) { floorTiles.Add(new Vector3Int(x, y, 0)); } } } if (floorTiles.Count == 0) { Debug.LogError("No floor tiles available for spawning objects."); yield break; } Vector3Int playerSpawnPos = floorTiles[Random.Range(0, floorTiles.Count)]; for (int i = 0; i < chestCount; i++) { if (chestPrefab != null) { Vector3Int chestPos = GetValidPositionInRoom(playerSpawnPos, 2); if (chestPos != Vector3Int.zero && IsWithinMapBounds(chestPos)) { Debug.Log($"Chest spawn position: {chestPos}"); Instantiate(chestPrefab, new Vector3(chestPos.x + 0.5f, chestPos.y + 0.5f, 0), Quaternion.identity); } else { Debug.LogWarning("No valid position found for a chest."); } } } yield return null; } private IEnumerator SpawnDecorations() { Debug.Log("Spawning decorations..."); List<Vector3Int> floorTiles = new List<Vector3Int>(); for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { if (map[x, y] == TileType.Floor) { floorTiles.Add(new Vector3Int(x, y, 0)); } } } if (floorTiles.Count == 0) { Debug.LogError("No floor tiles available for spawning objects."); yield break; } foreach (var pos in floorTiles) { if (Random.value < 0.1f) { GameObject decorationPrefab = decorationPrefabs[Random.Range(0, decorationPrefabs.Length)]; if (IsValidDecorationPosition(pos.x, pos.y)) { Debug.Log($"Decoration spawn position: {pos}"); Instantiate(decorationPrefab, new Vector3(pos.x + 0.5f, pos.y + 0.5f, 0), Quaternion.identity); } } } yield return null; } public Vector3 GetRandomFloorPosition() { List<Vector3Int> floorTiles = new List<Vector3Int>(); for (int x = 0; x < mapWidth; x++) { for (int y = 0; y < mapHeight; y++) { if (map[x, y] == TileType.Floor) { floorTiles.Add(new Vector3Int(x, y, 0)); } } } if (floorTiles.Count == 0) { Debug.LogError("No floor tiles available."); return Vector3.zero; } Vector3Int randomTile = floorTiles[Random.Range(0, floorTiles.Count)]; return new Vector3(randomTile.x + 0.5f, randomTile.y + 0.5f, 0); } private Vector3Int GetValidPositionInRoom(Vector3Int playerSpawnPos, int minDistance) { List<Vector3Int> validPositions = new List<Vector3Int>(); foreach (var room in rooms) { for (int x = room.xMin; x < room.xMax; x++) { for (int y = room.yMin; y < room.yMax; y++) { if (x <= 0 || x >= mapWidth - 1 || y <= 0 || y >= mapHeight - 1) continue; // Ensure within bounds Vector3Int pos = new Vector3Int(x, y, 0); if (map[x, y] == TileType.Floor && Vector3Int.Distance(pos, playerSpawnPos) >= minDistance) { if (IsPositionValidForPortal(pos)) { validPositions.Add(pos); } } } } } if (validPositions.Count == 0) { Debug.LogWarning("No valid positions found for portal or chest."); return Vector3Int.zero; } return validPositions[Random.Range(0, validPositions.Count)]; } private bool IsPositionValidForPortal(Vector3Int position) { for (int dx = -1; dx <= 1; dx++) { for (int dy = -1; dy <= 1; dy++) { int newX = position.x + dx; int newY = position.y + dy; if (newX >= 0 && newY >= 0 && newX < mapWidth && newY < mapHeight) { if (map[newX, newY] == TileType.Wall) { return false; } } } } return true; } private bool IsWithinMapBounds(Vector3Int position) { return position.x > 0 && position.x < mapWidth - 1 && position.y > 0 && position.y < mapHeight - 1; } private bool IsValidDecorationPosition(int x, int y) { return IsValidChestPosition(x, y); } private bool IsValidChestPosition(int x, int y) { bool left = x > 0 && map[x - 1, y] == TileType.Floor; bool right = x < mapWidth - 1 && map[x + 1, y] == TileType.Floor; bool top = y < mapHeight - 1 && map[x, y + 1] == TileType.Floor; bool bottom = y > 0 && map[x, y - 1] == TileType.Floor; int adjacentFloorTiles = 0; if (left) adjacentFloorTiles++; if (right) adjacentFloorTiles++; if (top) adjacentFloorTiles++; if (bottom) adjacentFloorTiles++; return adjacentFloorTiles >= 2; } }
July 3, 2024 at 1:47 pm #15177::Vagdalt, the enemy spawning system is separate from the map generation one, so just create another empty GameObject and attach the Enemy Spawner script to it, then configure it to spawn enemies.
Here is the link to the section in the Part 7 article that covers the
EnemySpawner
script: https://blog.terresquall.com/2023/02/creating-a-rogue-like-vampire-survivors-part-7/#class-structureOr if you are feeling adventurous, we recently released the Part 21 article, which revamps the enemy spawning system to make it more powerful and easier to use. You can use the
SpawnManager
script from there as well, though you will also need to copy theWaveData
andSpawnData
script and create scriptable objects to assign to theSpawnManager
.Here’s the Part 21 article, which will be free until we release the video: https://blog.terresquall.com/2024/07/creating-a-rogue-like-vampire-survivors-part-21/
July 4, 2024 at 5:19 pm #15186 -
AuthorPosts
- You must be logged in to reply to this topic.
Advertisement below: