GML Generating Level "Chunks"?

Hi all! I'm relatively new to working with GMS2 and am running into an issue figuring out how to code a project I'm working on for my game design course.

I'm working on an endless runner 2D platformer game, and want to have a setup where a randomly chosen pre-designed "chunk" of level all generates at once when the player gets close enough to the edge of the "generated" part of the level (although I might have this work in reverse, where the level moves and the player remains stationary to simulate movement).

Only issue, I have no idea how to go about doing this. What would be the easiest (and quickest) way to design this. I've considered having each chunk layout's information stored in a 2D array, but going through and individually assigning each wall and enemy to a value for each layout would take far longer than I have to get this done. I also considered having each layout be it's own entire object, but I wouldn't be able to have things like obstacles and enemies this way.

Any brainstorming help would be amazing!
 
R

robproctor83

Guest
Making a level generator that uses chunks is not an easy task, I would consider it probably advanced level programming. If however you didn't have to do chunks you could just make random objects spawn as your player moves around. Well, technically you wouldn't move the player, you would move the room around the player and then when the player moves some set distance in some direction you would generate the next upcoming group of objects. But, they wouldn't be conencted in anyway, it wouldn't be a "chunk". Chunk loading is something specific, and requires a lot of planning and organization. If your goal is just to make an endless runner forgo the chunk idea and just use normal procedural generation. Also, you will need to work out some internal system for saving if you need that. Saving randomly genrated rooms like this will be a challenge, especially if you want to retain say the whole explored map... If the player traverses 10k pixels, do you really want to save all that data? Would be a huge headache.

If you did HAVE to use chunks though your in for a complicated project. First you gotta decide, what even is a chunk? What does it consist of? Would it be the same 100x100 chunk repeated over and over, or would there need to be randomization to it? Will these chunks have potentially path blocking objects? If so you will have to make sure there is always a open path for the player to take. Can the player progress in the room and then turn around and go back to the beginning of the room? If so you will need a way to load/unload not only procedural chunks but also saved chunks where the player has already explored. Then, you will need an algorithm for how to apply the chunks, when does it swap over from one chunk to another, when does it load and unload chunks. Then there are further considerations like will connecting chunks need to be tiled? Meaning, if you had 10 randomly generated chunks can you just connect any 2 chunks to make a single flowing area, or will chunks possibly have seams where they are connecting? If you need the chunks to tile together, meaning they cant randomly be connected, then you will need another system on top that figures out which tiles can be connected to each other.

Anyways, this is a rather complex issue. Best of luck.
 

NightFrost

Member
Storing data into a grid and using that to generate pieces of a level is certainly doable. I've done it myself, starting like this:
sample.png
A color-coded image that was read into a buffer and used to generate a chunk of a platformer room (each pixel being a basis for a 16x16 sprite piece). This particular image being five variations of platform arrangement that only open upwards. You can use a similar idea to describe a chunk in 2d data. Pure data is of course less visual and more typing. The advantage of using images was, once the buffer operation functions were working I just needed a graphics tool to draw the layouts. But your case is also simpler, as infinite runner only ever needs to generate into one direction and having the pieces fit together is easier. You just need a large variety so the terrain doesn't become too predictable.
 
C

Catastrophe

Guest
Typically if you're doing an infinite world, you would also do "procedural generation" for the chunks rather than predesigned ones, and only have presdesigned rooms for bosses. That's not always the case, some games go through the extra effort of predesigning absolutely everything, but it saves time and overhead.

This is the code we call for perlin noise, but you'll want to google how to use it. Replace "GameController.seed" with some seed of your choice or a generated one.

Edit: I didn't write this, I just ripped it. Also, I find it funny this is copyrighted 18,000 years into the future.

Code:
//FastNoise(x, y, nbOctave)
///FastNoise(x, y, nbOctave)
// Source of original Java version:
// http://www.java-gaming.org/index.php?topic=23771.0
/*
* This file is part of 3DzzD http://dzzd.net/.
*
* Released under LGPL
*
* 3DzzD is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 3DzzD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with 3DzzD.  If not, see <http://www.gnu.org/licenses/>.
*
* Copyright 2005 - 20010 Bruno Augier
*/
/**
 * Fast perlin noise generation
 *
 * Generate a perlin noise with 8 octave and a persistence of 0.5
 *
 * NB:
 * - output range between 0 and 255
 * - maximum octave = 7
 *
 * you can change type of noise between grad & value noise by commenting/uncommenting block
 * you can change type of interpolation between bicubic/bilinear by commenting/uncommenting block
 */
