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

Viewing 3 posts - 1 through 3 (of 3 total)
  • Author
    Posts
  • #15176
    C Vagdalt
    Participant

    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 map

    photo of the hierarchy from the walkergenerator script:

    View post on imgur.com

    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;
        }
    }
    #15177
    Terence
    Keymaster

    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-structure

    Or 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 the WaveData and SpawnData script and create scriptable objects to assign to the SpawnManager.

    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/

    #15186
    C Vagdalt
    Participant

    thankyuo for the advice, i will try something out

Viewing 3 posts - 1 through 3 (of 3 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: