123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- using UnityEngine;
- using System.Collections;
- using System.Collections.Generic;
- namespace Pathfinding.Examples {
- /// <summary>Example script for generating an infinite procedural world</summary>
- [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_procedural_world.php")]
- public class ProceduralWorld : MonoBehaviour {
- public Transform target;
- public ProceduralPrefab[] prefabs;
- /// <summary>How far away to generate tiles</summary>
- public int range = 1;
- public int disableAsyncLoadWithinRange = 1;
- /// <summary>World size of tiles</summary>
- public float tileSize = 100;
- public int subTiles = 20;
- /// <summary>
- /// Enable static batching on generated tiles.
- /// Will improve overall FPS, but might cause FPS drops on
- /// some frames when static batching is done
- /// </summary>
- public bool staticBatching = false;
- Queue<IEnumerator> tileGenerationQueue = new Queue<IEnumerator>();
- public enum RotationRandomness {
- AllAxes,
- Y
- }
- [System.Serializable]
- public class ProceduralPrefab {
- /// <summary>Prefab to use</summary>
- public GameObject prefab;
- /// <summary>Number of objects per square world unit</summary>
- public float density = 0;
- /// <summary>
- /// Multiply by [perlin noise].
- /// Value from 0 to 1 indicating weight.
- /// </summary>
- public float perlin = 0;
- /// <summary>
- /// Perlin will be raised to this power.
- /// A higher value gives more distinct edges
- /// </summary>
- public float perlinPower = 1;
- /// <summary>Some offset to avoid identical density maps</summary>
- public Vector2 perlinOffset = Vector2.zero;
- /// <summary>
- /// Perlin noise scale.
- /// A higher value spreads out the maximums and minimums of the density.
- /// </summary>
- public float perlinScale = 1;
- /// <summary>
- /// Multiply by [random].
- /// Value from 0 to 1 indicating weight.
- /// </summary>
- public float random = 1;
- public RotationRandomness randomRotation = RotationRandomness.AllAxes;
- /// <summary>If checked, a single object will be created in the center of each tile</summary>
- public bool singleFixed = false;
- }
- /// <summary>All tiles</summary>
- Dictionary<Int2, ProceduralTile> tiles = new Dictionary<Int2, ProceduralTile>();
- // 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<Int2, ProceduralTile> 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<GameObject> objs = new List<GameObject>();
- 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;
- }
- }
- }
- }
|