Created
August 19, 2019 16:54
-
-
Save tudddorrr/3e1d0842f26bd089a01df6ff47f8f1b6 to your computer and use it in GitHub Desktop.
Chunks based world generation for Crawle 2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using UnityEngine; | |
| using Photon.Pun; | |
| using System.Collections.Generic; | |
| using Pathfinding; | |
| using System.Linq; | |
| using ExitGames.Client.Photon; | |
| using System.Collections; | |
| public class WorldGenerator : MonoBehaviour { | |
| public static System.Random rng, masterRng; | |
| public static float worldSize; | |
| // Chunks | |
| GameObject[,] chunks; | |
| public int chunkSize; | |
| // Resources | |
| GameObject chunk, dirt, tallGrass, fruitBush, oreNode, decal, swampGrass, riverSeeker, water; | |
| GameObject[] trees, ambience; | |
| // Spawning ranges | |
| public Vector2 dirtRange, tallGrassRange, fruitBushRange, oreNodeRange, decalRange, treeRange, ambienceRange; | |
| // Max objects | |
| public int maxAnimals, maxRivers, maxBranches, maxRocks; | |
| // Colliders | |
| public List<Collider2D> colliders; | |
| public void Start() { | |
| // World size is based off grass size | |
| worldSize = GameObject.Find("Grass").GetComponent<SpriteRenderer>().size.x; | |
| // Load resources | |
| chunk = Resources.Load("WorldObjects/WorldGen/Chunk") as GameObject; | |
| dirt = Resources.Load("WorldObjects/Dirt") as GameObject; | |
| tallGrass = Resources.Load("WorldObjects/TallGrass") as GameObject; | |
| fruitBush = Resources.Load("WorldObjects/FruitBush") as GameObject; | |
| oreNode = Resources.Load("WorldObjects/OreNode") as GameObject; | |
| decal = Resources.Load("WorldObjects/Decal") as GameObject; | |
| swampGrass = Resources.Load("WorldObjects/SwampGrass") as GameObject; | |
| riverSeeker = Resources.Load("WorldObjects/RiverSeeker") as GameObject; | |
| water = Resources.Load("WorldObjects/Water") as GameObject; | |
| trees = new GameObject[] { | |
| Resources.Load("WorldObjects/Trees/Birch") as GameObject, | |
| Resources.Load("WorldObjects/Trees/Oak") as GameObject, | |
| Resources.Load("WorldObjects/Trees/Willow") as GameObject | |
| }; | |
| ambience = new GameObject[] { | |
| Resources.Load("Sounds/Birds Ambience") as GameObject, | |
| Resources.Load("Sounds/Cicadas Ambience") as GameObject | |
| }; | |
| } | |
| public void Generate() { | |
| // If we're the master client, set the seed, otherwise retrieve it | |
| int seed; | |
| if (PhotonNetwork.IsMasterClient) { | |
| string sDate = System.DateTime.Now.ToUniversalTime().ToString(); | |
| System.DateTime datevalue = System.Convert.ToDateTime(sDate); | |
| seed = (datevalue.DayOfYear + 1) * (datevalue.Minute + 1); | |
| ExitGames.Client.Photon.Hashtable hash = new ExitGames.Client.Photon.Hashtable { | |
| { "SEED", seed } | |
| }; | |
| PhotonNetwork.CurrentRoom.SetCustomProperties(hash); | |
| } else { | |
| seed = (int)PhotonNetwork.CurrentRoom.CustomProperties["SEED"]; | |
| } | |
| rng = new System.Random(seed); | |
| masterRng = new System.Random(seed); | |
| if(PhotonNetwork.IsMasterClient) { | |
| GenAnimals(); | |
| GenRivers(); | |
| GenWorldItems(); | |
| } | |
| // Chunks | |
| Transform chunkParent = GameObject.Find("Chunks").transform; | |
| chunks = new GameObject[(int) worldSize / chunkSize, (int) worldSize / chunkSize]; | |
| for (int x = (int)-worldSize / 2, xIndex = 0; x < worldSize / 2; x += chunkSize, xIndex++) { | |
| for (int y = (int)-worldSize / 2, yIndex = 0; y < worldSize / 2; y += chunkSize, yIndex++) { | |
| chunks[xIndex, yIndex] = Instantiate(chunk, new Vector2(x + chunkSize / 2, y + chunkSize / 2), Quaternion.identity, chunkParent); | |
| chunks[xIndex, yIndex].GetComponent<Chunk>().Index = new Vector2(xIndex, yIndex); | |
| } | |
| } | |
| } | |
| public void ChunkBecameVisible(Chunk c, bool genNeighbours) { | |
| if (!c.Generated) { | |
| c.Generated = true; | |
| StartCoroutine(Generate(c)); | |
| } | |
| if (genNeighbours) { | |
| int range = 1; | |
| for (int x = -range; x < range + 1; x++) { | |
| for (int y = -range; y < range + 1; y++) { | |
| int xIndex = (int)(c.Index.x + x); | |
| int yIndex = (int)(c.Index.y + y); | |
| if (xIndex > 0 && yIndex > 0 && xIndex < chunks.GetLength(0) && yIndex < chunks.GetLength(1)) { | |
| Chunk other = chunks[xIndex, yIndex].GetComponent<Chunk>(); | |
| if (!other.Generated) ChunkBecameVisible(other, false); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| IEnumerator Generate(Chunk c) { | |
| for (int x = 0; x < chunkSize; x++) { | |
| for (int y = 0; y < chunkSize; y++) { | |
| float perlin = Mathf.PerlinNoise((float)rng.NextDouble(), (float)rng.NextDouble()); | |
| Vector2 pos = new Vector2(c.transform.position.x + x, c.transform.position.y + y); | |
| if(!Collides(pos)) { | |
| // Managed | |
| if (InHeightRange(perlin, dirtRange)) { | |
| InitiateWorldObject(c, dirt, pos); | |
| } | |
| if (InHeightRange(perlin, tallGrassRange)) { | |
| InitiateWorldObject(c, tallGrass, pos); | |
| } | |
| // Managed | |
| if (InHeightRange(perlin, fruitBushRange)) { | |
| InitiateWorldObject(c, fruitBush, pos); | |
| } | |
| // Managed | |
| if (InHeightRange(perlin, oreNodeRange)) { | |
| string oreType = ""; | |
| switch (masterRng.Next(0, 4)) { | |
| case 0: | |
| oreType = "coppernode"; | |
| break; | |
| case 1: | |
| oreType = "tinnode"; | |
| break; | |
| case 2: | |
| oreType = "ironnode"; | |
| break; | |
| case 3: | |
| oreType = "coalnode"; | |
| break; | |
| } | |
| GameObject node = InitiateWorldObject(c, oreNode, pos); | |
| node.GetComponent<OreNode>().spriteName = oreType; | |
| node.GetComponent<OreNode>().SetSprite(); | |
| } | |
| if (InHeightRange(perlin, decalRange)) { | |
| InitiateWorldObject(c, decal, pos); | |
| } | |
| // Managed | |
| if (InHeightRange(perlin, treeRange)) { | |
| InitiateWorldObject(c, trees[masterRng.Next(0, 2)], pos); | |
| } | |
| if (InHeightRange(perlin, ambienceRange)) { | |
| InitiateWorldObject(c, ambience[rng.Next(0, 2)], pos); | |
| } | |
| if(x % 2 == 0 || y % 2 == 0) yield return new WaitForEndOfFrame(); | |
| } | |
| } | |
| } | |
| yield return null; | |
| } | |
| private bool InHeightRange(float perlin, Vector2 range) { | |
| return perlin >= range.x && perlin <= range.y; | |
| } | |
| private GameObject InitiateWorldObject(Chunk parent, GameObject prefab, Vector2 pos) { | |
| return Instantiate(prefab, pos, Quaternion.identity, parent.transform); | |
| } | |
| public Vector2[] RandomPerlinPoint(System.Random generator) { | |
| float randomX1 = (float)generator.NextDouble(); | |
| float randomX2 = (float)generator.NextDouble(); | |
| float randomY1 = (float)generator.NextDouble(); | |
| float randomY2 = (float)generator.NextDouble(); | |
| return new Vector2[] { new Vector2(randomX1, randomY1), new Vector2(randomX2, randomY2) }; | |
| } | |
| public bool Collides(Vector2 pos) { | |
| for(int i = 0; i < colliders.Count; i++) { | |
| if (colliders[i].gameObject.layer == LayerMask.NameToLayer("Obstacles") && colliders[i].bounds.Contains(pos)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| public Vector2 RandomPerlinNoisePos(System.Random generator) { | |
| Vector2[] points = RandomPerlinPoint(generator); | |
| Vector2 point = new Vector2( | |
| Mathf.PerlinNoise(points[0].x, points[0].y) + generator.Next((int)-worldSize / 2, (int)worldSize / 2), | |
| Mathf.PerlinNoise(points[1].x, points[1].y) + generator.Next((int)-worldSize / 2, (int)worldSize / 2) | |
| ); | |
| while (Collides(point)) { | |
| point = new Vector2( | |
| Mathf.PerlinNoise(points[0].x, points[0].y) + generator.Next((int)-worldSize / 2, (int)worldSize / 2), | |
| Mathf.PerlinNoise(points[1].x, points[1].y) + generator.Next((int)-worldSize / 2, (int)worldSize / 2) | |
| ); | |
| } | |
| return point; | |
| } | |
| void GenAnimals() { | |
| for (int i = 0; i < maxAnimals; i++) { | |
| string randomAnimal = ""; | |
| switch (masterRng.Next(0, 3)) { | |
| case 0: | |
| randomAnimal = "Bear"; | |
| break; | |
| case 1: | |
| randomAnimal = "Chicken"; | |
| break; | |
| case 2: | |
| randomAnimal = "Rabbit"; | |
| break; | |
| } | |
| PhotonNetwork.InstantiateSceneObject("Animals/" + randomAnimal, RandomPerlinNoisePos(masterRng), Quaternion.identity); | |
| } | |
| } | |
| void GenRivers() { | |
| AstarPath astarPath = GameObject.Find("A*").GetComponent<AstarPath>(); | |
| astarPath.Scan(astarPath.graphs[0]); | |
| List<GameObject> waterPoints = new List<GameObject>(); | |
| for (int i = 0; i < maxRivers; i++) { | |
| List<Vector2> points = new List<Vector2>(); | |
| int numPoints = masterRng.Next(8, 12); | |
| for (int j = 0; j < numPoints; j++) { | |
| if (j == 0 || j == numPoints - 1) { | |
| // First and last points are on the edge of the map | |
| switch (Random.Range(0, 4)) { | |
| case 0: | |
| // Top | |
| points.Add(new Vector2(RandomPerlinNoisePos(masterRng).x, worldSize/2)); | |
| break; | |
| case 1: | |
| // Left | |
| points.Add(new Vector2(-worldSize/2, RandomPerlinNoisePos(masterRng).y)); | |
| break; | |
| case 2: | |
| // Bottom | |
| points.Add(new Vector2(RandomPerlinNoisePos(masterRng).x, -worldSize/2)); | |
| break; | |
| case 3: | |
| // Right | |
| points.Add(new Vector2(worldSize/2, RandomPerlinNoisePos(masterRng).y)); | |
| break; | |
| } | |
| } else { | |
| points.Add(RandomPerlinNoisePos(masterRng)); | |
| } | |
| waterPoints.Add(Instantiate(riverSeeker, points[j], Quaternion.identity)); | |
| } | |
| for (int j = 0; j < numPoints - 1; j++) { | |
| waterPoints[j].GetComponent<Seeker>().StartPath(points[j], points[j + 1], OnRiverPathComplete); | |
| } | |
| } | |
| } | |
| void OnRiverPathComplete(Path p) { | |
| for (int i = 0; i < p.vectorPath.Count; i++) { | |
| GameObject waterObject; | |
| if(i < p.vectorPath.Count - 1) { | |
| float angle = Mathf.Rad2Deg * Mathf.Atan2(p.vectorPath[i].y - p.vectorPath[i + 1].y, p.vectorPath[i].x - p.vectorPath[i + 1].x); | |
| waterObject = Instantiate(water, p.vectorPath[i], Quaternion.Euler(0, 0, angle)); | |
| } else { | |
| waterObject = Instantiate(water, p.vectorPath[i], Quaternion.identity); | |
| } | |
| colliders.Add(waterObject.GetComponent<Collider2D>()); | |
| } | |
| } | |
| void GenWorldItems() { | |
| GameController.instance.SetLoadingMessage("Spawning world items..."); | |
| for (int i = 0; i < maxBranches; i++) { | |
| GameObject branch = PhotonNetwork.InstantiateSceneObject("WorldItems/Branch", RandomPerlinNoisePos(masterRng), Quaternion.identity); | |
| colliders.Add(branch.GetComponent<Collider2D>()); | |
| } | |
| for (int i = 0; i < maxRocks; i++) { | |
| GameObject rock = PhotonNetwork.InstantiateSceneObject("WorldItems/Rock", RandomPerlinNoisePos(masterRng), Quaternion.identity); | |
| colliders.Add(rock.GetComponent<Collider2D>()); | |
| } | |
| } | |
| } | |
| // CHUNKS | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using UnityEngine; | |
| using System.Linq; | |
| public class Chunk : MonoBehaviour { | |
| WorldGenerator worldGen; | |
| public bool Generated { get; set; } | |
| public Vector2 Index { get; set; } | |
| private void Start() { | |
| worldGen = GameController.instance.worldController.gameObject.GetComponent<WorldGenerator>(); | |
| int chunkSize = worldGen.chunkSize; | |
| RectTransform rect = GetComponent<RectTransform>(); | |
| rect.sizeDelta = new Vector2(chunkSize, chunkSize); | |
| // OnBecameVisible doesn't trigger without a texture because Unity | |
| GetComponent<SpriteRenderer>().sprite = Sprite.Create(new Texture2D(chunkSize, chunkSize), new Rect(0, 0, chunkSize, chunkSize), Vector2.zero, 16); | |
| GetComponent<SpriteRenderer>().color = new Color(1f, 1f, 1f, 0f); | |
| } | |
| private void OnBecameVisible() { | |
| if(!Generated) { | |
| worldGen.ChunkBecameVisible(this, true); | |
| } else { | |
| gameObject.transform.GetComponentsInChildren<Collider2D>().All(c2d => c2d.enabled = true); | |
| gameObject.transform.GetComponentsInChildren<SpriteRenderer>().All(sr => sr.enabled = true); | |
| } | |
| } | |
| private void OnBecameInvisible() { | |
| gameObject.transform.GetComponentsInChildren<Collider2D>().All(c2d => c2d.enabled = false); | |
| gameObject.transform.GetComponentsInChildren<SpriteRenderer>().All(sr => sr.enabled = false); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment