ProceduralWorld.cs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. namespace Pathfinding.Examples {
  5. /// <summary>Example script for generating an infinite procedural world</summary>
  6. [HelpURL("http://arongranberg.com/astar/documentation/stable/class_pathfinding_1_1_examples_1_1_procedural_world.php")]
  7. public class ProceduralWorld : MonoBehaviour {
  8. public Transform target;
  9. public ProceduralPrefab[] prefabs;
  10. /// <summary>How far away to generate tiles</summary>
  11. public int range = 1;
  12. public int disableAsyncLoadWithinRange = 1;
  13. /// <summary>World size of tiles</summary>
  14. public float tileSize = 100;
  15. public int subTiles = 20;
  16. /// <summary>
  17. /// Enable static batching on generated tiles.
  18. /// Will improve overall FPS, but might cause FPS drops on
  19. /// some frames when static batching is done
  20. /// </summary>
  21. public bool staticBatching = false;
  22. Queue<IEnumerator> tileGenerationQueue = new Queue<IEnumerator>();
  23. public enum RotationRandomness {
  24. AllAxes,
  25. Y
  26. }
  27. [System.Serializable]
  28. public class ProceduralPrefab {
  29. /// <summary>Prefab to use</summary>
  30. public GameObject prefab;
  31. /// <summary>Number of objects per square world unit</summary>
  32. public float density = 0;
  33. /// <summary>
  34. /// Multiply by [perlin noise].
  35. /// Value from 0 to 1 indicating weight.
  36. /// </summary>
  37. public float perlin = 0;
  38. /// <summary>
  39. /// Perlin will be raised to this power.
  40. /// A higher value gives more distinct edges
  41. /// </summary>
  42. public float perlinPower = 1;
  43. /// <summary>Some offset to avoid identical density maps</summary>
  44. public Vector2 perlinOffset = Vector2.zero;
  45. /// <summary>
  46. /// Perlin noise scale.
  47. /// A higher value spreads out the maximums and minimums of the density.
  48. /// </summary>
  49. public float perlinScale = 1;
  50. /// <summary>
  51. /// Multiply by [random].
  52. /// Value from 0 to 1 indicating weight.
  53. /// </summary>
  54. public float random = 1;
  55. public RotationRandomness randomRotation = RotationRandomness.AllAxes;
  56. /// <summary>If checked, a single object will be created in the center of each tile</summary>
  57. public bool singleFixed = false;
  58. }
  59. /// <summary>All tiles</summary>
  60. Dictionary<Int2, ProceduralTile> tiles = new Dictionary<Int2, ProceduralTile>();
  61. // Use this for initialization
  62. void Start () {
  63. // Calculate the closest tiles
  64. // and then recalculate the graph
  65. Update();
  66. AstarPath.active.Scan();
  67. StartCoroutine(GenerateTiles());
  68. }
  69. // Update is called once per frame
  70. void Update () {
  71. // Calculate the tile the target is standing on
  72. Int2 p = new Int2(Mathf.RoundToInt((target.position.x - tileSize*0.5f) / tileSize), Mathf.RoundToInt((target.position.z - tileSize*0.5f) / tileSize));
  73. // Clamp range
  74. range = range < 1 ? 1 : range;
  75. // Remove tiles which are out of range
  76. bool changed = true;
  77. while (changed) {
  78. changed = false;
  79. foreach (KeyValuePair<Int2, ProceduralTile> pair in tiles) {
  80. if (Mathf.Abs(pair.Key.x-p.x) > range || Mathf.Abs(pair.Key.y-p.y) > range) {
  81. pair.Value.Destroy();
  82. tiles.Remove(pair.Key);
  83. changed = true;
  84. break;
  85. }
  86. }
  87. }
  88. // Add tiles which have come in range
  89. // and start calculating them
  90. for (int x = p.x-range; x <= p.x+range; x++) {
  91. for (int z = p.y-range; z <= p.y+range; z++) {
  92. if (!tiles.ContainsKey(new Int2(x, z))) {
  93. ProceduralTile tile = new ProceduralTile(this, x, z);
  94. var generator = tile.Generate();
  95. // Tick it one step forward
  96. generator.MoveNext();
  97. // Calculate the rest later
  98. tileGenerationQueue.Enqueue(generator);
  99. tiles.Add(new Int2(x, z), tile);
  100. }
  101. }
  102. }
  103. // The ones directly adjacent to the current one
  104. // should always be completely calculated
  105. // make sure they are
  106. for (int x = p.x-disableAsyncLoadWithinRange; x <= p.x+disableAsyncLoadWithinRange; x++) {
  107. for (int z = p.y-disableAsyncLoadWithinRange; z <= p.y+disableAsyncLoadWithinRange; z++) {
  108. tiles[new Int2(x, z)].ForceFinish();
  109. }
  110. }
  111. }
  112. IEnumerator GenerateTiles () {
  113. while (true) {
  114. if (tileGenerationQueue.Count > 0) {
  115. var generator = tileGenerationQueue.Dequeue();
  116. yield return StartCoroutine(generator);
  117. }
  118. yield return null;
  119. }
  120. }
  121. class ProceduralTile {
  122. int x, z;
  123. System.Random rnd;
  124. ProceduralWorld world;
  125. public bool destroyed { get; private set; }
  126. public ProceduralTile (ProceduralWorld world, int x, int z) {
  127. this.x = x;
  128. this.z = z;
  129. this.world = world;
  130. rnd = new System.Random((x * 10007) ^ (z*36007));
  131. }
  132. Transform root;
  133. IEnumerator ie;
  134. public IEnumerator Generate () {
  135. ie = InternalGenerate();
  136. GameObject rt = new GameObject("Tile " + x + " " + z);
  137. root = rt.transform;
  138. while (ie != null && root != null && ie.MoveNext()) yield return ie.Current;
  139. ie = null;
  140. }
  141. public void ForceFinish () {
  142. while (ie != null && root != null && ie.MoveNext()) {}
  143. ie = null;
  144. }
  145. Vector3 RandomInside () {
  146. Vector3 v = new Vector3();
  147. v.x = (x + (float)rnd.NextDouble())*world.tileSize;
  148. v.z = (z + (float)rnd.NextDouble())*world.tileSize;
  149. return v;
  150. }
  151. Vector3 RandomInside (float px, float pz) {
  152. Vector3 v = new Vector3();
  153. v.x = (px + (float)rnd.NextDouble()/world.subTiles)*world.tileSize;
  154. v.z = (pz + (float)rnd.NextDouble()/world.subTiles)*world.tileSize;
  155. return v;
  156. }
  157. Quaternion RandomYRot (ProceduralPrefab prefab) {
  158. 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);
  159. }
  160. IEnumerator InternalGenerate () {
  161. Debug.Log("Generating tile " + x + ", " + z);
  162. int counter = 0;
  163. float[, ] ditherMap = new float[world.subTiles+2, world.subTiles+2];
  164. //List<GameObject> objs = new List<GameObject>();
  165. for (int i = 0; i < world.prefabs.Length; i++) {
  166. ProceduralPrefab pref = world.prefabs[i];
  167. if (pref.singleFixed) {
  168. Vector3 p = new Vector3((x+0.5f) * world.tileSize, 0, (z+0.5f) * world.tileSize);
  169. GameObject ob = GameObject.Instantiate(pref.prefab, p, Quaternion.identity) as GameObject;
  170. ob.transform.parent = root;
  171. } else {
  172. float subSize = world.tileSize/world.subTiles;
  173. for (int sx = 0; sx < world.subTiles; sx++) {
  174. for (int sz = 0; sz < world.subTiles; sz++) {
  175. ditherMap[sx+1, sz+1] = 0;
  176. }
  177. }
  178. for (int sx = 0; sx < world.subTiles; sx++) {
  179. for (int sz = 0; sz < world.subTiles; sz++) {
  180. float px = x + sx/(float)world.subTiles;//sx / world.tileSize;
  181. float pz = z + sz/(float)world.subTiles;//sz / world.tileSize;
  182. float perl = Mathf.Pow(Mathf.PerlinNoise((px + pref.perlinOffset.x)*pref.perlinScale, (pz + pref.perlinOffset.y)*pref.perlinScale), pref.perlinPower);
  183. float density = pref.density * Mathf.Lerp(1, perl, pref.perlin) * Mathf.Lerp(1, (float)rnd.NextDouble(), pref.random);
  184. float fcount = subSize*subSize*density + ditherMap[sx+1, sz+1];
  185. int count = Mathf.RoundToInt(fcount);
  186. // Apply dithering
  187. // See http://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
  188. ditherMap[sx+1+1, sz+1+0] += (7f/16f) * (fcount - count);
  189. ditherMap[sx+1-1, sz+1+1] += (3f/16f) * (fcount - count);
  190. ditherMap[sx+1+0, sz+1+1] += (5f/16f) * (fcount - count);
  191. ditherMap[sx+1+1, sz+1+1] += (1f/16f) * (fcount - count);
  192. // Create a number of objects
  193. for (int j = 0; j < count; j++) {
  194. // Find a random position inside the current sub-tile
  195. Vector3 p = RandomInside(px, pz);
  196. GameObject ob = GameObject.Instantiate(pref.prefab, p, RandomYRot(pref)) as GameObject;
  197. ob.transform.parent = root;
  198. //ob.SetActive ( false );
  199. //objs.Add ( ob );
  200. counter++;
  201. if (counter % 2 == 0)
  202. yield return null;
  203. }
  204. }
  205. }
  206. }
  207. }
  208. ditherMap = null;
  209. yield return null;
  210. yield return null;
  211. //Batch everything for improved performance
  212. if (Application.HasProLicense() && world.staticBatching) {
  213. StaticBatchingUtility.Combine(root.gameObject);
  214. }
  215. }
  216. public void Destroy () {
  217. if (root != null) {
  218. Debug.Log("Destroying tile " + x + ", " + z);
  219. GameObject.Destroy(root.gameObject);
  220. root = null;
  221. }
  222. // Make sure the tile generator coroutine is destroyed
  223. ie = null;
  224. }
  225. }
  226. }
  227. }