using UnityEngine; using System.Collections; using System.Collections.Generic; namespace Pathfinding.Examples { /// Example script for generating an infinite procedural world [HelpURL("http://arongranberg.com/astar/docs/class_pathfinding_1_1_examples_1_1_procedural_world.php")] public class ProceduralWorld : MonoBehaviour { public Transform target; public ProceduralPrefab[] prefabs; /// How far away to generate tiles public int range = 1; public int disableAsyncLoadWithinRange = 1; /// World size of tiles public float tileSize = 100; public int subTiles = 20; /// /// Enable static batching on generated tiles. /// Will improve overall FPS, but might cause FPS drops on /// some frames when static batching is done /// public bool staticBatching = false; Queue tileGenerationQueue = new Queue(); public enum RotationRandomness { AllAxes, Y } [System.Serializable] public class ProceduralPrefab { /// Prefab to use public GameObject prefab; /// Number of objects per square world unit public float density = 0; /// /// Multiply by [perlin noise]. /// Value from 0 to 1 indicating weight. /// public float perlin = 0; /// /// Perlin will be raised to this power. /// A higher value gives more distinct edges /// public float perlinPower = 1; /// Some offset to avoid identical density maps public Vector2 perlinOffset = Vector2.zero; /// /// Perlin noise scale. /// A higher value spreads out the maximums and minimums of the density. /// public float perlinScale = 1; /// /// Multiply by [random]. /// Value from 0 to 1 indicating weight. /// public float random = 1; public RotationRandomness randomRotation = RotationRandomness.AllAxes; /// If checked, a single object will be created in the center of each tile public bool singleFixed = false; } /// All tiles Dictionary tiles = new Dictionary(); // Use this for initialization void Start () { // Calculate the closest tiles // and then recalculate the graph Update(); AstarPath.active.Scan(); StartCoroutine(GenerateTiles()); } // Update is called once per frame void Update () { // Calculate the tile the target is standing on Int2 p = new Int2(Mathf.RoundToInt((target.position.x - tileSize*0.5f) / tileSize), Mathf.RoundToInt((target.position.z - tileSize*0.5f) / tileSize)); // Clamp range range = range < 1 ? 1 : range; // Remove tiles which are out of range bool changed = true; while (changed) { changed = false; foreach (KeyValuePair pair in tiles) { if (Mathf.Abs(pair.Key.x-p.x) > range || Mathf.Abs(pair.Key.y-p.y) > range) { pair.Value.Destroy(); tiles.Remove(pair.Key); changed = true; break; } } } // Add tiles which have come in range // and start calculating them for (int x = p.x-range; x <= p.x+range; x++) { for (int z = p.y-range; z <= p.y+range; z++) { if (!tiles.ContainsKey(new Int2(x, z))) { ProceduralTile tile = new ProceduralTile(this, x, z); var generator = tile.Generate(); // Tick it one step forward generator.MoveNext(); // Calculate the rest later tileGenerationQueue.Enqueue(generator); tiles.Add(new Int2(x, z), tile); } } } // The ones directly adjacent to the current one // should always be completely calculated // make sure they are for (int x = p.x-disableAsyncLoadWithinRange; x <= p.x+disableAsyncLoadWithinRange; x++) { for (int z = p.y-disableAsyncLoadWithinRange; z <= p.y+disableAsyncLoadWithinRange; z++) { tiles[new Int2(x, z)].ForceFinish(); } } } IEnumerator GenerateTiles () { while (true) { if (tileGenerationQueue.Count > 0) { var generator = tileGenerationQueue.Dequeue(); yield return StartCoroutine(generator); } yield return null; } } class ProceduralTile { int x, z; System.Random rnd; ProceduralWorld world; public bool destroyed { get; private set; } public ProceduralTile (ProceduralWorld world, int x, int z) { this.x = x; this.z = z; this.world = world; rnd = new System.Random((x * 10007) ^ (z*36007)); } Transform root; IEnumerator ie; public IEnumerator Generate () { ie = InternalGenerate(); GameObject rt = new GameObject("Tile " + x + " " + z); root = rt.transform; while (ie != null && root != null && ie.MoveNext()) yield return ie.Current; ie = null; } public void ForceFinish () { while (ie != null && root != null && ie.MoveNext()) {} ie = null; } Vector3 RandomInside () { Vector3 v = new Vector3(); v.x = (x + (float)rnd.NextDouble())*world.tileSize; v.z = (z + (float)rnd.NextDouble())*world.tileSize; return v; } Vector3 RandomInside (float px, float pz) { Vector3 v = new Vector3(); v.x = (px + (float)rnd.NextDouble()/world.subTiles)*world.tileSize; v.z = (pz + (float)rnd.NextDouble()/world.subTiles)*world.tileSize; return v; } Quaternion RandomYRot (ProceduralPrefab prefab) { return prefab.randomRotation == RotationRandomness.AllAxes ? Quaternion.Euler(360*(float)rnd.NextDouble(), 360*(float)rnd.NextDouble(), 360*(float)rnd.NextDouble()) : Quaternion.Euler(0, 360 * (float)rnd.NextDouble(), 0); } IEnumerator InternalGenerate () { Debug.Log("Generating tile " + x + ", " + z); int counter = 0; float[, ] ditherMap = new float[world.subTiles+2, world.subTiles+2]; //List objs = new List(); for (int i = 0; i < world.prefabs.Length; i++) { ProceduralPrefab pref = world.prefabs[i]; if (pref.singleFixed) { Vector3 p = new Vector3((x+0.5f) * world.tileSize, 0, (z+0.5f) * world.tileSize); GameObject ob = GameObject.Instantiate(pref.prefab, p, Quaternion.identity) as GameObject; ob.transform.parent = root; } else { float subSize = world.tileSize/world.subTiles; for (int sx = 0; sx < world.subTiles; sx++) { for (int sz = 0; sz < world.subTiles; sz++) { ditherMap[sx+1, sz+1] = 0; } } for (int sx = 0; sx < world.subTiles; sx++) { for (int sz = 0; sz < world.subTiles; sz++) { float px = x + sx/(float)world.subTiles;//sx / world.tileSize; float pz = z + sz/(float)world.subTiles;//sz / world.tileSize; float perl = Mathf.Pow(Mathf.PerlinNoise((px + pref.perlinOffset.x)*pref.perlinScale, (pz + pref.perlinOffset.y)*pref.perlinScale), pref.perlinPower); float density = pref.density * Mathf.Lerp(1, perl, pref.perlin) * Mathf.Lerp(1, (float)rnd.NextDouble(), pref.random); float fcount = subSize*subSize*density + ditherMap[sx+1, sz+1]; int count = Mathf.RoundToInt(fcount); // Apply dithering // See http://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering ditherMap[sx+1+1, sz+1+0] += (7f/16f) * (fcount - count); ditherMap[sx+1-1, sz+1+1] += (3f/16f) * (fcount - count); ditherMap[sx+1+0, sz+1+1] += (5f/16f) * (fcount - count); ditherMap[sx+1+1, sz+1+1] += (1f/16f) * (fcount - count); // Create a number of objects for (int j = 0; j < count; j++) { // Find a random position inside the current sub-tile Vector3 p = RandomInside(px, pz); GameObject ob = GameObject.Instantiate(pref.prefab, p, RandomYRot(pref)) as GameObject; ob.transform.parent = root; //ob.SetActive ( false ); //objs.Add ( ob ); counter++; if (counter % 2 == 0) yield return null; } } } } } ditherMap = null; yield return null; yield return null; //Batch everything for improved performance if (Application.HasProLicense() && world.staticBatching) { StaticBatchingUtility.Combine(root.gameObject); } } public void Destroy () { if (root != null) { Debug.Log("Destroying tile " + x + ", " + z); GameObject.Destroy(root.gameObject); root = null; } // Make sure the tile generator coroutine is destroyed ie = null; } } } }