Working with hundreds of levels

I know I asked something similar before, but in this topic, I would prefer to go more in-depth about how to achieve this. Preferably tutorials, documentation, and definitely a lot of explanation. I remember asking this before, someone had said to use JSON to load my levels. How would I “set this up”? The gameplay would be the same, yet with different backgrounds, enemies, etc…

Thank you.

(Sorry, for the topic frenzy, 9th grade starts in 2 days, and I want to get the most of the weekend as possible)

I’m doing something like this now in the game I’m working on in my spare time. I’m not using JSON but a simple Lua module that’s a really big table. Perhaps code will help :-) 

-- -- campaign class -- -- A library to manage backgrounds and other campaign needs -- local myData = require("classes.mydata") local M = {} M.maxCampaigns = 8 M.levelsPerCampaign = 5 M.campaign = {} M.campaign[1] = {} M.campaign[1].name="San Fransciso" M.campaign[1].startLevel = 1 M.campaign[1].endLevel = 5 M.campaign[1].minimumStars = 0 M.campaign[1].mapLocation = { x = -98, y = -11 } M.campaign[1].layers = 3 M.campaign[1].layer = {} M.campaign[1].layer[1] = {} M.campaign[1].layer[1].type = "gradient" M.campaign[1].layer[1].start = { 0.0666, 0.0319, 0.0000} M.campaign[1].layer[1].stop = { 0.5906, 0.2542, 0.0000} M.campaign[1].layer[2] = {} M.campaign[1].layer[2].type = "image" M.campaign[1].layer[2].images = {} M.campaign[1].layer[2].images[1] = { filename = "images/sanfran01.png", width = 1201, height = 375 } M.campaign[1].layer[2].images[2] = { filename = "images/sanfran02.png", width = 1201, height = 375 } M.campaign[1].layer[2].images[3] = { filename = "images/sanfran01.png", width = 1201, height = 375 } M.campaign[1].layer[2].images[4] = { filename = "images/sanfran02.png", width = 1201, height = 375 } M.campaign[1].layer[3] = {} M.campaign[1].layer[3].type = "image" M.campaign[1].layer[3].images = {} M.campaign[1].layer[3].images[1] = { filename = "images/buildings01.png", width = 1024, height = 512 } M.campaign[1].layer[3].images[2] = { filename = "images/buildings02.png", width = 1024, height = 512 } M.campaign[1].layer[3].images[3] = { filename = "images/buildings03.png", width = 1024, height = 512 } M.campaign[2] = {} M.campaign[2].name="Chicago" M.campaign[2].startLevel = 6 M.campaign[2].endLevel = 10 M.campaign[2].minimumStars = 12 M.campaign[2].mapLocation = { x = -72, y = -10 } M.campaign[2].layers = 3 M.campaign[2].layer = {} M.campaign[2].layer[1] = {} M.campaign[2].layer[1].type = "gradient" M.campaign[2].layer[1].start = { 0.0912, 0.1302, 0.1348} M.campaign[2].layer[1].stop = { 0.9965, 0.9965, 0.9965} M.campaign[2].layer[2] = {} M.campaign[2].layer[2].type = "image" M.campaign[2].layer[2].images = {} M.campaign[2].layer[2].images[1] = { filename = "images/chicago01.png", width = 750, height = 375 } M.campaign[2].layer[2].images[2] = { filename = "images/chicago02.png", width = 750, height = 375 } M.campaign[2].layer[2].images[3] = { filename = "images/chicago03.png", width = 750, height = 375 } M.campaign[2].layer[2].images[4] = { filename = "images/chicago01.png", width = 750, height = 375 } M.campaign[2].layer[2].images[5] = { filename = "images/chicago02.png", width = 750, height = 375 } M.campaign[2].layer[2].images[6] = { filename = "images/chicago03.png", width = 750, height = 375 } M.campaign[2].layer[3] = {} M.campaign[2].layer[3].type = "image" M.campaign[2].layer[3].images = {} M.campaign[2].layer[3].images[1] = { filename = "images/buildings04.png", width = 1024, height = 512 } M.campaign[2].layer[3].images[2] = { filename = "images/buildings05.png", width = 1024, height = 512 }

My game is organized into campaigns (cities) that you have to defend. I create a master campaign table, then each array entry represents one campaign.  It’s got basic information like where it is on the world map. How many stars you need to have earned to play that campaign, start, stop levels etc.  Then each campaign has three parallax backgrounds, a background that doesn’t move, a silhouette layer and a buildings layer. There may be multiple images needed to make up each background, so I have an array of images. Some levels don’t have all three layers.  But by using this type of construct I can easily set various parameters needed.

Then I have another Lua module dedicated to level specific information:

local M = {} M[1] = { droneType = 1, droneMovement = "straight", spawnSpeed = 3000, spawnCount = 20, bossType = 1, microDroneType = 0, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 100, freezeChance = 0, toxicChance = 0, nukeChance = 0 } M[2] = { droneType = 2, droneMovement = "sweep", spawnSpeed = 2750, spawnCount = 20, bossType = 2, microDroneType = 0, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 98, freezeChance = 2, toxicChance = 0, nukeChance = 0 } M[3] = { droneType = 3, droneMovement = "seek", spawnSpeed = 2500, spawnCount = 20, bossType = 1, microDroneType = 0, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 96, freezeChance = 4, toxicChance = 0, nukeChance = 0 } M[4] = { droneType = 0, droneMovement = "", spawnSpeed = 3000, spawnCount = 25, bossType = 2, microDroneType = 1, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 94, freezeChance = 6, toxicChance = 0, nukeChance = 0 } M[5] = { droneType = 0, droneMovement = "", spawnSpeed = 3000, spawnCount = 0, bossType = 9, microDroneType = 1, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 94, freezeChance = 6, toxicChance = 0, nukeChance = 0 }

I have an entry for each table that provides data that I use to drive the enemies.  Each enemy (drone, boss, microDrone, minelayer, campaignBoss) has their own .lua file that has all the code for that enemy type, but another table that contains enemy specific features: 

local droneData = {} droneData[1] = {} droneData[1].exhaustOffset = { x = -15, y = 0 } droneData[1].fireOffset = { x = 0, y = 0 } droneData[1].health = 1 droneData[1].points = 100 droneData[1].speed = 1 droneData[1].damage = 1 droneData[1].guns = 1 droneData[1].fireSpeed = 900 droneData[1].seeks = false droneData[1].sweeps = false droneData[2] = {} droneData[2].exhaustOffset = { x = -28, y = 5 } droneData[2].fireOffset = { x = -20, y = 12 } droneData[2].health = 2 droneData[2].points = 200 droneData[2].speed = 1 droneData[2].damage = 1 droneData[2].guns = 1 droneData[2].fireSpeed = 850 droneData[2].seeks = false droneData[2].sweeps = true

In my case the drones are all in a single image sheet and the frame index is the same as the index for the droneData. So if my droneType is 1 I will spawn a drone with frame 1 of the image sheet, and give it the above properties.

In my game the basic start up flow is:  Menu->Campaign Select->Level Select->Game->Next Level or Game Over with Next Level going back to Game.  Each enemy and the player have their own lua files. 

Rob

Wow, thanks for this Rob! I was thinking of going for something similar in a future game I want to make. Since there would be hundreds of levels, characters, and enemies, this seems like a good way to store the data. It just might take some time. :slight_smile:

This approach is also ideal since the enemies and characters will have a certain number of abilities such as weaken, freeze, slow, increased attacked as they lose health, survive lethal strike, etc…

Have you thought about trying a procedural approach? Depending on how your game works it might be quicker to design an algorithm to generate your levels for you from a random seed, rather than design hundreds of levels by hand. And if it works you might have millions of levels instead :wink:

I haven’t considered that. However, I don’t want to do this because I am making a campaign mode, and I want people to work together and develop strategies to beat levels and know what units to bring into battle.

I’m doing something like this now in the game I’m working on in my spare time. I’m not using JSON but a simple Lua module that’s a really big table. Perhaps code will help :-) 

-- -- campaign class -- -- A library to manage backgrounds and other campaign needs -- local myData = require("classes.mydata") local M = {} M.maxCampaigns = 8 M.levelsPerCampaign = 5 M.campaign = {} M.campaign[1] = {} M.campaign[1].name="San Fransciso" M.campaign[1].startLevel = 1 M.campaign[1].endLevel = 5 M.campaign[1].minimumStars = 0 M.campaign[1].mapLocation = { x = -98, y = -11 } M.campaign[1].layers = 3 M.campaign[1].layer = {} M.campaign[1].layer[1] = {} M.campaign[1].layer[1].type = "gradient" M.campaign[1].layer[1].start = { 0.0666, 0.0319, 0.0000} M.campaign[1].layer[1].stop = { 0.5906, 0.2542, 0.0000} M.campaign[1].layer[2] = {} M.campaign[1].layer[2].type = "image" M.campaign[1].layer[2].images = {} M.campaign[1].layer[2].images[1] = { filename = "images/sanfran01.png", width = 1201, height = 375 } M.campaign[1].layer[2].images[2] = { filename = "images/sanfran02.png", width = 1201, height = 375 } M.campaign[1].layer[2].images[3] = { filename = "images/sanfran01.png", width = 1201, height = 375 } M.campaign[1].layer[2].images[4] = { filename = "images/sanfran02.png", width = 1201, height = 375 } M.campaign[1].layer[3] = {} M.campaign[1].layer[3].type = "image" M.campaign[1].layer[3].images = {} M.campaign[1].layer[3].images[1] = { filename = "images/buildings01.png", width = 1024, height = 512 } M.campaign[1].layer[3].images[2] = { filename = "images/buildings02.png", width = 1024, height = 512 } M.campaign[1].layer[3].images[3] = { filename = "images/buildings03.png", width = 1024, height = 512 } M.campaign[2] = {} M.campaign[2].name="Chicago" M.campaign[2].startLevel = 6 M.campaign[2].endLevel = 10 M.campaign[2].minimumStars = 12 M.campaign[2].mapLocation = { x = -72, y = -10 } M.campaign[2].layers = 3 M.campaign[2].layer = {} M.campaign[2].layer[1] = {} M.campaign[2].layer[1].type = "gradient" M.campaign[2].layer[1].start = { 0.0912, 0.1302, 0.1348} M.campaign[2].layer[1].stop = { 0.9965, 0.9965, 0.9965} M.campaign[2].layer[2] = {} M.campaign[2].layer[2].type = "image" M.campaign[2].layer[2].images = {} M.campaign[2].layer[2].images[1] = { filename = "images/chicago01.png", width = 750, height = 375 } M.campaign[2].layer[2].images[2] = { filename = "images/chicago02.png", width = 750, height = 375 } M.campaign[2].layer[2].images[3] = { filename = "images/chicago03.png", width = 750, height = 375 } M.campaign[2].layer[2].images[4] = { filename = "images/chicago01.png", width = 750, height = 375 } M.campaign[2].layer[2].images[5] = { filename = "images/chicago02.png", width = 750, height = 375 } M.campaign[2].layer[2].images[6] = { filename = "images/chicago03.png", width = 750, height = 375 } M.campaign[2].layer[3] = {} M.campaign[2].layer[3].type = "image" M.campaign[2].layer[3].images = {} M.campaign[2].layer[3].images[1] = { filename = "images/buildings04.png", width = 1024, height = 512 } M.campaign[2].layer[3].images[2] = { filename = "images/buildings05.png", width = 1024, height = 512 }