var nx = argument0 + GameController.seed;
var ny = argument1 + GameController.seed;
var nbOctave = argument2;
var result=0; 
var frequence256=argument3;
var sx=floor((nx)*frequence256);
var sy=floor((ny)*frequence256);
var octave=nbOctave;
while(octave!=0)
{
   var bX=sx & $FF;
   var bY=sy & $FF;
   var sxp=sx>>8;
   var syp=sy>>8;
  
   //Compute noise for each corner of current cell
   var Y1376312589_00=syp*1376312589;
   var Y1376312589_01=Y1376312589_00+1376312589;
   var XY1376312589_00=sxp+Y1376312589_00;
   var XY1376312589_10=XY1376312589_00+1;
   var XY1376312589_01=sxp+Y1376312589_01;
   var XY1376312589_11=XY1376312589_01+1;
   var XYBASE_00=(XY1376312589_00<<13)^XY1376312589_00;
   var XYBASE_10=(XY1376312589_10<<13)^XY1376312589_10;
   var XYBASE_01=(XY1376312589_01<<13)^XY1376312589_01;
   var XYBASE_11=(XY1376312589_11<<13)^XY1376312589_11;
   var alt1=(XYBASE_00 * (XYBASE_00 * XYBASE_00 * 15731 + 789221) + 1376312589) ;
   var alt2=(XYBASE_10 * (XYBASE_10 * XYBASE_10 * 15731 + 789221) + 1376312589) ;
   var alt3=(XYBASE_01 * (XYBASE_01 * XYBASE_01 * 15731 + 789221) + 1376312589) ;
   var alt4=(XYBASE_11 * (XYBASE_11 * XYBASE_11 * 15731 + 789221) + 1376312589) ;
  
   /*
   *NOTE : on  for true grandiant noise uncomment following block
   * for true gradiant we need to perform scalar product here, gradiant vector are created/deducted using
   * the above pseudo random values (alt1...alt4) : by cutting thoses values in twice values to get for each a fixed x,y vector
   * gradX1= alt1&0xFF
   * gradY1= (alt1&0xFF00)>>8
   *
   * the last part of the PRN (alt1&0xFF0000)>>8 is used as an offset to correct one of the gradiant problem wich is zero on cell edge
   *
   * source vector (sXN;sYN) for scalar product are computed using (bX,bY)
   *
   * each four values  must be replaced by the result of the following
   * altN=(gradXN;gradYN) scalar (sXN;sYN)
   *
   * all the rest of the code (interpolation+accumulation) is identical for value & gradiant noise
   */
    
    
   /*START BLOCK FOR TRUE GRADIANT NOISE*/
   var grad1X=(alt1 & $FF)-128;
   var grad1Y=((alt1 >> 8) & $FF)-128;
   var grad2X=(alt2 & $FF)-128;
   var grad2Y=((alt2 >> 8) & $FF)-128;
   var grad3X=(alt3 & $FF)-128;
   var grad3Y=((alt3 >> 8) & $FF)-128;
   var grad4X=(alt4 & $FF)-128;
   var grad4Y=((alt4 >> 8) & $FF)-128;
    
    
   var sX1=bX>>1;
   var sY1=bY>>1;
   var sX2=128-sX1;
   var sY2=sY1;
   var sX3=sX1;
   var sY3=128-sY1;
   var sX4=128-sX1;
   var sY4=128-sY1;
   alt1=(grad1X*sX1+grad1Y*sY1)+16384+((alt1 & $FF0000)>>9); //to avoid seams to be 0 we use an offset
   alt2=(grad2X*sX2+grad2Y*sY2)+16384+((alt2 & $FF0000)>>9);
   alt3=(grad3X*sX3+grad3Y*sY3)+16384+((alt3 & $FF0000)>>9);
   alt4=(grad4X*sX4+grad4Y*sY4)+16384+((alt4 & $FF0000)>>9);
    
   /*END BLOCK FOR TRUE GRADIANT NOISE */
  
  
   /*START BLOCK FOR VALUE NOISE*/
   /*/
   alt1 &= $FFFF;
   alt2 &= $FFFF;
   alt3 &= $FFFF;
   alt4 &= $FFFF;
   //*/
   /*END BLOCK FOR VALUE NOISE*/
  
  
   /*START BLOCK FOR LINEAR INTERPOLATION*/
   //BiLinear interpolation
   /*/
   var f24=(bX*bY)>>8;
   var f23=bX-f24;
   var f14=bY-f24;
   var f13=256-f14-f23-f24;
   var val=(alt1*f13+alt2*f23+alt3*f14+alt4*f24);
   //*/
   /*END BLOCK FOR LINEAR INTERPOLATION*/
  
  
  
   //BiCubic interpolation ( in the form alt(bX) = alt[n] - (3*bX^2 - 2*bX^3) * (alt[n] - alt[n+1]) )
   /*START BLOCK FOR BICUBIC INTERPOLATION*/
   //*/
   var bX2=(bX*bX)>>8;
   var bX3=(bX2*bX)>>8;
   var _3bX2=3*bX2;
   var _2bX3=2*bX3;
   var alt12= alt1 - (((_3bX2 - _2bX3) * (alt1-alt2)) >> 8);
   var alt34= alt3 - (((_3bX2 - _2bX3) * (alt3-alt4)) >> 8);
  
  
   var bY2=(bY*bY)>>8;
   var bY3=(bY2*bY)>>8;
   var _3bY2=3*bY2;
   var _2bY3=2*bY3;
   var val= alt12 - (((_3bY2 - _2bY3) * (alt12-alt34)) >> 8);
  
   val*=256;
   //*/
   /*END BLOCK FOR BICUBIC INTERPOLATION*/
  
  
   //Accumulate in result
   result+=(val<<octave);
  
   octave--;
   sx = sx << 1;
   sy = sy << 1;
  
}
result = result >> (16+nbOctave+1);
return result;
 
Last edited by a moderator:

NightFrost

Member
You can also look at Spelunky. My approach I described above is based on that. I just used a maze algorithm instead (recursive backtracker) and the chunks are laid on top of that as in Spelunky. I had five or seven variations of each chunk, and again like Spelunky some content - enemies, items, traps - had a random chance to appear instead of always spawning. I recall I wanted to add terrain too that only sometimes appeared (Spelunky does that, for greater variety) but never got around doing that as I moved on to other projects.
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Spelunky-styled approach is pretty well-suited, you have a few options
  • Generate from text snippets (like Spelunky itself does)
  • Generate from sprites (as shown in NightFrost's reply)
  • Generate from JSON describing objects and their locations (if your levels aren't really grid-based)
  • Make a small level editor to work any of above
If you'd like to use the room editor, I made an extension for that; Room Dog can also be used, although is more limited in what it can do.
 
Thank you all so much for the replies! I'll give the sprite-based approach NightFrost suggested a shot. Appreciate the advice!
 
Top