OK basically I'm making an endless runner where you can't control the player at all (no moving, no jumping, just go right) and theres a giant spike ball rolling towards you. Eventually, there'll be like drawbridges and doors and things you have to open with the crank, and all of that is pretty easy (i hope because I haven't really started on those yet but it shouldn't be too hard). The thing I'm having trouble with is procedurally generating the levels. My plan was to have 20 or so "templates" and then it would just randomly pick one for you to go to next, and it just keeps picking random ones and getting faster until you die, because actual procedural generation would be WAY too intensive for the PlayDate. I can't really figure out how to do it though. I could probably have it generate a random number 1-20 5 times at the start of the game, and each time the screen scrolls a certain amount, it gets rid of the one at the start and puts a new one at the end, but I don't really know how I would make that work with LDtk.
You can create the 20 parts in Ldtk and stitch them in-game. You don’t need to do the stitching part in Ldtk.
I am doing something like this in DuckBlur to handle the open world part. I am using invisible sprites to detect when the player is nearing the edge of a map and I load the next map with a translation to make it appear after the current one.
Could you share your code, just to make it a little easier to understand?
Thanks
Choosing from 20 that way would work, but the Playdate is more than capable of procedural generation too—if you ever wanted to try that. (Might be a complexity to worry about later, after other things are working.)
(I'm not an LDtk user.)
I'm using Cotton, which compiles all the LDtk levels to .lua files, so it doesn't really have anything to do with LDtk now that I'm thinking about it, but I don't really know how I would "queue" up the next one
Here is my code, it is in C and I don't use LDtk either but I hope that can help anyway.
In the editor, the loader sprite looks like that:
Left map:
Right map:
The width is a bit more than 400 pixel wide to avoid clipping.
Here is the MapLoader update method:
static void update(LCDSprite * _Nonnull sprite) {
MapLoaderSprite *self = playdate->sprite->getUserdata(sprite);
MapLoader *mapLoader = self->mapLoader;
if (self->collided == Collided) {
// Loads next map
MapLoaderLoadMap(mapLoader);
if (mapLoader->status == MapLoaderStatusHeaderLoaded) {
// When loaded, add a padding to the next map to show it stitched to the current one.
mapLoader->maps[self->target]->padding = self->mapPadding;
}
} else if (self->collided == NotColliding && mapLoader->status != MapLoaderStatusNotLoaded && mapLoader->current != self->target && mapLoader->next == self->target) {
// Free the map from memory.
cancelLoading(mapLoader);
}
}
The padding value is set to the width of the current map. I use this value in the draw
method of my TileMap implementation to find which tile to show in the top left corner of the screen:
static void drawLayer(LCDSprite * _Nonnull sprite, PDRect bounds, PDRect drawrect) {
LayerSprite *self = playdate->sprite->getUserdata(sprite);
const MELLayer layer = *self->layer;
const MELIntPoint topLeft = (MELIntPoint) {
(camera.frame.origin.x - self->padding) * layer.scrollRate.x,
camera.frame.origin.y * layer.scrollRate.y
};
const MELIntPoint topLeftTile = (MELIntPoint) {
.x = topLeft.x / tileSize.width - layer.frame.origin.x,
.y = topLeft.y / tileSize.height - layer.frame.origin.y,
};
// [Drawing the tiles...]
}
When the player is out of bound from the current map, I switch the current map with the next map and set its padding to 0:
static void MapLoaderChangeForNextMap(MapLoader * _Nonnull self) {
MELMap *oldMap = self->current;
MELMap *currentMap = self->current = self->next;
const int padding = currentMap->padding;
// Exchanging old map and next map position.
oldMap->padding = -padding;
currentMap->padding = 0;
// Moving sprites to the new coordinates.
SpriteLoaderTranslateSprites(self->spriteLoader, -padding);
}
Here is how It looks in game: