Skip to content

Instantly share code, notes, and snippets.

@tudddorrr
Created August 19, 2019 16:54
Show Gist options
  • Select an option

  • Save tudddorrr/3e1d0842f26bd089a01df6ff47f8f1b6 to your computer and use it in GitHub Desktop.

Select an option

Save tudddorrr/3e1d0842f26bd089a01df6ff47f8f1b6 to your computer and use it in GitHub Desktop.
Chunks based world generation for Crawle 2
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