RefresherTowel

Towel Stomping

Dec 14th, 2020 (edited)
1,036
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*
  2.  
  3. ------------ MAP GENERATION ------------
  4. This is for use in GMS2.3, to be plunked straight into a script file. I call it Towel Stomping. It generates and returns a ds_grid that is "noisy" and normalised between 0 and 1. Each grid cell will be somewhat similar to the grid cells around it, in the same way that Simplex noise or Perlin noise is, but this works much faster than Perlin noise (at least in pure GML terms, not always when it comes to shaders/buffers). It uses a combination of ideas taken from normal noise generation and "drunkards walk" generation, creating generations of walkers that move across the grid, adding or subtracting to the values in the grid.
  5.  
  6. ------------ HOW TO USE ------------
  7. Simply paste the entirety of the code after this multi-line comment into a script file, then create an object and put this code in it's Create Event (or wherever you want the generation to happen):
  8.  
  9.  
  10.  
  11. map = new Map(); // Creates the map struct for use with the below functions
  12. my_map = map.instantiateNew(10,8,250,CIRCLE); // instantiateNew() returns the id of the generated ds_grid, the arguments are just example arguments, tweak them and see how the generation changes
  13. map.run(); // The meat of the generator, this creates and runs the walkers and normalises the grid once they've finished doing their job
  14.  
  15.  
  16.  
  17. And that's it, you now have a ds_grid stored in my_map that is normalised and holds random but related values, useful for map creation or whatever else you might use a 2D height/noise map for. If you want to use different sized cells, just edit the #macro CELLSIZE to the size of the cells you want it to use. Finally, just as a side note, CIRCLE is best used as the grid_type when you want more organic type stuff (so perhaps a world generator) and SQUARE is best used when you want a more "organised" type thing, such as perhaps a city layout.
  18.  
  19. Go here for a demo gif of how the generated grid might be used: https://refreshertowelgames.files.wordpress.com/2020/12/map-gen.gif
  20.  
  21. Mayhaps you might want to have a look over my site if you like this: https://refreshertowel.games/
  22.  
  23. */
  24.  
  25. // 2D Height/Noise Grid Generator
  26.  
  27. #macro CELLSIZE 64
  28. #macro SQUARE 0
  29. #macro CIRCLE 1
  30.  
  31. global.walkers = [];
  32.  
  33. function MakeWalker(_multiplier,_lifespan) constructor {
  34.     ///@function MakeWalker(granularity,multiplier,lifespan);
  35.    
  36.     x = irandom_range(1,global.map_width-1);
  37.     y = irandom_range(1,global.map_height-1);
  38.    
  39.     // Pick a random 45 degree angle
  40.     dir = irandom(7)*45;
  41.    
  42.     multiplier = _multiplier;
  43.     lifespan = _lifespan;
  44.     life = 0;
  45.     height = 1;
  46.    
  47.     doStep = function(_granularity,_grid_type,_map_grid) {
  48.         ///@func doStep(granularity,grid_type,map_grid);
  49.         ///@desc Runs through a single step for the walker
  50.         ///@param {int}     granularity The size (in cells) that the walker will alter as it steps
  51.         ///@param {macro}   grid_type   Either CIRCLE or SQUARE, the type of shape the walker should use to alter the grid
  52.         ///@param {ds_grid} map_grid    The grid that the walker should alter
  53.        
  54.         // Move the walker
  55.         x += round(lengthdir_x(_granularity,dir));
  56.         y += round(lengthdir_y(_granularity,dir));
  57.  
  58.         // Wrap around the map if the walker has gone out of bounds
  59.         if (x < 0) {
  60.             x = global.map_width-_granularity;
  61.         }
  62.         else if (x > global.map_width-1) {
  63.             x = 0;
  64.         }
  65.         if (y < 0) {
  66.             y = global.map_height-_granularity;
  67.         }
  68.         else if (y > global.map_height-1) {
  69.             y = 0;
  70.         }
  71.  
  72.         var _height_change = 0;
  73.        
  74.         // Randomly rolls to see if it should change direction
  75.         var _roll = random(100);
  76.         if (_roll < 25) {
  77.             prev_dir = dir;
  78.             do {
  79.                 dir = irandom(7)*45;
  80.             }
  81.             until (dir != prev_dir);
  82.            
  83.             // Does a second roll to determine if it should change whether it is adding to or subtracting from the grid
  84.             var _roll2 = random(100);
  85.             if (_roll2 < 50) {
  86.                 height = height*-1;
  87.             }
  88.         }
  89.        
  90.         // Sets the height change to whatever was decided above multiplied by the "influence" the current walker should have (each generation of walker has a smaller influence on the map)
  91.         _height_change = height*multiplier;
  92.  
  93.         // Sets the grid region
  94.         if (_grid_type == CIRCLE) {
  95.             ds_grid_add_disk(_map_grid,x,y,_granularity,_height_change);
  96.         }
  97.         else {
  98.             ds_grid_add_region(_map_grid,x,y,x+_granularity,y+_granularity,_height_change);
  99.         }
  100.        
  101.         // Increases life and kills the walker if it has reached the end of it's lifespan
  102.         life++;
  103.         if (life > lifespan/_granularity) {
  104.             for (var i=0;i<array_length(global.walkers);i++) {
  105.                 if (global.walkers[i] == self) {
  106.                     delete global.walkers[i];
  107.                     array_delete(global.walkers,i,1);
  108.                     break;
  109.                 }
  110.             }
  111.         }
  112.     }
  113. }
  114.  
  115. function Map() constructor {
  116.    
  117.     // The width and height of your map in cells
  118.     global.map_width = room_width div CELLSIZE;
  119.     global.map_height = room_height div CELLSIZE;
  120.    
  121.     map_grid = ds_grid_create(global.map_width,global.map_height);
  122.    
  123.     num = 20;
  124.     multiplier = 1;
  125.     granularity = 8;
  126.     lifespan = 200;
  127.     grid_type = CIRCLE;
  128.    
  129.     instantiateNew = function(_num_of_walkers,_granularity,_walker_lifespan,_grid_type) {
  130.         ///@func instantiateNew(num_of_walkers,granularity,walker_lifespan,grid_type)
  131.         ///@desc This, alongside run() are the only functions you should be calling in your game. Sets up the map to a default non-generated state.
  132.         ///@param {int}     num_of_walkers  The amount of walkers you want to generate, this is procedurally altered based on how much granularity you want, the larger the granularity, the less walkers get generated (this also means that as the generator steps through granularity sizes it produces more walkers than the initial generation)
  133.         ///@param {int}     granularity     This is how large an area (in terms of cells) you want the first generation of walkers to alter, each subsequent generation halves the size of this
  134.         ///@param {int}     walker_lifespan How long you want each walker to live for in terms of steps
  135.         ///@param {macro}   grid_type       CIRCLE or SQUARE, determines the shape that the walkers will use to alter the grid
  136.        
  137.         ds_grid_clear(map_grid,0);
  138.        
  139.         num = _num_of_walkers;
  140.         multiplier = 1; // Multiplier is used to determine how much influence each generation of walkers has, it's divided by 1.5 each generation, but this can be altered for different effects if necessary
  141.         granularity = _granularity;
  142.         lifespan = _walker_lifespan;
  143.         grid_type = _grid_type
  144.        
  145.         return map_grid;
  146.     }
  147.    
  148.     createWalkers = function() {
  149.         ///@func createWalkers();
  150.         ///@desc Creates the walkers
  151.        
  152.         var _walkers = max(1,num/granularity);
  153.         repeat(_walkers) {
  154.             array_push(global.walkers,new MakeWalker(multiplier,lifespan));
  155.         }
  156.     }
  157.    
  158.     stepWalkers = function() {
  159.         ///@func stepWalkers();
  160.         ///@desc Runs through the actual grid alteration with the walkers, returns false when all the current walkers have died off
  161.        
  162.         // Loop through the walkers and make them each do a step
  163.         for (var i=0;i<array_length(global.walkers);i++) {
  164.             with (global.walkers[i]) {
  165.                 doStep(other.granularity,other.grid_type,other.map_grid);
  166.             }
  167.         }
  168.        
  169.         // Each step increases their life, if they live too long, they die, and we want to know when all of them are dead
  170.         if (array_length(global.walkers) > 0) {
  171.             return true;
  172.         }
  173.         return false;
  174.        
  175.     }
  176.    
  177.     finishSetup = function() {
  178.         ///@func finishSetup();
  179.         ///@desc Normalises the grid between 0 and 1 as the final step before the map is ready
  180.        
  181.         var _min = ds_grid_get_min(map_grid,0,0,global.map_width,global.map_height);
  182.         var _max = ds_grid_get_max(map_grid,0,0,global.map_width,global.map_height);
  183.         for (var xx=0;xx<global.map_width;xx++) {
  184.             for (var yy=0;yy<global.map_height;yy++) {
  185.                 var _val = map_grid[# xx,yy];
  186.                 var _norm = (_val-_min)/(_max-_min);
  187.                 map_grid[# xx,yy] = _norm;
  188.             }
  189.         }
  190.     }
  191.    
  192.     run = function() {
  193.         ///@func run();
  194.         ///@desc This, alongside instantiate() are the only functions you should actually be calling in your game. Runs through the creation, walking and finishing functions to generate a new map.
  195.        
  196.         // Granularity gets halved each generation, so we want to loop until granularity is less than one (if granularity is 1, then the walkers are altering a single cell each time and going lower is useless)
  197.         while (granularity >= 1) {
  198.             // Make the current generation of walkers
  199.             createWalkers();
  200.            
  201.             // As long as stepWalkers is returning true, we know the current generation have not all died
  202.             while (stepWalkers()) {
  203.                
  204.             }
  205.            
  206.             // Now we know all the walkers are dead, we halve granularity and adjust the multiplier (or amount of influence) the next generation will have on the map and then rinse and repeat
  207.             granularity = granularity div 2;
  208.             multiplier /= 1.5;
  209.         }
  210.        
  211.         finishSetup();
  212.     }  
  213. }
RAW Paste Data