This post will tell you how I used LDtk to make a level for Project Jupiter
If like me you want to add Enums, Entities as well as tile sets with a simple layering system LDtk lets you do that in a productive way. And adding all this into your AppGameKit project is easy too, with the LDtkFileType.load(loadFileStr) command. But before programming you will need to make your level in LDtk.
When you are happy with your creation, set the 'Save Levels To Separate Files' box and ensure you are saving the .ldtk extension.
For project Jupiter I made a really simple file with three layers, a wall layer, a fill layer, and an entity layer. The wall layer is where the ship cannot go and has physics applied, the fill layer is just a sprite with no physics and the entity is where I want the enemies to be.
The next part of the import into AppGameKit requires the use of TYPES. If you don't know about setting up types you can read up all about them here:
https://www.appgamekit.com/documentation/guides/types_001.htm All we need to do here is set up a TYPE and then latter we read our .json file into it so the data is stored in memory ready to be used. The code below shows the TYPE I made for Project Jupiter.
// File: ldTKmulti.agc
// Created: 22-06-27
TYPE TileSetUidT
tileSetUid as integer
x as integer
y as integer
w as integer
h as integer
ENDTYPE
TYPE realEditorValuesT
id as string
params as string[]
ENDTYPE
TYPE fieldInstanceT
__identifier as string
__value as string
__type as string
__tile as TileSetUidT
defUid as integer
realEditorValues as realEditorValuesT[]
ENDTYPE
TYPE layerInstancesT
__identifier as string
__type as string
__cWid as integer
__cHei as integer
_gridSize as integer
__opacity as integer
__pxTotalOffsetX as integer
__pxTotalOffsetY as integer
__tilesetRelPath as string
iid as string
levelId as integer
layerDefUid as integer
pxOffsetX as integer
pxOffsetY as integer
visible as integer
optionalRules as integer[]
intGridCsv as integer[]
autoLayerTiles as integer[]
seed as integer
overrideTilesetUid as string
gridTiles as gridTilesT[]
entityInstances as entityInstanceT[]
ENDTYPE
TYPE entityInstanceT
__identifier as string
__grid as integer[2]
__pivot as integer[2]
__tags as string[]
__tile as TileSetUidT
__smartColor as string
iid as string
width as float
height as float
defUid as integer
px as float[2]
fieldInstances as fieldInstanceT[]
ENDTYPE
TYPE gridTilesT
px as integer[2]
src as integer[2]
f as integer
t as integer
d as integer
ENDTYPE
TYPE ldTKrootT
__header__ as HeaderMultiT
identifier as string
iid as string
uid as integer
worldX as integer
worldY as integer
worldDepth as integer
pxWid as integer
pxHei as integer
__bgColor as string
bgColor as string
useAutoIdentifier as integer
bgRelPath as integer
bgPos as integer
bgPivotX as float
bgPivotY as float
__smartColor as string
__bgPos as integer
externalRelPath as string
fieldInstances as fieldInstanceT[]
layerInstances as layerInstancesT[]
__neighbours as levelIidT[]
ENDTYPE
TYPE levelIidT
levelIid as string
levelUid as integer
dir as string
ENDTYPE
TYPE HeaderMultiT
fileType as string
app as string
doc as string
schema as string
appAuthor as string
appVersion as string
url as string
ENDTYPE
TYPE levelDataT
ID as integer
levelData as ldTKrootT[]
ENDTYPE
TYPE staticSpritesT
spriteID as integer
iid as string
status as integer
name as string
ori as string
doorTween as integer
doorOpenPlaying as integer
doorIsLocked as integer
doorLockCode as integer
ENDTYPE
and for those who like to read .json files, here it is from the .LDtk save.
{
"__header__": {
"fileType": "LDtk Project JSON",
"app": "LDtk",
"doc": "https://ldtk.io/json",
"schema": "https://ldtk.io/files/JSON_SCHEMA.json",
"appAuthor": "Sebastien 'deepnight' Benard",
"appVersion": "1.1.3",
"url": "https://ldtk.io"
},
"jsonVersion": "1.1.3",
"appBuildId": 458364,
"nextUid": 13,
"identifierStyle": "Capitalize",
"worldLayout": "Free",
"worldGridWidth": 256,
"worldGridHeight": 256,
"defaultLevelWidth": 256,
"defaultLevelHeight": 256,
"defaultPivotX": 0,
"defaultPivotY": 0,
"defaultGridSize": 16,
"bgColor": "#40465B",
"defaultLevelBgColor": "#696A79",
"minifyJson": false,
"externalLevels": true,
"exportTiled": false,
"simplifiedExport": false,
"imageExportMode": "None",
"pngFilePattern": null,
"backupOnSave": false,
"backupLimit": 10,
"levelNamePattern": "Level_%idx",
"tutorialDesc": null,
"flags": [],
"defs": { "layers": [
{
"__type": "Tiles",
"identifier": "Tiles",
"type": "Tiles",
"uid": 5,
"gridSize": 32,
"guideGridWid": 0,
"guideGridHei": 0,
"displayOpacity": 1,
"inactiveOpacity": 1,
"hideInList": false,
"hideFieldsWhenInactive": false,
"pxOffsetX": 0,
"pxOffsetY": 0,
"parallaxFactorX": 0,
"parallaxFactorY": 0,
"parallaxScaling": true,
"requiredTags": [],
"excludedTags": [],
"intGridValues": [],
"autoTilesetDefUid": 4,
"autoRuleGroups": [],
"autoSourceLayerDefUid": null,
"tilesetDefUid": 4,
"tilePivotX": 0,
"tilePivotY": 0
},
{
"__type": "Tiles",
"identifier": "Fill",
"type": "Tiles",
"uid": 6,
"gridSize": 32,
"guideGridWid": 0,
"guideGridHei": 0,
"displayOpacity": 1,
"inactiveOpacity": 1,
"hideInList": false,
"hideFieldsWhenInactive": false,
"pxOffsetX": 0,
"pxOffsetY": 0,
"parallaxFactorX": 0,
"parallaxFactorY": 0,
"parallaxScaling": true,
"requiredTags": [],
"excludedTags": [],
"intGridValues": [],
"autoTilesetDefUid": 4,
"autoRuleGroups": [],
"autoSourceLayerDefUid": null,
"tilesetDefUid": 4,
"tilePivotX": 0,
"tilePivotY": 0
},
{
"__type": "Entities",
"identifier": "Entities",
"type": "Entities",
"uid": 9,
"gridSize": 32,
"guideGridWid": 0,
"guideGridHei": 0,
"displayOpacity": 1,
"inactiveOpacity": 0.6,
"hideInList": false,
"hideFieldsWhenInactive": true,
"pxOffsetX": 0,
"pxOffsetY": 0,
"parallaxFactorX": 0,
"parallaxFactorY": 0,
"parallaxScaling": true,
"requiredTags": [],
"excludedTags": [],
"intGridValues": [],
"autoTilesetDefUid": null,
"autoRuleGroups": [],
"autoSourceLayerDefUid": null,
"tilesetDefUid": null,
"tilePivotX": 0,
"tilePivotY": 0
}
], "entities": [
{
"identifier": "Entity",
"uid": 11,
"tags": [],
"width": 32,
"height": 32,
"resizableX": false,
"resizableY": false,
"keepAspectRatio": false,
"tileOpacity": 1,
"fillOpacity": 1,
"lineOpacity": 1,
"hollow": false,
"color": "#94D9B3",
"renderMode": "Rectangle",
"showName": true,
"tilesetId": null,
"tileId": null,
"tileRenderMode": "FitInside",
"tileRect": null,
"nineSliceBorders": [],
"maxCount": 0,
"limitScope": "PerLevel",
"limitBehavior": "MoveLastOne",
"pivotX": 0,
"pivotY": 0,
"fieldDefs": [
{
"identifier": "StaticEnemy",
"__type": "LocalEnum.StaticEnemy",
"uid": 12,
"type": "F_Enum(10)",
"isArray": false,
"canBeNull": false,
"arrayMinLength": null,
"arrayMaxLength": null,
"editorDisplayMode": "Hidden",
"editorDisplayPos": "Above",
"editorAlwaysShow": false,
"editorCutLongValues": true,
"editorTextSuffix": null,
"editorTextPrefix": null,
"useForSmartColor": false,
"min": null,
"max": null,
"regex": null,
"acceptFileTypes": null,
"defaultOverride": null,
"textLanguageMode": null,
"symmetricalRef": false,
"autoChainRef": true,
"allowOutOfLevelRef": true,
"allowedRefs": "OnlySame",
"allowedRefTags": [],
"tilesetUid": null
}
]
}
], "tilesets": [
{
"__cWid": 4,
"__cHei": 8,
"identifier": "MainTiles_Tile_32",
"uid": 4,
"relPath": "media/Tiles/MainTiles-Tile_32.png",
"embedAtlas": null,
"pxWid": 128,
"pxHei": 256,
"tileGridSize": 32,
"spacing": 0,
"padding": 0,
"tags": [],
"tagsSourceEnumUid": null,
"enumTags": [],
"customData": [],
"savedSelections": [],
"cachedPixelData": {
"opaqueTiles": "11111111111111111111111111111000",
"averageColors": "f666f666f666f666f555f555f555f555f444f444f445f444f334f767f767f555f767f767f777f445f445f445f445f666f666f666f666f666f666000000000000"
}
}
], "enums": [{ "identifier": "StaticEnemy", "uid": 10, "values": [{ "id": "Turret", "tileId": null, "color": 0, "__tileSrcRect": null }], "iconTilesetUid": null, "externalRelPath": null, "externalFileChecksum": null, "tags": [] }], "externalEnums": [], "levelFields": [] },
"levels": [
{
"identifier": "Level_0",
"iid": "affe5db0-02f0-11ed-84a4-29f5b78754e6",
"uid": 0,
"worldX": 0,
"worldY": 32,
"worldDepth": 0,
"pxWid": 6976,
"pxHei": 1120,
"__bgColor": "#696A79",
"bgColor": null,
"useAutoIdentifier": true,
"bgRelPath": null,
"bgPos": null,
"bgPivotX": 0.5,
"bgPivotY": 0.5,
"__smartColor": "#ADADB5",
"__bgPos": null,
"externalRelPath": "Project Jupiter/Level_0.ldtkl",
"fieldInstances": [],
"layerInstances": null,
"__neighbours": []
}
],
"worlds": []
}
Now, here's the bit that got me stuck for a bit and I had to do a bit of head scratching to figure out what was going on. If you take a look at the .json file, it starts with a { and ends with a }. But AGK's .json command e.g. loadmyData.load(thisfile$) wants the file to be bracketed in a [ and a ]. This is because loading .json files into AppGameKit is an iterative function and the command expects it to be denoted as such. The solution is then to add [ at the file start and a ] at the file end. And this can be done easily in code:
function copyLevelDataFromLDtk()
sourceFileStr as string
destFileStr as string
for i = 0 to NUMBER_OF_LEVELS
sourceFileStr = "raw:H:\AGK\AGK Games\Project Jupiter\Project Jupiter\Level_" + str(i) + ".ldtkl"
destFileStr = "raw:H:\AGK\AGK Games\Project Jupiter\media\LevelData\Level_" + str(i) + ".ldtkl"
copyFile(sourceFileStr,destFileStr)
next i
endfunction
function copyFile(fileName$, newName$)
// open the two files
sourceFile = OpenToRead(fileName$)
destFile = OpenToWrite(newName$)
// while we are not at the end of the source file
WriteLine(destFile,"[")
while FileEOF(sourceFile) = 0
// write the next byte in the source file to the dest file
// by going through it byte by byte, this function will be able to copy any file, regardless of format
WriteLine(destFile, ReadLine(sourceFile))
endwhile
WriteLine(destFile,"]")
// close the files
CloseFile(sourceFile)
CloseFile(destFile)
endfunction
Next load in your tile sets. How you do this is up to you. I loaded them into an array using subimage.txt file as described here :
https://www.appgamekit.com/documentation/Reference/Image/LoadSubImage.htm
With everything now ready, to add our level into our game we need to parse the data we loaded from .json file add in the tiles and put them into the world in the correct places. The code I used to do this is shown below.
function loadAllLevelData3() /// this function loads levelData.ldtk and stores the information into array2's. There is one array2 for each of the layer.
tempWalls as gridTilesT[] // use gridTilesT
tempFill as gridTilesT[]
tempEntities as entityInstanceT[] // use entityInstanceT
tempLevel as ldTKrootT[]
tempLevelData as levelDataT
loadFileStr as string
for i = 0 to NUMBER_OF_LEVELS // We will want to do this for every level. But for this example N
loadFileStr = "LevelData/Level_" + str(i) + ".ldtkl"
tempLevel.load(loadFileStr) // this is the AGK command to load our level data. Remember to ensure to add [] at the start and end of the file!
tempLevelData.ID = i
tempLevelData.levelData = tempLevel
levelDataMain.insert(tempLevelData)
next i
for i = 0 to NUMBER_OF_LEVELS
for s = 0 to 2
if levelDataMain[i].levelData[0].layerInstances[s].__identifier = "Tiles"
tempWalls = levelDataMain[i].levelData[0].layerInstances[s].gridTiles
Walls.insert(tempWalls)
endif
if levelDataMain[i].levelData[0].layerInstances[s].__identifier = "Fill"
tempFill = levelDataMain[i].levelData[0].layerInstances[s].gridTiles
Fill.insert(tempFill)
endif
if levelDataMain[i].levelData[0].layerInstances[s].__identifier = "Entities"
tempEntities = levelDataMain[i].levelData[0].layerInstances[s].entityInstances
Entities.insert(tempEntities)
endif
next s
next i
endfunction
function displayLevelDIM(level) /// this function takes information from the data created by the .json file, loads the correct tile and makes a sprite for it in the correct place
tempSprite as staticSpritesT
for i = 0 to Walls[level].length
subImageToUse = Walls[level,i].t
tempSprite.spriteID = createSprite(subImageList[subImageToUse])
SetSpriteShape(tempSprite.spriteID,3)
SetSpritePosition(tempSprite.spriteID, Walls[level,i].px[0], Walls[level,i].px[1])
SetSpriteDepth(tempSprite.spriteID, 805)
SetSpritePhysicsOn(tempSprite.spriteID,1)
SetSpriteCategoryBit(tempSprite.spriteID,1,0)
SetSpriteCategoryBit(tempSprite.spriteID,3,1)
SetspriteColor(tempSprite.spriteID,128,128,128,255)
staticSpritesList.insert(tempSprite)
next i
for i = 0 to Fill[level].length
subImageToUse = Fill[level,i].t
tempSprite.spriteID = createSprite(subImageList[subImageToUse])
SetSpriteShape(tempSprite.spriteID,3)
SetSpritePosition(tempSprite.spriteID, Fill[level,i].px[0], Fill[level,i].px[1])
SetSpriteDepth(tempSprite.spriteID, 805)
//SetSpritePhysicsOn(tempSprite.spriteID,1)
//SetSpriteCategoryBit(tempSprite.spriteID,1,0)
//SetSpriteCategoryBit(tempSprite.spriteID,3,1)
SetspriteColor(tempSprite.spriteID,128,128,128,255)
staticSpritesList.insert(tempSprite)
next i
for i = 0 to Entities[level].length
if Entities[level,i].__identifier = "Entity"
//doorPlacement(Entities[level,i].px[0],Entities[level,i].px[1],Entities[level,i].fieldInstances[0].__value,Entities[level,i].fieldInstances[1].__value,Entities[level,i].fieldInstances[2].__value, Entities[level,i].iid)
turretPlacement(Entities[level,i].px[0],Entities[level,i].px[1])
//Message(str(Entities.length))
endif
next i
endfunction
LDtk has much to offer in terms of 2D level design and it is worth a look. Because the data can be saved as a .json then to read and parse the data is easy, just remember to add your '[]' on the file or AppGameKit can't load it. Also, LDtk records all the information into the .json so this is accessible from the .json structure, if you know where to look. Everything you need is well documented :
https://ldtk.io/json/
If you want to play Project Jupiter it is here for download :
https://dewarinversion.itch.io/project-jupiter