shifted from proprietary ascii rawlevel to LDTK (using ldtkgo@master)

This commit is contained in:
Thomas von Dein 2024-03-10 13:05:31 +01:00
parent a2ed7782b2
commit af19ccb833
15 changed files with 819 additions and 173 deletions

View File

@ -22,6 +22,14 @@
slog.Debug("get observer", "minmoves", observer.LevelScore,
"file", source.File, "line", source.Line)
- LDTK:
- to each level add these properties:
- level (num)
- name
- description
- background (image name w/o .png)
- min-moves (num, moves required to win)
## Collider Rework [abandoned: see branch collider-system, fails]
- do not use the map anymore for collision detection

View File

@ -0,0 +1,686 @@
{
"__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.5.3",
"url": "https://ldtk.io"
},
"iid": "267e9380-d7b0-11ee-a97e-35bec9c19d52",
"jsonVersion": "1.5.3",
"appBuildId": 473703,
"nextUid": 11,
"identifierStyle": "Capitalize",
"toc": [],
"worldLayout": "Free",
"worldGridWidth": 256,
"worldGridHeight": 256,
"defaultLevelWidth": 256,
"defaultLevelHeight": 256,
"defaultPivotX": 0,
"defaultPivotY": 0,
"defaultGridSize": 32,
"defaultEntityWidth": 32,
"defaultEntityHeight": 32,
"bgColor": "#40465B",
"defaultLevelBgColor": "#696A79",
"minifyJson": false,
"externalLevels": false,
"exportTiled": false,
"simplifiedExport": false,
"imageExportMode": "None",
"exportLevelBg": true,
"pngFilePattern": null,
"backupOnSave": false,
"backupLimit": 10,
"backupRelPath": null,
"levelNamePattern": "Level_%idx",
"tutorialDesc": null,
"customCommands": [],
"flags": [],
"defs": { "layers": [
{
"__type": "Entities",
"identifier": "Entities",
"type": "Entities",
"uid": 5,
"doc": null,
"uiColor": null,
"gridSize": 32,
"guideGridWid": 0,
"guideGridHei": 0,
"displayOpacity": 1,
"inactiveOpacity": 0.6,
"hideInList": false,
"hideFieldsWhenInactive": true,
"canSelectWhenInactive": true,
"renderInWorldView": true,
"pxOffsetX": 0,
"pxOffsetY": 0,
"parallaxFactorX": 0,
"parallaxFactorY": 0,
"parallaxScaling": true,
"requiredTags": [],
"excludedTags": [],
"autoTilesKilledByOtherLayerUid": null,
"uiFilterTags": [],
"useAsyncRender": false,
"intGridValues": [],
"intGridValuesGroups": [],
"autoRuleGroups": [],
"autoSourceLayerDefUid": null,
"tilesetDefUid": null,
"tilePivotX": 0,
"tilePivotY": 0,
"biomeFieldUid": null
},
{
"__type": "Tiles",
"identifier": "Tiles",
"type": "Tiles",
"uid": 2,
"doc": null,
"uiColor": null,
"gridSize": 32,
"guideGridWid": 0,
"guideGridHei": 0,
"displayOpacity": 1,
"inactiveOpacity": 1,
"hideInList": false,
"hideFieldsWhenInactive": false,
"canSelectWhenInactive": true,
"renderInWorldView": true,
"pxOffsetX": 0,
"pxOffsetY": 0,
"parallaxFactorX": 0,
"parallaxFactorY": 0,
"parallaxScaling": true,
"requiredTags": [],
"excludedTags": [],
"autoTilesKilledByOtherLayerUid": null,
"uiFilterTags": [],
"useAsyncRender": false,
"intGridValues": [],
"intGridValuesGroups": [],
"autoRuleGroups": [],
"autoSourceLayerDefUid": null,
"tilesetDefUid": 1,
"tilePivotX": 0,
"tilePivotY": 0,
"biomeFieldUid": null
}
], "entities": [
{
"identifier": "Player_Primary",
"uid": 3,
"tags": [],
"exportToToc": false,
"allowOutOfBounds": false,
"doc": null,
"width": 32,
"height": 32,
"resizableX": false,
"resizableY": false,
"minWidth": null,
"maxWidth": null,
"minHeight": null,
"maxHeight": null,
"keepAspectRatio": false,
"tileOpacity": 1,
"fillOpacity": 0.08,
"lineOpacity": 0,
"hollow": false,
"color": "#2F3BBE",
"renderMode": "Tile",
"showName": true,
"tilesetId": 1,
"tileRenderMode": "FitInside",
"tileRect": { "tilesetUid": 1, "x": 32, "y": 96, "w": 32, "h": 32 },
"uiTileRect": null,
"nineSliceBorders": [],
"maxCount": 0,
"limitScope": "PerLevel",
"limitBehavior": "MoveLastOne",
"pivotX": 0,
"pivotY": 0,
"fieldDefs": []
},
{
"identifier": "Collectible",
"uid": 4,
"tags": [],
"exportToToc": false,
"allowOutOfBounds": false,
"doc": null,
"width": 32,
"height": 32,
"resizableX": false,
"resizableY": false,
"minWidth": null,
"maxWidth": null,
"minHeight": null,
"maxHeight": null,
"keepAspectRatio": false,
"tileOpacity": 1,
"fillOpacity": 0.08,
"lineOpacity": 0,
"hollow": false,
"color": "#FEAE34",
"renderMode": "Tile",
"showName": true,
"tilesetId": 1,
"tileRenderMode": "FitInside",
"tileRect": { "tilesetUid": 1, "x": 32, "y": 32, "w": 32, "h": 32 },
"uiTileRect": null,
"nineSliceBorders": [],
"maxCount": 0,
"limitScope": "PerLevel",
"limitBehavior": "MoveLastOne",
"pivotX": 0,
"pivotY": 0,
"fieldDefs": []
},
{
"identifier": "Obstacle_West",
"uid": 7,
"tags": [],
"exportToToc": false,
"allowOutOfBounds": false,
"doc": null,
"width": 32,
"height": 32,
"resizableX": false,
"resizableY": false,
"minWidth": null,
"maxWidth": null,
"minHeight": null,
"maxHeight": null,
"keepAspectRatio": false,
"tileOpacity": 1,
"fillOpacity": 0.08,
"lineOpacity": 0,
"hollow": false,
"color": "#BE4A2F",
"renderMode": "Tile",
"showName": true,
"tilesetId": 1,
"tileRenderMode": "FitInside",
"tileRect": { "tilesetUid": 1, "x": 0, "y": 96, "w": 32, "h": 32 },
"uiTileRect": null,
"nineSliceBorders": [],
"maxCount": 0,
"limitScope": "PerLevel",
"limitBehavior": "MoveLastOne",
"pivotX": 0,
"pivotY": 0,
"fieldDefs": []
},
{
"identifier": "Obstacle_East",
"uid": 8,
"tags": [],
"exportToToc": false,
"allowOutOfBounds": false,
"doc": null,
"width": 32,
"height": 32,
"resizableX": false,
"resizableY": false,
"minWidth": null,
"maxWidth": null,
"minHeight": null,
"maxHeight": null,
"keepAspectRatio": false,
"tileOpacity": 1,
"fillOpacity": 0.08,
"lineOpacity": 0,
"hollow": false,
"color": "#D77643",
"renderMode": "Tile",
"showName": true,
"tilesetId": 1,
"tileRenderMode": "FitInside",
"tileRect": { "tilesetUid": 1, "x": 64, "y": 32, "w": 32, "h": 32 },
"uiTileRect": null,
"nineSliceBorders": [],
"maxCount": 0,
"limitScope": "PerLevel",
"limitBehavior": "MoveLastOne",
"pivotX": 0,
"pivotY": 0,
"fieldDefs": []
},
{
"identifier": "Obstacle_North",
"uid": 9,
"tags": [],
"exportToToc": false,
"allowOutOfBounds": false,
"doc": null,
"width": 32,
"height": 32,
"resizableX": false,
"resizableY": false,
"minWidth": null,
"maxWidth": null,
"minHeight": null,
"maxHeight": null,
"keepAspectRatio": false,
"tileOpacity": 1,
"fillOpacity": 0.08,
"lineOpacity": 0,
"hollow": false,
"color": "#F77622",
"renderMode": "Tile",
"showName": true,
"tilesetId": 1,
"tileRenderMode": "FitInside",
"tileRect": { "tilesetUid": 1, "x": 0, "y": 64, "w": 32, "h": 32 },
"uiTileRect": null,
"nineSliceBorders": [],
"maxCount": 0,
"limitScope": "PerLevel",
"limitBehavior": "MoveLastOne",
"pivotX": 0,
"pivotY": 0,
"fieldDefs": []
},
{
"identifier": "Obstacle_South",
"uid": 10,
"tags": [],
"exportToToc": false,
"allowOutOfBounds": false,
"doc": null,
"width": 32,
"height": 32,
"resizableX": false,
"resizableY": false,
"minWidth": null,
"maxWidth": null,
"minHeight": null,
"maxHeight": null,
"keepAspectRatio": false,
"tileOpacity": 1,
"fillOpacity": 0.08,
"lineOpacity": 0,
"hollow": false,
"color": "#E43B44",
"renderMode": "Tile",
"showName": true,
"tilesetId": 1,
"tileRenderMode": "FitInside",
"tileRect": { "tilesetUid": 1, "x": 32, "y": 64, "w": 32, "h": 32 },
"uiTileRect": null,
"nineSliceBorders": [],
"maxCount": 0,
"limitScope": "PerLevel",
"limitBehavior": "MoveLastOne",
"pivotX": 0,
"pivotY": 0,
"fieldDefs": []
}
], "tilesets": [
{
"__cWid": 3,
"__cHei": 5,
"identifier": "Map2",
"uid": 1,
"relPath": "../sprites/map.png",
"embedAtlas": null,
"pxWid": 96,
"pxHei": 160,
"tileGridSize": 32,
"spacing": 0,
"padding": 0,
"tags": [],
"tagsSourceEnumUid": null,
"enumTags": [],
"customData": [],
"savedSelections": [],
"cachedPixelData": { "opaqueTiles": "110000000000000", "averageColors": "f777f766f766f9533c965c355c355c354b355c35987d687d1b4700000000" }
}
], "enums": [], "externalEnums": [], "levelFields": [] },
"levels": [
{
"identifier": "Level_0",
"iid": "267fa4f0-d7b0-11ee-a97e-d90512e08363",
"uid": 0,
"worldX": -224,
"worldY": 0,
"worldDepth": 0,
"pxWid": 640,
"pxHei": 480,
"__bgColor": "#696A79",
"bgColor": null,
"useAutoIdentifier": true,
"bgRelPath": null,
"bgPos": null,
"bgPivotX": 0.5,
"bgPivotY": 0.5,
"__smartColor": "#ADADB5",
"__bgPos": null,
"externalRelPath": null,
"fieldInstances": [],
"layerInstances": [
{
"__identifier": "Entities",
"__type": "Entities",
"__cWid": 20,
"__cHei": 15,
"__gridSize": 32,
"__opacity": 1,
"__pxTotalOffsetX": 0,
"__pxTotalOffsetY": 0,
"__tilesetDefUid": null,
"__tilesetRelPath": null,
"iid": "99cd5060-d7b0-11ee-a97e-3f143b461cd1",
"levelId": 0,
"layerDefUid": 5,
"pxOffsetX": 0,
"pxOffsetY": 0,
"visible": true,
"optionalRules": [],
"intGridCsv": [],
"autoLayerTiles": [],
"seed": 1148260,
"overrideTilesetUid": null,
"gridTiles": [],
"entityInstances": [
{
"__identifier": "Player_Primary",
"__grid": [7,5],
"__pivot": [0,0],
"__tags": [],
"__tile": { "tilesetUid": 1, "x": 32, "y": 96, "w": 32, "h": 32 },
"__smartColor": "#2F3BBE",
"iid": "c33a2c20-d7b0-11ee-a97e-03409aba0392",
"width": 32,
"height": 32,
"defUid": 3,
"px": [224,160],
"fieldInstances": [],
"__worldX": 0,
"__worldY": 160
},
{
"__identifier": "Collectible",
"__grid": [11,7],
"__pivot": [0,0],
"__tags": [],
"__tile": { "tilesetUid": 1, "x": 32, "y": 32, "w": 32, "h": 32 },
"__smartColor": "#FEAE34",
"iid": "f2b2ff50-d7b0-11ee-98fb-b34c447bc14d",
"width": 32,
"height": 32,
"defUid": 4,
"px": [352,224],
"fieldInstances": [],
"__worldX": 128,
"__worldY": 224
}
]
},
{
"__identifier": "Tiles",
"__type": "Tiles",
"__cWid": 20,
"__cHei": 15,
"__gridSize": 32,
"__opacity": 1,
"__pxTotalOffsetX": 0,
"__pxTotalOffsetY": 0,
"__tilesetDefUid": 1,
"__tilesetRelPath": "../sprites/map.png",
"iid": "3f368c70-d7b0-11ee-a97e-65a98b860ebf",
"levelId": 0,
"layerDefUid": 2,
"pxOffsetX": 0,
"pxOffsetY": 0,
"visible": true,
"optionalRules": [],
"intGridCsv": [],
"autoLayerTiles": [],
"seed": 4985586,
"overrideTilesetUid": null,
"gridTiles": [
{ "px": [192,128], "src": [64,0], "f": 0, "t": 2, "d": [86], "a": 1 },
{ "px": [224,128], "src": [64,0], "f": 0, "t": 2, "d": [87], "a": 1 },
{ "px": [256,128], "src": [64,0], "f": 0, "t": 2, "d": [88], "a": 1 },
{ "px": [288,128], "src": [64,0], "f": 0, "t": 2, "d": [89], "a": 1 },
{ "px": [320,128], "src": [64,0], "f": 0, "t": 2, "d": [90], "a": 1 },
{ "px": [352,128], "src": [64,0], "f": 0, "t": 2, "d": [91], "a": 1 },
{ "px": [384,128], "src": [64,0], "f": 0, "t": 2, "d": [92], "a": 1 },
{ "px": [416,128], "src": [0,0], "f": 0, "t": 0, "d": [93], "a": 1 },
{ "px": [416,128], "src": [64,0], "f": 0, "t": 2, "d": [93], "a": 1 },
{ "px": [192,160], "src": [64,0], "f": 0, "t": 2, "d": [106], "a": 1 },
{ "px": [256,160], "src": [64,0], "f": 0, "t": 2, "d": [108], "a": 1 },
{ "px": [416,160], "src": [64,0], "f": 0, "t": 2, "d": [113], "a": 1 },
{ "px": [192,192], "src": [64,0], "f": 0, "t": 2, "d": [126], "a": 1 },
{ "px": [256,192], "src": [64,0], "f": 0, "t": 2, "d": [128], "a": 1 },
{ "px": [320,192], "src": [64,0], "f": 0, "t": 2, "d": [130], "a": 1 },
{ "px": [352,192], "src": [64,0], "f": 0, "t": 2, "d": [131], "a": 1 },
{ "px": [416,192], "src": [64,0], "f": 0, "t": 2, "d": [133], "a": 1 },
{ "px": [192,224], "src": [64,0], "f": 0, "t": 2, "d": [146], "a": 1 },
{ "px": [256,224], "src": [64,0], "f": 0, "t": 2, "d": [148], "a": 1 },
{ "px": [320,224], "src": [64,0], "f": 0, "t": 2, "d": [150], "a": 1 },
{ "px": [416,224], "src": [64,0], "f": 0, "t": 2, "d": [153], "a": 1 },
{ "px": [192,256], "src": [64,0], "f": 0, "t": 2, "d": [166], "a": 1 },
{ "px": [256,256], "src": [64,0], "f": 0, "t": 2, "d": [168], "a": 1 },
{ "px": [320,256], "src": [64,0], "f": 0, "t": 2, "d": [170], "a": 1 },
{ "px": [416,256], "src": [64,0], "f": 0, "t": 2, "d": [173], "a": 1 },
{ "px": [192,288], "src": [64,0], "f": 0, "t": 2, "d": [186], "a": 1 },
{ "px": [320,288], "src": [64,0], "f": 0, "t": 2, "d": [190], "a": 1 },
{ "px": [416,288], "src": [64,0], "f": 0, "t": 2, "d": [193], "a": 1 },
{ "px": [192,320], "src": [64,0], "f": 0, "t": 2, "d": [206], "a": 1 },
{ "px": [224,320], "src": [64,0], "f": 0, "t": 2, "d": [207], "a": 1 },
{ "px": [256,320], "src": [64,0], "f": 0, "t": 2, "d": [208], "a": 1 },
{ "px": [288,320], "src": [64,0], "f": 0, "t": 2, "d": [209], "a": 1 },
{ "px": [320,320], "src": [64,0], "f": 0, "t": 2, "d": [210], "a": 1 },
{ "px": [352,320], "src": [64,0], "f": 0, "t": 2, "d": [211], "a": 1 },
{ "px": [384,320], "src": [64,0], "f": 0, "t": 2, "d": [212], "a": 1 },
{ "px": [416,320], "src": [64,0], "f": 0, "t": 2, "d": [213], "a": 1 }
],
"entityInstances": []
}
],
"__neighbours": []
},
{
"identifier": "_First_Try",
"iid": "024390f0-d7b0-11ee-98fb-1bc6ad7e4172",
"uid": 6,
"worldX": 480,
"worldY": 0,
"worldDepth": 0,
"pxWid": 640,
"pxHei": 480,
"__bgColor": "#696A79",
"bgColor": null,
"useAutoIdentifier": false,
"bgRelPath": "../sprites/background-lila.png",
"bgPos": "Cover",
"bgPivotX": 0.5,
"bgPivotY": 0.5,
"__smartColor": "#ADADB5",
"__bgPos": { "topLeftPx": [0,0], "scale": [1,1], "cropRect": [0,0,640,480] },
"externalRelPath": null,
"fieldInstances": [],
"layerInstances": [
{
"__identifier": "Entities",
"__type": "Entities",
"__cWid": 20,
"__cHei": 15,
"__gridSize": 32,
"__opacity": 1,
"__pxTotalOffsetX": 0,
"__pxTotalOffsetY": 0,
"__tilesetDefUid": null,
"__tilesetRelPath": null,
"iid": "0243b800-d7b0-11ee-98fb-f1914bde56c8",
"levelId": 6,
"layerDefUid": 5,
"pxOffsetX": 0,
"pxOffsetY": 0,
"visible": true,
"optionalRules": [],
"intGridCsv": [],
"autoLayerTiles": [],
"seed": 2072933,
"overrideTilesetUid": null,
"gridTiles": [],
"entityInstances": [
{
"__identifier": "Obstacle_West",
"__grid": [6,8],
"__pivot": [0,0],
"__tags": [],
"__tile": { "tilesetUid": 1, "x": 0, "y": 96, "w": 32, "h": 32 },
"__smartColor": "#BE4A2F",
"iid": "8aab4e10-d7b0-11ee-98fb-2171f9170515",
"width": 32,
"height": 32,
"defUid": 7,
"px": [192,256],
"fieldInstances": [],
"__worldX": 672,
"__worldY": 256
},
{
"__identifier": "Player_Primary",
"__grid": [4,5],
"__pivot": [0,0],
"__tags": [],
"__tile": { "tilesetUid": 1, "x": 32, "y": 96, "w": 32, "h": 32 },
"__smartColor": "#2F3BBE",
"iid": "8d2c2c90-d7b0-11ee-98fb-d523a27935ba",
"width": 32,
"height": 32,
"defUid": 3,
"px": [128,160],
"fieldInstances": [],
"__worldX": 608,
"__worldY": 160
},
{
"__identifier": "Collectible",
"__grid": [4,9],
"__pivot": [0,0],
"__tags": [],
"__tile": { "tilesetUid": 1, "x": 32, "y": 32, "w": 32, "h": 32 },
"__smartColor": "#FEAE34",
"iid": "a1dfb440-d7b0-11ee-98fb-933f35aeb72b",
"width": 32,
"height": 32,
"defUid": 4,
"px": [128,288],
"fieldInstances": [],
"__worldX": 608,
"__worldY": 288
},
{
"__identifier": "Collectible",
"__grid": [14,6],
"__pivot": [0,0],
"__tags": [],
"__tile": { "tilesetUid": 1, "x": 32, "y": 32, "w": 32, "h": 32 },
"__smartColor": "#FEAE34",
"iid": "f7ec8340-d7b0-11ee-98fb-1fa75c7ce959",
"width": 32,
"height": 32,
"defUid": 4,
"px": [448,192],
"fieldInstances": [],
"__worldX": 928,
"__worldY": 192
},
{
"__identifier": "Obstacle_South",
"__grid": [15,6],
"__pivot": [0,0],
"__tags": [],
"__tile": { "tilesetUid": 1, "x": 32, "y": 64, "w": 32, "h": 32 },
"__smartColor": "#E43B44",
"iid": "45c2d010-d7b0-11ee-98fb-13d8f32ffff0",
"width": 32,
"height": 32,
"defUid": 10,
"px": [480,192],
"fieldInstances": [],
"__worldX": 960,
"__worldY": 192
}
]
},
{
"__identifier": "Tiles",
"__type": "Tiles",
"__cWid": 20,
"__cHei": 15,
"__gridSize": 32,
"__opacity": 1,
"__pxTotalOffsetX": 0,
"__pxTotalOffsetY": 0,
"__tilesetDefUid": 1,
"__tilesetRelPath": "../sprites/map.png",
"iid": "0243b801-d7b0-11ee-98fb-79b1102fb094",
"levelId": 6,
"layerDefUid": 2,
"pxOffsetX": 0,
"pxOffsetY": 0,
"visible": true,
"optionalRules": [],
"intGridCsv": [],
"autoLayerTiles": [],
"seed": 2952425,
"overrideTilesetUid": null,
"gridTiles": [
{ "px": [96,128], "src": [64,0], "f": 0, "t": 2, "d": [83], "a": 1 },
{ "px": [128,128], "src": [64,0], "f": 0, "t": 2, "d": [84], "a": 1 },
{ "px": [160,128], "src": [64,0], "f": 0, "t": 2, "d": [85], "a": 1 },
{ "px": [192,128], "src": [64,0], "f": 0, "t": 2, "d": [86], "a": 1 },
{ "px": [224,128], "src": [64,0], "f": 0, "t": 2, "d": [87], "a": 1 },
{ "px": [384,128], "src": [64,0], "f": 0, "t": 2, "d": [92], "a": 1 },
{ "px": [416,128], "src": [64,0], "f": 0, "t": 2, "d": [93], "a": 1 },
{ "px": [448,128], "src": [64,0], "f": 0, "t": 2, "d": [94], "a": 1 },
{ "px": [480,128], "src": [64,0], "f": 0, "t": 2, "d": [95], "a": 1 },
{ "px": [512,128], "src": [64,0], "f": 0, "t": 2, "d": [96], "a": 1 },
{ "px": [96,160], "src": [64,0], "f": 0, "t": 2, "d": [103], "a": 1 },
{ "px": [224,160], "src": [64,0], "f": 0, "t": 2, "d": [107], "a": 1 },
{ "px": [384,160], "src": [64,0], "f": 0, "t": 2, "d": [112], "a": 1 },
{ "px": [512,160], "src": [64,0], "f": 0, "t": 2, "d": [116], "a": 1 },
{ "px": [96,192], "src": [64,0], "f": 0, "t": 2, "d": [123], "a": 1 },
{ "px": [224,192], "src": [64,0], "f": 0, "t": 2, "d": [127], "a": 1 },
{ "px": [384,192], "src": [64,0], "f": 0, "t": 2, "d": [132], "a": 1 },
{ "px": [512,192], "src": [64,0], "f": 0, "t": 2, "d": [136], "a": 1 },
{ "px": [224,224], "src": [64,0], "f": 0, "t": 2, "d": [147], "a": 1 },
{ "px": [384,224], "src": [64,0], "f": 0, "t": 2, "d": [152], "a": 1 },
{ "px": [224,256], "src": [64,0], "f": 0, "t": 2, "d": [167], "a": 1 },
{ "px": [384,256], "src": [64,0], "f": 0, "t": 2, "d": [172], "a": 1 },
{ "px": [448,256], "src": [64,0], "f": 0, "t": 2, "d": [174], "a": 1 },
{ "px": [224,288], "src": [64,0], "f": 0, "t": 2, "d": [187], "a": 1 },
{ "px": [384,288], "src": [64,0], "f": 0, "t": 2, "d": [192], "a": 1 },
{ "px": [448,288], "src": [64,0], "f": 0, "t": 2, "d": [194], "a": 1 },
{ "px": [96,320], "src": [64,0], "f": 0, "t": 2, "d": [203], "a": 1 },
{ "px": [128,320], "src": [64,0], "f": 0, "t": 2, "d": [204], "a": 1 },
{ "px": [160,320], "src": [64,0], "f": 0, "t": 2, "d": [205], "a": 1 },
{ "px": [192,320], "src": [64,0], "f": 0, "t": 2, "d": [206], "a": 1 },
{ "px": [224,320], "src": [64,0], "f": 0, "t": 2, "d": [207], "a": 1 },
{ "px": [384,320], "src": [64,0], "f": 0, "t": 2, "d": [212], "a": 1 },
{ "px": [416,320], "src": [64,0], "f": 0, "t": 2, "d": [213], "a": 1 },
{ "px": [448,320], "src": [64,0], "f": 0, "t": 2, "d": [214], "a": 1 },
{ "px": [480,320], "src": [64,0], "f": 0, "t": 2, "d": [215], "a": 1 },
{ "px": [512,320], "src": [64,0], "f": 0, "t": 2, "d": [216], "a": 1 }
],
"entityInstances": []
}
],
"__neighbours": []
}
],
"worlds": [],
"dummyWorldIid": "267ee1a0-d7b0-11ee-a97e-53f0a359eae1"
}

