From 001b67f97ad3370b23e7fd2ccb92cdcc24573785 Mon Sep 17 00:00:00 2001 From: Thomas von Dein Date: Tue, 2 Apr 2024 20:05:23 +0200 Subject: [PATCH] renamed particle* to animation*, added asesprite animation loading --- TODO.md | 11 + assets/levels/openquell.ldtk | 300 +++++++++++++++--- assets/loader-levels.go | 96 +++--- assets/loader-sprites.go | 122 +++++-- assets/sprites/collectible-detonating.json | 164 ++++++++++ assets/sprites/collectible-detonating.png | Bin 0 -> 6722 bytes components/animation.go | 11 + components/components.go | 6 - config/static.go | 10 +- game/levels.go | 19 +- observers/game_observer.go | 18 +- ...particle_system.go => animation_system.go} | 34 +- systems/collectible_system.go | 24 +- util/ldtkhelpers.go | 20 +- 14 files changed, 673 insertions(+), 162 deletions(-) create mode 100644 assets/sprites/collectible-detonating.json create mode 100644 assets/sprites/collectible-detonating.png create mode 100644 components/animation.go rename systems/{particle_system.go => animation_system.go} (55%) diff --git a/TODO.md b/TODO.md index 6f5a2f7..7226249 100644 --- a/TODO.md +++ b/TODO.md @@ -37,6 +37,17 @@ "collectible-detonating*.png" or we could use an animation map and specify a list of coordinates, etcpp. +- Part I of the above: DONE. +- Part II: + - grid/grid.go: + - add Animation component to mappers + - check if the tile has tile.AnimateOnDestruct (or any other + triggers) set and configure animation compontent accordingly + - check if the tile has tile.AnimationSpriteSheet, assign to Animation.Tiles[] + - do it for any entity not just collectible so that any entity can have an animation + - rename Animation.Tiles to Sprites + - change animation_system to use this stuff instead of any hardcoded values. + ## Collider Rework [abandoned: see branch collider-system, fails] diff --git a/assets/levels/openquell.ldtk b/assets/levels/openquell.ldtk index f0cf787..e2b46aa 100644 --- a/assets/levels/openquell.ldtk +++ b/assets/levels/openquell.ldtk @@ -11,7 +11,7 @@ "iid": "267e9380-d7b0-11ee-a97e-35bec9c19d52", "jsonVersion": "1.5.3", "appBuildId": 473703, - "nextUid": 76, + "nextUid": 78, "identifierStyle": "Capitalize", "toc": [], "worldLayout": "Free", @@ -215,7 +215,88 @@ "limitBehavior": "MoveLastOne", "pivotX": 0, "pivotY": 0, - "fieldDefs": [] + "fieldDefs": [ + { + "identifier": "AnimateOnDestruct", + "doc": null, + "__type": "Bool", + "uid": 76, + "type": "F_Bool", + "isArray": false, + "canBeNull": false, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": { + "id": "V_Bool", + "params": [ true ] + }, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + }, + { + "identifier": "AnimateSpriteSheet", + "doc": null, + "__type": "String", + "uid": 77, + "type": "F_String", + "isArray": false, + "canBeNull": true, + "arrayMinLength": null, + "arrayMaxLength": null, + "editorDisplayMode": "Hidden", + "editorDisplayScale": 1, + "editorDisplayPos": "Above", + "editorLinkStyle": "StraightArrow", + "editorDisplayColor": null, + "editorAlwaysShow": false, + "editorShowInWorld": true, + "editorCutLongValues": true, + "editorTextSuffix": null, + "editorTextPrefix": null, + "useForSmartColor": false, + "exportToToc": false, + "searchable": false, + "min": null, + "max": null, + "regex": null, + "acceptFileTypes": null, + "defaultOverride": { + "id": "V_String", + "params": ["collectible-detonating"] + }, + "textLanguageMode": null, + "symmetricalRef": false, + "autoChainRef": true, + "allowOutOfLevelRef": true, + "allowedRefs": "OnlySame", + "allowedRefsEntityUid": null, + "allowedRefTags": [], + "tilesetUid": null + } + ] }, { "identifier": "ObstacleWest", @@ -1429,7 +1510,10 @@ "height": 32, "defUid": 4, "px": [352,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 128, "__worldY": 224 }, @@ -1445,7 +1529,10 @@ "height": 32, "defUid": 4, "px": [288,160], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 64, "__worldY": 160 }, @@ -1461,7 +1548,10 @@ "height": 32, "defUid": 4, "px": [384,160], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 160, "__worldY": 160 } @@ -1613,7 +1703,10 @@ "height": 32, "defUid": 4, "px": [128,288], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 608, "__worldY": 288 }, @@ -1629,7 +1722,10 @@ "height": 32, "defUid": 4, "px": [480,192], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 960, "__worldY": 192 } @@ -1786,7 +1882,10 @@ "height": 32, "defUid": 4, "px": [96,160], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1280, "__worldY": 160 } @@ -1906,7 +2005,10 @@ "height": 32, "defUid": 4, "px": [448,128], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2336, "__worldY": 128 }, @@ -1922,7 +2024,10 @@ "height": 32, "defUid": 4, "px": [128,384], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2016, "__worldY": 384 } @@ -2095,7 +2200,10 @@ "height": 32, "defUid": 4, "px": [288,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 64, "__worldY": 768 }, @@ -2159,7 +2267,10 @@ "height": 32, "defUid": 4, "px": [352,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 128, "__worldY": 768 }, @@ -2349,7 +2460,10 @@ "height": 32, "defUid": 4, "px": [288,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 768, "__worldY": 768 }, @@ -2571,7 +2685,10 @@ "height": 32, "defUid": 4, "px": [96,160], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1280, "__worldY": 704 }, @@ -2619,7 +2736,10 @@ "height": 32, "defUid": 4, "px": [320,352], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1504, "__worldY": 896 }, @@ -2651,7 +2771,10 @@ "height": 32, "defUid": 4, "px": [128,128], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1312, "__worldY": 672 }, @@ -2831,7 +2954,10 @@ "height": 32, "defUid": 4, "px": [224,160], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2112, "__worldY": 704 }, @@ -2863,7 +2989,10 @@ "height": 32, "defUid": 4, "px": [416,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2304, "__worldY": 768 }, @@ -2911,7 +3040,10 @@ "height": 32, "defUid": 4, "px": [288,288], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2176, "__worldY": 832 }, @@ -3089,7 +3221,10 @@ "height": 32, "defUid": 4, "px": [160,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": -64, "__worldY": 1312 }, @@ -3105,7 +3240,10 @@ "height": 32, "defUid": 4, "px": [192,288], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": -32, "__worldY": 1376 }, @@ -3309,7 +3447,10 @@ "height": 32, "defUid": 4, "px": [352,288], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 832, "__worldY": 1376 }, @@ -3561,7 +3702,10 @@ "height": 32, "defUid": 4, "px": [416,288], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1600, "__worldY": 1376 }, @@ -3609,7 +3753,10 @@ "height": 32, "defUid": 4, "px": [416,160], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1600, "__worldY": 1248 }, @@ -3917,7 +4064,10 @@ "height": 32, "defUid": 4, "px": [192,256], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2080, "__worldY": 1344 }, @@ -4113,7 +4263,10 @@ "height": 32, "defUid": 4, "px": [320,192], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 96, "__worldY": 1824 }, @@ -4129,7 +4282,10 @@ "height": 32, "defUid": 4, "px": [288,256], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 64, "__worldY": 1888 }, @@ -4145,7 +4301,10 @@ "height": 32, "defUid": 4, "px": [352,256], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 128, "__worldY": 1888 }, @@ -4369,7 +4528,10 @@ "height": 32, "defUid": 4, "px": [160,320], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 640, "__worldY": 1952 }, @@ -4385,7 +4547,10 @@ "height": 32, "defUid": 4, "px": [192,320], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 672, "__worldY": 1952 }, @@ -4417,7 +4582,10 @@ "height": 32, "defUid": 4, "px": [416,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 896, "__worldY": 1856 }, @@ -4623,7 +4791,10 @@ "height": 32, "defUid": 4, "px": [480,320], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1664, "__worldY": 1952 }, @@ -4655,7 +4826,10 @@ "height": 32, "defUid": 4, "px": [288,192], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1472, "__worldY": 1824 }, @@ -4687,7 +4861,10 @@ "height": 32, "defUid": 4, "px": [352,128], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1536, "__worldY": 1760 }, @@ -4703,7 +4880,10 @@ "height": 32, "defUid": 4, "px": [192,288], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1376, "__worldY": 1920 }, @@ -5106,7 +5286,10 @@ "height": 32, "defUid": 4, "px": [224,192], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2112, "__worldY": 1824 } @@ -5269,7 +5452,10 @@ "height": 32, "defUid": 4, "px": [256,224], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 32, "__worldY": 2432 }, @@ -5525,7 +5711,10 @@ "height": 32, "defUid": 4, "px": [384,256], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 896, "__worldY": 2464 }, @@ -5605,7 +5794,10 @@ "height": 32, "defUid": 4, "px": [352,192], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 864, "__worldY": 2400 } @@ -5767,7 +5959,10 @@ "height": 32, "defUid": 4, "px": [320,256], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1536, "__worldY": 2464 }, @@ -5783,7 +5978,10 @@ "height": 32, "defUid": 4, "px": [224,128], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1440, "__worldY": 2336 }, @@ -5799,7 +5997,10 @@ "height": 32, "defUid": 4, "px": [384,320], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 1600, "__worldY": 2528 } @@ -5997,7 +6198,10 @@ "height": 32, "defUid": 4, "px": [416,352], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2304, "__worldY": 2560 }, @@ -6013,7 +6217,10 @@ "height": 32, "defUid": 4, "px": [160,128], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": 2048, "__worldY": 2336 }, @@ -6255,7 +6462,10 @@ "height": 32, "defUid": 4, "px": [160,160], - "fieldInstances": [], + "fieldInstances": [ + { "__identifier": "AnimateOnDestruct", "__type": "Bool", "__value": true, "__tile": null, "defUid": 76, "realEditorValues": [] }, + { "__identifier": "AnimateSpriteSheet", "__type": "String", "__value": "collectible-detonating", "__tile": null, "defUid": 77, "realEditorValues": [] } + ], "__worldX": -64, "__worldY": 2912 } diff --git a/assets/loader-levels.go b/assets/loader-levels.go index df6b5d4..0f539f9 100644 --- a/assets/loader-levels.go +++ b/assets/loader-levels.go @@ -17,53 +17,57 @@ var Tiles = InitTiles() // Tile: contains image, identifier (as used in level data) and // additional properties type Tile struct { - Id, Ref string - Sprite *ebiten.Image - ToggleSprite *ebiten.Image - Solid bool // wall brick - Player bool // player sphere - IsPrimary bool // primary player sphere - Renderable bool // visible, has sprite - Velocity bool // movable - Collectible bool // collectible, vanishes once collected - Transient bool // turns into brick wall when traversed - Destroyable bool // turns into empty floor when bumped into twice - Particle int // -1=unused, 0-3 = show image of slice - Tiles []*ebiten.Image // has N sprites - TileNames []string // same thing, only the names - Obstacle bool // is an obstacle/enemy - Direction int // obstacle business end shows into this direction - Shader *ebiten.Shader - Alpha *ebiten.Image - Bond bool // denotes an entity which can have a relation to another - Door bool // a door, can be manipulated by a switch - Switch bool // opens|closes a door + Id, Ref string + Sprite *ebiten.Image + ToggleSprite *ebiten.Image + Solid bool // wall brick + Player bool // player sphere + IsPrimary bool // primary player sphere + Renderable bool // visible, has sprite + Velocity bool // movable + Collectible bool // collectible, vanishes once collected + Transient bool // turns into brick wall when traversed + Destroyable bool // turns into empty floor when bumped into twice + Animation int // -1=unused, 0-3 = show image of slice + Tiles []*ebiten.Image // has N sprites + TileNames []string // same thing, only the names + Obstacle bool // is an obstacle/enemy + Direction int // obstacle business end shows into this direction + Shader *ebiten.Shader + Alpha *ebiten.Image + Bond bool // denotes an entity which can have a relation to another + Door bool // a door, can be manipulated by a switch + Switch bool // opens|closes a door + AnimateOnDestruct bool // wether to animate destruction + AnimationSpriteSheet AnimationSet // which sprites to use (refers to an entry in assets.Animations[name]) } func (tile *Tile) Clone() *Tile { newtile := &Tile{ - Id: tile.Id, - Ref: tile.Ref, - Sprite: tile.Sprite, - ToggleSprite: tile.ToggleSprite, - Solid: tile.Solid, - Player: tile.Player, - IsPrimary: tile.IsPrimary, - Renderable: tile.Renderable, - Velocity: tile.Velocity, - Collectible: tile.Collectible, - Transient: tile.Transient, - Destroyable: tile.Destroyable, - Particle: tile.Particle, - Tiles: tile.Tiles, - TileNames: tile.TileNames, - Obstacle: tile.Obstacle, - Direction: tile.Direction, - Alpha: tile.Alpha, - Shader: tile.Shader, - Bond: tile.Bond, - Door: tile.Door, - Switch: tile.Switch, + Id: tile.Id, + Ref: tile.Ref, + Sprite: tile.Sprite, + ToggleSprite: tile.ToggleSprite, + Solid: tile.Solid, + Player: tile.Player, + IsPrimary: tile.IsPrimary, + Renderable: tile.Renderable, + Velocity: tile.Velocity, + Collectible: tile.Collectible, + Transient: tile.Transient, + Destroyable: tile.Destroyable, + Animation: tile.Animation, + Tiles: tile.Tiles, + TileNames: tile.TileNames, + Obstacle: tile.Obstacle, + Direction: tile.Direction, + Alpha: tile.Alpha, + Shader: tile.Shader, + Bond: tile.Bond, + Door: tile.Door, + Switch: tile.Switch, + AnimateOnDestruct: tile.AnimateOnDestruct, + AnimationSpriteSheet: tile.AnimationSpriteSheet, } return newtile @@ -129,13 +133,13 @@ func NewTileObstacle(direction int) *Tile { } } -func NewTileParticle(class []string) *Tile { +func NewTileAnimation(class []string) *Tile { sprites := GetSprites(class) return &Tile{ Solid: false, Renderable: false, - Particle: 0, + Animation: 0, Tiles: sprites, } } @@ -192,7 +196,7 @@ func InitTiles() TileRegistry { "ObstacleSouth": NewTileObstacle(config.South), "ObstacleWest": NewTileObstacle(config.West), "ObstacleEast": NewTileObstacle(config.East), - "Particle": NewTileParticle([]string{ + "Animation": NewTileAnimation([]string{ "collectible-detonating1", "collectible-detonating2", "collectible-detonating3", diff --git a/assets/loader-sprites.go b/assets/loader-sprites.go index 933484c..c6dee07 100644 --- a/assets/loader-sprites.go +++ b/assets/loader-sprites.go @@ -1,7 +1,9 @@ package assets import ( + "bytes" "embed" + "encoding/json" "image" _ "image/png" "log" @@ -9,19 +11,55 @@ import ( "path" "strings" + "github.com/alecthomas/repr" "github.com/hajimehoshi/ebiten/v2" ) // Maps image name to image data type AssetRegistry map[string]*ebiten.Image -//go:embed sprites/*.png fonts/*.ttf levels/*.ldtk shaders/*.kg +type AnimationGeo struct { + X int `json:"x"` + Y int `json:"y"` + Width int `json:"w"` + Height int `json:"h"` +} + +type AnimationFrame struct { + Position AnimationGeo `json:"frame"` + // FIXME: maybe also add delay etc? might be cool to tweak these things from LDTK +} + +type AnimationMeta struct { + Name string `json:"image"` + Geo AnimationGeo `json:"size"` +} + +// Needed to thaw sprite set written by asesprite +type AnimationJSON struct { + Meta AnimationMeta `json:"Meta"` + Frames []AnimationFrame `json:"frames"` +} + +// Animation data +type AnimationSet struct { + Width, Height int + Sprites []*ebiten.Image + File string +} + +type AnimationRegistry map[string]AnimationSet + +//go:embed sprites/*.png fonts/*.ttf levels/*.ldtk shaders/*.kg sprites/*.json var assetfs embed.FS -var Assets = LoadImages("sprites") +var Assets, Animations = LoadImages() -func LoadImages(dir string) AssetRegistry { +func LoadImages() (AssetRegistry, AnimationRegistry) { + dir := "sprites" images := AssetRegistry{} + rawanimations := []AnimationJSON{} + animations := AnimationRegistry{} // we use embed.FS to iterate over all files in ./assets/ entries, err := assetfs.ReadDir(dir) @@ -31,23 +69,69 @@ func LoadImages(dir string) AssetRegistry { for _, imagefile := range entries { path := path.Join(dir, imagefile.Name()) - fd, err := assetfs.Open(path) - if err != nil { - log.Fatalf("failed to open image file %s: %s", imagefile.Name(), err) + + switch { + case strings.HasSuffix(path, ".png"): + fd, err := assetfs.Open(path) + if err != nil { + log.Fatalf("failed to open image file %s: %s", imagefile.Name(), err) + } + defer fd.Close() + + name := strings.TrimSuffix(imagefile.Name(), ".png") + + img, _, err := image.Decode(fd) + if err != nil { + log.Fatalf("failed to decode image %s: %s", imagefile.Name(), err) + } + + images[name] = ebiten.NewImageFromImage(img) + + slog.Debug("loaded asset", "path", path) + case strings.HasSuffix(path, ".json"): + fd, err := assetfs.Open(path) + if err != nil { + log.Fatalf("failed to open json file %s: %s", imagefile.Name(), err) + } + defer fd.Close() + + buf := new(bytes.Buffer) + buf.ReadFrom(fd) + + animationjson := AnimationJSON{} + + err = json.Unmarshal(buf.Bytes(), &animationjson) + if err != nil { + log.Fatalf("failed to parse JSON: %s", err) + } + + rawanimations = append(rawanimations, animationjson) } - defer fd.Close() - - name := strings.TrimSuffix(imagefile.Name(), ".png") - - img, _, err := image.Decode(fd) - if err != nil { - log.Fatalf("failed to decode image %s: %s", imagefile.Name(), err) - } - - images[name] = ebiten.NewImageFromImage(img) - - slog.Debug("loaded asset", "path", path) } - return images + // preprocess animation sprites + for _, animation := range rawanimations { + animationset := AnimationSet{} + + animationset.File = strings.TrimSuffix(animation.Meta.Name, ".png") + animationset.Width = animation.Meta.Geo.Width + animationset.Height = animation.Meta.Geo.Height + + for _, frame := range animation.Frames { + sprite := images[animationset.File].SubImage( + image.Rect( + frame.Position.X, + frame.Position.Y, + frame.Position.Width, + frame.Position.Height, + )).(*ebiten.Image) + + animationset.Sprites = append(animationset.Sprites, sprite) + } + + animations[animationset.File] = animationset + } + + repr.Println(animations) + return images, animations } diff --git a/assets/sprites/collectible-detonating.json b/assets/sprites/collectible-detonating.json new file mode 100644 index 0000000..b69bff9 --- /dev/null +++ b/assets/sprites/collectible-detonating.json @@ -0,0 +1,164 @@ +{ "frames": [ + { + "filename": "collectible-detonating 0.ase", + "frame": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 1.ase", + "frame": { "x": 64, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 2.ase", + "frame": { "x": 128, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 3.ase", + "frame": { "x": 192, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 4.ase", + "frame": { "x": 256, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 5.ase", + "frame": { "x": 320, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 6.ase", + "frame": { "x": 384, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 7.ase", + "frame": { "x": 448, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 8.ase", + "frame": { "x": 512, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 9.ase", + "frame": { "x": 576, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 10.ase", + "frame": { "x": 640, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 11.ase", + "frame": { "x": 704, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 12.ase", + "frame": { "x": 768, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 13.ase", + "frame": { "x": 832, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + }, + { + "filename": "collectible-detonating 14.ase", + "frame": { "x": 896, "y": 0, "w": 64, "h": 64 }, + "rotated": false, + "trimmed": false, + "spriteSourceSize": { "x": 0, "y": 0, "w": 64, "h": 64 }, + "sourceSize": { "w": 64, "h": 64 }, + "duration": 100 + } + ], + "meta": { + "app": "http://www.aseprite.org/", + "version": "1.x-dev", + "image": "collectible-detonating.png", + "format": "RGBA8888", + "size": { "w": 960, "h": 64 }, + "scale": "1", + "frameTags": [ + ], + "layers": [ + { "name": "Yellow Sphere", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 8", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 7", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 6", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 5", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 4", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 3", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 2", "opacity": 255, "blendMode": "normal" }, + { "name": "Layer 1", "opacity": 255, "blendMode": "normal" }, + { "name": "Blitz Outer", "opacity": 70, "blendMode": "normal" }, + { "name": "Blitz Middle", "opacity": 84, "blendMode": "normal" }, + { "name": "Blitz Inner", "opacity": 97, "blendMode": "normal" } + ], + "slices": [ + ] + } +} diff --git a/assets/sprites/collectible-detonating.png b/assets/sprites/collectible-detonating.png new file mode 100644 index 0000000000000000000000000000000000000000..77284840720de3637bfe44ab75f735b98eb3d5c0 GIT binary patch literal 6722 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 literal 0 HcmV?d00001 diff --git a/components/animation.go b/components/animation.go new file mode 100644 index 0000000..b26868a --- /dev/null +++ b/components/animation.go @@ -0,0 +1,11 @@ +package components + +import "github.com/hajimehoshi/ebiten/v2" + +type Animation struct { + Show bool + Index int + Loop bool + Tiles []*ebiten.Image + Width, Height int // single sprite measurements +} diff --git a/components/components.go b/components/components.go index f101b13..a451193 100644 --- a/components/components.go +++ b/components/components.go @@ -14,12 +14,6 @@ type Renderable struct { Shader *ebiten.Shader } -type Particle struct { - Show bool - Index int - Tiles []*ebiten.Image -} - // only tile entities will have those type Tilish struct{} type Solid struct{} diff --git a/config/static.go b/config/static.go index a5d8f62..e4bcfc0 100644 --- a/config/static.go +++ b/config/static.go @@ -14,11 +14,11 @@ const ( ) const ( - PLAYERSPEED int = 5 - PARTICLE_STARTWAIT time.Duration = 30 * time.Millisecond // how long to wait to start collectible particle - PARTICLE_LOOPWAIT time.Duration = 40 * time.Millisecond // how much time to wait between the sprites - LEVEL_END_WAIT time.Duration = 500 * time.Millisecond - version string = "1.2.3" + PLAYERSPEED int = 5 + ANIMATION_STARTWAIT time.Duration = 30 * time.Millisecond // how long to wait to start collectible animation + ANIMATION_LOOPWAIT time.Duration = 40 * time.Millisecond // how much time to wait between the sprites + LEVEL_END_WAIT time.Duration = 500 * time.Millisecond + version string = "1.2.3" MenuRectX int = 600 MenuRectY int = 0 diff --git a/game/levels.go b/game/levels.go index c698755..1ee2a71 100644 --- a/game/levels.go +++ b/game/levels.go @@ -2,6 +2,7 @@ package game import ( "image" + "log" "log/slog" "openquell/assets" "openquell/components" @@ -51,7 +52,7 @@ func NewLevel(game *Game, cellsize int, plan *ldtkgo.Level) *Level { systemlist = append(systemlist, systems.NewPlayerSystem(game.World, gridcontainer, game.ScreenWidth, game.ScreenHeight)) - systemlist = append(systemlist, systems.NewParticleSystem(game.World, game.Cellsize)) + systemlist = append(systemlist, systems.NewAnimationSystem(game.World, game.Cellsize)) systemlist = append(systemlist, systems.NewTransientSystem(game.World, gridcontainer)) @@ -197,6 +198,22 @@ func LevelToSlice(game *Game, level *ldtkgo.Level, tilesize int) (Map, Map) { } tileRect := entity.TileRect + animateondestruct := util.GetPropertyBool(entity, "AnimateOnDestruct") + // FIXME: also check for AnimationLoop and other animation reasons + if animateondestruct { + tile.AnimateOnDestruct = true + + animation := util.GetPropertyString(entity, "AnimateSpriteSheet") + if animation != "" { + if !util.Exists(assets.Animations, animation) { + log.Fatalf("entity %s refers to non existent animation set %s", + entity.Identifier, animation) + } + } + + tile.AnimationSpriteSheet = assets.Animations[animation] + } + tile.Sprite = tileset.SubImage( image.Rect(tileRect.X, tileRect.Y, tileRect.X+tileRect.W, diff --git a/observers/game_observer.go b/observers/game_observer.go index 9427fed..7a48e42 100644 --- a/observers/game_observer.go +++ b/observers/game_observer.go @@ -57,16 +57,16 @@ func NewGameObserver( playerID := ecs.ComponentID[components.Player](world) obstacleID := ecs.ComponentID[components.Obstacle](world) - particleID := ecs.ComponentID[components.Particle](world) + animationID := ecs.ComponentID[components.Animation](world) playerListener := observer.GetListenerCallback(playerID) obstacleListener := observer.GetListenerCallback(obstacleID) - particleListener := observer.GetListenerCallback(particleID) + animationListener := observer.GetListenerCallback(animationID) listen := listener.NewDispatch( &playerListener, &obstacleListener, - &particleListener, + &animationListener, ) world.SetListener(&listen) @@ -74,7 +74,7 @@ func NewGameObserver( observer.Entities = make(map[ecs.ID]map[ecs.Entity]int) observer.Entities[playerID] = make(map[ecs.Entity]int) observer.Entities[obstacleID] = make(map[ecs.Entity]int) - observer.Entities[particleID] = make(map[ecs.Entity]int) + observer.Entities[animationID] = make(map[ecs.Entity]int) resmanger := generic.NewResource[GameObserver](world) resmanger.Add(observer) @@ -119,20 +119,20 @@ func (observer *GameObserver) GetObstacles() []ecs.Entity { return observer.GetEntities(obstacleID) } -func (observer *GameObserver) GetParticles() []ecs.Entity { - particleID := ecs.ComponentID[components.Particle](observer.World) - return observer.GetEntities(particleID) +func (observer *GameObserver) GetAnimations() []ecs.Entity { + animationID := ecs.ComponentID[components.Animation](observer.World) + return observer.GetEntities(animationID) } func (observer *GameObserver) RemoveEntities() { playerID := ecs.ComponentID[components.Player](observer.World) obstacleID := ecs.ComponentID[components.Obstacle](observer.World) - particleID := ecs.ComponentID[components.Particle](observer.World) + animationID := ecs.ComponentID[components.Animation](observer.World) observer.Entities = make(map[ecs.ID]map[ecs.Entity]int) observer.Entities[playerID] = make(map[ecs.Entity]int) observer.Entities[obstacleID] = make(map[ecs.Entity]int) - observer.Entities[particleID] = make(map[ecs.Entity]int) + observer.Entities[animationID] = make(map[ecs.Entity]int) } func (observer *GameObserver) SetupLevelScore(min []int) { diff --git a/systems/particle_system.go b/systems/animation_system.go similarity index 55% rename from systems/particle_system.go rename to systems/animation_system.go index 2c7dcc7..7566ea2 100644 --- a/systems/particle_system.go +++ b/systems/animation_system.go @@ -9,15 +9,15 @@ import ( "github.com/mlange-42/arche/generic" ) -type ParticleSystem struct { +type AnimationSystem struct { World *ecs.World - Selector *generic.Filter3[Position, Particle, Timer] + Selector *generic.Filter3[Position, Animation, Timer] Cellsize int } -func NewParticleSystem(world *ecs.World, cellsize int) System { - system := &ParticleSystem{ - Selector: generic.NewFilter3[Position, Particle, Timer](), +func NewAnimationSystem(world *ecs.World, cellsize int) System { + system := &AnimationSystem{ + Selector: generic.NewFilter3[Position, Animation, Timer](), World: world, Cellsize: cellsize, } @@ -25,7 +25,7 @@ func NewParticleSystem(world *ecs.World, cellsize int) System { return system } -func (system *ParticleSystem) Update() error { +func (system *AnimationSystem) Update() error { // display debris after collecting EntitiesToRemove := []ecs.Entity{} @@ -33,16 +33,16 @@ func (system *ParticleSystem) Update() error { for query.Next() { // we loop, but it's only one anyway - _, particle, timer := query.Get() + _, animation, timer := query.Get() - particle.Show = true + animation.Show = true if timer.IsReady() { switch { - // particle shows from earlier tick, animate - case particle.Index > -1 && particle.Index < len(particle.Tiles)-1: - particle.Index++ - timer.Start(config.PARTICLE_LOOPWAIT) + // animation shows from earlier tick, animate + case animation.Index > -1 && animation.Index < len(animation.Tiles)-1: + animation.Index++ + timer.Start(config.ANIMATION_LOOPWAIT) default: // last sprite reached, remove it EntitiesToRemove = append(EntitiesToRemove, query.Entity()) @@ -60,18 +60,18 @@ func (system *ParticleSystem) Update() error { return nil } -func (system *ParticleSystem) Draw(screen *ebiten.Image) { - // write particles (these are no tiles!) +func (system *AnimationSystem) Draw(screen *ebiten.Image) { + // write animations (these are no tiles!) op := &ebiten.DrawImageOptions{} query := system.Selector.Query(system.World) for query.Next() { - pos, particle, _ := query.Get() + pos, animation, _ := query.Get() - if particle.Show { + if animation.Show { op.GeoM.Reset() op.GeoM.Translate(float64(pos.X), float64(pos.Y)) - screen.DrawImage(particle.Tiles[particle.Index], op) + screen.DrawImage(animation.Tiles[animation.Index], op) } } } diff --git a/systems/collectible_system.go b/systems/collectible_system.go index 40c5761..c06fe9a 100644 --- a/systems/collectible_system.go +++ b/systems/collectible_system.go @@ -33,7 +33,7 @@ func (system *CollectibleSystem) Update() error { posID := ecs.ComponentID[components.Position](system.World) veloID := ecs.ComponentID[components.Velocity](system.World) - particlepositions := []*components.Position{} + animationpositions := []*components.Position{} EntitiesToRemove := []ecs.Entity{} query := system.Selector.Query(system.World) @@ -58,14 +58,14 @@ func (system *CollectibleSystem) Update() error { ok, _ := colposition.Intersects(playerposition, playervelocity) if ok { //slog.Debug("bumped into collectible", "collectible", collectible) - particlepositions = append(particlepositions, colposition) + animationpositions = append(animationpositions, colposition) EntitiesToRemove = append(EntitiesToRemove, query.Entity()) } } } - for _, pos := range particlepositions { - system.AddParticle(pos) + for _, pos := range animationpositions { + system.AddAnimation(pos) } for _, entity := range EntitiesToRemove { @@ -99,23 +99,23 @@ func (system *CollectibleSystem) Draw(screen *ebiten.Image) { } } -func (system *CollectibleSystem) AddParticle(position *components.Position) { +func (system *CollectibleSystem) AddAnimation(position *components.Position) { observer := observers.GetGameObserver(system.World) ptmapper := generic.NewMap3[ components.Position, - components.Particle, + components.Animation, components.Timer, ](system.World) - particleID := ecs.ComponentID[components.Particle](system.World) + animationID := ecs.ComponentID[components.Animation](system.World) entity := ptmapper.New() - pos, particle, timer := ptmapper.Get(entity) - observer.AddEntity(entity, particleID) + pos, animation, timer := ptmapper.Get(entity) + observer.AddEntity(entity, animationID) - particle.Index = assets.Tiles["Particle"].Particle - particle.Tiles = assets.Tiles["Particle"].Tiles + animation.Index = assets.Tiles["Animation"].Animation + animation.Tiles = assets.Tiles["Animation"].Tiles pos.Update( position.X-(16), // FIXME: use global tilesize! @@ -123,5 +123,5 @@ func (system *CollectibleSystem) AddParticle(position *components.Position) { 64, ) - timer.Start(config.PARTICLE_STARTWAIT) + timer.Start(config.ANIMATION_STARTWAIT) } diff --git a/util/ldtkhelpers.go b/util/ldtkhelpers.go index ee409f1..08a2303 100644 --- a/util/ldtkhelpers.go +++ b/util/ldtkhelpers.go @@ -11,7 +11,6 @@ type TileSetSubRect struct { } func Map2Subrect(raw map[string]any) *TileSetSubRect { - // we need to translate this map for less typing return &TileSetSubRect{ W: int(raw["w"].(float64)), @@ -37,8 +36,25 @@ func GetPropertyRef(entity *ldtkgo.Entity) string { return "" } -func GetPropertyToggleTile(entity *ldtkgo.Entity) *TileSetSubRect { +func GetPropertyString(entity *ldtkgo.Entity, property string) string { + ref := entity.PropertyByIdentifier(property) + if ref != nil { + return ref.AsString() + } + return "" +} + +func GetPropertyBool(entity *ldtkgo.Entity, property string) bool { + ref := entity.PropertyByIdentifier(property) + if ref != nil { + return ref.AsBool() + } + + return false +} + +func GetPropertyToggleTile(entity *ldtkgo.Entity) *TileSetSubRect { ref := entity.PropertyByIdentifier(config.LDTK_Toggle_Tile) if ref != nil { return Map2Subrect(ref.AsMap())