Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- ------------ MAP GENERATION ------------
- 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.
- ------------ HOW TO USE ------------
- 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):
- map = new Map(); // Creates the map struct for use with the below functions
- 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
- map.run(); // The meat of the generator, this creates and runs the walkers and normalises the grid once they've finished doing their job
- 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.
- Go here for a demo gif of how the generated grid might be used: https://refreshertowelgames.files.wordpress.com/2020/12/map-gen.gif
- Mayhaps you might want to have a look over my site if you like this: https://refreshertowel.games/
- */
- // 2D Height/Noise Grid Generator
- #macro CELLSIZE 64
- #macro SQUARE 0
- #macro CIRCLE 1
- global.walkers = [];
- function MakeWalker(_multiplier,_lifespan) constructor {
- ///@function MakeWalker(granularity,multiplier,lifespan);
- x = irandom_range(1,global.map_width-1);
- y = irandom_range(1,global.map_height-1);
- // Pick a random 45 degree angle
- dir = irandom(7)*45;
- multiplier = _multiplier;
- lifespan = _lifespan;
- life = 0;
- height = 1;
- doStep = function(_granularity,_grid_type,_map_grid) {
- ///@func doStep(granularity,grid_type,map_grid);
- ///@desc Runs through a single step for the walker
- ///@param {int} granularity The size (in cells) that the walker will alter as it steps
- ///@param {macro} grid_type Either CIRCLE or SQUARE, the type of shape the walker should use to alter the grid
- ///@param {ds_grid} map_grid The grid that the walker should alter
- // Move the walker
- x += round(lengthdir_x(_granularity,dir));
- y += round(lengthdir_y(_granularity,dir));
- // Wrap around the map if the walker has gone out of bounds
- if (x < 0) {
- x = global.map_width-_granularity;
- }
- else if (x > global.map_width-1) {
- x = 0;
- }
- if (y < 0) {
- y = global.map_height-_granularity;
- }
- else if (y > global.map_height-1) {
- y = 0;
- }
- var _height_change = 0;
- // Randomly rolls to see if it should change direction
- var _roll = random(100);
- if (_roll < 25) {
- prev_dir = dir;
- do {
- dir = irandom(7)*45;
- }
- until (dir != prev_dir);
- // Does a second roll to determine if it should change whether it is adding to or subtracting from the grid
- var _roll2 = random(100);
- if (_roll2 < 50) {
- height = height*-1;
- }
- }
- // 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)
- _height_change = height*multiplier;
- // Sets the grid region
- if (_grid_type == CIRCLE) {
- ds_grid_add_disk(_map_grid,x,y,_granularity,_height_change);
- }
- else {
- ds_grid_add_region(_map_grid,x,y,x+_granularity,y+_granularity,_height_change);
- }
- // Increases life and kills the walker if it has reached the end of it's lifespan
- life++;
- if (life > lifespan/_granularity) {
- for (var i=0;i<array_length(global.walkers);i++) {
- if (global.walkers[i] == self) {
- delete global.walkers[i];
- array_delete(global.walkers,i,1);
- break;
- }
- }
- }
- }
- }
- function Map() constructor {
- // The width and height of your map in cells
- global.map_width = room_width div CELLSIZE;
- global.map_height = room_height div CELLSIZE;
- map_grid = ds_grid_create(global.map_width,global.map_height);
- num = 20;
- multiplier = 1;
- granularity = 8;
- lifespan = 200;
- grid_type = CIRCLE;
- instantiateNew = function(_num_of_walkers,_granularity,_walker_lifespan,_grid_type) {
- ///@func instantiateNew(num_of_walkers,granularity,walker_lifespan,grid_type)
- ///@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.
- ///@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)
- ///@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
- ///@param {int} walker_lifespan How long you want each walker to live for in terms of steps
- ///@param {macro} grid_type CIRCLE or SQUARE, determines the shape that the walkers will use to alter the grid
- ds_grid_clear(map_grid,0);
- num = _num_of_walkers;
- 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
- granularity = _granularity;
- lifespan = _walker_lifespan;
- grid_type = _grid_type
- return map_grid;
- }
- createWalkers = function() {
- ///@func createWalkers();
- ///@desc Creates the walkers
- var _walkers = max(1,num/granularity);
- repeat(_walkers) {
- array_push(global.walkers,new MakeWalker(multiplier,lifespan));
- }
- }
- stepWalkers = function() {
- ///@func stepWalkers();
- ///@desc Runs through the actual grid alteration with the walkers, returns false when all the current walkers have died off
- // Loop through the walkers and make them each do a step
- for (var i=0;i<array_length(global.walkers);i++) {
- with (global.walkers[i]) {
- doStep(other.granularity,other.grid_type,other.map_grid);
- }
- }
- // Each step increases their life, if they live too long, they die, and we want to know when all of them are dead
- if (array_length(global.walkers) > 0) {
- return true;
- }
- return false;
- }
- finishSetup = function() {
- ///@func finishSetup();
- ///@desc Normalises the grid between 0 and 1 as the final step before the map is ready
- var _min = ds_grid_get_min(map_grid,0,0,global.map_width,global.map_height);
- var _max = ds_grid_get_max(map_grid,0,0,global.map_width,global.map_height);
- for (var xx=0;xx<global.map_width;xx++) {
- for (var yy=0;yy<global.map_height;yy++) {
- var _val = map_grid[# xx,yy];
- var _norm = (_val-_min)/(_max-_min);
- map_grid[# xx,yy] = _norm;
- }
- }
- }
- run = function() {
- ///@func run();
- ///@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.
- // 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)
- while (granularity >= 1) {
- // Make the current generation of walkers
- createWalkers();
- // As long as stepWalkers is returning true, we know the current generation have not all died
- while (stepWalkers()) {
- }
- // 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
- granularity = granularity div 2;
- multiplier /= 1.5;
- }
- finishSetup();
- }
- }
Add Comment
Please, Sign In to add comment