View File

@ -1,23 +1,17 @@
package assets
import (
"bufio"
_ "image/png"
"io/fs"
"log"
"log/slog"
"openquell/config"
"openquell/util"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/solarlune/ldtkgo"
)
var Levels = LoadLevels("levels")
var Project = LoadLDTK("levels")
var Tiles = InitTiles()
// Tile: contains image, identifier (as used in level data) and
@ -186,44 +180,22 @@ func NewTileHiddenDoor(class []string) *Tile {
}
// used to map level data bytes to actual tiles
type TileRegistry map[byte]*Tile
// holds a raw level spec:
//
// Name: the name of the level file w/o the .lvl extension
// Background: an image name used as game background for this level
// Description: text to display on top
// Data: a level spec consisting of chars of the above mapping and spaces, e.g.:
// ####
// # #
// ####
//
// Each level data must be 20 chars wide (= 640 px width) and 15 chars
// high (=480 px height).
type RawLevel struct {
Number int
Name string
Description string
Background *ebiten.Image
Data []byte
MinMoves int
}
type TileRegistry map[string]*Tile
func InitTiles() TileRegistry {
return TileRegistry{
' ': {Id: ' ', Class: "floor", Renderable: false},
//'#': NewTileBlock("block-grey32"),
'#': NewTileBlock("block-greycolored"),
'B': NewTileBlock("block-orange-32"),
'S': NewTilePlayer(Primary),
's': NewTilePlayer(Secondary),
'o': NewTileCollectible("collectible-orange"),
'+': NewTileObstacle("obstacle-star", config.All),
'^': NewTileObstacle("obstacle-north", config.North),
'v': NewTileObstacle("obstacle-south", config.South),
'<': NewTileObstacle("obstacle-west", config.West),
'>': NewTileObstacle("obstacle-east", config.East),
'*': NewTileParticle([]string{
"floor": {Id: ' ', Class: "floor", Renderable: false},
"default": NewTileBlock("block-greycolored"),
"solidorange": NewTileBlock("block-orange-32"),
"player-primary": NewTilePlayer(Primary),
"player-secondary": NewTilePlayer(Secondary),
"collectible": NewTileCollectible("collectible-orange"),
"obstacle-star": NewTileObstacle("obstacle-star", config.All),
"obstacle-north": NewTileObstacle("obstacle-north", config.North),
"obstacle-south": NewTileObstacle("obstacle-south", config.South),
"obstacle-west": NewTileObstacle("obstacle-west", config.West),
"obstacle-east": NewTileObstacle("obstacle-east", config.East),
"particle": NewTileParticle([]string{
//"particle-ring-1",
"particle-ring-2",
"particle-ring-3",
@ -231,91 +203,56 @@ func InitTiles() TileRegistry {
"particle-ring-5",
"particle-ring-6",
}),
't': NewTileTranswall([]string{"transwall", "block-orange-32"}),
'W': NewTileHiddenDoor([]string{"block-greycolored", "block-greycolored-damaged"}),
"transient": NewTileTranswall([]string{"transwall", "block-orange-32"}),
"hiddendoor": NewTileHiddenDoor([]string{"block-greycolored", "block-greycolored-damaged"}),
}
}
// load levels at compile time into ram, creates a slice of raw levels
func LoadLevels(dir string) []RawLevel {
levels := []RawLevel{}
// we use embed.FS to iterate over all files in ./levels/
entries, err := assetfs.ReadDir(dir)
// load LDTK project at compile time into ram
func LoadLDTK(dir string) *ldtkgo.Project {
fd, err := assetfs.Open("levels/openquell.ldtk")
if err != nil {
log.Fatalf("failed to read level dir %s: %s", dir, err)
}
sort.Slice(entries, func(i, j int) bool {
return entries[i].Name() < entries[j].Name()
})
for idx, levelfile := range entries {
if levelfile.Type().IsRegular() && strings.Contains(levelfile.Name(), ".lvl") {
path := filepath.Join("assets", dir)
level := ParseRawLevel(path, levelfile)
level.Number = idx
levels = append(levels, level)
slog.Debug("loaded level", "path", path, "file", levelfile)
}
}
return levels
}
func ParseRawLevel(dir string, levelfile fs.DirEntry) RawLevel {
fd, err := os.Open(filepath.Join(dir, levelfile.Name()))
if err != nil {
log.Fatalf("failed to read level file %s: %s", levelfile.Name(), err)
log.Fatalf("failed to open LDTK file levels/openquell.ldtk: %s", err)
}
defer fd.Close()
name := strings.TrimSuffix(levelfile.Name(), ".lvl")
name = name[3:]
des := ""
background := &ebiten.Image{}
data := []byte{}
minmoves := 0
fileinfo, err := fd.Stat()
if err != nil {
log.Fatalf("failed to stat() LDTK file levels/openquell.ldtk: %s", err)
}
scanner := bufio.NewScanner(fd)
for scanner.Scan() {
// ignore any whitespace
line := scanner.Text()
filesize := fileinfo.Size()
buffer := make([]byte, filesize)
// ignore empty lines
if len(line) == 0 {
continue
_, err = fd.Read(buffer)
if err != nil {
log.Fatalf("failed to read bytes from LDTK file levels/openquell.ldtk: %s", err)
}
project, err := ldtkgo.Read(buffer)
if err != nil {
panic(err)
}
// do some sanity checks
properties := []string{"min-moves", "background", "level", "name", "descrption"}
need := len(properties)
for idx, level := range project.Levels {
have := 0
for _, property := range level.Properties {
if util.Contains(properties, property.Identifier) {
have++
}
}
switch {
case strings.Contains(line, "Background:"):
haveit := strings.Split(line, ": ")
if util.Exists(Assets, haveit[1]) {
background = Assets[haveit[1]]
}
case strings.Contains(line, "Description:"):
haveit := strings.Split(line, ": ")
des = haveit[1]
case strings.Contains(line, "MinMoves:"):
haveit := strings.Split(line, ": ")
minmoves, err = strconv.Atoi(haveit[1])
if err != nil {
log.Fatal("Failed to convert MinMoves to int: %w", err)
}
default:
// all other non-empty and non-equalsign lines are
// level definition matrix data, merge thes into data
data = append(data, line+"\n"...)
if have != need {
log.Fatalf("level definition for level %d (%s) invalid: %d missing properties\n required: %s",
idx, level.Identifier, need-have, strings.Join(properties, ", "),
)
}
}
return RawLevel{
Name: name,
Data: data,
Background: background,
Description: des,
MinMoves: minmoves,
}
return project
}

View File

@ -16,7 +16,7 @@ import (
// Maps image name to image data
type AssetRegistry map[string]*ebiten.Image
//go:embed levels/*.lvl sprites/*.png fonts/*.ttf
//go:embed sprites/*.png fonts/*.ttf levels/*.ldtk
var assetfs embed.FS
var Assets = LoadImages("sprites")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

View File

@ -0,0 +1,2 @@
#!/bin/sh
montage -tile 4x0 -geometry +0+0 block* collectible-orange.png obstacle-* sphere-blue* transwall.png map.png && okular map.png

BIN
assets/sprites/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -28,10 +28,11 @@ func NewLevelScene(game *Game, startlevel int) Scene {
func (scene *LevelScene) GenerateLevels(game *Game) {
min := []int{}
for _, level := range assets.Levels {
for _, level := range assets.Project.Levels {
level := level
scene.Levels = append(scene.Levels, NewLevel(game, 32, &level))
min = append(min, level.MinMoves)
scene.Levels = append(scene.Levels, NewLevel(game, 32, level))
level.PropertyByIdentifier("min-moves")
min = append(min, level.PropertyByIdentifier("min-moves").AsInt())
}
scene.Game.Observer.SetupLevelScore(min)

View File

@ -2,18 +2,17 @@ package game
import (
"image"
"log"
"log/slog"
"openquell/assets"
"openquell/components"
"openquell/grid"
"openquell/observers"
"openquell/systems"
"openquell/util"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/mlange-42/arche/ecs"
"github.com/solarlune/ldtkgo"
)
type Map map[image.Point]*assets.Tile
@ -31,13 +30,14 @@ type Level struct {
Grid *grid.Grid
}
func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
func NewLevel(game *Game, cellsize int, plan *ldtkgo.Level) *Level {
systemlist := []systems.System{}
gridcontainer := &grid.GridContainer{}
systemlist = append(systemlist,
systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize, plan.Background))
systems.NewGridSystem(game.World, game.ScreenWidth, game.ScreenHeight, cellsize,
assets.Assets[plan.PropertyByIdentifier("background").AsString()]))
systemlist = append(systemlist, systems.NewCollectibleSystem(game.World))
@ -63,8 +63,8 @@ func NewLevel(game *Game, cellsize int, plan *assets.RawLevel) *Level {
World: game.World,
Width: game.ScreenWidth,
Height: game.ScreenHeight,
Description: plan.Description,
Name: plan.Name,
Description: plan.PropertyByIdentifier("description").AsString(),
Name: plan.Identifier,
GridContainer: gridcontainer,
Systems: systemlist,
}
@ -113,25 +113,51 @@ func (level *Level) SetupGrid(game *Game) {
}
// parses a RawLevel and generates a mapslice from it, which is being used as grid
func LevelToSlice(game *Game, level *assets.RawLevel, tilesize int) (Map, Map) {
func LevelToSlice(game *Game, level *ldtkgo.Level, tilesize int) (Map, Map) {
size := game.ScreenWidth * game.ScreenHeight
mapslice := make(Map, size)
backupmap := make(Map, size)
for y, line := range strings.Split(string(level.Data), "\n") {
if len(line) != game.ScreenWidth/tilesize && y < game.ScreenHeight/tilesize {
log.Fatalf("line %d doesn't contain %d tiles, but %d",
y, game.ScreenWidth/tilesize, len(line))
}
for _, layer := range level.Layers {
switch layer.Type {
case ldtkgo.LayerTypeTile:
// load tile from LDTK tile layer, use sprites from associated map.
for x, char := range line {
if !util.Exists(assets.Tiles, byte(char)) {
log.Fatalf("unregistered tile type %c encountered", char)
if tiles := layer.AllTiles(); len(tiles) > 0 {
for _, tileData := range tiles {
// Subimage the Tile from the already loaded map,
// but referenced from LDTK file, that way we
// could use multiple tileset images
tile := assets.Tiles["default"]
tile.Sprite = assets.Assets[strings.TrimSuffix(layer.Tileset.Path, ".png")].SubImage(
image.Rect(tileData.Src[0],
tileData.Src[1],
tileData.Src[0]+layer.GridSize,
tileData.Src[1]+layer.GridSize)).(*ebiten.Image)
mapslice[image.Point{tileData.Position[0], tileData.Position[1]}] = tile
backupmap[image.Point{tileData.Position[0], tileData.Position[1]}] = tile.Clone()
}
}
tile := assets.Tiles[byte(char)]
mapslice[image.Point{x, y}] = tile
backupmap[image.Point{x, y}] = tile.Clone()
case ldtkgo.LayerTypeEntity:
// load mobile tiles (they call them entities) using static map map.png.
tileset := assets.Assets["map"]
for _, entity := range layer.Entities {
if entity.TileRect != nil {
tile := assets.Tiles[entity.Identifier]
tileRect := entity.TileRect
tile.Sprite = tileset.SubImage(
image.Rect(tileRect.X, tileRect.Y,
tileRect.X+tileRect.W,
tileRect.Y+tileRect.H)).(*ebiten.Image)
mapslice[image.Point{entity.Position[0], entity.Position[1]}] = tile
backupmap[image.Point{entity.Position[0], entity.Position[1]}] = tile.Clone()
}
}
}
}

4
go.mod
View File

@ -22,7 +22,11 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/solarlune/ldtkgo v0.9.4-0.20240310011150-66aa15c2ab56 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/tidwall/gjson v1.9.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tinne26/etxt v0.0.8 // indirect
github.com/tlinden/yadu v0.1.3 // indirect
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect

8
go.sum
View File

@ -253,6 +253,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/solarlune/ldtkgo v0.9.4-0.20240310011150-66aa15c2ab56 h1:QW8w9YQbIlIW053jM2SfBKAFGyd4maoq0AawZsb9rO4=
github.com/solarlune/ldtkgo v0.9.4-0.20240310011150-66aa15c2ab56/go.mod h1:PP4XtlnCSwWo7iexI/NJ3aS3INmiT42o066o6Dx52rs=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -262,6 +264,12 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/gjson v1.9.3 h1:hqzS9wAHMO+KVBBkLxYdkEeeFHuqr95GfClRLKlgK0E=
github.com/tidwall/gjson v1.9.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tinne26/etxt v0.0.8 h1:rjb58jkMkapRGLmhBMWnT76E/nMTXC5P1Q956BRZkoc=
github.com/tinne26/etxt v0.0.8/go.mod h1:QM/hlNkstsKC39elTFNKAR34xsMb9QoVosf+g9wlYxM=
github.com/tlinden/yadu v0.1.2 h1:TYYVnUJwziRJ9YPbIbRf9ikmDw0Q8Ifixm+J/kBQFh8=

View File

@ -158,7 +158,7 @@ func (grid *Grid) RemoveTile(point image.Point) {
}
func (grid *Grid) SetFloorTile(point image.Point) {
grid.Map[point] = assets.Tiles[' ']
grid.Map[point] = assets.Tiles["floor"]
}
func (grid *Grid) SetSolidTile(tile *assets.Tile, point image.Point) {

View File

@ -114,8 +114,8 @@ func (system *CollectibleSystem) AddParticle(position *components.Position) {
pos, particle, timer := ptmapper.Get(entity)
observer.AddEntity(entity, particleID)
particle.Index = assets.Tiles['*'].Particle
particle.Tiles = assets.Tiles['*'].Tiles
particle.Index = assets.Tiles["particle"].Particle
particle.Tiles = assets.Tiles["particle"].Tiles
pos.Update(
position.X-(16), // FIXME: use global tilesize!

View File

@ -8,16 +8,17 @@ import (
"github.com/hajimehoshi/ebiten/v2"
"github.com/mlange-42/arche/ecs"
"github.com/solarlune/ldtkgo"
)
type HudSystem struct {
World *ecs.World
Cellsize int
Observer *observers.GameObserver
Plan *assets.RawLevel
Plan *ldtkgo.Level
}
func NewHudSystem(world *ecs.World, plan *assets.RawLevel) System {
func NewHudSystem(world *ecs.World, plan *ldtkgo.Level) System {
system := &HudSystem{
Observer: observers.GetGameObserver(world),
World: world,
@ -45,13 +46,14 @@ func (system *HudSystem) Draw(screen *ebiten.Image) {
*/
score := fmt.Sprintf("Score: %d", system.Observer.GetScore())
level := fmt.Sprintf("Level %d %s", system.Plan.Number, system.Plan.Name)
level := fmt.Sprintf("Level %d %s", system.Plan.PropertyByIdentifier("level").AsInt(),
system.Plan.PropertyByIdentifier("name").AsString())
assets.FontRenderer.Renderer.SetSizePx(20)
assets.FontRenderer.Renderer.SetTarget(screen)
system.Print(score, 515, 22)
system.Print(system.Plan.Description, 10, 470)
system.Print(system.Plan.PropertyByIdentifier("description").AsString(), 10, 470)
system.Print(level, 10, 22)
}

View File

@ -2,7 +2,6 @@ package systems
import (
"log/slog"
"openquell/assets"
"openquell/components"
. "openquell/components"
. "openquell/config"
@ -146,33 +145,6 @@ func (system *ObstacleSystem) Draw(screen *ebiten.Image) {
}
}
func (system *ObstacleSystem) AddParticle(position *components.Position) {
observer := observers.GetGameObserver(system.World)
ptmapper := generic.NewMap3[
components.Position,
components.Particle,
components.Timer,
](system.World)
particleID := ecs.ComponentID[components.Particle](system.World)
entity := ptmapper.New()
pos, particle, timer := ptmapper.Get(entity)
observer.AddEntity(entity, particleID)
particle.Index = assets.Tiles['*'].Particle
particle.Tiles = assets.Tiles['*'].Tiles
pos.Update(
position.X-(16), // FIXME: use global tilesize!
position.Y-(16),
64,
)
timer.Start(PARTICLE_LOOPWAIT)
}
// return true if obstacle weapon points into the direction the player is moving
func CheckObstacleSide(playervelocity *Velocity, obsdirection int) bool {
movingdirection := playervelocity.InvertDirection()