My game is organized into campaigns (cities) that you have to defend. I create a master campaign table, then each array entry represents one campaign.  It’s got basic information like where it is on the world map. How many stars you need to have earned to play that campaign, start, stop levels etc.  Then each campaign has three parallax backgrounds, a background that doesn’t move, a silhouette layer and a buildings layer. There may be multiple images needed to make up each background, so I have an array of images. Some levels don’t have all three layers.  But by using this type of construct I can easily set various parameters needed.

Then I have another Lua module dedicated to level specific information:

local M = {} M[1] = { droneType = 1, droneMovement = "straight", spawnSpeed = 3000, spawnCount = 20, bossType = 1, microDroneType = 0, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 100, freezeChance = 0, toxicChance = 0, nukeChance = 0 } M[2] = { droneType = 2, droneMovement = "sweep", spawnSpeed = 2750, spawnCount = 20, bossType = 2, microDroneType = 0, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 98, freezeChance = 2, toxicChance = 0, nukeChance = 0 } M[3] = { droneType = 3, droneMovement = "seek", spawnSpeed = 2500, spawnCount = 20, bossType = 1, microDroneType = 0, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 96, freezeChance = 4, toxicChance = 0, nukeChance = 0 } M[4] = { droneType = 0, droneMovement = "", spawnSpeed = 3000, spawnCount = 25, bossType = 2, microDroneType = 1, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 94, freezeChance = 6, toxicChance = 0, nukeChance = 0 } M[5] = { droneType = 0, droneMovement = "", spawnSpeed = 3000, spawnCount = 0, bossType = 9, microDroneType = 1, microDroneMovement = "straight", microDroneSpawnPattern = "wall", spawnHealthRate = 20000, spawnPickupRate = 30000, energyChance = 94, freezeChance = 6, toxicChance = 0, nukeChance = 0 }

I have an entry for each table that provides data that I use to drive the enemies.  Each enemy (drone, boss, microDrone, minelayer, campaignBoss) has their own .lua file that has all the code for that enemy type, but another table that contains enemy specific features: 

local droneData = {} droneData[1] = {} droneData[1].exhaustOffset = { x = -15, y = 0 } droneData[1].fireOffset = { x = 0, y = 0 } droneData[1].health = 1 droneData[1].points = 100 droneData[1].speed = 1 droneData[1].damage = 1 droneData[1].guns = 1 droneData[1].fireSpeed = 900 droneData[1].seeks = false droneData[1].sweeps = false droneData[2] = {} droneData[2].exhaustOffset = { x = -28, y = 5 } droneData[2].fireOffset = { x = -20, y = 12 } droneData[2].health = 2 droneData[2].points = 200 droneData[2].speed = 1 droneData[2].damage = 1 droneData[2].guns = 1 droneData[2].fireSpeed = 850 droneData[2].seeks = false droneData[2].sweeps = true

In my case the drones are all in a single image sheet and the frame index is the same as the index for the droneData. So if my droneType is 1 I will spawn a drone with frame 1 of the image sheet, and give it the above properties.

In my game the basic start up flow is:  Menu->Campaign Select->Level Select->Game->Next Level or Game Over with Next Level going back to Game.  Each enemy and the player have their own lua files. 

Rob

Wow, thanks for this Rob! I was thinking of going for something similar in a future game I want to make. Since there would be hundreds of levels, characters, and enemies, this seems like a good way to store the data. It just might take some time. :slight_smile:

This approach is also ideal since the enemies and characters will have a certain number of abilities such as weaken, freeze, slow, increased attacked as they lose health, survive lethal strike, etc…

Have you thought about trying a procedural approach? Depending on how your game works it might be quicker to design an algorithm to generate your levels for you from a random seed, rather than design hundreds of levels by hand. And if it works you might have millions of levels instead :wink:

I haven’t considered that. However, I don’t want to do this because I am making a campaign mode, and I want people to work together and develop strategies to beat levels and know what units to bring into battle.