Пример #1
0
    public void generateMap(List <Player> players, int width = 16, int height = 16, int tilesBetweenIslands = 2)
    {
        hasBeenGenerated = true;
        mapWidth         = width;
        mapHeight        = height;
        tileMap          = new List <List <Tile> >();
        buildings        = new List <Building>();

        // Default the entire map to water.
        RandomSet <Vector2> waterTiles = new RandomSet <Vector2>();

        for (int x = 0; x < mapWidth; ++x)
        {
            tileMap.Add(new List <Tile>());
            for (int y = 0; y < mapHeight; ++y)
            {
                tileMap[x].Add(Tile.WATER);
                // The border of the map is water, but we don't want to spawn islands that touch the edge.
                if (x > 0 && x < mapWidth - 1 && y > 0 && y < mapHeight - 1)
                {
                    waterTiles.Add(new Vector2(x, y));
                }
            }
        }

        // Terrain
        int numislands    = 12;     //15;
        int islandSizeMin = 3;
        int islandSizeMax = 18;
        int treeDensity   = 4;
        int numcolonies   = 20;

        //Random.seed =
        for (int islandsCreated = 0; islandsCreated < numislands && waterTiles.Count > 0; islandsCreated++)
        {
            Vector2 initialTile = waterTiles.popRandom();

            // A set of water values that could be made into land
            RandomSet <Vector2> adjacentWater = new RandomSet <Vector2>();
            RandomSet <Vector2> islandTiles   = new RandomSet <Vector2>();
            adjacentWater.Add(initialTile);

            int islandSize = Random.Range(islandSizeMin, islandSizeMax + 1);
            for (int n = 0; n < islandSize && adjacentWater.Count > 0; n++)
            {
                Vector2 randTile = adjacentWater.popRandom();
                waterTiles.Remove(randTile);
                adjacentWater.UnionWith(getValidNeighbour4(randTile, waterTiles));

                setTile(randTile, Tile.GRASS);
                GameObject island = Instantiate(islandObject);
                island.transform.parent        = transform;
                island.transform.localPosition = mapToGame(randTile);
                islandTiles.Add(randTile);
            }
            // Remove water tiles near the island from the available water tiles for future island generation.
            for (int i = 0; i < tilesBetweenIslands; i++)
            {
                RandomSet <Vector2> temporarySet = new RandomSet <Vector2>();
                foreach (Vector2 tile in islandTiles)
                {
                    temporarySet.UnionWith(getValidNeighbour8(tile, waterTiles));
                }
                islandTiles = temporarySet;
                waterTiles.DifferenceWith(islandTiles);
            }
        }

        // Floodfill water tiles that are reachable from the corner of the map.
        // For each tile, count the number of adjacent land tiles.
        // Any water tile with 1-2 adjacent land tiles is a valid place for a dock.
        // The set of land tiles adjacent to validDockTiles is the set of validBuildingTiles.
        HashSet <Vector2>   seen               = new HashSet <Vector2>();
        Queue <Vector2>     todo               = new Queue <Vector2>();
        HashSet <Vector2>   validDockTiles     = new HashSet <Vector2>();
        RandomSet <Vector2> validBuildingTiles = new RandomSet <Vector2>();

        // since I excluded the outer border, it is definitely water
        todo.Enqueue(new Vector2(0, 0));
        seen.Add(todo.Peek());
        while (todo.Count > 0)
        {
            Vector2           randTile       = todo.Dequeue();
            HashSet <Vector2> adjacentCoasts = new HashSet <Vector2>();
            foreach (Vector2 adj in getNeighbours4(randTile))
            {
                if (isWater(getTile(adj)))
                {
                    if (!seen.Contains(adj))
                    {
                        todo.Enqueue(adj);
                    }
                }
                else
                {
                    adjacentCoasts.Add(adj);
                }
                seen.Add(adj);
            }
            if (adjacentCoasts.Count > 0 && adjacentCoasts.Count < 3)
            {
                validDockTiles.Add(randTile);
                validBuildingTiles.UnionWith(adjacentCoasts);
            }
        }

        // Put each player's base at the potential tile closest to their side of the map. Player1 should spawn at -y
        // Player 0 is neutral. Player 1 should spawn at +y.
        for (int playerIndex = 1; playerIndex < players.Count; playerIndex++)
        {
            float   angleFromCenter = (Mathf.PI / 2f) + (playerIndex * 2f * Mathf.PI / (players.Count - 1));
            Vector2 preferredTile   = 0.9f * new Vector2(
                mapWidth * (0.5f + 0.5f * Mathf.Cos(angleFromCenter)),
                mapHeight * (0.5f + 0.5f * Mathf.Sin(angleFromCenter)));
            float   minDistFromPreferred = float.MaxValue;
            Vector2 buildingPos          = Vector2.zero;
            foreach (Vector2 possibleTile in validBuildingTiles)
            {
                // TODO: ignore docks that face away from the center of the map.
                // (The dot product of dockDirection and angleFromCenter should be negative)
                float distFromPreferred = (possibleTile - preferredTile).sqrMagnitude;
                if (distFromPreferred < minDistFromPreferred)
                {
                    minDistFromPreferred = distFromPreferred;
                    buildingPos          = possibleTile;
                }
            }
            validBuildingTiles.Remove(buildingPos);
            Vector2 dockTile = getValidNeighbour4(buildingPos, validDockTiles).popRandom();
            setTile(buildingPos, Tile.BUILDING);
            Building building = Instantiate(baseObject).GetComponent <Building>();
            building.init(buildingPos, dockTile, BuildingType.BASE);
            building.setOwner(players[playerIndex]);
            buildings.Add(building);
        }

        // colonies
        for (int i = 0; i < numcolonies && validBuildingTiles.Count > 0; i++)
        {
            Vector2 buildingPos = validBuildingTiles.popRandom();
            Vector2 dockTile    = getValidNeighbour4(buildingPos, validDockTiles).popRandom();
            setTile(buildingPos, Tile.BUILDING);
            Building building = Instantiate(buildingObject).GetComponent <Building>();
            building.init(buildingPos, dockTile, BuildingType.COLONY);
            building.setOwner(players[0]);             // Neutral
            buildings.Add(building);
        }
        Pathing.updateMap(this);

        // Spawn trees.
        for (int x = 0; x < mapWidth; ++x)
        {
            for (int y = 0; y < mapHeight; ++y)
            {
                if (tileMap[x][y] == Tile.GRASS)
                {
                    for (int tr = 0; tr < treeDensity; ++tr)
                    {
                        GameObject tree = Instantiate(treeObject);
                        tree.transform.parent = transform;
                        // z = -0.2f puts the tree in front of the sand
                        tree.transform.localPosition = mapToGame(x, y) + new Vector3(
                            Random.Range(-tileSize * 0.45f, tileSize * 0.45f),
                            Random.Range(-tileSize * 0.45f, tileSize * 0.45f),
                            -0.45f);
                        float sz = Random.Range(tileSize * 0.8f, tileSize * 1.2f);                         // tree radius is about 0.29 by default
                        tree.transform.localScale = new Vector3(sz, sz, 1f);
                    }
                }
            }
        }
    }