├── media ├── logo.png └── custom_types.png ├── resources ├── maps_list.json ├── tilesets_list.json ├── panthera_atlases │ └── game.json ├── animations │ ├── button.json │ ├── health_visual_circle.json │ ├── health_visual_rectangle.json │ └── gui_main.json └── components.json ├── tiled ├── images │ ├── pit.png │ ├── wall.png │ ├── enemy.png │ ├── explosion.png │ ├── wall_pit.png │ ├── bullet_sniper.png │ ├── ui_circle_32.png │ ├── ui_circle_64.png │ ├── arena_background.png │ └── enemy_rectangle.png └── game_shooting_circle.tiled-session ├── assets ├── fonts │ ├── troika.otf │ └── text.font ├── images │ ├── empty.png │ ├── pixel.png │ ├── button.png │ ├── icon_arrow.png │ ├── ui_circle_16.png │ ├── ui_circle_32.png │ ├── ui_circle_64.png │ ├── ui_circle_8.png │ ├── enemy_rectangle.png │ └── arena_background.png ├── sounds │ ├── explosion.wav │ └── laser_shoot.wav └── atlases │ └── game_shooting_circle.atlas ├── docs ├── archive │ ├── game0.arcd │ ├── game0.arci │ ├── game0.dmanifest │ ├── game0.public.der │ └── archive_files.json └── ShootingCircles.wasm ├── .github └── FUNDING.yml ├── test ├── test.gui ├── test.ini ├── test.collection └── test.gui_script ├── game ├── spawner │ ├── spawner.collection │ └── spawner.go ├── misc │ ├── game.display_profiles │ ├── game.texture_profiles │ ├── sprite.fp │ ├── particlefx.material │ ├── sprite.vp │ ├── sprite.material │ └── game.appmanifest ├── objects │ ├── explosion │ │ ├── explosion.collection │ │ └── explosion_enemy.particlefx │ ├── circle.collection │ ├── wall │ │ ├── wall_pit.collection │ │ └── wall.collection │ ├── damage_number │ │ └── damage_number.collection │ ├── background │ │ └── background.collection │ ├── player │ │ └── player.collection │ ├── bullet │ │ ├── bullet.collection │ │ ├── bullet_shotgun.collection │ │ └── rocket.collection │ ├── pit │ │ └── pit.collection │ └── enemy │ │ ├── enemy.collection │ │ └── enemy_big.collection └── game.collection ├── gui ├── gui_main │ ├── gui_main.collection │ ├── button.gui │ ├── gui_main_command.lua │ ├── gui_main.lua │ └── gui_main.gui_script └── bindings.lua ├── .gitignore ├── decore ├── decore_data.lua ├── templates │ ├── system_template.lua │ ├── system_event_template.lua │ └── system_command_template.lua └── decore_internal.lua ├── systems ├── death │ ├── death.lua │ └── death_command.lua ├── input │ ├── input.lua │ └── input_event.lua ├── health │ ├── health_event.lua │ ├── health_command.lua │ ├── health.lua │ └── test_health.lua ├── color │ ├── color_event.lua │ ├── color_command.lua │ └── color.lua ├── target_tracker │ ├── target_tracker_event.lua │ ├── target_tracker.lua │ └── on_target_count_command │ │ └── on_target_count_command.lua ├── health_circle_visual │ ├── health_circle_visual.lua │ └── health_circle_visual_command.lua ├── remove_with_delay │ └── remove_with_delay.lua ├── play_fx_on_remove │ └── play_fx_on_remove.lua ├── on_key_released │ ├── on_key_released_command.lua │ └── on_key_released.lua ├── collision │ ├── collision_event.lua │ └── on_collision │ │ ├── on_collision_remove.lua │ │ └── on_collision_damage.lua ├── on_spawn_command │ └── on_spawn_command.lua ├── debug │ ├── debug.lua │ └── debug_command.lua ├── transform │ ├── transform_event.lua │ ├── transform.lua │ └── transform_command.lua ├── physics │ ├── physics_command.lua │ └── physics.lua ├── explosion │ ├── explosion.lua │ └── explosion_command.lua ├── acceleration │ └── acceleration.lua ├── window │ └── window_event.lua ├── level_loader │ ├── level_loader_command.lua │ └── level_loader.lua ├── physics_movement │ ├── physics_movement_command.lua │ └── physics_movement.lua ├── movement_controller │ ├── movement_controller.lua │ └── movement_controller_command.lua ├── shooter_controller │ └── shooter_controller_command.lua ├── game_object │ ├── game_object_command.lua │ └── game_object.lua ├── panthera │ ├── panthera_command.lua │ └── panthera_decore.lua └── damage_number │ └── damage_number.lua ├── loader ├── loader.collection └── loader.script ├── annotations ├── annotations.lua ├── defold_annotations │ ├── camera.lua │ ├── zlib.lua │ ├── b2d.lua │ ├── label.lua │ ├── builtins.lua │ ├── json.lua │ ├── html5.lua │ ├── collectionproxy.lua │ ├── http.lua │ ├── msg.lua │ ├── sprite.lua │ ├── defold_types.lua │ ├── image.lua │ ├── timer.lua │ └── factory.lua └── event_annotations.lua ├── settings_deployer ├── .gitattributes ├── detiled ├── detiled.lua └── annotations.lua ├── .vscode └── settings.json ├── input └── game.input_binding └── game.project /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/media/logo.png -------------------------------------------------------------------------------- /resources/maps_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "tiled": { 3 | "game": "/resources/maps/game.json" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /tiled/images/pit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/pit.png -------------------------------------------------------------------------------- /tiled/images/wall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/wall.png -------------------------------------------------------------------------------- /assets/fonts/troika.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/fonts/troika.otf -------------------------------------------------------------------------------- /assets/images/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/empty.png -------------------------------------------------------------------------------- /assets/images/pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/pixel.png -------------------------------------------------------------------------------- /docs/archive/game0.arcd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/docs/archive/game0.arcd -------------------------------------------------------------------------------- /docs/archive/game0.arci: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/docs/archive/game0.arci -------------------------------------------------------------------------------- /media/custom_types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/media/custom_types.png -------------------------------------------------------------------------------- /tiled/images/enemy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/enemy.png -------------------------------------------------------------------------------- /assets/images/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/button.png -------------------------------------------------------------------------------- /docs/ShootingCircles.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/docs/ShootingCircles.wasm -------------------------------------------------------------------------------- /tiled/images/explosion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/explosion.png -------------------------------------------------------------------------------- /tiled/images/wall_pit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/wall_pit.png -------------------------------------------------------------------------------- /assets/images/icon_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/icon_arrow.png -------------------------------------------------------------------------------- /assets/sounds/explosion.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/sounds/explosion.wav -------------------------------------------------------------------------------- /docs/archive/game0.dmanifest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/docs/archive/game0.dmanifest -------------------------------------------------------------------------------- /resources/tilesets_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "tilesets": [ 3 | "/resources/tilesets/shooting_circle.json" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /assets/images/ui_circle_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/ui_circle_16.png -------------------------------------------------------------------------------- /assets/images/ui_circle_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/ui_circle_32.png -------------------------------------------------------------------------------- /assets/images/ui_circle_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/ui_circle_64.png -------------------------------------------------------------------------------- /assets/images/ui_circle_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/ui_circle_8.png -------------------------------------------------------------------------------- /assets/sounds/laser_shoot.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/sounds/laser_shoot.wav -------------------------------------------------------------------------------- /docs/archive/game0.public.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/docs/archive/game0.public.der -------------------------------------------------------------------------------- /tiled/images/bullet_sniper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/bullet_sniper.png -------------------------------------------------------------------------------- /tiled/images/ui_circle_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/ui_circle_32.png -------------------------------------------------------------------------------- /tiled/images/ui_circle_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/ui_circle_64.png -------------------------------------------------------------------------------- /assets/images/enemy_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/enemy_rectangle.png -------------------------------------------------------------------------------- /tiled/images/arena_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/arena_background.png -------------------------------------------------------------------------------- /tiled/images/enemy_rectangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/tiled/images/enemy_rectangle.png -------------------------------------------------------------------------------- /assets/images/arena_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Insality/shooting_circles/HEAD/assets/images/arena_background.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: insality 4 | ko_fi: insality 5 | buy_me_a_coffee: insality 6 | -------------------------------------------------------------------------------- /test/test.gui: -------------------------------------------------------------------------------- 1 | script: "/test/test.gui_script" 2 | material: "/builtins/materials/gui.material" 3 | adjust_reference: ADJUST_REFERENCE_PARENT 4 | -------------------------------------------------------------------------------- /test/test.ini: -------------------------------------------------------------------------------- 1 | [bootstrap] 2 | main_collection = /test/test.collectionc 3 | 4 | [display] 5 | height = 256 6 | width = 256 7 | 8 | [test] 9 | report = 1 -------------------------------------------------------------------------------- /game/spawner/spawner.collection: -------------------------------------------------------------------------------- 1 | name: "spawner" 2 | instances { 3 | id: "spawner" 4 | prototype: "/game/spawner/spawner.go" 5 | } 6 | scale_along_z: 0 7 | -------------------------------------------------------------------------------- /test/test.collection: -------------------------------------------------------------------------------- 1 | name: "default" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "test" 5 | data: "components {\n" 6 | " id: \"test\"\n" 7 | " component: \"/test/test.gui\"\n" 8 | "}\n" 9 | "" 10 | } 11 | -------------------------------------------------------------------------------- /gui/gui_main/gui_main.collection: -------------------------------------------------------------------------------- 1 | name: "gui_main" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "components {\n" 6 | " id: \"gui_main\"\n" 7 | " component: \"/gui/gui_main/gui_main.gui\"\n" 8 | "}\n" 9 | "" 10 | } 11 | -------------------------------------------------------------------------------- /game/misc/game.display_profiles: -------------------------------------------------------------------------------- 1 | profiles { 2 | name: "Landscape" 3 | qualifiers { 4 | width: 1920 5 | height: 1080 6 | } 7 | } 8 | profiles { 9 | name: "Portrait" 10 | qualifiers { 11 | width: 1080 12 | height: 1920 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/test.gui_script: -------------------------------------------------------------------------------- 1 | local deftest = require("deftest.deftest") 2 | 3 | 4 | function init() 5 | deftest.add(require("systems.health.test_health")) 6 | 7 | local is_report = sys.get_config_string("test.report") == "1" 8 | deftest.run({ coverage = { enabled = is_report } }) 9 | end 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.internal 2 | /build 3 | .externalToolBuilders 4 | .DS_Store 5 | Thumbs.db 6 | .lock-wscript 7 | *.pyc 8 | .project 9 | .cproject 10 | builtins 11 | deployer_version_settings.txt 12 | 13 | .cache_deployer 14 | dist 15 | manifest.private.der 16 | manifest.public.der 17 | deployer_build_stats.csv -------------------------------------------------------------------------------- /game/misc/game.texture_profiles: -------------------------------------------------------------------------------- 1 | path_settings { 2 | path: "**" 3 | profile: "Default" 4 | } 5 | profiles { 6 | name: "Default" 7 | platforms { 8 | os: OS_ID_GENERIC 9 | formats { 10 | format: TEXTURE_FORMAT_RGBA 11 | compression_level: BEST 12 | } 13 | mipmaps: false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /game/objects/explosion/explosion.collection: -------------------------------------------------------------------------------- 1 | name: "circle" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "components {\n" 6 | " id: \"explosion\"\n" 7 | " component: \"/game/objects/explosion/explosion.particlefx\"\n" 8 | "}\n" 9 | "" 10 | position { 11 | z: 2.0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /decore/decore_data.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | ---@type table> 4 | M.entities = {} 5 | M.entities_order = {} 6 | 7 | ---@type table> 8 | M.components = {} 9 | M.components_order = {} 10 | 11 | ---@type table> 12 | M.worlds = {} 13 | M.worlds_order = {} 14 | 15 | return M 16 | -------------------------------------------------------------------------------- /game/misc/sprite.fp: -------------------------------------------------------------------------------- 1 | varying mediump vec2 var_texcoord0; 2 | varying mediump vec4 var_color0; 3 | 4 | uniform lowp sampler2D texture_sampler; 5 | 6 | void main() 7 | { 8 | // Pre-multiply alpha since all runtime textures already are 9 | lowp vec4 color_pm = vec4(var_color0.xyz * var_color0.w, var_color0.w); 10 | gl_FragColor = texture2D(texture_sampler, var_texcoord0.xy) * color_pm; 11 | } 12 | -------------------------------------------------------------------------------- /game/misc/particlefx.material: -------------------------------------------------------------------------------- 1 | name: "particle" 2 | tags: "tile" 3 | vertex_program: "/builtins/materials/particlefx.vp" 4 | fragment_program: "/builtins/materials/particlefx.fp" 5 | vertex_constants { 6 | name: "view_proj" 7 | type: CONSTANT_TYPE_VIEWPROJ 8 | } 9 | fragment_constants { 10 | name: "tint" 11 | type: CONSTANT_TYPE_USER 12 | value { 13 | x: 1.0 14 | y: 1.0 15 | z: 1.0 16 | w: 1.0 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /game/misc/sprite.vp: -------------------------------------------------------------------------------- 1 | uniform highp mat4 view_proj; 2 | 3 | // positions are in world space 4 | attribute highp vec4 position; 5 | attribute mediump vec2 texcoord0; 6 | attribute mediump vec4 color; 7 | 8 | varying mediump vec2 var_texcoord0; 9 | varying mediump vec4 var_color0; 10 | 11 | void main() 12 | { 13 | var_color0 = color; 14 | var_texcoord0 = texcoord0; 15 | gl_Position = view_proj * vec4(position.xyz, 1.0); 16 | } 17 | -------------------------------------------------------------------------------- /docs/archive/archive_files.json: -------------------------------------------------------------------------------- 1 | {"content":[{"name":"game.projectc","size":4321,"pieces":[{"name":"game0.projectc","offset":0}]},{"name":"game.arci","size":15728,"pieces":[{"name":"game0.arci","offset":0}]},{"name":"game.arcd","size":309720,"pieces":[{"name":"game0.arcd","offset":0}]},{"name":"game.dmanifest","size":16524,"pieces":[{"name":"game0.dmanifest","offset":0}]},{"name":"game.public.der","size":162,"pieces":[{"name":"game0.public.der","offset":0}]}],"total_size":346455} -------------------------------------------------------------------------------- /systems/death/death.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local death_command = require("systems.death.death_command") 4 | 5 | ---@class entity 6 | 7 | ---@class system.death: system 8 | local M = {} 9 | 10 | 11 | ---@static 12 | ---@return system.death, system.death_command 13 | function M.create_system() 14 | local system = setmetatable(ecs.system(), { __index = M }) 15 | 16 | return system, death_command.create_system(system) 17 | end 18 | 19 | 20 | return M 21 | -------------------------------------------------------------------------------- /loader/loader.collection: -------------------------------------------------------------------------------- 1 | name: "loader" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "system" 5 | data: "components {\n" 6 | " id: \"loader\"\n" 7 | " component: \"/loader/loader.script\"\n" 8 | "}\n" 9 | "embedded_components {\n" 10 | " id: \"game_proxy\"\n" 11 | " type: \"collectionproxy\"\n" 12 | " data: \"collection: \\\"/game/game.collection\\\"\\n" 13 | "\"\n" 14 | "}\n" 15 | "" 16 | } 17 | embedded_instances { 18 | id: "go" 19 | data: "" 20 | } 21 | -------------------------------------------------------------------------------- /game/misc/sprite.material: -------------------------------------------------------------------------------- 1 | name: "sprite" 2 | tags: "tile" 3 | vertex_program: "/game/misc/sprite.vp" 4 | fragment_program: "/game/misc/sprite.fp" 5 | vertex_constants { 6 | name: "view_proj" 7 | type: CONSTANT_TYPE_VIEWPROJ 8 | } 9 | samplers { 10 | name: "texture_sampler" 11 | wrap_u: WRAP_MODE_CLAMP_TO_EDGE 12 | wrap_v: WRAP_MODE_CLAMP_TO_EDGE 13 | filter_min: FILTER_MODE_MIN_DEFAULT 14 | filter_mag: FILTER_MODE_MAG_DEFAULT 15 | } 16 | attributes { 17 | name: "color" 18 | semantic_type: SEMANTIC_TYPE_COLOR 19 | element_count: 4 20 | double_values { 21 | v: 1.0 22 | v: 1.0 23 | v: 1.0 24 | v: 1.0 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /annotations/annotations.lua: -------------------------------------------------------------------------------- 1 | ---Download Defold annotations from here: https://github.com/astrochili/defold-annotations/releases/ 2 | 3 | ---@class log 4 | ---@field get_logger fun(name: string, force_debug_level: string|nil): logger 5 | 6 | ---@class logger 7 | ---@field trace fun(logger: logger, message: string, context:any) 8 | ---@field debug fun(logger: logger, message: string, context:any) 9 | ---@field info fun(logger: logger, message: string, context:any) 10 | ---@field warn fun(logger: logger, message: string, context:any) 11 | ---@field error fun(logger: logger, message: string, context:any) 12 | 13 | ---@class entities 14 | ---@field on_action_window boolean 15 | -------------------------------------------------------------------------------- /systems/input/input.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local system_input_event = require("systems.input.input_event") 4 | 5 | ---@class system.input: system 6 | ---@field entities entity[] 7 | local M = {} 8 | 9 | 10 | ---@static 11 | ---@return system.input, system.input_event 12 | function M.create_system() 13 | local system = setmetatable(ecs.processingSystem(), { __index = M }) 14 | system.id = "input" 15 | 16 | return system, system_input_event.create_system() 17 | end 18 | 19 | 20 | function M:onAddToWorld() 21 | msg.post(".", "acquire_input_focus") 22 | end 23 | 24 | 25 | function M:onRemoveFromWorld() 26 | msg.post(".", "release_input_focus") 27 | end 28 | 29 | 30 | return M 31 | -------------------------------------------------------------------------------- /annotations/defold_annotations/camera.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.7.0 4 | 5 | Camera API documentation 6 | 7 | Messages to control camera components and camera focus. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---@class camera 16 | camera = {} 17 | 18 | ---makes camera active 19 | ---@param url string|hash|url url of camera component 20 | function camera.acquire_focus(url) end 21 | 22 | ---deactivate camera 23 | ---@param url string|hash|url url of camera component 24 | function camera.release_focus(url) end 25 | 26 | return camera -------------------------------------------------------------------------------- /assets/atlases/game_shooting_circle.atlas: -------------------------------------------------------------------------------- 1 | images { 2 | image: "/assets/images/ui_circle_8.png" 3 | } 4 | images { 5 | image: "/assets/images/ui_circle_16.png" 6 | } 7 | images { 8 | image: "/assets/images/ui_circle_32.png" 9 | } 10 | images { 11 | image: "/assets/images/ui_circle_64.png" 12 | } 13 | images { 14 | image: "/assets/images/arena_background.png" 15 | } 16 | images { 17 | image: "/tiled/images/wall.png" 18 | } 19 | images { 20 | image: "/assets/images/button.png" 21 | } 22 | images { 23 | image: "/assets/images/icon_arrow.png" 24 | } 25 | images { 26 | image: "/assets/images/empty.png" 27 | } 28 | images { 29 | image: "/assets/images/pixel.png" 30 | } 31 | images { 32 | image: "/assets/images/enemy_rectangle.png" 33 | } 34 | extrude_borders: 2 35 | -------------------------------------------------------------------------------- /game/objects/circle.collection: -------------------------------------------------------------------------------- 1 | name: "circle" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "embedded_components {\n" 6 | " id: \"sprite\"\n" 7 | " type: \"sprite\"\n" 8 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 9 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 10 | "attributes {\\n" 11 | " name: \\\"color\\\"\\n" 12 | " double_values {\\n" 13 | " v: 1.0\\n" 14 | " v: 1.0\\n" 15 | " v: 1.0\\n" 16 | " v: 1.0\\n" 17 | " }\\n" 18 | "}\\n" 19 | "textures {\\n" 20 | " sampler: \\\"texture_sampler\\\"\\n" 21 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 22 | "}\\n" 23 | "\"\n" 24 | " position {\n" 25 | " z: -0.1\n" 26 | " }\n" 27 | "}\n" 28 | "" 29 | } 30 | -------------------------------------------------------------------------------- /game/objects/wall/wall_pit.collection: -------------------------------------------------------------------------------- 1 | name: "wall" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "embedded_components {\n" 6 | " id: \"collisionobject\"\n" 7 | " type: \"collisionobject\"\n" 8 | " data: \"type: COLLISION_OBJECT_TYPE_STATIC\\n" 9 | "mass: 0.0\\n" 10 | "friction: 0.1\\n" 11 | "restitution: 0.5\\n" 12 | "group: \\\"solid\\\"\\n" 13 | "mask: \\\"solid\\\"\\n" 14 | "mask: \\\"player\\\"\\n" 15 | "embedded_collision_shape {\\n" 16 | " shapes {\\n" 17 | " shape_type: TYPE_BOX\\n" 18 | " position {\\n" 19 | " }\\n" 20 | " rotation {\\n" 21 | " }\\n" 22 | " index: 0\\n" 23 | " count: 3\\n" 24 | " }\\n" 25 | " data: 100.0\\n" 26 | " data: 25.0\\n" 27 | " data: 10.0\\n" 28 | "}\\n" 29 | "\"\n" 30 | "}\n" 31 | "" 32 | } 33 | -------------------------------------------------------------------------------- /annotations/defold_annotations/zlib.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Zlib compression API documentation 6 | 7 | Functions for compression and decompression of string buffers. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---@class defold_api.zlib 16 | zlib = {} 17 | 18 | ---A lua error is raised is on error 19 | ---@param buf string buffer to deflate 20 | ---@return string buf deflated buffer 21 | function zlib.deflate(buf) end 22 | 23 | ---A lua error is raised is on error 24 | ---@param buf string buffer to inflate 25 | ---@return string buf inflated buffer 26 | function zlib.inflate(buf) end 27 | 28 | return zlib -------------------------------------------------------------------------------- /game/objects/wall/wall.collection: -------------------------------------------------------------------------------- 1 | name: "wall" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "embedded_components {\n" 6 | " id: \"collisionobject\"\n" 7 | " type: \"collisionobject\"\n" 8 | " data: \"type: COLLISION_OBJECT_TYPE_STATIC\\n" 9 | "mass: 0.0\\n" 10 | "friction: 0.1\\n" 11 | "restitution: 0.5\\n" 12 | "group: \\\"solid\\\"\\n" 13 | "mask: \\\"solid\\\"\\n" 14 | "mask: \\\"player\\\"\\n" 15 | "mask: \\\"bullet\\\"\\n" 16 | "embedded_collision_shape {\\n" 17 | " shapes {\\n" 18 | " shape_type: TYPE_BOX\\n" 19 | " position {\\n" 20 | " }\\n" 21 | " rotation {\\n" 22 | " }\\n" 23 | " index: 0\\n" 24 | " count: 3\\n" 25 | " }\\n" 26 | " data: 100.0\\n" 27 | " data: 25.0\\n" 28 | " data: 10.0\\n" 29 | "}\\n" 30 | "\"\n" 31 | "}\n" 32 | "" 33 | } 34 | -------------------------------------------------------------------------------- /annotations/defold_annotations/b2d.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Box2D documentation 6 | 7 | Functions for interacting with Box2D. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---@class defold_api.b2d 16 | b2d = {} 17 | 18 | ---Get the Box2D body from a collision object 19 | ---@param url string|hash|url the url to the game object collision component 20 | ---@return b2Body body the body if successful. Otherwise nil. 21 | function b2d.get_body(url) end 22 | 23 | ---Get the Box2D world from the current collection 24 | ---@return b2World world the world if successful. Otherwise nil. 25 | function b2d.get_world() end 26 | 27 | return b2d -------------------------------------------------------------------------------- /systems/health/health_event.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field health_event component.health_event|nil 5 | 6 | ---@class entity.health_event: entity 7 | ---@field health_event component.health_event 8 | 9 | ---@class component.health_event 10 | ---@field entity entity 11 | ---@field damage number 12 | 13 | ---@class system.health_event: system 14 | ---@field entities entity.health_event[] 15 | local M = {} 16 | 17 | 18 | ---@static 19 | ---@return system.health_event 20 | function M.create_system() 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAll("health_event") 23 | 24 | return system 25 | end 26 | 27 | 28 | function M:postWrap() 29 | for index = #self.entities, 1, -1 do 30 | self.world:removeEntity(self.entities[index]) 31 | end 32 | end 33 | 34 | 35 | return M 36 | -------------------------------------------------------------------------------- /gui/bindings.lua: -------------------------------------------------------------------------------- 1 | -- Global module for pass GUI binding events from GUI to the game logic 2 | 3 | local M = {} 4 | 5 | M.CROSS_CONTEXT_DATA = {} 6 | 7 | 8 | ---@param data any 9 | ---@return any @data 10 | function M.set(data) 11 | local object = msg.url() 12 | object.fragment = nil 13 | 14 | M.CROSS_CONTEXT_DATA[object.socket] = M.CROSS_CONTEXT_DATA[object.socket] or {} 15 | M.CROSS_CONTEXT_DATA[object.socket][object.path] = data 16 | 17 | return data 18 | end 19 | 20 | 21 | ---@param object_url string|userdata|url @root object 22 | ---@return table|nil 23 | function M.get(object_url) 24 | object_url = msg.url(object_url --[[@as string]]) 25 | 26 | local socket_events = M.CROSS_CONTEXT_DATA[object_url.socket] 27 | if not socket_events then 28 | return nil 29 | end 30 | 31 | return socket_events[object_url.path] 32 | end 33 | 34 | 35 | return M 36 | -------------------------------------------------------------------------------- /game/objects/damage_number/damage_number.collection: -------------------------------------------------------------------------------- 1 | name: "damage_number" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "embedded_components {\n" 6 | " id: \"label\"\n" 7 | " type: \"label\"\n" 8 | " data: \"size {\\n" 9 | " x: 32.0\\n" 10 | " y: 32.0\\n" 11 | "}\\n" 12 | "color {\\n" 13 | " x: 0.843\\n" 14 | " y: 0.902\\n" 15 | " z: 0.925\\n" 16 | "}\\n" 17 | "outline {\\n" 18 | " x: 0.376\\n" 19 | " y: 0.173\\n" 20 | " z: 0.173\\n" 21 | "}\\n" 22 | "shadow {\\n" 23 | " w: 0.0\\n" 24 | "}\\n" 25 | "text: \\\"1\\\"\\n" 26 | "font: \\\"/assets/fonts/text.font\\\"\\n" 27 | "material: \\\"/builtins/fonts/label-df.material\\\"\\n" 28 | "\"\n" 29 | " position {\n" 30 | " z: 5.0\n" 31 | " }\n" 32 | " scale {\n" 33 | " x: 1.5\n" 34 | " y: 1.5\n" 35 | " }\n" 36 | "}\n" 37 | "" 38 | } 39 | -------------------------------------------------------------------------------- /systems/color/color_event.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field color_event component.color_event|nil 5 | 6 | ---@class entity.color_event: entity 7 | ---@field color_event component.color_event 8 | 9 | ---@class component.color_event 10 | ---@field entity entity 11 | ---@field color vector4 12 | 13 | ---@class system.color_event: system 14 | ---@field entities entity.color_event[] 15 | local M = {} 16 | 17 | 18 | ---@static 19 | ---@return system.color_event 20 | function M.create_system() 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAll("color_event") 23 | system.id = "color_event" 24 | 25 | return system 26 | end 27 | 28 | 29 | function M:postWrap() 30 | for index = #self.entities, 1, -1 do 31 | self.world:removeEntity(self.entities[index]) 32 | end 33 | end 34 | 35 | 36 | return M 37 | -------------------------------------------------------------------------------- /systems/input/input_event.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field input_event component.input_event|nil 5 | 6 | ---@class entity.input_event: entity 7 | ---@field input_event component.input_event 8 | 9 | ---@class component.input_event 10 | ---@field action_id hash|nil 11 | ---@field action action 12 | 13 | ---@class system.input_event: system 14 | ---@field entities entity.input_event[] 15 | local M = {} 16 | 17 | 18 | ---@static 19 | ---@return system.input_event 20 | function M.create_system() 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAll("input_event") 23 | system.id = "input_event" 24 | 25 | return system 26 | end 27 | 28 | 29 | function M:postWrap() 30 | for index = #self.entities, 1, -1 do 31 | self.world:removeEntity(self.entities[index]) 32 | end 33 | end 34 | 35 | 36 | return M 37 | -------------------------------------------------------------------------------- /decore/templates/system_template.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field TEMPLATE component.TEMPLATE|nil 5 | 6 | ---@class entity.TEMPLATE: entity 7 | ---@field TEMPLATE component.TEMPLATE 8 | 9 | ---@class component.TEMPLATE 10 | 11 | ---@class system.TEMPLATE: system 12 | ---@field entities entity.TEMPLATE[] 13 | local M = {} 14 | 15 | 16 | ---@static 17 | ---@return system.TEMPLATE 18 | function M.create_system() 19 | local system = setmetatable(ecs.system(), { __index = M }) 20 | system.filter = ecs.requireAll("TEMPLATE") 21 | system.id = "TEMPLATE" 22 | 23 | return system 24 | end 25 | 26 | 27 | ---@param entity entity.TEMPLATE 28 | function M:onAdd(entity) 29 | end 30 | 31 | 32 | ---@param entity entity.TEMPLATE 33 | function M:onRemove(entity) 34 | end 35 | 36 | 37 | ---@param dt number 38 | function M:update(dt) 39 | end 40 | 41 | 42 | return M 43 | -------------------------------------------------------------------------------- /decore/templates/system_event_template.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field TEMPLATE_event component.TEMPLATE_event|nil 5 | 6 | ---@class entity.TEMPLATE_event: entity 7 | ---@field TEMPLATE_event component.TEMPLATE_event 8 | 9 | ---@class component.TEMPLATE_event 10 | ---@field entity entity|nil 11 | 12 | ---@class system.TEMPLATE_event: system 13 | ---@field entities entity.TEMPLATE_event[] 14 | local M = {} 15 | 16 | 17 | ---@static 18 | ---@return system.TEMPLATE_event 19 | function M.create_system() 20 | local system = setmetatable(ecs.system(), { __index = M }) 21 | system.filter = ecs.requireAll("TEMPLATE_event") 22 | system.id = "TEMPLATE_event" 23 | 24 | return system 25 | end 26 | 27 | 28 | function M:postWrap() 29 | for index = #self.entities, 1, -1 do 30 | self.world:removeEntity(self.entities[index]) 31 | end 32 | end 33 | 34 | 35 | return M 36 | -------------------------------------------------------------------------------- /systems/target_tracker/target_tracker_event.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field target_tracker_event component.target_tracker_event|nil 5 | 6 | ---@class entity.target_tracker_event: entity 7 | ---@field target_tracker_event component.target_tracker_event 8 | 9 | ---@class component.target_tracker_event 10 | ---@field target_count number 11 | 12 | ---@class system.target_tracker_event: system 13 | ---@field entities entity.target_tracker_event[] 14 | local M = {} 15 | 16 | 17 | ---@static 18 | ---@return system.target_tracker_event 19 | function M.create_system() 20 | local system = setmetatable(ecs.system(), { __index = M }) 21 | system.filter = ecs.requireAll("target_tracker_event") 22 | 23 | return system 24 | end 25 | 26 | 27 | function M:postWrap() 28 | for index = #self.entities, 1, -1 do 29 | self.world:removeEntity(self.entities[index]) 30 | end 31 | end 32 | 33 | 34 | return M 35 | -------------------------------------------------------------------------------- /assets/fonts/text.font: -------------------------------------------------------------------------------- 1 | font: "/assets/fonts/troika.otf" 2 | material: "/builtins/fonts/font-df.material" 3 | size: 30 4 | outline_alpha: 1.0 5 | outline_width: 3.0 6 | shadow_alpha: 0.55 7 | shadow_blur: 1 8 | shadow_y: -4.0 9 | output_format: TYPE_DISTANCE_FIELD 10 | render_mode: MODE_MULTI_LAYER 11 | characters: " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\320\260\320\261\320\262\320\263\320\264\320\265\321\221\320\266\320\267\320\270\320\271\320\272\320\273\320\274\320\275\320\276\320\277\321\200\321\201\321\202\321\203\321\204\321\205\321\206\321\207\321\210\321\211\321\212\321\213\321\214\321\215\321\216\321\217\320\220\320\221\320\222\320\223\320\224\320\225\320\201\320\226\320\227\320\230\320\231\320\232\320\233\320\234\320\235\320\236\320\237\320\240\320\241\320\242\320\243\320\244\320\245\320\246\320\247\320\250\320\251\320\252\320\253\320\254\320\255\320\256\320\257\302\247\303\227" 12 | -------------------------------------------------------------------------------- /systems/health_circle_visual/health_circle_visual.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local health_circle_visual_command = require("systems.health_circle_visual.health_circle_visual_command") 4 | 5 | ---@class entity 6 | ---@field health_circle_visual component.health_circle_visual|nil 7 | 8 | ---@class entity.health_circle_visual: entity 9 | ---@field health_circle_visual component.health_circle_visual 10 | 11 | ---@class component.health_circle_visual 12 | 13 | ---@class system.health_circle_visual: system 14 | ---@field entities entity.health_circle_visual[] 15 | local M = {} 16 | 17 | 18 | ---@static 19 | ---@return system.health_circle_visual, system.health_circle_visual_command 20 | function M.create_system() 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAll("health_circle_visual") 23 | 24 | return system, health_circle_visual_command.create_system(system) 25 | end 26 | 27 | 28 | return M 29 | -------------------------------------------------------------------------------- /systems/remove_with_delay/remove_with_delay.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field remove_with_delay component.remove_with_delay|nil 5 | 6 | ---@class entity.remove_with_delay: entity 7 | ---@field remove_with_delay component.remove_with_delay 8 | 9 | ---@class component.remove_with_delay 10 | ---@field delay number 11 | 12 | ---@class system.remove_with_delay: system 13 | ---@field entities entity.remove_with_delay[] 14 | local M = {} 15 | 16 | 17 | ---@static 18 | ---@return system.remove_with_delay 19 | function M.create_system() 20 | local system = setmetatable(ecs.system(), { __index = M }) 21 | system.filter = ecs.requireAll("remove_with_delay") 22 | 23 | return system 24 | end 25 | 26 | 27 | ---@param entity entity.remove_with_delay 28 | function M:onAdd(entity) 29 | timer.delay(entity.remove_with_delay.delay, false, function() 30 | self.world:removeEntity(entity) 31 | end) 32 | end 33 | 34 | 35 | return M 36 | -------------------------------------------------------------------------------- /game/objects/background/background.collection: -------------------------------------------------------------------------------- 1 | name: "bullet" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "embedded_components {\n" 6 | " id: \"sprite\"\n" 7 | " type: \"sprite\"\n" 8 | " data: \"default_animation: \\\"arena_background\\\"\\n" 9 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 10 | "slice9 {\\n" 11 | " x: 40.0\\n" 12 | " y: 40.0\\n" 13 | " z: 40.0\\n" 14 | " w: 40.0\\n" 15 | "}\\n" 16 | "size {\\n" 17 | " x: 200.0\\n" 18 | " y: 200.0\\n" 19 | "}\\n" 20 | "size_mode: SIZE_MODE_MANUAL\\n" 21 | "attributes {\\n" 22 | " name: \\\"color\\\"\\n" 23 | " double_values {\\n" 24 | " v: 1.0\\n" 25 | " v: 1.0\\n" 26 | " v: 1.0\\n" 27 | " v: 1.0\\n" 28 | " }\\n" 29 | "}\\n" 30 | "textures {\\n" 31 | " sampler: \\\"texture_sampler\\\"\\n" 32 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 33 | "}\\n" 34 | "\"\n" 35 | "}\n" 36 | "" 37 | } 38 | -------------------------------------------------------------------------------- /annotations/defold_annotations/label.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Label API documentation 6 | --]] 7 | 8 | ---@diagnostic disable: lowercase-global 9 | ---@diagnostic disable: missing-return 10 | ---@diagnostic disable: duplicate-doc-param 11 | ---@diagnostic disable: duplicate-set-field 12 | 13 | ---@class defold_api.label 14 | label = {} 15 | 16 | ---Gets the text from a label component 17 | ---@param url string|hash|url the label to get the text from 18 | ---@return string metrics the label text 19 | function label.get_text(url) end 20 | 21 | ---Sets the text of a label component 22 | --- This method uses the message passing that means the value will be set after dispatch messages step. 23 | ---More information is available in the Application Lifecycle manual. 24 | ---@param url string|hash|url the label that should have a constant set 25 | ---@param text string the text 26 | function label.set_text(url, text) end 27 | 28 | return label -------------------------------------------------------------------------------- /systems/play_fx_on_remove/play_fx_on_remove.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field play_fx_on_remove component.play_fx_on_remove|nil 5 | 6 | ---@class entity.play_fx_on_remove: entity 7 | ---@field play_fx_on_remove component.play_fx_on_remove 8 | ---@field game_object component.game_object 9 | 10 | ---@class component.play_fx_on_remove 11 | ---@field fx_url string 12 | 13 | ---@class system.play_fx_on_remove: system 14 | ---@field entities entity.play_fx_on_remove[] 15 | local M = {} 16 | 17 | 18 | ---@static 19 | ---@return system.play_fx_on_remove 20 | function M.create_system() 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAll("play_fx_on_remove", "game_object") 23 | 24 | return system 25 | end 26 | 27 | 28 | ---@param entity entity.play_fx_on_remove 29 | function M:onRemove(entity) 30 | local fx_url = msg.url(nil, entity.game_object.root, entity.play_fx_on_remove.fx_url) 31 | particlefx.play(fx_url) 32 | end 33 | 34 | 35 | return M 36 | -------------------------------------------------------------------------------- /systems/on_key_released/on_key_released_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class system.on_key_released_command: system 4 | ---@field on_key_released system.on_key_released 5 | local M = {} 6 | 7 | 8 | ---@static 9 | ---@return system.on_key_released_command 10 | function M.create_system(on_key_released) 11 | local system = setmetatable(ecs.system(), { __index = M }) 12 | system.filter = ecs.requireAny("input_event") 13 | system.id = "on_key_released_command" 14 | system.on_key_released = on_key_released 15 | 16 | return system 17 | end 18 | 19 | 20 | ---@param entity entity 21 | function M:onAdd(entity) 22 | local input_event = entity.input_event 23 | if input_event then 24 | self:process_input_event(input_event) 25 | end 26 | end 27 | 28 | 29 | ---@param input_event component.input_event 30 | function M:process_input_event(input_event) 31 | local entities = self.on_key_released.entities 32 | for index = 1, #entities do 33 | local entity = entities[index] 34 | self.on_key_released:apply_input(entity, input_event.action_id, input_event.action) 35 | end 36 | end 37 | 38 | 39 | return M 40 | -------------------------------------------------------------------------------- /systems/collision/collision_event.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field collision_event component.collision_event|nil 5 | 6 | ---@class entity.collision_event: entity 7 | ---@field collision_event component.collision_event 8 | 9 | ---@class component.collision_event 10 | ---@field entity entity 11 | ---@field other entity 12 | ---@field trigger_event physics.collision.trigger_event|nil 13 | ---@field collision_event physics.collision.collision_event|nil 14 | 15 | ---@class component.collider_event.trigger 16 | 17 | ---@class component.collider_event.collision 18 | 19 | ---@class system.collision_event: system 20 | ---@field entities entity.collision_event[] 21 | local M = {} 22 | 23 | 24 | ---@static 25 | ---@return system.collision_event 26 | function M.create_system() 27 | local system = setmetatable(ecs.system(), { __index = M }) 28 | system.filter = ecs.requireAll("collision_event") 29 | 30 | return system 31 | end 32 | 33 | 34 | function M:postWrap() 35 | for index = #self.entities, 1, -1 do 36 | self.world:removeEntity(self.entities[index]) 37 | end 38 | end 39 | 40 | 41 | return M 42 | -------------------------------------------------------------------------------- /systems/on_spawn_command/on_spawn_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field on_spawn_command component.on_spawn_command|nil 5 | 6 | ---@class entity.on_spawn_command: entity 7 | ---@field on_spawn_command component.on_spawn_command 8 | 9 | ---@class component.on_spawn_command 10 | ---@field command string 11 | 12 | ---@class system.on_spawn_command: system 13 | ---@field entities entity.on_spawn_command[] 14 | local M = {} 15 | 16 | 17 | ---@static 18 | ---@return system.on_spawn_command 19 | function M.create_system() 20 | local system = setmetatable(ecs.system(), { __index = M }) 21 | system.filter = ecs.requireAny("on_spawn_command") 22 | 23 | return system 24 | end 25 | 26 | 27 | ---@param entity entity.on_spawn_command 28 | function M:onAdd(entity) 29 | local command = entity.on_spawn_command 30 | if command then 31 | self:process_command(command) 32 | end 33 | end 34 | 35 | 36 | ---@param command component.on_spawn_command 37 | function M:process_command(command) 38 | if command.command then 39 | local data = json.decode(command.command) 40 | self.world:addEntity(data) 41 | end 42 | end 43 | 44 | 45 | return M 46 | -------------------------------------------------------------------------------- /decore/templates/system_command_template.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field TEMPLATE_command component.TEMPLATE_command|nil 5 | 6 | ---@class entity.TEMPLATE_command: entity 7 | ---@field TEMPLATE_command component.TEMPLATE_command 8 | 9 | ---@class component.TEMPLATE_command 10 | 11 | ---@class system.TEMPLATE_command: system 12 | ---@field entities entity.TEMPLATE_command[] 13 | ---@field TEMPLATE system.TEMPLATE 14 | local M = {} 15 | 16 | 17 | ---@static 18 | ---@return system.TEMPLATE_command 19 | function M.create_system(TEMPLATE) 20 | local system = setmetatable(ecs.system(), { __index = M }) 21 | system.filter = ecs.requireAny("TEMPLATE_command") 22 | system.TEMPLATE = TEMPLATE 23 | system.id = "TEMPLATE_command" 24 | 25 | return system 26 | end 27 | 28 | 29 | ---@param entity entity.TEMPLATE_command 30 | function M:onAdd(entity) 31 | local command = entity.TEMPLATE_command 32 | if command then 33 | self:process_command(command) 34 | self.world:removeEntity(entity) 35 | end 36 | end 37 | 38 | 39 | ---@param command component.TEMPLATE_command 40 | function M:process_command(command) 41 | end 42 | 43 | 44 | return M 45 | -------------------------------------------------------------------------------- /systems/debug/debug.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | local log = require("log.log") 3 | 4 | local logger = log.get_logger("system.debug") 5 | 6 | local system_debug_command = require("systems.debug.debug_command") 7 | 8 | ---@class entity 9 | ---@field debug component.debug|nil 10 | 11 | ---@class entity.debug: entity 12 | ---@field debug component.debug 13 | 14 | ---@class component.debug 15 | ---@field is_profiler_active boolean 16 | 17 | ---@class system.debug: system 18 | ---@field entities entity.debug[] 19 | local M = {} 20 | 21 | 22 | ---@static 23 | ---@return system.debug, system.debug_command 24 | function M.create_system() 25 | local system = setmetatable(ecs.system(), { __index = M }) 26 | system.filter = ecs.requireAny("debug") 27 | system.id = "debug" 28 | 29 | return system, system_debug_command.create_system(system) 30 | end 31 | 32 | 33 | ---@param entity entity.debug 34 | function M:toggle_profiler(entity) 35 | local d = entity.debug 36 | d.is_profiler_active = not d.is_profiler_active 37 | profiler.enable_ui(d.is_profiler_active) 38 | 39 | logger:info("Profiler is active: " .. tostring(d.is_profiler_active)) 40 | end 41 | 42 | 43 | return M 44 | -------------------------------------------------------------------------------- /annotations/defold_annotations/builtins.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Built-ins API documentation 6 | 7 | Built-in scripting functions. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---All ids in the engine are represented as hashes, so a string needs to be hashed 16 | ---before it can be compared with an id. 17 | ---@param s string string to hash 18 | ---@return hash hash a hashed string 19 | function hash(s) end 20 | 21 | ---Returns a hexadecimal representation of a hash value. 22 | ---The returned string is always padded with leading zeros. 23 | ---@param h hash hash value to get hex string for 24 | ---@return string hex hex representation of the hash 25 | function hash_to_hex(h) end 26 | 27 | ---Pretty printing of Lua values. This function prints Lua values 28 | ---in a manner similar to +print()+, but will also recurse into tables 29 | ---and pretty print them. There is a limit to how deep the function 30 | ---will recurse. 31 | ---@param ... any value to print 32 | function pprint(...) end -------------------------------------------------------------------------------- /systems/death/death_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field death_command component.death_command|nil 5 | 6 | ---@class entity.death_command: entity 7 | ---@field death_command component.death_command 8 | 9 | ---@class component.death_command 10 | 11 | ---@class system.death_command: system 12 | ---@field entities entity.death_command[] 13 | ---@field death system.death 14 | local M = {} 15 | 16 | 17 | ---@static 18 | ---@return system.death_command 19 | function M.create_system(death) 20 | local system = setmetatable(ecs.system(), { __index = M }) 21 | system.filter = ecs.requireAny("health_event") 22 | system.death = death 23 | 24 | return system 25 | end 26 | 27 | 28 | ---@param entity entity.death_command 29 | function M:onAdd(entity) 30 | local health_event = entity.health_event 31 | if health_event then 32 | self:process_health_event(health_event) 33 | end 34 | end 35 | 36 | 37 | ---@param health_event component.health_event 38 | function M:process_health_event(health_event) 39 | if health_event.damage then 40 | if health_event.entity.health.current_health == 0 then 41 | self.world:removeEntity(health_event.entity) 42 | end 43 | end 44 | end 45 | 46 | 47 | return M 48 | -------------------------------------------------------------------------------- /systems/health/health_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field health_command component.health_command|nil 5 | 6 | ---@class entity.health_command: entity 7 | ---@field health_command component.health_command 8 | 9 | ---@class component.health_command 10 | ---@field entity entity 11 | ---@field damage number|nil 12 | 13 | ---@class system.health_command: system 14 | ---@field entities entity.health_command[] 15 | ---@field health system.health 16 | local M = {} 17 | 18 | 19 | ---@static 20 | ---@return system.health_command 21 | function M.create_system(health) 22 | local system = setmetatable(ecs.system(), { __index = M }) 23 | system.filter = ecs.requireAny("health_command") 24 | system.health = health 25 | 26 | return system 27 | end 28 | 29 | 30 | ---@param entity entity.health_command 31 | function M:onAdd(entity) 32 | local command = entity.health_command 33 | if command then 34 | self:process_command(command) 35 | self.world:removeEntity(entity) 36 | end 37 | end 38 | 39 | 40 | ---@param command component.health_command 41 | function M:process_command(command) 42 | if command.damage then 43 | self.health:apply_damage(command.entity, command.damage) 44 | end 45 | end 46 | 47 | 48 | return M 49 | -------------------------------------------------------------------------------- /settings_deployer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # https://github.com/Insality/defold-deployer 3 | 4 | # You can point bob version for project in format "filename:sha" 5 | bob_sha="180:9141d9d3605e3f5d51c71293116d769da2613d39" 6 | 7 | # Select Defold channel. Values: stable, beta, alpha 8 | bob_channel="stable" 9 | 10 | # If true, it will check and download latest bob version. It will ignore bob_sha param 11 | use_latest_bob=false 12 | 13 | # Select Defold build server 14 | build_server="https://build.defold.com" 15 | 16 | # Set patch (last value after dot) game version value as total git commits count (1.2.0 -> 1.2.{commits_count}) 17 | # You allow to get SHA commit from version via: git rev-list --all --reverse | sed -n {N}p 18 | enable_incremental_version=true 19 | 20 | # Use git commits count as android.version_code on build 21 | enable_incremental_android_version_code=true 22 | 23 | # Local resource cache folder for deployer script. This folder will be added to gitignore if exists 24 | resource_cache_local=".cache_deployer" 25 | 26 | # If true, add `-l yes` build param for publish live content 27 | is_live_content=false 28 | 29 | # Set to true, if you do not need to strip executables 30 | no_strip_executable=false 31 | 32 | # Is need to build html report 33 | is_build_html_report=true 34 | 35 | -------------------------------------------------------------------------------- /game/game.collection: -------------------------------------------------------------------------------- 1 | name: "game" 2 | collection_instances { 3 | id: "spawner" 4 | collection: "/game/spawner/spawner.collection" 5 | } 6 | scale_along_z: 0 7 | embedded_instances { 8 | id: "system" 9 | data: "components {\n" 10 | " id: \"game\"\n" 11 | " component: \"/game/game.script\"\n" 12 | "}\n" 13 | "" 14 | } 15 | embedded_instances { 16 | id: "camera" 17 | children: "offset" 18 | data: "" 19 | position { 20 | z: 10.0 21 | } 22 | } 23 | embedded_instances { 24 | id: "sound" 25 | data: "embedded_components {\n" 26 | " id: \"laser_shoot\"\n" 27 | " type: \"sound\"\n" 28 | " data: \"sound: \\\"/assets/sounds/laser_shoot.wav\\\"\\n" 29 | "group: \\\"sound\\\"\\n" 30 | "gain: 30.0\\n" 31 | "\"\n" 32 | "}\n" 33 | "embedded_components {\n" 34 | " id: \"explosion\"\n" 35 | " type: \"sound\"\n" 36 | " data: \"sound: \\\"/assets/sounds/explosion.wav\\\"\\n" 37 | "group: \\\"sound\\\"\\n" 38 | "gain: 30.0\\n" 39 | "\"\n" 40 | "}\n" 41 | "" 42 | } 43 | embedded_instances { 44 | id: "offset" 45 | data: "embedded_components {\n" 46 | " id: \"camera\"\n" 47 | " type: \"camera\"\n" 48 | " data: \"aspect_ratio: 1.0\\n" 49 | "fov: 0.5\\n" 50 | "near_z: -100.0\\n" 51 | "far_z: 100.0\\n" 52 | "orthographic_projection: 1\\n" 53 | "\"\n" 54 | "}\n" 55 | "" 56 | } 57 | -------------------------------------------------------------------------------- /systems/transform/transform_event.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field transform_event component.transform_event|nil 5 | 6 | ---@class entity.transform_event: entity 7 | ---@field transform_event component.transform_event 8 | 9 | ---@class component.transform_event 10 | ---@field entity entity @The entity that was changed. 11 | ---@field is_position_changed boolean|nil @If true, the position was changed. 12 | ---@field is_scale_changed boolean|nil @If true, the scale was changed. 13 | ---@field is_rotation_changed boolean|nil @If true, the rotation was changed. 14 | ---@field is_size_changed boolean|nil @If true, the size was changed. 15 | ---@field animate_time number|nil @If true, the time it took to animate the transform. 16 | ---@field easing userdata|nil @The easing function used for the animation. 17 | 18 | ---@class system.transform_event: system 19 | ---@field entities entity.transform_event[] 20 | local M = {} 21 | 22 | 23 | ---@static 24 | ---@return system.transform_event 25 | function M.create_system() 26 | local system = setmetatable(ecs.system(), { __index = M }) 27 | system.filter = ecs.requireAll("transform_event") 28 | system.id = "transform_event" 29 | 30 | return system 31 | end 32 | 33 | 34 | function M:postWrap() 35 | for index = #self.entities, 1, -1 do 36 | self.world:removeEntity(self.entities[index]) 37 | end 38 | end 39 | 40 | 41 | return M 42 | -------------------------------------------------------------------------------- /game/objects/player/player.collection: -------------------------------------------------------------------------------- 1 | name: "circle" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "embedded_components {\n" 6 | " id: \"sprite\"\n" 7 | " type: \"sprite\"\n" 8 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 9 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 10 | "attributes {\\n" 11 | " name: \\\"color\\\"\\n" 12 | " double_values {\\n" 13 | " v: 1.0\\n" 14 | " v: 1.0\\n" 15 | " v: 1.0\\n" 16 | " v: 1.0\\n" 17 | " }\\n" 18 | "}\\n" 19 | "textures {\\n" 20 | " sampler: \\\"texture_sampler\\\"\\n" 21 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 22 | "}\\n" 23 | "\"\n" 24 | "}\n" 25 | "embedded_components {\n" 26 | " id: \"collisionobject\"\n" 27 | " type: \"collisionobject\"\n" 28 | " data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n" 29 | "mass: 1.0\\n" 30 | "friction: 0.0\\n" 31 | "restitution: 0.2\\n" 32 | "group: \\\"player\\\"\\n" 33 | "mask: \\\"solid\\\"\\n" 34 | "embedded_collision_shape {\\n" 35 | " shapes {\\n" 36 | " shape_type: TYPE_SPHERE\\n" 37 | " position {\\n" 38 | " }\\n" 39 | " rotation {\\n" 40 | " }\\n" 41 | " index: 0\\n" 42 | " count: 1\\n" 43 | " }\\n" 44 | " data: 32.741936\\n" 45 | "}\\n" 46 | "linear_damping: 0.995\\n" 47 | "locked_rotation: true\\n" 48 | "\"\n" 49 | "}\n" 50 | "" 51 | } 52 | -------------------------------------------------------------------------------- /systems/target_tracker/target_tracker.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local target_tracker_event = require("systems.target_tracker.target_tracker_event") 4 | 5 | ---@class entity 6 | ---@field target_tracker component.target_tracker|nil 7 | 8 | ---@class entity.target_tracker: entity 9 | ---@field target_tracker component.target_tracker 10 | 11 | ---@class component.target_tracker 12 | 13 | ---@class system.target_tracker: system 14 | ---@field entities entity.target_tracker[] 15 | local M = {} 16 | 17 | 18 | ---@static 19 | ---@return system.target_tracker, system.target_tracker_event 20 | function M.create_system() 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAll("target") 23 | 24 | return system, target_tracker_event.create_system() 25 | end 26 | 27 | 28 | ---@param entity entity.target_tracker 29 | function M:onAdd(entity) 30 | ---@type component.target_tracker_event 31 | local target_tracker_event = { 32 | target_count = #self.entities, 33 | } 34 | self.world:addEntity({ target_tracker_event = target_tracker_event }) 35 | end 36 | 37 | 38 | ---@param entity entity.target_tracker 39 | function M:onRemove(entity) 40 | ---@type component.target_tracker_event 41 | local target_tracker_event = { 42 | target_count = #self.entities, 43 | } 44 | self.world:addEntity({ target_tracker_event = target_tracker_event }) 45 | end 46 | 47 | 48 | return M 49 | -------------------------------------------------------------------------------- /systems/physics/physics_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field physics_command component.physics_command|nil 5 | 6 | ---@class entity.physics_command: entity 7 | ---@field physics_command component.physics_command 8 | 9 | ---@class component.physics_command 10 | ---@field entity entity 11 | ---@field force_x number|nil 12 | ---@field force_y number|nil 13 | 14 | ---@class system.physics_command: system 15 | ---@field entities entity.physics_command[] 16 | ---@field physics system.physics 17 | local M = {} 18 | 19 | 20 | ---@static 21 | ---@return system.physics_command 22 | function M.create_system(physics) 23 | local system = setmetatable(ecs.system(), { __index = M }) 24 | system.filter = ecs.requireAny("physics_command") 25 | system.physics = physics 26 | system.id = "physics_command" 27 | 28 | return system 29 | end 30 | 31 | 32 | ---@param entity entity.physics_command 33 | function M:onAdd(entity) 34 | local command = entity.physics_command 35 | if command then 36 | self:process_command(command) 37 | self.world:removeEntity(entity) 38 | end 39 | end 40 | 41 | 42 | ---@param command component.physics_command 43 | function M:process_command(command) 44 | local e = command.entity --[[@as entity.physics]] 45 | 46 | if command.force_x or command.force_y then 47 | self.physics:add_force(e, command.force_x, command.force_y) 48 | end 49 | end 50 | 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /annotations/defold_annotations/json.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | JSON API documentation 6 | 7 | Manipulation of JSON data strings. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---@class defold_api.json 16 | json = {} 17 | 18 | ---Represents the null primitive from a json file 19 | json.null = nil 20 | 21 | ---Decode a string of JSON data into a Lua table. 22 | ---A Lua error is raised for syntax errors. 23 | ---@param json string json data 24 | ---@param options { decode_null_as_userdata:boolean|nil }|nil table with decode options 25 | --- 26 | ---bool decode_null_as_userdata: wether to decode a JSON null value as json.null or nil (default is nil) 27 | --- 28 | ---@return table data decoded json 29 | function json.decode(json, options) end 30 | 31 | ---Encode a lua table to a JSON string. 32 | ---A Lua error is raised for syntax errors. 33 | ---@param tbl table lua table to encode 34 | ---@param options { encode_empty_table_as_object:string }|nil table with encode options 35 | --- 36 | ---string encode_empty_table_as_object: wether to encode an empty table as an JSON object or array (default is object) 37 | --- 38 | ---@return string json encoded json 39 | function json.encode(tbl, options) end 40 | 41 | return json -------------------------------------------------------------------------------- /systems/collision/on_collision/on_collision_remove.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field on_collision_remove boolean|nil 5 | 6 | ---@class entity.on_collision_remove: entity 7 | ---@field on_collision_remove boolean 8 | 9 | ---@class component.on_collision_remove: boolean 10 | 11 | ---@class system.on_collision_remove: system 12 | ---@field entities entity.on_collision_remove[] 13 | local M = {} 14 | 15 | local VECTOR_ZERO = vmath.vector3(0) 16 | 17 | 18 | ---@static 19 | ---@return system.on_collision_remove 20 | function M.create_system() 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAny("collision_event") 23 | 24 | return system 25 | end 26 | 27 | 28 | ---@param entity entity.on_collision_remove 29 | function M:onAdd(entity) 30 | local collision_event = entity.collision_event 31 | if collision_event then 32 | self:process_collision_event(collision_event) 33 | self.world:removeEntity(entity) 34 | end 35 | end 36 | 37 | 38 | ---@param collision_event component.collision_event 39 | function M:process_collision_event(collision_event) 40 | local entity = collision_event.entity 41 | local on_collision_remove = entity.on_collision_remove 42 | if on_collision_remove then 43 | b2d.body.set_linear_velocity(entity.physics.box2d_body, VECTOR_ZERO) 44 | b2d.body.set_awake(entity.physics.box2d_body, false) 45 | self.world:removeEntity(entity) 46 | end 47 | end 48 | 49 | 50 | return M 51 | -------------------------------------------------------------------------------- /systems/health/health.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local health_command = require("systems.health.health_command") 4 | local health_event = require("systems.health.health_event") 5 | 6 | ---@class entity 7 | ---@field health component.health|nil 8 | 9 | ---@class entity.health: entity 10 | ---@field health component.health 11 | 12 | ---@class component.health 13 | ---@field health number 14 | ---@field current_health number|nil 15 | 16 | ---@class system.health: system 17 | ---@field entities entity.health[] 18 | local M = {} 19 | 20 | 21 | ---@static 22 | ---@return system.health, system.health_command, system.health_event 23 | function M.create_system() 24 | local system = setmetatable(ecs.system(), { __index = M }) 25 | system.filter = ecs.requireAll("health") 26 | 27 | return system, health_command.create_system(system), health_event.create_system() 28 | end 29 | 30 | 31 | ---@param entity entity.health 32 | function M:onAdd(entity) 33 | local health = entity.health 34 | health.current_health = health.health 35 | end 36 | 37 | 38 | ---@param entity entity.health 39 | function M:apply_damage(entity, damage) 40 | local health = entity.health 41 | if health then 42 | health.current_health = math.max(0, health.current_health - damage) 43 | 44 | ---@type component.health_event 45 | local event = { 46 | entity = entity, 47 | damage = damage, 48 | } 49 | self.world:addEntity({ health_event = event }) 50 | end 51 | end 52 | 53 | 54 | return M 55 | -------------------------------------------------------------------------------- /systems/explosion/explosion.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local explosion_command = require("systems.explosion.explosion_command") 4 | 5 | ---@class entity 6 | ---@field explosion component.explosion|nil 7 | 8 | ---@class entity.explosion: entity 9 | ---@field explosion component.explosion 10 | 11 | ---@class component.explosion 12 | ---@field power number 13 | ---@field distance number 14 | 15 | ---@class system.explosion: system 16 | ---@field entities entity.explosion[] 17 | local M = {} 18 | 19 | 20 | ---@static 21 | ---@return system.explosion, system.explosion_command 22 | function M.create_system() 23 | local system = setmetatable(ecs.system(), { __index = M }) 24 | system.filter = ecs.requireAll("physics", "transform") 25 | 26 | return system, explosion_command.create_system(system) 27 | end 28 | 29 | 30 | function M:apply_explosion(entity, position_x, position_y, power) 31 | local target_x = entity.transform.position_x 32 | local target_y = entity.transform.position_y 33 | local force_x = target_x - position_x 34 | local force_y = target_y - position_y 35 | local distance = math.sqrt(force_x * force_x + force_y * force_y) 36 | force_x = force_x / distance * power 37 | force_y = force_y / distance * power 38 | 39 | ---@type component.physics_command 40 | local physics_command = { 41 | entity = entity, 42 | force_x = force_x, 43 | force_y = force_y, 44 | } 45 | self.world:addEntity({ physics_command = physics_command }) 46 | end 47 | 48 | 49 | return M 50 | -------------------------------------------------------------------------------- /gui/gui_main/button.gui: -------------------------------------------------------------------------------- 1 | script: "" 2 | textures { 3 | name: "game" 4 | texture: "/assets/atlases/game_shooting_circle.atlas" 5 | } 6 | nodes { 7 | size { 8 | x: 278.0 9 | y: 278.0 10 | } 11 | color { 12 | x: 0.38 13 | y: 0.173 14 | z: 0.173 15 | } 16 | type: TYPE_BOX 17 | texture: "game/button" 18 | id: "root" 19 | layer: "game" 20 | inherit_alpha: true 21 | slice9 { 22 | x: 30.0 23 | y: 30.0 24 | z: 30.0 25 | w: 30.0 26 | } 27 | } 28 | nodes { 29 | type: TYPE_BOX 30 | texture: "game/empty" 31 | id: "group_arrow" 32 | parent: "root" 33 | layer: "game" 34 | inherit_alpha: true 35 | size_mode: SIZE_MODE_AUTO 36 | visible: false 37 | } 38 | nodes { 39 | position { 40 | y: -8.0 41 | } 42 | color { 43 | x: 0.38 44 | y: 0.173 45 | z: 0.173 46 | } 47 | type: TYPE_BOX 48 | texture: "game/icon_arrow" 49 | id: "icon_arrow_shadow" 50 | parent: "group_arrow" 51 | layer: "game" 52 | inherit_alpha: true 53 | size_mode: SIZE_MODE_AUTO 54 | } 55 | nodes { 56 | position { 57 | y: 8.0 58 | } 59 | color { 60 | x: 0.933 61 | y: 0.435 62 | z: 0.435 63 | } 64 | type: TYPE_BOX 65 | texture: "game/icon_arrow" 66 | id: "icon_arrow" 67 | parent: "group_arrow" 68 | layer: "game" 69 | inherit_alpha: true 70 | size_mode: SIZE_MODE_AUTO 71 | } 72 | layers { 73 | name: "game" 74 | } 75 | layers { 76 | name: "text" 77 | } 78 | material: "/builtins/materials/gui.material" 79 | adjust_reference: ADJUST_REFERENCE_PARENT 80 | -------------------------------------------------------------------------------- /systems/acceleration/acceleration.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field acceleration component.acceleration|nil 5 | 6 | ---@class entity.acceleration: entity 7 | ---@field acceleration component.acceleration 8 | ---@field physics component.physics 9 | ---@field game_object component.game_object 10 | 11 | ---@class component.acceleration 12 | ---@field value number 13 | 14 | ---@class system.acceleration: system 15 | ---@field entities entity.acceleration[] 16 | local M = {} 17 | 18 | 19 | ---@static 20 | ---@return system.acceleration 21 | function M.create_system() 22 | local system = setmetatable(ecs.processingSystem(), { __index = M }) 23 | system.filter = ecs.requireAll("acceleration", "physics", "game_object") 24 | 25 | return system 26 | end 27 | 28 | 29 | ---@param entity entity.acceleration 30 | ---@param dt number 31 | function M:process(entity, dt) 32 | -- Add force to the direction of the velocity 33 | local physics = entity.physics 34 | local velocity_x = physics.velocity_x 35 | local velocity_y = physics.velocity_y 36 | local acceleration = entity.acceleration.value 37 | 38 | local force_x = velocity_x * acceleration * dt 39 | local force_y = velocity_y * acceleration * dt 40 | 41 | -- Add force to the physics system 42 | ---@type component.physics_command 43 | local physics_command = { 44 | entity = entity, 45 | force_x = force_x, 46 | force_y = force_y 47 | } 48 | self.world:addEntity({ physics_command = physics_command }) 49 | end 50 | 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /gui/gui_main/gui_main_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field gui_main_command component.gui_main_command|nil 5 | 6 | ---@class entity.gui_main_command: entity 7 | ---@field gui_main_command component.gui_main_command 8 | 9 | ---@class component.gui_main_command 10 | ---@field text string|nil 11 | ---@field level_complete boolean|nil 12 | 13 | ---@class system.gui_main_command: system 14 | ---@field entities entity.gui_main_command[] 15 | ---@field gui_main system.gui_main 16 | local M = {} 17 | 18 | 19 | ---@static 20 | ---@return system.gui_main_command 21 | function M.create_system(gui_main) 22 | local system = setmetatable(ecs.system(), { __index = M }) 23 | system.filter = ecs.requireAny("gui_main_command") 24 | system.gui_main = gui_main 25 | 26 | return system 27 | end 28 | 29 | 30 | ---@param entity entity.gui_main_command 31 | function M:onAdd(entity) 32 | local command = entity.gui_main_command 33 | if command then 34 | self:process_command(command) 35 | self.world:removeEntity(entity) 36 | end 37 | end 38 | 39 | 40 | ---@param command component.gui_main_command 41 | function M:process_command(command) 42 | if command.text then 43 | for _, entity in ipairs(self.gui_main.entities) do 44 | entity.gui_main.bindings.show_text:trigger(command.text) 45 | end 46 | end 47 | 48 | if command.level_complete then 49 | for _, entity in ipairs(self.gui_main.entities) do 50 | entity.gui_main.bindings.level_completed:trigger() 51 | end 52 | end 53 | end 54 | 55 | 56 | return M 57 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Defold Protocol Buffer Text Files (https://github.com/github/linguist/issues/5091) 2 | *.animationset linguist-language=JSON5 3 | *.atlas linguist-language=JSON5 4 | *.camera linguist-language=JSON5 5 | *.collection linguist-language=JSON5 6 | *.collectionfactory linguist-language=JSON5 7 | *.collectionproxy linguist-language=JSON5 8 | *.collisionobject linguist-language=JSON5 9 | *.cubemap linguist-language=JSON5 10 | *.display_profiles linguist-language=JSON5 11 | *.factory linguist-language=JSON5 12 | *.font linguist-language=JSON5 13 | *.gamepads linguist-language=JSON5 14 | *.go linguist-language=JSON5 15 | *.gui linguist-language=JSON5 16 | *.input_binding linguist-language=JSON5 17 | *.label linguist-language=JSON5 18 | *.material linguist-language=JSON5 19 | *.mesh linguist-language=JSON5 20 | *.model linguist-language=JSON5 21 | *.particlefx linguist-language=JSON5 22 | *.render linguist-language=JSON5 23 | *.sound linguist-language=JSON5 24 | *.sprite linguist-language=JSON5 25 | *.spinemodel linguist-language=JSON5 26 | *.spinescene linguist-language=JSON5 27 | *.texture_profiles linguist-language=JSON5 28 | *.tilemap linguist-language=JSON5 29 | *.tilesource linguist-language=JSON5 30 | 31 | # Defold JSON Files 32 | *.buffer linguist-language=JSON 33 | 34 | # Defold GLSL Shaders 35 | *.fp linguist-language=GLSL 36 | *.vp linguist-language=GLSL 37 | 38 | # Defold Lua Files 39 | *.editor_script linguist-language=Lua 40 | *.render_script linguist-language=Lua 41 | *.script linguist-language=Lua 42 | *.gui_script linguist-language=Lua 43 | -------------------------------------------------------------------------------- /game/objects/bullet/bullet.collection: -------------------------------------------------------------------------------- 1 | name: "bullet" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "components {\n" 6 | " id: \"explosion\"\n" 7 | " component: \"/game/objects/explosion/explosion.particlefx\"\n" 8 | "}\n" 9 | "embedded_components {\n" 10 | " id: \"sprite\"\n" 11 | " type: \"sprite\"\n" 12 | " data: \"default_animation: \\\"ui_circle_32\\\"\\n" 13 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 14 | "attributes {\\n" 15 | " name: \\\"color\\\"\\n" 16 | " double_values {\\n" 17 | " v: 1.0\\n" 18 | " v: 1.0\\n" 19 | " v: 1.0\\n" 20 | " v: 1.0\\n" 21 | " }\\n" 22 | "}\\n" 23 | "textures {\\n" 24 | " sampler: \\\"texture_sampler\\\"\\n" 25 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 26 | "}\\n" 27 | "\"\n" 28 | "}\n" 29 | "embedded_components {\n" 30 | " id: \"collisionobject\"\n" 31 | " type: \"collisionobject\"\n" 32 | " data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n" 33 | "mass: 0.003\\n" 34 | "friction: 0.1\\n" 35 | "restitution: 1.0\\n" 36 | "group: \\\"bullet\\\"\\n" 37 | "mask: \\\"solid\\\"\\n" 38 | "embedded_collision_shape {\\n" 39 | " shapes {\\n" 40 | " shape_type: TYPE_SPHERE\\n" 41 | " position {\\n" 42 | " }\\n" 43 | " rotation {\\n" 44 | " }\\n" 45 | " index: 0\\n" 46 | " count: 1\\n" 47 | " }\\n" 48 | " data: 16.811594\\n" 49 | "}\\n" 50 | "locked_rotation: true\\n" 51 | "bullet: true\\n" 52 | "\"\n" 53 | "}\n" 54 | "" 55 | } 56 | -------------------------------------------------------------------------------- /annotations/defold_annotations/html5.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | HTML5 API documentation 6 | 7 | HTML5 platform specific functions. 8 | The following functions are only available on HTML5 builds, the html5.* Lua namespace will not be available on other platforms. 9 | --]] 10 | 11 | ---@diagnostic disable: lowercase-global 12 | ---@diagnostic disable: missing-return 13 | ---@diagnostic disable: duplicate-doc-param 14 | ---@diagnostic disable: duplicate-set-field 15 | 16 | ---@class defold_api.html5 17 | html5 = {} 18 | 19 | ---Executes the supplied string as JavaScript inside the browser. 20 | ---A call to this function is blocking, the result is returned as-is, as a string. 21 | ---(Internally this will execute the string using the eval() JavaScript function.) 22 | ---@param code string Javascript code to run 23 | ---@return string result result as string 24 | function html5.run(code) end 25 | 26 | ---Set a JavaScript interaction listener callaback from lua that will be 27 | ---invoked when a user interacts with the web page by clicking, touching or typing. 28 | ---The callback can then call DOM restricted actions like requesting a pointer lock, 29 | ---or start playing sounds the first time the callback is invoked. 30 | ---@param callback fun(self)|nil The interaction callback. Pass an empty function or nil if you no longer wish to receive callbacks. 31 | --- 32 | ---self 33 | ---object The calling script 34 | --- 35 | function html5.set_interaction_listener(callback) end 36 | 37 | return html5 -------------------------------------------------------------------------------- /game/objects/bullet/bullet_shotgun.collection: -------------------------------------------------------------------------------- 1 | name: "bullet" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "components {\n" 6 | " id: \"explosion\"\n" 7 | " component: \"/game/objects/explosion/explosion.particlefx\"\n" 8 | "}\n" 9 | "embedded_components {\n" 10 | " id: \"sprite\"\n" 11 | " type: \"sprite\"\n" 12 | " data: \"default_animation: \\\"ui_circle_32\\\"\\n" 13 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 14 | "attributes {\\n" 15 | " name: \\\"color\\\"\\n" 16 | " double_values {\\n" 17 | " v: 1.0\\n" 18 | " v: 1.0\\n" 19 | " v: 1.0\\n" 20 | " v: 1.0\\n" 21 | " }\\n" 22 | "}\\n" 23 | "textures {\\n" 24 | " sampler: \\\"texture_sampler\\\"\\n" 25 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 26 | "}\\n" 27 | "\"\n" 28 | "}\n" 29 | "embedded_components {\n" 30 | " id: \"collisionobject\"\n" 31 | " type: \"collisionobject\"\n" 32 | " data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n" 33 | "mass: 0.1\\n" 34 | "friction: 0.2\\n" 35 | "restitution: 1.0\\n" 36 | "group: \\\"bullet\\\"\\n" 37 | "mask: \\\"solid\\\"\\n" 38 | "embedded_collision_shape {\\n" 39 | " shapes {\\n" 40 | " shape_type: TYPE_SPHERE\\n" 41 | " position {\\n" 42 | " }\\n" 43 | " rotation {\\n" 44 | " }\\n" 45 | " index: 0\\n" 46 | " count: 1\\n" 47 | " }\\n" 48 | " data: 16.811594\\n" 49 | "}\\n" 50 | "locked_rotation: true\\n" 51 | "bullet: true\\n" 52 | "\"\n" 53 | "}\n" 54 | "" 55 | } 56 | -------------------------------------------------------------------------------- /systems/color/color_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field color_command component.color_command|nil 5 | 6 | ---@class entity.color_command: entity 7 | ---@field color_command component.color_command 8 | 9 | ---@class component.color_command 10 | ---@field entity entity 11 | ---@field color vector4 12 | 13 | ---@class system.color_command: system 14 | ---@field entities entity.color_command[] 15 | ---@field color system.color 16 | local M = {} 17 | 18 | 19 | ---@static 20 | ---@param color system.color 21 | ---@return system.color_command 22 | function M.create_system(color) 23 | local system = ecs.system() 24 | system.filter = ecs.requireAny("color_command") 25 | system.color = color 26 | system.id = "color_command" 27 | 28 | return setmetatable(system, { __index = M }) 29 | end 30 | 31 | 32 | ---@param entity entity.color_command 33 | function M:onAdd(entity) 34 | local command = entity.color_command 35 | if command then 36 | self:process_command(command) 37 | end 38 | 39 | self.world:removeEntity(entity) 40 | end 41 | 42 | 43 | ---@param command component.color_command 44 | function M:process_command(command) 45 | local entity = command.entity 46 | local color = command.color 47 | if entity and color then 48 | entity.color.color = color 49 | entity.color_command = nil 50 | 51 | ---@type component.color_event 52 | local color_event = { 53 | entity = entity, 54 | color = color, 55 | } 56 | self.world:addEntity({ color_event = color_event }) 57 | end 58 | end 59 | 60 | 61 | return M 62 | -------------------------------------------------------------------------------- /systems/collision/on_collision/on_collision_damage.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field on_collision_damage component.on_collision_damage|nil 5 | 6 | ---@class entity.on_collision_damage: entity 7 | ---@field on_collision_damage component.on_collision_damage 8 | 9 | ---@class component.on_collision_damage 10 | ---@field damage number 11 | 12 | ---@class system.on_collision_damage: system 13 | ---@field entities entity.on_collision_damage[] 14 | local M = {} 15 | 16 | 17 | ---@static 18 | ---@return system.on_collision_damage 19 | function M.create_system() 20 | local system = setmetatable(ecs.system(), { __index = M }) 21 | system.filter = ecs.requireAny("collision_event") 22 | 23 | return system 24 | end 25 | 26 | 27 | ---@param entity entity.on_collision_damage 28 | function M:onAdd(entity) 29 | local collision_event = entity.collision_event 30 | if collision_event then 31 | self:process_collision_event(collision_event) 32 | self.world:removeEntity(entity) 33 | end 34 | end 35 | 36 | 37 | ---@param collision_event component.collision_event 38 | function M:process_collision_event(collision_event) 39 | local entity = collision_event.entity 40 | local on_collision_damage = entity.on_collision_damage 41 | local other = collision_event.other 42 | if on_collision_damage and other and other.health then 43 | ---@type component.health_command 44 | local command = { 45 | entity = other, 46 | damage = on_collision_damage.damage 47 | } 48 | self.world:addEntity({ health_command = command }) 49 | end 50 | end 51 | 52 | 53 | return M 54 | -------------------------------------------------------------------------------- /annotations/event_annotations.lua: -------------------------------------------------------------------------------- 1 | ---@class event 2 | ---@field create fun(callback: function|nil, callback_context: any|nil): event 3 | ---@field subscribe fun(self: event, callback: function, callback_context: any|nil): boolean 4 | ---@field unsubscribe fun(self: event, callback: function, callback_context: any|nil): boolean 5 | ---@field is_subscribed fun(self: event, callback: function, callback_context: any|nil): boolean 6 | ---@field trigger fun(self: event, a: any, b: any, c: any, d: any, e: any, f: any, g: any, h: any, i: any, j: any): nil 7 | ---@field clear fun(self: event): nil 8 | ---@field is_empty fun(self: event): boolean 9 | 10 | ---@class events 11 | ---@field subscribe fun(event_name: string, callback: function, callback_context: any|nil): boolean 12 | ---@field unsubscribe fun(event_name: string, callback: function, callback_context: any|nil): boolean 13 | ---@field is_subscribed fun(event_name: string, callback: function, callback_context: any|nil): boolean 14 | ---@field trigger fun(event_name: string, ...: any): any @Result of the last callback 15 | ---@field clear fun(name: string): nil 16 | ---@field clear_all fun(): nil 17 | ---@field is_empty fun(name: string): boolean 18 | 19 | ---@class event.logger 20 | ---@field trace fun(logger: event.logger, message: string, data: any|nil) 21 | ---@field debug fun(logger: event.logger, message: string, data: any|nil) 22 | ---@field info fun(logger: event.logger, message: string, data: any|nil) 23 | ---@field warn fun(logger: event.logger, message: string, data: any|nil) 24 | ---@field error fun(logger: event.logger, message: string, data: any|nil) -------------------------------------------------------------------------------- /systems/window/window_event.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | local events = require("event.events") 3 | 4 | ---@class entity 5 | ---@field window_event component.window_event|nil 6 | 7 | ---@class entity.window_event: entity 8 | ---@field window_event component.window_event 9 | 10 | ---@class component.window_event 11 | ---@field is_focus_gained boolean 12 | ---@field is_focus_lost boolean 13 | ---@field is_resized boolean 14 | 15 | ---@class system.window_event: system 16 | ---@field entities entity.window_event[] 17 | local M = {} 18 | 19 | 20 | ---@static 21 | ---@return system.window_event 22 | function M.create_system() 23 | local system = setmetatable(ecs.system(), { __index = M }) 24 | system.filter = ecs.requireAll("window_event") 25 | system.id = "window_event" 26 | 27 | return system 28 | end 29 | 30 | 31 | function M:onAddToWorld() 32 | events.subscribe("window_event", self.on_window_event, self) 33 | end 34 | 35 | 36 | function M:onRemoveFromWorld() 37 | events.unsubscribe("window_event", self.on_window_event, self) 38 | end 39 | 40 | 41 | function M:on_window_event(event) 42 | ---@type component.window_event 43 | local window_event = { 44 | is_focus_gained = event == window.WINDOW_EVENT_FOCUS_GAINED, 45 | is_focus_lost = event == window.WINDOW_EVENT_FOCUS_LOST, 46 | is_resized = event == window.WINDOW_EVENT_RESIZED, 47 | } 48 | 49 | self.world:addEntity({ window_event = window_event }) 50 | end 51 | 52 | 53 | function M:postWrap() 54 | for index = #self.entities, 1, -1 do 55 | self.world:removeEntity(self.entities[index]) 56 | end 57 | end 58 | 59 | 60 | return M 61 | -------------------------------------------------------------------------------- /systems/level_loader/level_loader_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field level_loader_command component.level_loader_command|nil 5 | 6 | ---@class entity.level_loader_command: entity 7 | ---@field level_loader_command component.level_loader_command 8 | 9 | ---@class component.level_loader_command 10 | ---@field world_id string 11 | ---@field pack_id string|nil 12 | ---@field slot_id string|nil @If exists, will keep only one world with this slot_id, previous worlds will be removed 13 | ---@field offset_x number|nil 14 | ---@field offset_y number|nil 15 | 16 | ---@class system.level_loader_command: system 17 | ---@field entities entity.level_loader_command[] 18 | ---@field level_loader system.level_loader 19 | local M = {} 20 | 21 | 22 | ---@static 23 | ---@return system.level_loader_command 24 | function M.create_system(level_loader) 25 | local system = setmetatable(ecs.system(), { __index = M }) 26 | system.filter = ecs.requireAny("level_loader_command") 27 | system.level_loader = level_loader 28 | system.id = "level_loader_command" 29 | 30 | return system 31 | end 32 | 33 | 34 | ---@param entity entity.level_loader_command 35 | function M:onAdd(entity) 36 | local command = entity.level_loader_command 37 | if command then 38 | self:process_command(command) 39 | self.world:removeEntity(entity) 40 | end 41 | end 42 | 43 | 44 | ---@param command component.level_loader_command 45 | function M:process_command(command) 46 | self.level_loader:load_world(command.world_id, command.pack_id, command.offset_x, command.offset_y, command.slot_id) 47 | end 48 | 49 | 50 | return M 51 | -------------------------------------------------------------------------------- /systems/debug/debug_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field debug_command component.debug_command|nil 5 | 6 | ---@class entity.debug_command: entity 7 | ---@field debug_command component.debug_command 8 | 9 | ---@class component.debug_command 10 | ---@field toggle_profiler boolean|nil 11 | ---@field restart boolean|nil 12 | ---@field reset_game boolean|nil 13 | 14 | ---@class system.debug_command: system 15 | ---@field entities entity.debug_command[] 16 | ---@field debug system.debug 17 | local M = {} 18 | 19 | 20 | ---@static 21 | ---@param debug system.debug 22 | ---@return system.debug_command 23 | function M.create_system(debug) 24 | local system = setmetatable(ecs.system(), { __index = M }) 25 | system.filter = ecs.requireAny("debug_command") 26 | system.id = "debug_command" 27 | system.debug = debug 28 | 29 | return system 30 | end 31 | 32 | 33 | ---@param entity entity.debug_command 34 | function M:onAdd(entity) 35 | local command = entity.debug_command 36 | if command then 37 | self:process_command(entity, entity.debug_command) 38 | self.world:removeEntity(entity) 39 | end 40 | end 41 | 42 | 43 | ---@param entity entity.debug_command 44 | ---@param command component.debug_command 45 | function M:process_command(entity, command) 46 | if command.toggle_profiler then 47 | for _, e in ipairs(self.debug.entities) do 48 | self.debug:toggle_profiler(e) 49 | end 50 | end 51 | 52 | if command.restart then 53 | if html5 then 54 | html5.run('document.location.reload();') 55 | else 56 | msg.post("@system:", "reboot") 57 | end 58 | end 59 | end 60 | 61 | 62 | return M 63 | -------------------------------------------------------------------------------- /systems/target_tracker/on_target_count_command/on_target_count_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field on_target_count_command component.on_target_count_command|nil 5 | 6 | ---@class entity.on_target_count_command: entity 7 | ---@field on_target_count_command component.on_target_count_command 8 | 9 | ---@class component.on_target_count_command 10 | ---@field amount number 11 | ---@field command string 12 | 13 | ---@class system.on_target_count_command: system 14 | ---@field entities entity.on_target_count_command[] 15 | local M = {} 16 | 17 | 18 | ---@static 19 | ---@return system.on_target_count_command 20 | function M.create_system(on_target_count) 21 | local system = setmetatable(ecs.system(), { __index = M }) 22 | system.filter = ecs.requireAny("on_target_count_command", "target_tracker_event") 23 | system.on_target_count = on_target_count 24 | 25 | return system 26 | end 27 | 28 | 29 | ---@param entity entity.on_target_count_command 30 | function M:onAdd(entity) 31 | local target_tracker_event = entity.target_tracker_event 32 | if target_tracker_event then 33 | self:process_target_tracker_event(target_tracker_event) 34 | end 35 | end 36 | 37 | 38 | ---@param target_tracker_event component.target_tracker_event 39 | function M:process_target_tracker_event(target_tracker_event) 40 | for _, entity in ipairs(self.entities) do 41 | local command = entity.on_target_count_command 42 | if command then 43 | if target_tracker_event.target_count == command.amount then 44 | local data = json.decode(entity.on_target_count_command.command) 45 | self.world:addEntity(data) 46 | end 47 | end 48 | end 49 | end 50 | 51 | 52 | return M 53 | -------------------------------------------------------------------------------- /game/objects/pit/pit.collection: -------------------------------------------------------------------------------- 1 | name: "wall" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "embedded_components {\n" 6 | " id: \"sprite\"\n" 7 | " type: \"sprite\"\n" 8 | " data: \"default_animation: \\\"ui_circle_16\\\"\\n" 9 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 10 | "slice9 {\\n" 11 | " x: 8.0\\n" 12 | " y: 8.0\\n" 13 | " z: 8.0\\n" 14 | " w: 8.0\\n" 15 | "}\\n" 16 | "size {\\n" 17 | " x: 1080.0\\n" 18 | " y: 140.0\\n" 19 | "}\\n" 20 | "size_mode: SIZE_MODE_MANUAL\\n" 21 | "attributes {\\n" 22 | " name: \\\"color\\\"\\n" 23 | " double_values {\\n" 24 | " v: 0.38\\n" 25 | " v: 0.173\\n" 26 | " v: 0.173\\n" 27 | " v: 1.0\\n" 28 | " }\\n" 29 | "}\\n" 30 | "textures {\\n" 31 | " sampler: \\\"texture_sampler\\\"\\n" 32 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 33 | "}\\n" 34 | "\"\n" 35 | " position {\n" 36 | " z: -1.0\n" 37 | " }\n" 38 | "}\n" 39 | "embedded_components {\n" 40 | " id: \"collisionobject\"\n" 41 | " type: \"collisionobject\"\n" 42 | " data: \"type: COLLISION_OBJECT_TYPE_STATIC\\n" 43 | "mass: 0.0\\n" 44 | "friction: 0.1\\n" 45 | "restitution: 0.5\\n" 46 | "group: \\\"solid\\\"\\n" 47 | "mask: \\\"solid\\\"\\n" 48 | "mask: \\\"player\\\"\\n" 49 | "embedded_collision_shape {\\n" 50 | " shapes {\\n" 51 | " shape_type: TYPE_BOX\\n" 52 | " position {\\n" 53 | " }\\n" 54 | " rotation {\\n" 55 | " }\\n" 56 | " index: 0\\n" 57 | " count: 3\\n" 58 | " }\\n" 59 | " data: 541.17645\\n" 60 | " data: 65.76923\\n" 61 | " data: 10.0\\n" 62 | "}\\n" 63 | "\"\n" 64 | "}\n" 65 | "" 66 | } 67 | -------------------------------------------------------------------------------- /systems/physics_movement/physics_movement_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field physics_movement_command component.physics_movement_command|nil 5 | 6 | ---@class entity.physics_movement_command: entity 7 | ---@field physics_movement_command component.physics_movement_command 8 | 9 | ---@class component.physics_movement_command 10 | ---@field entity entity.physics_movement 11 | ---@field velocity_x number|nil 12 | ---@field velocity_y number|nil 13 | ---@field force_x number|nil 14 | ---@field force_y number|nil 15 | 16 | ---@class system.physics_movement_command: system 17 | ---@field entities entity.physics_movement_command[] 18 | ---@field physics_movement system.physics_movement 19 | local M = {} 20 | 21 | 22 | ---@static 23 | ---@return system.physics_movement_command 24 | function M.create_system(physics_movement) 25 | local system = setmetatable(ecs.system(), { __index = M }) 26 | system.filter = ecs.requireAny("physics_movement_command") 27 | system.physics_movement = physics_movement 28 | system.id = "physics_movement_command" 29 | 30 | return system 31 | end 32 | 33 | 34 | ---@param entity entity.physics_movement_command 35 | function M:onAdd(entity) 36 | local command = entity.physics_movement_command 37 | if command then 38 | self:process_command(command) 39 | self.world:removeEntity(entity) 40 | end 41 | end 42 | 43 | 44 | ---@param command component.physics_movement_command 45 | function M:process_command(command) 46 | local entity = command.entity 47 | local physics_movement = self.physics_movement 48 | 49 | if command.force_x or command.force_y then 50 | self.physics_movement:add_force(entity, command.force_x, command.force_y) 51 | end 52 | end 53 | 54 | 55 | return M 56 | -------------------------------------------------------------------------------- /systems/physics_movement/physics_movement.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local physics_movement_command = require("systems.physics_movement.physics_movement_command") 4 | 5 | local TEMP_APPLY_FORCE = { 6 | force = vmath.vector3(), 7 | position = vmath.vector3() 8 | } 9 | 10 | ---@class entity 11 | ---@field physics_movement component.physics_movement|nil 12 | 13 | ---@class entity.physics_movement: entity 14 | ---@field physics_movement component.physics_movement 15 | ---@field transform component.transform 16 | ---@field game_object component.game_object 17 | 18 | ---@class component.physics_movement 19 | ---@field velocity_x number 20 | ---@field velocity_y number 21 | ---@field friction number 22 | 23 | ---@class system.physics_movement: system 24 | ---@field entities entity.physics_movement[] 25 | local M = {} 26 | 27 | 28 | ---@static 29 | ---@return system.physics_movement, system.physics_movement_command 30 | function M.create_system() 31 | local system = setmetatable(ecs.processingSystem(), { __index = M }) 32 | system.filter = ecs.requireAll("physics_movement", "game_object") 33 | system.id = "physics_movement" 34 | 35 | return system, physics_movement_command.create_system(system) 36 | end 37 | 38 | 39 | ---@param entity entity.physics_movement 40 | ---@param x number|nil 41 | ---@param y number|nil 42 | function M:add_force(entity, x, y) 43 | local collision_url = msg.url(nil, entity.game_object.root, "collisionobject") 44 | 45 | TEMP_APPLY_FORCE.force.x = x or 0 46 | TEMP_APPLY_FORCE.force.y = y or 0 47 | TEMP_APPLY_FORCE.position.x = entity.transform.position_x 48 | TEMP_APPLY_FORCE.position.y = entity.transform.position_y 49 | 50 | msg.post(collision_url, "apply_force", TEMP_APPLY_FORCE) 51 | end 52 | 53 | 54 | return M 55 | -------------------------------------------------------------------------------- /systems/movement_controller/movement_controller.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local movement_controller_command = require("systems.movement_controller.movement_controller_command") 4 | 5 | ---@class entity 6 | ---@field movement_controller component.movement_controller|nil 7 | 8 | ---@class entity.movement_controller: entity 9 | ---@field movement_controller component.movement_controller 10 | ---@field physics component.physics 11 | 12 | ---@class component.movement_controller 13 | ---@field speed number 14 | ---@field movement_x number @Runtime 15 | ---@field movement_y number @Runtime 16 | 17 | ---@class system.movement_controller: system 18 | ---@field entities entity.movement_controller[] 19 | local M = {} 20 | 21 | 22 | ---@static 23 | ---@return system.movement_controller, system.movement_controller_command 24 | function M.create_system() 25 | local system = setmetatable(ecs.processingSystem(), { __index = M }) 26 | system.filter = ecs.requireAll("movement_controller", "physics") 27 | system.id = "movement_controller" 28 | 29 | return system, movement_controller_command.create_system(system) 30 | end 31 | 32 | 33 | function M:process(entity, dt) 34 | local movement_controller = entity.movement_controller 35 | 36 | local speed = movement_controller.speed 37 | local movement_x = movement_controller.movement_x 38 | local movement_y = movement_controller.movement_y 39 | 40 | if movement_x ~= 0 or movement_y ~= 0 then 41 | ---@type component.physics_command 42 | local physics_command = { 43 | entity = entity, 44 | } 45 | physics_command.force_x = movement_x * speed * dt * 60 46 | physics_command.force_y = movement_y * speed * dt * 60 47 | 48 | self.world:addEntity({ physics_command = physics_command }) 49 | end 50 | end 51 | 52 | 53 | return M 54 | -------------------------------------------------------------------------------- /detiled/detiled.lua: -------------------------------------------------------------------------------- 1 | local detiled_internal = require("detiled.detiled_internal") 2 | local detiled_decore = require("detiled.detiled_decore") 3 | 4 | ---@class detiled 5 | local M = {} 6 | 7 | 8 | ---@param logger_instance logger|nil 9 | function M.set_logger(logger_instance) 10 | detiled_internal.logger = logger_instance 11 | end 12 | 13 | 14 | ---@param tilesets_path string 15 | ---@return decore.entities_pack_data[]|nil 16 | function M.get_entities_packs_data(tilesets_path) 17 | local tileset_list = detiled_internal.load_json(tilesets_path) 18 | if not tileset_list then 19 | return 20 | end 21 | 22 | local entity_packs = {} 23 | for index = 1, #tileset_list.tilesets do 24 | local tileset_path = tileset_list.tilesets[index] 25 | 26 | local entities = detiled_decore.create_entities_from_tiled_tileset(tileset_path) 27 | if entities then 28 | table.insert(entity_packs, entities) 29 | end 30 | end 31 | 32 | return entity_packs 33 | end 34 | 35 | 36 | ---@param maps_list_path string 37 | ---@return decore.worlds_pack_data[]|nil 38 | function M.get_worlds_packs_data(maps_list_path) 39 | local map_list = detiled_internal.load_json(maps_list_path) 40 | if not map_list then 41 | return 42 | end 43 | 44 | local world_packs = {} 45 | for pack_id, maps_table in pairs(map_list) do 46 | ---@type decore.worlds_pack_data 47 | local world_pack = { 48 | pack_id = pack_id, 49 | worlds = {} 50 | } 51 | 52 | for map_id, map_path in pairs(maps_table) do 53 | local worlds = detiled_decore.create_worlds_from_tiled_map(map_id, map_path) 54 | if worlds then 55 | for world_id, world in pairs(worlds) do 56 | world_pack.worlds[world_id] = world 57 | end 58 | end 59 | end 60 | 61 | table.insert(world_packs, world_pack) 62 | end 63 | 64 | return world_packs 65 | end 66 | 67 | 68 | return M 69 | -------------------------------------------------------------------------------- /resources/panthera_atlases/game.json: -------------------------------------------------------------------------------- 1 | {"data":{"atlas_id":"game","atlas_path":"/Users/insality/code/defold/shooting_circles/assets/atlases/game_shooting_circle.atlas","images":[{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/ui_circle_8.png","height":8,"image_id":"ui_circle_8","is_external":true,"width":8},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/ui_circle_16.png","height":16,"image_id":"ui_circle_16","is_external":true,"width":16},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/ui_circle_32.png","height":32,"image_id":"ui_circle_32","is_external":true,"width":32},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/ui_circle_64.png","height":64,"image_id":"ui_circle_64","is_external":true,"width":64},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/arena_background.png","height":200,"image_id":"arena_background","is_external":true,"width":200},{"filepath":"/Users/insality/code/defold/shooting_circles/tiled/images/wall.png","height":50,"image_id":"wall","is_external":true,"width":200},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/button.png","height":100,"image_id":"button","is_external":true,"width":100},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/icon_arrow.png","height":172,"image_id":"icon_arrow","is_external":true,"width":153},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/empty.png","height":12,"image_id":"empty","is_external":true,"width":12},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/pixel.png","height":4,"image_id":"pixel","is_external":true,"width":4},{"filepath":"/Users/insality/code/defold/shooting_circles/assets/images/enemy_rectangle.png","height":64,"image_id":"enemy_rectangle","is_external":true,"width":128}]},"format":"json","type":"atlas","version":1} -------------------------------------------------------------------------------- /annotations/defold_annotations/collectionproxy.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Collection proxy API documentation 6 | 7 | Messages for controlling and interacting with collection proxies 8 | which are used to dynamically load collections into the runtime. 9 | --]] 10 | 11 | ---@diagnostic disable: lowercase-global 12 | ---@diagnostic disable: missing-return 13 | ---@diagnostic disable: duplicate-doc-param 14 | ---@diagnostic disable: duplicate-set-field 15 | 16 | ---@class defold_api.collectionproxy 17 | collectionproxy = {} 18 | 19 | ---return an indexed table of resources for a collection proxy. Each 20 | ---entry is a hexadecimal string that represents the data of the specific 21 | ---resource. This representation corresponds with the filename for each 22 | ---individual resource that is exported when you bundle an application with 23 | ---LiveUpdate functionality. 24 | ---@param collectionproxy url the collectionproxy to check for resources. 25 | ---@return string[] resources the resources 26 | function collectionproxy.get_resources(collectionproxy) end 27 | 28 | ---return an array of missing resources for a collection proxy. Each 29 | ---entry is a hexadecimal string that represents the data of the specific 30 | ---resource. This representation corresponds with the filename for each 31 | ---individual resource that is exported when you bundle an application with 32 | ---LiveUpdate functionality. It should be considered good practise to always 33 | ---check whether or not there are any missing resources in a collection proxy 34 | ---before attempting to load the collection proxy. 35 | ---@param collectionproxy url the collectionproxy to check for missing 36 | ---resources. 37 | ---@return string[] resources the missing resources 38 | function collectionproxy.missing_resources(collectionproxy) end 39 | 40 | return collectionproxy -------------------------------------------------------------------------------- /game/objects/bullet/rocket.collection: -------------------------------------------------------------------------------- 1 | name: "bullet" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | data: "components {\n" 6 | " id: \"explosion_rocket\"\n" 7 | " component: \"/game/objects/explosion/explosion_rocket.particlefx\"\n" 8 | "}\n" 9 | "embedded_components {\n" 10 | " id: \"sprite\"\n" 11 | " type: \"sprite\"\n" 12 | " data: \"default_animation: \\\"ui_circle_32\\\"\\n" 13 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 14 | "slice9 {\\n" 15 | " y: 16.0\\n" 16 | " w: 16.0\\n" 17 | "}\\n" 18 | "size {\\n" 19 | " x: 32.0\\n" 20 | " y: 64.0\\n" 21 | "}\\n" 22 | "size_mode: SIZE_MODE_MANUAL\\n" 23 | "attributes {\\n" 24 | " name: \\\"color\\\"\\n" 25 | " double_values {\\n" 26 | " v: 1.0\\n" 27 | " v: 1.0\\n" 28 | " v: 1.0\\n" 29 | " v: 1.0\\n" 30 | " }\\n" 31 | "}\\n" 32 | "textures {\\n" 33 | " sampler: \\\"texture_sampler\\\"\\n" 34 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 35 | "}\\n" 36 | "\"\n" 37 | "}\n" 38 | "embedded_components {\n" 39 | " id: \"collisionobject\"\n" 40 | " type: \"collisionobject\"\n" 41 | " data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n" 42 | "mass: 1.0\\n" 43 | "friction: 0.2\\n" 44 | "restitution: 1.0\\n" 45 | "group: \\\"bullet\\\"\\n" 46 | "mask: \\\"solid\\\"\\n" 47 | "embedded_collision_shape {\\n" 48 | " shapes {\\n" 49 | " shape_type: TYPE_SPHERE\\n" 50 | " position {\\n" 51 | " y: 15.0\\n" 52 | " }\\n" 53 | " rotation {\\n" 54 | " }\\n" 55 | " index: 0\\n" 56 | " count: 1\\n" 57 | " }\\n" 58 | " shapes {\\n" 59 | " shape_type: TYPE_SPHERE\\n" 60 | " position {\\n" 61 | " y: -15.0\\n" 62 | " }\\n" 63 | " rotation {\\n" 64 | " }\\n" 65 | " index: 1\\n" 66 | " count: 1\\n" 67 | " }\\n" 68 | " data: 16.811594\\n" 69 | " data: 16.811594\\n" 70 | "}\\n" 71 | "bullet: true\\n" 72 | "\"\n" 73 | "}\n" 74 | "" 75 | } 76 | -------------------------------------------------------------------------------- /annotations/defold_annotations/http.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | HTTP API documentation 6 | 7 | Functions for performing HTTP and HTTPS requests. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---@class defold_api.http 16 | http = {} 17 | 18 | ---Perform a HTTP/HTTPS request. 19 | --- If no timeout value is passed, the configuration value "network.http_timeout" is used. If that is not set, the timeout value is 0 (which blocks indefinitely). 20 | ---@param url string target url 21 | ---@param method string HTTP/HTTPS method, e.g. "GET", "PUT", "POST" etc. 22 | ---@param callback fun(self, id, response) response callback function 23 | --- 24 | ---self 25 | ---object The script instance 26 | ---id 27 | ---hash Internal message identifier. Do not use! 28 | ---response 29 | ---table The response data. Contains the fields: 30 | --- 31 | --- 32 | ---number status: the status of the response 33 | ---string response: the response data (if not saved on disc) 34 | ---table headers: all the returned headers 35 | ---string path: the stored path (if saved to disc) 36 | ---string error: if any unforeseen errors occurred (e.g. file I/O) 37 | --- 38 | ---@param headers table|nil optional table with custom headers 39 | ---@param post_data string|nil optional data to send 40 | ---@param options table|nil optional table with request parameters. Supported entries: 41 | --- 42 | ---number timeout: timeout in seconds 43 | ---string path: path on disc where to download the file. Only overwrites the path if status is 200. Path should be absolute 44 | ---boolean ignore_cache: don't return cached data if we get a 304. Not available in HTML5 build 45 | ---boolean chunked_transfer: use chunked transfer encoding for https requests larger than 16kb. Defaults to true. Not available in HTML5 build 46 | --- 47 | function http.request(url, method, callback, headers, post_data, options) end 48 | 49 | return http -------------------------------------------------------------------------------- /systems/health/test_health.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@param world world 4 | ---@param component_name string 5 | ---@return entity|nil 6 | local function get_entity_with_component(world, component_name) 7 | local entities = world.entities 8 | for _, entity in ipairs(entities) do 9 | if entity[component_name] then 10 | return entity 11 | end 12 | end 13 | 14 | -- We trying to look also in entities which will be changed in this frame 15 | local entities2c = world.entitiesToChange 16 | for _, entity in ipairs(entities2c) do 17 | if entity[component_name] then 18 | return entity 19 | end 20 | end 21 | 22 | return nil 23 | end 24 | 25 | 26 | return function() 27 | local system_health 28 | 29 | describe("System Health", function() 30 | local world = {} 31 | before(function() 32 | system_health = require("systems.health.health") 33 | 34 | world = ecs.world() 35 | world:add(system_health.create_system()) 36 | end) 37 | 38 | it("Should set current health", function() 39 | ---@type entity 40 | local entity = { health = { health = 100 } } 41 | world:add(entity) 42 | world:update() 43 | 44 | assert(entity.health.current_health == 100) 45 | end) 46 | 47 | it("Should catch health command", function() 48 | ---@type entity 49 | local entity = { health = { health = 100 } } 50 | world:add(entity) 51 | world:update() 52 | 53 | world:addEntity({ health_command = { entity = entity, damage = 10 } }) 54 | world:update() 55 | 56 | assert(entity.health.current_health == 90) 57 | end) 58 | 59 | it("Should produce health_event", function() 60 | ---@type entity 61 | local entity = { health = { health = 100 } } 62 | world:add(entity) 63 | world:update() 64 | 65 | world:addEntity({ health_command = { entity = entity, damage = 10 } }) 66 | world:update() 67 | 68 | local event_entity = get_entity_with_component(world, "health_event") 69 | assert(event_entity) 70 | assert(event_entity.health_event.entity == entity) 71 | assert(event_entity.health_event.damage == 10) 72 | end) 73 | end) 74 | end 75 | -------------------------------------------------------------------------------- /systems/level_loader/level_loader.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | local decore = require("decore.decore") 3 | 4 | local logger = decore.get_logger("system.level_loader") 5 | 6 | local level_loader_command = require("systems.level_loader.level_loader_command") 7 | 8 | ---@class system.level_loader: system 9 | ---@field loaded_world_list table @slot_id -> entity[] 10 | local M = {} 11 | 12 | 13 | ---@static 14 | ---@return system.level_loader, system.level_loader_command 15 | function M.create_system() 16 | local system = setmetatable(ecs.system(), { __index = M }) 17 | system.id = "level_loader" 18 | 19 | system.loaded_world_list = {} 20 | 21 | return system, level_loader_command.create_system(system) 22 | end 23 | 24 | 25 | ---@param world_id string 26 | ---@param pack_id string|nil 27 | ---@param offset_x number|nil 28 | ---@param offset_y number|nil 29 | ---@param slot_id string|nil 30 | function M:load_world(world_id, pack_id, offset_x, offset_y, slot_id) 31 | logger:debug("load_world", { world_id = world_id, 32 | pack_id = pack_id, 33 | offset_x = offset_x, 34 | offset_y = offset_y, 35 | slot_id = slot_id 36 | }) 37 | 38 | offset_x = offset_x or 0 39 | offset_y = offset_y or 0 40 | 41 | local entities = decore.create_world(world_id, pack_id) 42 | if not entities then 43 | logger:error("Failed to load world", world_id) 44 | return 45 | end 46 | 47 | -- Remove old world 48 | if slot_id then 49 | if self.loaded_world_list[slot_id] then 50 | local world_entities = self.loaded_world_list[slot_id] 51 | for index = 1, #world_entities do 52 | self.world:removeEntity(world_entities[index]) 53 | end 54 | end 55 | 56 | self.loaded_world_list[slot_id] = entities 57 | end 58 | 59 | -- Spawn new world 60 | for index = 1, #entities do 61 | local new_entity = entities[index] 62 | 63 | if new_entity.transform then 64 | new_entity.transform.position_x = new_entity.transform.position_x + offset_x 65 | new_entity.transform.position_y = new_entity.transform.position_y + offset_y 66 | end 67 | 68 | self.world:addEntity(new_entity) 69 | end 70 | end 71 | 72 | 73 | return M 74 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | ".deployer_cache": true, 4 | ".github": true, 5 | ".internal": true, 6 | "**/.DS_Store": true, 7 | "**/.git": true, 8 | "**/.gui": true, 9 | "**/node_modules": true, 10 | "build": true, 11 | "bundle": true, 12 | "dist": true, 13 | "input": true, 14 | "LICENSE": true, 15 | "node_modules": true 16 | }, 17 | 18 | "glsllint.additionalStageAssociations": { 19 | ".fp": "frag", 20 | ".vp": "vert" 21 | }, 22 | 23 | "files.associations": { 24 | "*.gui_script": "lua", 25 | "*.script": "lua", 26 | "*.render_script": "lua", 27 | "*.project": "ini", 28 | "*.editor_script": "lua", 29 | "*.fp": "glsl", 30 | "*.vp": "glsl", 31 | "*.go": "textproto", 32 | "*.animationset": "textproto", 33 | "*.atlas": "textproto", 34 | "*.buffer": "json", 35 | "*.camera": "textproto", 36 | "*.collection": "textproto", 37 | "*.collectionfactory": "textproto", 38 | "*.collectionproxy": "textproto", 39 | "*.collisionobject": "textproto", 40 | "*.display_profiles": "textproto", 41 | "*.factory": "textproto", 42 | "*.gamepads": "textproto", 43 | "*.gui": "textproto", 44 | "*.input_binding": "textproto", 45 | "*.label": "textproto", 46 | "*.material": "textproto", 47 | "*.mesh": "textproto", 48 | "*.model": "textproto", 49 | "*.particlefx": "textproto", 50 | "*.render": "textproto", 51 | "*.sound": "textproto", 52 | "*.spinemodel": "textproto", 53 | "*.spinescene": "textproto", 54 | "*.sprite": "textproto", 55 | "*.texture_profiles": "textproto", 56 | "*.tilemap": "textproto", 57 | "*.tilesource": "textproto", 58 | "*.manifest": "textproto" 59 | }, 60 | 61 | "Lua.workspace.checkThirdParty": false, 62 | 63 | "Lua.diagnostics.globals": [ 64 | "update", 65 | "on_message", 66 | "final", 67 | "on_input", 68 | "assert_equal", 69 | "defos", 70 | "lua_script_instance", 71 | "describe", 72 | "before", 73 | "after", 74 | "it", 75 | "assert_true", 76 | "hash", 77 | "pb", 78 | "fixed_update", 79 | "on_reload", 80 | "utf8", 81 | "clipboard", 82 | "spine", 83 | "init" 84 | ], 85 | 86 | "Lua.workspace.ignoreDir": [ 87 | ".vscode", 88 | "decore/templates", 89 | ], 90 | "Lua.runtime.version": "Lua 5.1" 91 | } 92 | -------------------------------------------------------------------------------- /systems/on_key_released/on_key_released.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local on_key_released_command = require("systems.on_key_released.on_key_released_command") 4 | 5 | ---@class entity 6 | ---@field on_key_released component.on_key_released|nil 7 | 8 | ---@class entity.on_key_released: entity 9 | ---@field on_key_released component.on_key_released 10 | 11 | ---@class component.on_key_released 12 | ---@field key_to_command_json string @JSON string table>. Will override key_to_command if exists 13 | ---@field key_to_command table> @ table>. 14 | 15 | ---@class system.on_key_released: system 16 | ---@field entities entity.on_key_released[] 17 | ---@field hash_to_string table 18 | local M = {} 19 | 20 | 21 | ---@static 22 | ---@return system.on_key_released, system.on_key_released_command 23 | function M.create_system() 24 | local system = setmetatable(ecs.system(), { __index = M }) 25 | system.filter = ecs.requireAll("on_key_released") 26 | system.id = "on_key_released" 27 | 28 | system.hash_to_string = {} 29 | 30 | return system, on_key_released_command.create_system(system) 31 | end 32 | 33 | 34 | ---@param entity entity.on_key_released 35 | function M:onAdd(entity) 36 | local on_key_released = entity.on_key_released 37 | 38 | if on_key_released.key_to_command_json then 39 | local data = json.decode(on_key_released.key_to_command_json) 40 | on_key_released.key_to_command = data 41 | end 42 | 43 | if on_key_released.key_to_command then 44 | for key_id, key_data in pairs(on_key_released.key_to_command) do 45 | local hash_id = hash(key_id) 46 | if not self.hash_to_string[hash_id] then 47 | self.hash_to_string[hash_id] = key_id 48 | end 49 | end 50 | end 51 | end 52 | 53 | 54 | ---@param entity entity.on_key_released 55 | function M:apply_input(entity, action_id, action) 56 | local command_data = entity.on_key_released.key_to_command 57 | local key_id = self.hash_to_string[action_id] 58 | if command_data[key_id] and action.released then 59 | local new_command = sys.deserialize(sys.serialize(command_data[key_id])) 60 | self.world:addEntity(new_command) 61 | end 62 | end 63 | 64 | 65 | return M 66 | -------------------------------------------------------------------------------- /input/game.input_binding: -------------------------------------------------------------------------------- 1 | key_trigger { 2 | input: KEY_R 3 | action: "key_r" 4 | } 5 | key_trigger { 6 | input: KEY_F 7 | action: "key_f" 8 | } 9 | key_trigger { 10 | input: KEY_G 11 | action: "key_g" 12 | } 13 | key_trigger { 14 | input: KEY_UP 15 | action: "move_up" 16 | } 17 | key_trigger { 18 | input: KEY_RIGHT 19 | action: "move_right" 20 | } 21 | key_trigger { 22 | input: KEY_LEFT 23 | action: "move_left" 24 | } 25 | key_trigger { 26 | input: KEY_DOWN 27 | action: "move_down" 28 | } 29 | key_trigger { 30 | input: KEY_SPACE 31 | action: "action" 32 | } 33 | key_trigger { 34 | input: KEY_BACKSPACE 35 | action: "cancel" 36 | } 37 | key_trigger { 38 | input: KEY_ESC 39 | action: "cancel" 40 | } 41 | key_trigger { 42 | input: KEY_1 43 | action: "key_1" 44 | } 45 | key_trigger { 46 | input: KEY_2 47 | action: "key_2" 48 | } 49 | key_trigger { 50 | input: KEY_P 51 | action: "key_p" 52 | } 53 | key_trigger { 54 | input: KEY_ESC 55 | action: "key_esc" 56 | } 57 | mouse_trigger { 58 | input: MOUSE_BUTTON_1 59 | action: "touch" 60 | } 61 | gamepad_trigger { 62 | input: GAMEPAD_LSTICK_LEFT 63 | action: "key_a" 64 | } 65 | gamepad_trigger { 66 | input: GAMEPAD_LSTICK_RIGHT 67 | action: "key_d" 68 | } 69 | gamepad_trigger { 70 | input: GAMEPAD_LSTICK_DOWN 71 | action: "key_s" 72 | } 73 | gamepad_trigger { 74 | input: GAMEPAD_LSTICK_UP 75 | action: "key_w" 76 | } 77 | gamepad_trigger { 78 | input: GAMEPAD_LPAD_DOWN 79 | action: "key_s" 80 | } 81 | gamepad_trigger { 82 | input: GAMEPAD_LPAD_UP 83 | action: "key_w" 84 | } 85 | gamepad_trigger { 86 | input: GAMEPAD_LPAD_LEFT 87 | action: "key_a" 88 | } 89 | gamepad_trigger { 90 | input: GAMEPAD_LPAD_RIGHT 91 | action: "key_d" 92 | } 93 | gamepad_trigger { 94 | input: GAMEPAD_RPAD_UP 95 | action: "restart" 96 | } 97 | gamepad_trigger { 98 | input: GAMEPAD_RSTICK_DOWN 99 | action: "key_s" 100 | } 101 | gamepad_trigger { 102 | input: GAMEPAD_RSTICK_UP 103 | action: "key_w" 104 | } 105 | gamepad_trigger { 106 | input: GAMEPAD_RSTICK_LEFT 107 | action: "key_a" 108 | } 109 | gamepad_trigger { 110 | input: GAMEPAD_RSTICK_RIGHT 111 | action: "key_d" 112 | } 113 | -------------------------------------------------------------------------------- /systems/transform/transform.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local transform_command = require("systems.transform.transform_command") 4 | local transform_event = require("systems.transform.transform_event") 5 | 6 | ---@class entity 7 | ---@field transform component.transform|nil 8 | 9 | ---@class entity.transform: entity 10 | ---@field transform component.transform 11 | 12 | ---@class entity.transform_command: entity 13 | ---@field transform component.transform 14 | 15 | ---@class component.transform 16 | ---@field position_x number 17 | ---@field position_y number 18 | ---@field position_z number 19 | ---@field size_x number 20 | ---@field size_y number 21 | ---@field size_z number 22 | ---@field scale_x number 23 | ---@field scale_y number 24 | ---@field scale_z number 25 | ---@field rotation number 26 | 27 | ---@class system.transform: system 28 | ---@field entities entity.transform[] 29 | local M = {} 30 | 31 | ---@static 32 | ---@return system.transform, system.transform_command, system.transform_event 33 | function M.create_system() 34 | local system = setmetatable(ecs.system(), { __index = M }) 35 | system.filter = ecs.requireAll("transform") 36 | system.id = "transform" 37 | 38 | return system, transform_command.create_system(system), transform_event.create_system() 39 | end 40 | 41 | 42 | ---@static 43 | ---Return node borders relative to the current node parent 44 | ---@param entity entity 45 | ---@return number, number, number, number @left, right, top, bottom 46 | function M.get_transform_borders(entity) 47 | local t = entity.transform --[[@as component.transform]] 48 | 49 | local left = t.position_x - t.size_x * 0.5 50 | local right = t.position_x + t.size_x * 0.5 51 | local top = t.position_y + t.size_y * 0.5 52 | local bottom = t.position_y - t.size_y * 0.5 53 | 54 | return left, right, top, bottom 55 | end 56 | 57 | 58 | ---@static 59 | ---Check if two entities are overlapping 60 | ---@param entity1 entity 61 | ---@param entity2 entity 62 | ---@return boolean 63 | function M.is_overlap(entity1, entity2) 64 | local left1, right1, top1, bottom1 = M.get_transform_borders(entity1) 65 | local left2, right2, top2, bottom2 = M.get_transform_borders(entity2) 66 | 67 | return left1 < right2 and right1 > left2 and top1 > bottom2 and bottom1 < top2 68 | end 69 | 70 | 71 | return M 72 | -------------------------------------------------------------------------------- /systems/explosion/explosion_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field explosion_command component.explosion_command|nil 5 | 6 | ---@class entity.explosion_command: entity 7 | ---@field explosion_command component.explosion_command 8 | 9 | ---@class component.explosion_command 10 | ---@field explosion component.explosion_command.explosion 11 | 12 | ---@class component.explosion_command.explosion 13 | ---@field power number 14 | ---@field position_x number 15 | ---@field position_y number 16 | ---@field distance number 17 | 18 | ---@class system.explosion_command: system 19 | ---@field entities entity.explosion_command[] 20 | ---@field explosion system.explosion 21 | local M = {} 22 | 23 | 24 | ---@static 25 | ---@return system.explosion_command 26 | function M.create_system(explosion) 27 | local system = setmetatable(ecs.system(), { __index = M }) 28 | system.filter = ecs.requireAny("explosion_command") 29 | system.explosion = explosion 30 | 31 | return system 32 | end 33 | 34 | 35 | ---@param entity entity.explosion_command 36 | function M:onAdd(entity) 37 | local command = entity.explosion_command 38 | if command then 39 | self:process_command(command) 40 | self.world:removeEntity(entity) 41 | end 42 | end 43 | 44 | 45 | ---@param command component.explosion_command 46 | function M:process_command(command) 47 | if command.explosion then 48 | local explosion = command.explosion 49 | local power = explosion.power 50 | local position_x = explosion.position_x 51 | local position_y = explosion.position_y 52 | 53 | -- Take all entities with movement and set velocity from explosion position 54 | for index = 1, #self.explosion.entities do 55 | local target_entity = self.explosion.entities[index] 56 | local target_x = target_entity.transform.position_x 57 | local target_y = target_entity.transform.position_y 58 | local distance = math.sqrt((target_x - position_x) ^ 2 + (target_y - position_y) ^ 2) 59 | if distance < explosion.distance then 60 | local adjusted_power = power * (1 - distance / explosion.distance) 61 | self.explosion:apply_explosion(target_entity, position_x, position_y, adjusted_power) 62 | end 63 | end 64 | 65 | sound.play("/sound#explosion", { 66 | speed = 0.95 + math.random() * 0.1, 67 | }) 68 | end 69 | end 70 | 71 | 72 | return M 73 | -------------------------------------------------------------------------------- /annotations/defold_annotations/msg.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Messaging API documentation 6 | 7 | Functions for passing messages and constructing URL objects. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---@class defold_api.msg 16 | msg = {} 17 | 18 | ---Post a message to a receiving URL. The most common case is to send messages 19 | ---to a component. If the component part of the receiver is omitted, the message 20 | ---is broadcast to all components in the game object. 21 | ---The following receiver shorthands are available: 22 | ---"." the current game object 23 | ---"#" the current component 24 | --- There is a 2 kilobyte limit to the message parameter table size. 25 | ---@param receiver string|url|hash The receiver must be a string in URL-format, a URL object or a hashed string. 26 | ---@param message_id string|hash The id must be a string or a hashed string. 27 | ---@param message table|nil a lua table with message parameters to send. 28 | function msg.post(receiver, message_id, message) end 29 | 30 | ---creates a new URL from separate arguments 31 | ---@param socket string|hash|nil socket of the URL 32 | ---@param path string|hash|nil path of the URL 33 | ---@param fragment string|hash|nil fragment of the URL 34 | ---@return url url a new URL 35 | function msg.url(socket, path, fragment) end 36 | 37 | ---The format of the string must be [socket:][path][#fragment], which is similar to a HTTP URL. 38 | ---When addressing instances: 39 | ---socket is the name of a valid world (a collection) 40 | ---path is the id of the instance, which can either be relative the instance of the calling script or global 41 | ---fragment would be the id of the desired component 42 | ---In addition, the following shorthands are available: 43 | ---"." the current game object 44 | ---"#" the current component 45 | ---@param urlstring string string to create the url from 46 | ---@return url url a new URL 47 | function msg.url(urlstring) end 48 | 49 | ---This is equivalent to msg.url(nil) or msg.url("#"), which creates an url to the current 50 | ---script component. 51 | ---@return url url a new URL 52 | function msg.url() end 53 | 54 | return msg -------------------------------------------------------------------------------- /gui/gui_main/gui_main.lua: -------------------------------------------------------------------------------- 1 | local bindings = require("gui.bindings") 2 | local ecs = require("decore.ecs") 3 | 4 | local gui_main_command = require("gui.gui_main.gui_main_command") 5 | 6 | ---@class entity 7 | ---@field gui_main component.gui_main|nil 8 | 9 | ---@class entity.gui_main: entity 10 | ---@field gui_main component.gui_main 11 | ---@field game_object component.game_object 12 | 13 | ---@class component.gui_main 14 | ---@field bindings gui.main.bindings 15 | ---@field current_level_index number 16 | 17 | ---@class system.gui_main: system 18 | ---@field entities entity.gui_main[] 19 | local M = {} 20 | 21 | local LEVELS = { 22 | "game.level1", 23 | "game.level2", 24 | "game.level3", 25 | "game.level4", 26 | "game.level5", 27 | "game.level6", 28 | "game.level7", 29 | "game.level8", 30 | "game.level9", 31 | "game.level10", 32 | "game.level11", 33 | "game.level12", 34 | "game.level13", 35 | "game.level14", 36 | } 37 | 38 | ---@static 39 | ---@return system.gui_main, system.gui_main_command 40 | function M.create_system() 41 | local system = setmetatable(ecs.system(), { __index = M }) 42 | system.filter = ecs.requireAll("gui_main", "game_object") 43 | 44 | return system, gui_main_command.create_system(system) 45 | end 46 | 47 | 48 | ---@param entity entity.gui_main 49 | function M:onAdd(entity) 50 | entity.gui_main.bindings = bindings.get(entity.game_object.root) --[[@as gui.main.bindings]] 51 | local gui_bindings = entity.gui_main.bindings 52 | 53 | entity.gui_main.current_level_index = 1 54 | 55 | gui_bindings.on_left:subscribe(function() 56 | local prev_index = entity.gui_main.current_level_index - 1 57 | if prev_index < 1 then 58 | prev_index = #LEVELS 59 | end 60 | entity.gui_main.current_level_index = prev_index 61 | self:spawn_world(LEVELS[prev_index]) 62 | end) 63 | 64 | gui_bindings.on_right:subscribe(function() 65 | local next_index = entity.gui_main.current_level_index + 1 66 | if next_index > #LEVELS then 67 | next_index = 1 68 | end 69 | entity.gui_main.current_level_index = next_index 70 | self:spawn_world(LEVELS[next_index]) 71 | end) 72 | 73 | self:spawn_world(LEVELS[entity.gui_main.current_level_index]) 74 | end 75 | 76 | 77 | function M:spawn_world(world_id) 78 | ---@type component.level_loader_command 79 | local level_loader_command = { 80 | world_id = world_id, 81 | slot_id = "level" 82 | } 83 | self.world:addEntity({ level_loader_command = level_loader_command }) 84 | end 85 | 86 | 87 | return M 88 | -------------------------------------------------------------------------------- /systems/movement_controller/movement_controller_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field movement_controller_command component.movement_controller_command|nil 5 | 6 | ---@class entity.movement_controller_command: entity 7 | ---@field movement_controller_command component.movement_controller_command 8 | 9 | ---@class component.movement_controller_command 10 | ---@field entity entity|nil 11 | 12 | ---@class system.movement_controller_command: system 13 | ---@field entities entity.movement_controller_command[] 14 | ---@field movement_controller system.movement_controller 15 | local M = {} 16 | 17 | 18 | local ACTION_ID_TO_SIDE = { 19 | [hash("key_w")] = { y = 1 }, 20 | [hash("key_s")] = { y = -1 }, 21 | [hash("key_a")] = { x = -1 }, 22 | [hash("key_d")] = { x = 1 }, 23 | [hash("key_up")] = { y = 1 }, 24 | [hash("key_down")] = { y = -1 }, 25 | [hash("key_left")] = { x = -1 }, 26 | [hash("key_right")] = { x = 1 }, 27 | } 28 | 29 | 30 | ---@static 31 | ---@return system.movement_controller_command 32 | function M.create_system(movement_controller) 33 | local system = setmetatable(ecs.system(), { __index = M }) 34 | system.filter = ecs.requireAny("input_event") 35 | system.movement_controller = movement_controller 36 | 37 | return system 38 | end 39 | 40 | 41 | ---@param entity entity.movement_controller_command 42 | function M:onAdd(entity) 43 | local input_event = entity.input_event 44 | if input_event then 45 | local action_id = input_event.action_id 46 | local side = ACTION_ID_TO_SIDE[action_id] 47 | if side then 48 | for _, e in ipairs(self.movement_controller.entities) do 49 | self:process_input_event(e, input_event) 50 | end 51 | end 52 | end 53 | end 54 | 55 | 56 | ---@param entity entity.movement_controller 57 | ---@param input_event component.input_event 58 | function M:process_input_event(entity, input_event) 59 | local action_id = input_event.action_id 60 | local action = input_event.action 61 | local movement_controller = entity.movement_controller 62 | 63 | local side = ACTION_ID_TO_SIDE[action_id] 64 | if action.pressed then 65 | if side.x then 66 | movement_controller.movement_x = side.x 67 | end 68 | if side.y then 69 | movement_controller.movement_y = side.y 70 | end 71 | end 72 | if action.released then 73 | if side.x then 74 | movement_controller.movement_x = 0 75 | end 76 | if side.y then 77 | movement_controller.movement_y = 0 78 | end 79 | end 80 | end 81 | 82 | 83 | return M 84 | -------------------------------------------------------------------------------- /game/spawner/spawner.go: -------------------------------------------------------------------------------- 1 | embedded_components { 2 | id: "player" 3 | type: "collectionfactory" 4 | data: "prototype: \"/game/objects/player/player.collection\"\n" 5 | "" 6 | } 7 | embedded_components { 8 | id: "bullet" 9 | type: "collectionfactory" 10 | data: "prototype: \"/game/objects/bullet/bullet.collection\"\n" 11 | "" 12 | } 13 | embedded_components { 14 | id: "explosion" 15 | type: "collectionfactory" 16 | data: "prototype: \"/game/objects/explosion/explosion.collection\"\n" 17 | "" 18 | } 19 | embedded_components { 20 | id: "enemy" 21 | type: "collectionfactory" 22 | data: "prototype: \"/game/objects/enemy/enemy.collection\"\n" 23 | "" 24 | } 25 | embedded_components { 26 | id: "background" 27 | type: "collectionfactory" 28 | data: "prototype: \"/game/objects/background/background.collection\"\n" 29 | "" 30 | } 31 | embedded_components { 32 | id: "wall" 33 | type: "collectionfactory" 34 | data: "prototype: \"/game/objects/wall/wall.collection\"\n" 35 | "" 36 | } 37 | embedded_components { 38 | id: "enemy_big" 39 | type: "collectionfactory" 40 | data: "prototype: \"/game/objects/enemy/enemy_big.collection\"\n" 41 | "" 42 | } 43 | embedded_components { 44 | id: "circle" 45 | type: "collectionfactory" 46 | data: "prototype: \"/game/objects/circle.collection\"\n" 47 | "" 48 | } 49 | embedded_components { 50 | id: "gui_main" 51 | type: "collectionfactory" 52 | data: "prototype: \"/gui/gui_main/gui_main.collection\"\n" 53 | "" 54 | } 55 | embedded_components { 56 | id: "damage_number" 57 | type: "collectionfactory" 58 | data: "prototype: \"/game/objects/damage_number/damage_number.collection\"\n" 59 | "" 60 | } 61 | embedded_components { 62 | id: "pit" 63 | type: "collectionfactory" 64 | data: "prototype: \"/game/objects/pit/pit.collection\"\n" 65 | "" 66 | } 67 | embedded_components { 68 | id: "wall_pit" 69 | type: "collectionfactory" 70 | data: "prototype: \"/game/objects/wall/wall_pit.collection\"\n" 71 | "" 72 | } 73 | embedded_components { 74 | id: "enemy_rectangle" 75 | type: "collectionfactory" 76 | data: "prototype: \"/game/objects/enemy/enemy_rectangle.collection\"\n" 77 | "" 78 | } 79 | embedded_components { 80 | id: "rocket" 81 | type: "collectionfactory" 82 | data: "prototype: \"/game/objects/bullet/rocket.collection\"\n" 83 | "" 84 | } 85 | embedded_components { 86 | id: "bullet_shotgun" 87 | type: "collectionfactory" 88 | data: "prototype: \"/game/objects/bullet/bullet_shotgun.collection\"\n" 89 | "" 90 | } 91 | -------------------------------------------------------------------------------- /resources/animations/button.json: -------------------------------------------------------------------------------- 1 | {"data":{"animations":[{"animation_id":"click","animation_keys":[{"duration":0.14,"easing":"outsine","end_value":-6,"key_type":"tween","node_id":"icon_arrow","property_id":"position_y","start_value":8},{"duration":0.15,"easing":"outsine","end_value":-2,"key_type":"tween","node_id":"group_arrow","property_id":"rotation_z"},{"duration":0.15,"easing":"outsine","end_value":1.1,"key_type":"tween","node_id":"group_arrow","property_id":"scale_x","start_value":1},{"duration":0.15,"easing":"outsine","end_value":1.1,"key_type":"tween","node_id":"group_arrow","property_id":"scale_y","start_value":1},{"duration":0.16,"easing":"outsine","end_value":1.7,"key_type":"tween","node_id":"root","property_id":"color_a","start_value":1},{"duration":0.16,"easing":"outquart","end_value":268,"key_type":"tween","node_id":"root","property_id":"size_y","start_value":278},{"duration":0.16,"easing":"outquart","end_value":298,"key_type":"tween","node_id":"root","property_id":"size_x","start_value":278},{"duration":0.18,"easing":"outsine","end_value":-13,"key_type":"tween","node_id":"icon_arrow_shadow","property_id":"position_y","start_value":-8},{"duration":0.29,"easing":"outsine","key_type":"tween","node_id":"group_arrow","property_id":"rotation_z","start_time":0.15,"start_value":-2},{"duration":0.29,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"group_arrow","property_id":"scale_x","start_time":0.15,"start_value":1.1},{"duration":0.29,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"group_arrow","property_id":"scale_y","start_time":0.15,"start_value":1.1},{"duration":0.22,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"root","property_id":"color_a","start_time":0.16,"start_value":1.7},{"duration":0.22,"easing":"insine","end_value":278,"key_type":"tween","node_id":"root","property_id":"size_x","start_time":0.16,"start_value":298},{"duration":0.22,"easing":"insine","end_value":278,"key_type":"tween","node_id":"root","property_id":"size_y","start_time":0.16,"start_value":268},{"duration":0.28,"easing":"outsine","end_value":-8,"key_type":"tween","node_id":"icon_arrow_shadow","property_id":"position_y","start_time":0.18,"start_value":-13},{"duration":0.29,"easing":"outback","end_value":8,"key_type":"tween","node_id":"icon_arrow","property_id":"position_y","start_time":0.21,"start_value":-6}],"duration":0.5}],"metadata":{"fps":60,"gizmo_steps":{"time":0.016667},"gui_path":"/gui/gui_main/button.gui","is_frame_view":true,"layers":[],"settings":{"font_size":40}},"nodes":[]},"format":"json","type":"animation_editor","version":1} -------------------------------------------------------------------------------- /systems/shooter_controller/shooter_controller_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field shooter_controller_command component.shooter_controller_command|nil 5 | 6 | ---@class entity.shooter_controller_command: entity 7 | ---@field shooter_controller_command component.shooter_controller_command 8 | 9 | ---@class component.shooter_controller_command 10 | ---@field entity entity|nil 11 | 12 | ---@class system.shooter_controller_command: system 13 | ---@field entities entity.shooter_controller_command[] 14 | ---@field shooter_controller system.shooter_controller 15 | local M = {} 16 | 17 | local HASH_TOUCH = hash("touch") 18 | local HASH_SPACE = hash("key_space") 19 | 20 | 21 | ---@static 22 | ---@return system.shooter_controller_command 23 | function M.create_system(shooter_controller) 24 | local system = setmetatable(ecs.system(), { __index = M }) 25 | system.filter = ecs.requireAny("input_event") 26 | system.shooter_controller = shooter_controller 27 | 28 | return system 29 | end 30 | 31 | 32 | ---@param entity entity.shooter_controller_command 33 | function M:onAdd(entity) 34 | local command = entity.shooter_controller_command 35 | if command then 36 | self:process_command(command) 37 | self.world:removeEntity(entity) 38 | end 39 | 40 | local input_event = entity.input_event 41 | if input_event then 42 | for _, e in ipairs(self.shooter_controller.entities) do 43 | self:process_input_event(e, input_event) 44 | end 45 | end 46 | end 47 | 48 | 49 | ---@param command component.shooter_controller_command 50 | function M:process_command(command) 51 | local entity = command.entity 52 | end 53 | 54 | 55 | ---@param entity entity.shooter_controller 56 | ---@param input_event component.input_event 57 | function M:process_input_event(entity, input_event) 58 | local action_id = input_event.action_id 59 | local action = input_event.action 60 | local sc = entity.shooter_controller 61 | 62 | if action.screen_x and action.screen_y then 63 | sc.last_screen_x = action.screen_x 64 | sc.last_screen_y = action.screen_y 65 | end 66 | 67 | if action_id == HASH_TOUCH or action_id == HASH_SPACE then 68 | if action.pressed then 69 | sc.burst_count_current = 0 70 | self.shooter_controller:shoot_at(entity, sc.last_screen_x, sc.last_screen_y) 71 | else 72 | if sc.is_auto_shoot then 73 | if sc.fire_rate_timer == 0 then 74 | self.shooter_controller:shoot_at(entity, sc.last_screen_x, sc.last_screen_y) 75 | end 76 | end 77 | end 78 | end 79 | end 80 | 81 | 82 | return M 83 | -------------------------------------------------------------------------------- /loader/loader.script: -------------------------------------------------------------------------------- 1 | local log = require("log.log") 2 | local event = require("event.event") 3 | local events = require("event.events") 4 | local decore = require("decore.decore") 5 | local detiled = require("detiled.detiled") 6 | local panthera = require("panthera.panthera") 7 | 8 | local HASH_START_GAME = hash("start_game") 9 | local HASH_PROXY_LOADED = hash("proxy_loaded") 10 | 11 | ---@class scene.loader 12 | 13 | 14 | ---@param self scene.loader 15 | local function init_random(self) 16 | math.randomseed(socket.gettime()) 17 | math.random() 18 | math.random() 19 | math.random() 20 | end 21 | 22 | 23 | ---@param self scene.loader 24 | local function init_logger(self) 25 | event.set_logger(log.get_logger("event")) 26 | decore.set_logger(log.get_logger("decore")) 27 | detiled.set_logger(log.get_logger("detiled")) 28 | panthera.set_logger(log.get_logger("panthera")) 29 | end 30 | 31 | 32 | ---@param self scene.loader 33 | local function init_decore(self) 34 | decore.register_components("/resources/components.json") 35 | decore.register_entities("/resources/entities.json") 36 | end 37 | 38 | 39 | ---@param self scene.loader 40 | local function init_detiled(self) 41 | local entities_packs_data = detiled.get_entities_packs_data("/resources/tilesets_list.json") 42 | if entities_packs_data then 43 | for _, pack_data in ipairs(entities_packs_data) do 44 | decore.register_entities(pack_data) 45 | end 46 | end 47 | 48 | local worlds_packs_data = detiled.get_worlds_packs_data("/resources/maps_list.json") 49 | if worlds_packs_data then 50 | for _, pack_data in ipairs(worlds_packs_data) do 51 | decore.register_worlds(pack_data) 52 | end 53 | end 54 | 55 | decore.print_loaded_packs_debug_info() 56 | end 57 | 58 | 59 | ---@param self scene.loader 60 | local function init_window_listener(self) 61 | window.set_listener(function(_, window_event) 62 | events.trigger("window_event", window_event) 63 | end) 64 | end 65 | 66 | 67 | ---@param self scene.loader 68 | function init(self) 69 | init_random(self) 70 | init_logger(self) 71 | init_decore(self) 72 | init_detiled(self) 73 | init_window_listener(self) 74 | 75 | msg.post(".", "start_game") 76 | end 77 | 78 | 79 | ---@param self scene.loader 80 | ---@param message_id hash 81 | ---@param message table 82 | ---@param sender url 83 | function on_message(self, message_id, message, sender) 84 | if message_id == HASH_START_GAME then 85 | msg.post("#game_proxy", "load") 86 | end 87 | 88 | if message_id == HASH_PROXY_LOADED then 89 | msg.post(sender, "init") 90 | msg.post(sender, "enable") 91 | msg.post(sender, "acquire_input_focus") 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /systems/health_circle_visual/health_circle_visual_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | local decore = require("decore.decore") 3 | 4 | ---@class entity 5 | ---@field health_circle_visual_command component.health_circle_visual_command|nil 6 | 7 | ---@class entity.health_circle_visual_command: entity 8 | ---@field health_circle_visual_command component.health_circle_visual_command 9 | 10 | ---@class component.health_circle_visual_command 11 | ---@field sprite_url string 12 | ---@field health_color string 13 | 14 | ---@class system.health_circle_visual_command: system 15 | ---@field entities entity.health_circle_visual_command[] 16 | ---@field health_circle_visual system.health_circle_visual 17 | local M = {} 18 | 19 | 20 | ---@static 21 | ---@return system.health_circle_visual_command 22 | function M.create_system(health_circle_visual) 23 | local system = setmetatable(ecs.system(), { __index = M }) 24 | system.filter = ecs.requireAny("health_event") 25 | system.health_circle_visual = health_circle_visual 26 | 27 | return system 28 | end 29 | 30 | 31 | ---@param entity entity.health_circle_visual_command 32 | function M:onAdd(entity) 33 | local health_event = entity.health_event 34 | if health_event then 35 | self:process_health_event(health_event) 36 | end 37 | end 38 | 39 | 40 | ---@param health_event component.health_event 41 | function M:process_health_event(health_event) 42 | local entity = health_event.entity 43 | if entity.health_circle_visual and health_event.damage then 44 | local progress = entity.health.current_health / entity.health.health 45 | ---@type component.panthera_command 46 | local panthera_command = { 47 | entity = entity, 48 | animation_id = "health", 49 | progress = progress, 50 | } 51 | self.world:addEntity({ panthera_command = panthera_command }) 52 | 53 | ---@type component.panthera_command 54 | local panthera_command = { 55 | entity = entity, 56 | detached = true, 57 | animation_id = "on_damage", 58 | } 59 | self.world:addEntity({ panthera_command = panthera_command }) 60 | 61 | -- Spawn damage number particle 62 | local damage_number_entity = decore.create_entity("damage_number") 63 | if damage_number_entity then 64 | local t = damage_number_entity.transform 65 | local et = entity.transform 66 | if et then 67 | t.position_x = et.position_x 68 | t.position_y = et.position_y + et.size_y/2 69 | t.position_z = et.position_z 70 | end 71 | 72 | damage_number_entity.damage_number.damage = math.abs(health_event.damage) 73 | 74 | self.world:addEntity(damage_number_entity) 75 | end 76 | end 77 | end 78 | 79 | 80 | return M 81 | -------------------------------------------------------------------------------- /annotations/defold_annotations/sprite.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Sprite API documentation 6 | --]] 7 | 8 | ---@diagnostic disable: lowercase-global 9 | ---@diagnostic disable: missing-return 10 | ---@diagnostic disable: duplicate-doc-param 11 | ---@diagnostic disable: duplicate-set-field 12 | 13 | ---@class defold_api.sprite 14 | sprite = {} 15 | 16 | ---Play an animation on a sprite component from its tile set 17 | ---An optional completion callback function can be provided that will be called when 18 | ---the animation has completed playing. If no function is provided, 19 | ---a animation_done message is sent to the script that started the animation. 20 | ---@param url string|hash|url the sprite that should play the animation 21 | ---@param id string|hash hashed id of the animation to play 22 | ---@param complete_function fun(self, message_id, message, sender)|nil function to call when the animation has completed. 23 | --- 24 | ---self 25 | ---object The current object. 26 | ---message_id 27 | ---hash The name of the completion message, "animation_done". 28 | ---message 29 | ---table Information about the completion: 30 | --- 31 | --- 32 | ---number current_tile - the current tile of the sprite. 33 | ---hash id - id of the animation that was completed. 34 | --- 35 | --- 36 | ---sender 37 | ---url The invoker of the callback: the sprite component. 38 | --- 39 | ---@param play_properties table|nil optional table with properties: 40 | --- 41 | ---offset 42 | ---number the normalized initial value of the animation cursor when the animation starts playing. 43 | ---playback_rate 44 | ---number the rate with which the animation will be played. Must be positive. 45 | --- 46 | function sprite.play_flipbook(url, id, complete_function, play_properties) end 47 | 48 | ---Sets horizontal flipping of the provided sprite's animations. 49 | ---The sprite is identified by its URL. 50 | ---If the currently playing animation is flipped by default, flipping it again will make it appear like the original texture. 51 | ---@param url string|hash|url the sprite that should flip its animations 52 | ---@param flip boolean true if the sprite should flip its animations, false if not 53 | function sprite.set_hflip(url, flip) end 54 | 55 | ---Sets vertical flipping of the provided sprite's animations. 56 | ---The sprite is identified by its URL. 57 | ---If the currently playing animation is flipped by default, flipping it again will make it appear like the original texture. 58 | ---@param url string|hash|url the sprite that should flip its animations 59 | ---@param flip boolean true if the sprite should flip its animations, false if not 60 | function sprite.set_vflip(url, flip) end 61 | 62 | return sprite -------------------------------------------------------------------------------- /annotations/defold_annotations/defold_types.lua: -------------------------------------------------------------------------------- 1 | ---@class action 2 | ---@field value number The amount of input given by the user. This is usually 1 for buttons and 0-1 for analogue inputs. This is not present for mouse movement. 3 | ---@field pressed boolean If the input was pressed this frame. This is not present for mouse movement. 4 | ---@field released boolean If the input was released this frame. This is not present for mouse movement. 5 | ---@field repeated boolean If the input was repeated this frame. This is similar to how a key on a keyboard is repeated when you hold it down. This is not present for mouse movement. 6 | ---@field x number The x value of a pointer device, if present. 7 | ---@field y number The y value of a pointer device, if present. 8 | ---@field screen_x number The screen space x value of a pointer device, if present. 9 | ---@field screen_y number The screen space y value of a pointer device, if present. 10 | ---@field dx number The change in x value of a pointer device, if present. 11 | ---@field dy number The change in y value of a pointer device, if present. 12 | ---@field screen_dx number The change in screen space x value of a pointer device, if present. 13 | ---@field screen_dy number The change in screen space y value of a pointer device, if present. 14 | ---@field gamepad number The index of the gamepad device that provided the input. 15 | ---@field touch touch[] List of touch input, one element per finger, if present. See table below about touch input 16 | 17 | ---@class touch 18 | ---@field id number A number identifying the touch input during its duration. 19 | ---@field pressed boolean True if the finger was pressed this frame. 20 | ---@field released boolean True if the finger was released this frame. 21 | ---@field tap_count number Number of taps, one for single, two for double-tap, etc 22 | ---@field x number The x touch location. 23 | ---@field y number The y touch location. 24 | ---@field dx number The change in x value. 25 | ---@field dy number The change in y value. 26 | ---@field acc_x number|nil Accelerometer x value (if present). 27 | ---@field acc_y number|nil Accelerometer y value (if present). 28 | ---@field acc_z number|nil Accelerometer z value (if present). 29 | 30 | ---@class utf8 31 | ---@field len fun(s: string) 32 | ---@field sub fun(s: string, start_index: number, length: number) 33 | ---@field reverse fun() 34 | ---@field char fun() 35 | ---@field unicode fun() 36 | ---@field gensub fun() 37 | ---@field byte fun() 38 | ---@field find fun() 39 | ---@field match fun(s: string, m: string) 40 | ---@field gmatch fun(s: string, m: string) 41 | ---@field gsub fun() 42 | ---@field dump fun() 43 | ---@field format fun() 44 | ---@field lower fun() 45 | ---@field upper fun() 46 | ---@field rep fun() 47 | -------------------------------------------------------------------------------- /annotations/defold_annotations/image.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Image API documentation 6 | 7 | Functions for creating image objects. 8 | --]] 9 | 10 | ---@diagnostic disable: lowercase-global 11 | ---@diagnostic disable: missing-return 12 | ---@diagnostic disable: duplicate-doc-param 13 | ---@diagnostic disable: duplicate-set-field 14 | 15 | ---@class defold_api.image 16 | image = {} 17 | 18 | ---luminance image type 19 | image.TYPE_LUMINANCE = nil 20 | 21 | ---luminance image type 22 | image.TYPE_LUMINANCE_ALPHA = nil 23 | 24 | ---RGB image type 25 | image.TYPE_RGB = nil 26 | 27 | ---RGBA image type 28 | image.TYPE_RGBA = nil 29 | 30 | ---Load image (PNG or JPEG) from buffer. 31 | ---@param buffer string image data buffer 32 | ---@param options table|nil An optional table containing parameters for loading the image. Supported entries: 33 | --- 34 | ---premultiply_alpha 35 | ---boolean True if alpha should be premultiplied into the color components. Defaults to false. 36 | ---flip_vertically 37 | ---boolean True if the image contents should be flipped vertically. Defaults to false. 38 | --- 39 | ---@return { width:number, height:number, type:constant, buffer:string }|nil image object or nil if loading fails. The object is a table with the following fields: 40 | --- 41 | ---number width: image width 42 | ---number height: image height 43 | ---constant type: image type 44 | ---image.TYPE_RGB 45 | ---image.TYPE_RGBA 46 | ---image.TYPE_LUMINANCE 47 | ---image.TYPE_LUMINANCE_ALPHA 48 | --- 49 | --- 50 | ---string buffer: the raw image data 51 | --- 52 | function image.load(buffer, options) end 53 | 54 | ---Load image (PNG or JPEG) from a string buffer. 55 | ---@param buffer string image data buffer 56 | ---@param options table|nil An optional table containing parameters for loading the image. Supported entries: 57 | --- 58 | ---premultiply_alpha 59 | ---boolean True if alpha should be premultiplied into the color components. Defaults to false. 60 | ---flip_vertically 61 | ---boolean True if the image contents should be flipped vertically. Defaults to false. 62 | --- 63 | ---@return { width:number, height:number, type:constant, buffer:buffer_data }|nil image object or nil if loading fails. The object is a table with the following fields: 64 | --- 65 | ---number width: image width 66 | ---number height: image height 67 | ---constant type: image type 68 | ---image.TYPE_RGB 69 | ---image.TYPE_RGBA 70 | ---image.TYPE_LUMINANCE 71 | ---image.TYPE_LUMINANCE_ALPHA 72 | --- 73 | --- 74 | ---buffer buffer: the script buffer that holds the decompressed image data. See buffer.create how to use the buffer. 75 | --- 76 | function image.load_buffer(buffer, options) end 77 | 78 | return image -------------------------------------------------------------------------------- /tiled/game_shooting_circle.tiled-session: -------------------------------------------------------------------------------- 1 | { 2 | "Map/SizeTest": { 3 | "height": 4300, 4 | "width": 2 5 | }, 6 | "activeFile": "tilesets/shooting_circle.tsx", 7 | "expandedProjectPaths": [ 8 | ".", 9 | "maps", 10 | "tilesets" 11 | ], 12 | "file.lastUsedOpenFilter": "All Files (*)", 13 | "fileStates": { 14 | "/Users/insality/code/defold/ecs_template/resources/tilesets/shooting_circle.tsx": { 15 | "dynamicWrapping": true, 16 | "scaleInDock": 0.2596, 17 | "scaleInEditor": 0.43 18 | }, 19 | "/Users/insality/code/defold/shooting_circles/resources/tilesets/shooting_circle.tsx": { 20 | "dynamicWrapping": false, 21 | "scaleInDock": 1 22 | }, 23 | "maps/game.tmx": { 24 | "expandedObjectLayers": [ 25 | 20 26 | ], 27 | "scale": 0.426, 28 | "selectedLayer": 1, 29 | "viewCenter": { 30 | "x": 491.7840375586855, 31 | "y": 505.8685446009389 32 | } 33 | }, 34 | "maps/loader.tmx": { 35 | "scale": 1.6173, 36 | "selectedLayer": 0, 37 | "viewCenter": { 38 | "x": 115.00649230198479, 39 | "y": 1077.4129722376802 40 | } 41 | }, 42 | "tilesets/game.tsx": { 43 | "dynamicWrapping": false, 44 | "scaleInDock": 1 45 | }, 46 | "tilesets/shooting_circle.tsx": { 47 | "dynamicWrapping": true, 48 | "scaleInDock": 0.1194, 49 | "scaleInEditor": 0.1398 50 | } 51 | }, 52 | "last.exportedFilePath": "/Users/insality/code/defold/shooting_circles/resources/tilesets", 53 | "last.externalTilesetPath": "/Users/insality/code/defold/shooting_circles/game/tiled/tilesets", 54 | "last.imagePath": "/Users/insality/code/defold/shooting_circles/game/tiled/images", 55 | "lastUsedTilesetExportFilter": "All Files (*)", 56 | "map.height": 18, 57 | "map.lastUsedExportFilter": "All Files (*)", 58 | "map.lastUsedFormat": "tmx", 59 | "map.tileHeight": 60, 60 | "map.tileWidth": 60, 61 | "map.width": 32, 62 | "openFiles": [ 63 | "maps/game.tmx", 64 | "tilesets/shooting_circle.tsx" 65 | ], 66 | "project": "game_shooting_circle.tiled-project", 67 | "property.type": "int", 68 | "recentFiles": [ 69 | "maps/game.tmx", 70 | "tilesets/shooting_circle.tsx", 71 | "/Users/insality/code/defold/ecs_template/resources/tilesets/shooting_circle.tsx", 72 | "maps/loader.tmx" 73 | ], 74 | "tileset.lastUsedFormat": "tsx", 75 | "tileset.type": 1 76 | } 77 | -------------------------------------------------------------------------------- /game/objects/explosion/explosion_enemy.particlefx: -------------------------------------------------------------------------------- 1 | emitters { 2 | mode: PLAY_MODE_ONCE 3 | duration: 0.1 4 | space: EMISSION_SPACE_WORLD 5 | position { 6 | z: 2.0 7 | } 8 | tile_source: "/assets/atlases/game_shooting_circle.atlas" 9 | animation: "ui_circle_64" 10 | material: "/game/misc/particlefx.material" 11 | max_particle_count: 64 12 | type: EMITTER_TYPE_CIRCLE 13 | properties { 14 | key: EMITTER_KEY_SPAWN_RATE 15 | points { 16 | y: 100.0 17 | } 18 | } 19 | properties { 20 | key: EMITTER_KEY_SIZE_X 21 | points { 22 | y: 32.0 23 | } 24 | } 25 | properties { 26 | key: EMITTER_KEY_SIZE_Y 27 | points { 28 | y: 32.0 29 | } 30 | } 31 | properties { 32 | key: EMITTER_KEY_PARTICLE_LIFE_TIME 33 | points { 34 | y: 0.55 35 | } 36 | spread: 0.2 37 | } 38 | properties { 39 | key: EMITTER_KEY_PARTICLE_SPEED 40 | points { 41 | y: 400.0 42 | } 43 | spread: 50.0 44 | } 45 | properties { 46 | key: EMITTER_KEY_PARTICLE_SIZE 47 | points { 48 | y: 48.0 49 | } 50 | } 51 | properties { 52 | key: EMITTER_KEY_PARTICLE_RED 53 | points { 54 | y: 0.38 55 | } 56 | } 57 | properties { 58 | key: EMITTER_KEY_PARTICLE_GREEN 59 | points { 60 | y: 0.172 61 | } 62 | } 63 | properties { 64 | key: EMITTER_KEY_PARTICLE_BLUE 65 | points { 66 | y: 0.172 67 | } 68 | } 69 | properties { 70 | key: EMITTER_KEY_PARTICLE_ALPHA 71 | points { 72 | y: 1.0 73 | } 74 | } 75 | particle_properties { 76 | key: PARTICLE_KEY_SCALE 77 | points { 78 | y: 1.8226749 79 | t_x: 0.28444543 80 | t_y: -0.95869225 81 | } 82 | points { 83 | x: 1.0 84 | y: 0.012899659 85 | t_x: 0.99894124 86 | t_y: -0.04600466 87 | } 88 | } 89 | particle_properties { 90 | key: PARTICLE_KEY_RED 91 | points { 92 | y: 1.0 93 | } 94 | } 95 | particle_properties { 96 | key: PARTICLE_KEY_GREEN 97 | points { 98 | y: 1.0 99 | } 100 | } 101 | particle_properties { 102 | key: PARTICLE_KEY_BLUE 103 | points { 104 | y: 1.0 105 | } 106 | } 107 | particle_properties { 108 | key: PARTICLE_KEY_ALPHA 109 | points { 110 | y: 1.0 111 | } 112 | } 113 | particle_properties { 114 | key: PARTICLE_KEY_ANGULAR_VELOCITY 115 | points { 116 | y: 1.0 117 | } 118 | } 119 | modifiers { 120 | type: MODIFIER_TYPE_DRAG 121 | properties { 122 | key: MODIFIER_KEY_MAGNITUDE 123 | points { 124 | y: 6.0 125 | } 126 | spread: 1.5 127 | } 128 | } 129 | stretch_with_velocity: true 130 | } 131 | -------------------------------------------------------------------------------- /systems/game_object/game_object_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field game_object_command component.game_object_command|nil 5 | 6 | ---@class entity.game_object_command: entity 7 | ---@field game_object_command component.game_object_command 8 | 9 | ---@class component.game_object_command 10 | ---@field entity entity|nil 11 | ---@field enabled boolean|nil 12 | 13 | ---@class system.game_object_command: system 14 | ---@field entities entity.game_object_command[] 15 | ---@field game_object system.game_object 16 | local M = {} 17 | 18 | local TEMP_VECTOR = vmath.vector3() 19 | 20 | ---@static 21 | ---@return system.game_object_command 22 | function M.create_system(game_object) 23 | local system = setmetatable(ecs.system(), { __index = M }) 24 | system.filter = ecs.requireAny("game_object_command", "transform_event") 25 | system.id = "game_object_command" 26 | system.game_object = game_object 27 | 28 | return system 29 | end 30 | 31 | 32 | ---@param entity entity.game_object_command|entity.transform_event 33 | function M:onAdd(entity) 34 | local command = entity.game_object_command 35 | if command then 36 | self:process_command(command) 37 | end 38 | 39 | local transform_event = entity.transform_event 40 | if transform_event and self.game_object.indices[transform_event.entity] then 41 | self:process_transform_event(transform_event) 42 | end 43 | 44 | self.world:removeEntity(entity) 45 | end 46 | 47 | 48 | ---@param command component.game_object_command 49 | function M:process_command(command) 50 | local entity = command.entity 51 | if not entity then 52 | return 53 | end 54 | 55 | if command.enabled ~= nil then 56 | for _, game_object in pairs(entity.game_object.object) do 57 | if command.enabled then 58 | msg.post(game_object, "enable") 59 | else 60 | msg.post(game_object, "disable") 61 | end 62 | end 63 | end 64 | end 65 | 66 | 67 | ---@param transform_event component.transform_event 68 | function M:process_transform_event(transform_event) 69 | local target_entity = transform_event.entity 70 | local game_object = target_entity.game_object 71 | if not game_object or target_entity.physics then 72 | return 73 | end 74 | 75 | local root = target_entity.game_object.root 76 | if root and transform_event.is_position_changed then 77 | TEMP_VECTOR.x = target_entity.transform.position_x 78 | TEMP_VECTOR.y = target_entity.transform.position_y 79 | TEMP_VECTOR.z = target_entity.transform.position_z 80 | 81 | local animate_time = transform_event.animate_time 82 | if animate_time then 83 | local easing = transform_event.easing or go.EASING_LINEAR 84 | go.animate(target_entity.game_object.root, "position", go.PLAYBACK_ONCE_FORWARD, TEMP_VECTOR, easing, animate_time) 85 | else 86 | go.set_position(TEMP_VECTOR, root) 87 | end 88 | end 89 | end 90 | 91 | 92 | return M 93 | -------------------------------------------------------------------------------- /game.project: -------------------------------------------------------------------------------- 1 | [bootstrap] 2 | main_collection = /loader/loader.collectionc 3 | render = /builtins/render/default.renderc 4 | 5 | [script] 6 | shared_state = 1 7 | 8 | [display] 9 | width = 1920 10 | height = 1080 11 | high_dpi = 1 12 | 13 | [android] 14 | input_method = HiddenInputField 15 | 16 | [project] 17 | title = Shooting Circles 18 | developer = Insality 19 | publisher = Insality 20 | custom_resources = /resources 21 | dependencies#0 = https://github.com/Insality/defold-tweener/archive/refs/tags/2.zip 22 | dependencies#1 = https://github.com/Insality/defold-log/archive/refs/tags/3.zip 23 | dependencies#2 = https://github.com/Insality/defold-event/archive/refs/tags/3.zip 24 | dependencies#3 = https://github.com/Insality/panthera/archive/refs/tags/runtime.3.zip 25 | dependencies#4 = https://github.com/subsoap/defos/archive/refs/tags/v2.7.1.zip 26 | dependencies#5 = https://github.com/britzl/deftest/archive/refs/tags/2.8.0.zip 27 | 28 | [safearea] 29 | resize_game_view = 0 30 | 31 | [gui] 32 | max_particlefx_count = 16 33 | max_count = 8 34 | max_particle_count = 128 35 | 36 | [network] 37 | http_timeout = 10.0 38 | http_thread_count = 2 39 | 40 | [tilemap] 41 | max_count = 0 42 | max_tile_count = 0 43 | 44 | [sprite] 45 | max_count = 1024 46 | 47 | [particle_fx] 48 | max_count = 512 49 | max_emitter_count = 512 50 | max_particle_count = 2048 51 | 52 | [mesh] 53 | max_count = 0 54 | 55 | [label] 56 | max_count = 1024 57 | 58 | [log] 59 | level = DEBUG 60 | level_release = WARN 61 | time_tracking = 0 62 | memory_tracking = 1 63 | info_block_length = 24 64 | info_block = %levelshort[%logger 65 | message_block = ]: %message %context %tab<%function> 66 | max_log_length = 650 67 | inspect_depth = 0 68 | 69 | [panthera] 70 | hotreload_animations = 1 71 | 72 | [event] 73 | memory_trashold_warning = 100 74 | 75 | [html5] 76 | heap_size = 256 77 | scale_mode = stretch 78 | show_made_with_defold = 0 79 | show_console_banner = 0 80 | show_fullscreen_button = 0 81 | 82 | [render] 83 | clear_color_red = 0.105 84 | clear_color_blue = 0.137 85 | clear_color_green = 0.098 86 | 87 | [input] 88 | game_binding = /builtins/input/all.input_bindingc 89 | 90 | [physics] 91 | debug_alpha = 0.6 92 | world_count = 4 93 | gravity_y = 0.0 94 | ray_cast_limit_3d = 0 95 | scale = 0.01 96 | use_fixed_timestep = 1 97 | max_collision_object_count = 512 98 | max_collisions = 512 99 | max_contacts = 512 100 | 101 | [collectionfactory] 102 | max_count = 32 103 | 104 | [collection] 105 | max_instances = 2048 106 | 107 | [collection_proxy] 108 | max_count = 4 109 | 110 | [factory] 111 | max_count = 0 112 | 113 | [model] 114 | max_count = 0 115 | 116 | [sound] 117 | max_sound_instances = 128 118 | 119 | [graphics] 120 | max_draw_calls = 256 121 | verify_graphics_calls = 0 122 | texture_profiles = /game/misc/game.texture_profiles 123 | 124 | [native_extension] 125 | app_manifest = /game/misc/game.appmanifest 126 | 127 | [library] 128 | include_dirs = decore,detiled 129 | 130 | -------------------------------------------------------------------------------- /annotations/defold_annotations/timer.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Timer API documentation 6 | 7 | Timers allow you to set a delay and a callback to be called when the timer completes. 8 | The timers created with this API are updated with the collection timer where they 9 | are created. If you pause or speed up the collection (using set_time_step) it will 10 | also affect the new timer. 11 | --]] 12 | 13 | ---@diagnostic disable: lowercase-global 14 | ---@diagnostic disable: missing-return 15 | ---@diagnostic disable: duplicate-doc-param 16 | ---@diagnostic disable: duplicate-set-field 17 | 18 | ---@class defold_api.timer 19 | timer = {} 20 | 21 | ---Indicates an invalid timer handle 22 | timer.INVALID_TIMER_HANDLE = nil 23 | 24 | ---You may cancel a timer from inside a timer callback. 25 | ---Cancelling a timer that is already executed or cancelled is safe. 26 | ---@param handle hash the timer handle returned by timer.delay() 27 | ---@return boolean true if the timer was active, false if the timer is already cancelled / complete 28 | function timer.cancel(handle) end 29 | 30 | ---Adds a timer and returns a unique handle. 31 | ---You may create more timers from inside a timer callback. 32 | ---Using a delay of 0 will result in a timer that triggers at the next frame just before 33 | ---script update functions. 34 | ---If you want a timer that triggers on each frame, set delay to 0.0f and repeat to true. 35 | ---Timers created within a script will automatically die when the script is deleted. 36 | ---@param delay number time interval in seconds 37 | ---@param repeating boolean true = repeat timer until cancel, false = one-shot timer 38 | ---@param callback fun(self, handle, time_elapsed) timer callback function 39 | --- 40 | ---self 41 | ---object The current object 42 | ---handle 43 | ---number The handle of the timer 44 | ---time_elapsed 45 | ---number The elapsed time - on first trigger it is time since timer.delay call, otherwise time since last trigger 46 | --- 47 | ---@return hash handle identifier for the create timer, returns timer.INVALID_TIMER_HANDLE if the timer can not be created 48 | function timer.delay(delay, repeating, callback) end 49 | 50 | ---Get information about timer. 51 | ---@param handle hash the timer handle returned by timer.delay() 52 | ---@return { time_remaining:number, delay:number, repeating:boolean }|nil data table or nil if timer is cancelled/completed. table with data in the following fields: 53 | --- 54 | ---time_remaining 55 | ---number Time remaining until the next time a timer.delay() fires. 56 | ---delay 57 | ---number Time interval. 58 | ---repeating 59 | ---boolean true = repeat timer until cancel, false = one-shot timer. 60 | --- 61 | function timer.get_info(handle) end 62 | 63 | ---Manual triggering a callback for a timer. 64 | ---@param handle hash the timer handle returned by timer.delay() 65 | ---@return boolean true if the timer was active, false if the timer is already cancelled / complete 66 | function timer.trigger(handle) end 67 | 68 | return timer -------------------------------------------------------------------------------- /resources/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "pack_id": "core", 3 | 4 | "components": { 5 | "id": "", 6 | "prefab_id": false, 7 | "pack_id": false, 8 | "name": false, 9 | "tiled_id": false, 10 | "layer_id": false, 11 | "hidden": false, 12 | 13 | "transform": { 14 | "position_x": 0, 15 | "position_y": 0, 16 | "position_z": 0, 17 | "size_x": 1, 18 | "size_y": 1, 19 | "size_z": 1, 20 | "scale_x": 1, 21 | "scale_y": 1, 22 | "scale_z": 1, 23 | "rotation": 0 24 | }, 25 | 26 | "game_object": { 27 | "factory_url": "", 28 | "is_slice9": false 29 | }, 30 | 31 | "color": { 32 | "hex_color": "", 33 | "sprite_url": "" 34 | }, 35 | 36 | "level_loader_command": { 37 | "world_id": null, 38 | "pack_id": null, 39 | "offset_x": 0, 40 | "offset_y": 0 41 | }, 42 | 43 | "debug": { 44 | "is_profiler_active": null 45 | }, 46 | 47 | "on_spawn_command": { 48 | "command": false 49 | }, 50 | 51 | "input": {}, 52 | 53 | "on_key_released": { 54 | "key_to_add_component_json": null, 55 | "key_to_add_component": null 56 | }, 57 | 58 | "window_event": { 59 | "is_focus_gained": false, 60 | "is_focus_lost": false, 61 | "is_resized": false 62 | }, 63 | 64 | "camera": { 65 | "camera_url": "" 66 | }, 67 | 68 | "health": { 69 | "health": 0 70 | }, 71 | 72 | "gui_main": {}, 73 | "health_circle_visual": {}, 74 | "cursor": {}, 75 | 76 | "damage_number": { 77 | "damage": 1 78 | }, 79 | 80 | "panthera": { 81 | "animation_path": "" 82 | }, 83 | 84 | "movement": { 85 | "velocity_x": 0, 86 | "velocity_y": 0, 87 | "friction": 0 88 | }, 89 | 90 | "movement_controller": { 91 | "speed": 1, 92 | "movement_x": 0, 93 | "movement_y": 0 94 | }, 95 | 96 | "remove_with_delay": { 97 | "delay": 0 98 | }, 99 | 100 | "explosion": {}, 101 | 102 | "make_explosion_on_spawn": { 103 | "power": 0, 104 | "distance": 0, 105 | "position_x": 0, 106 | "position_y": 0 107 | }, 108 | 109 | "physics": { 110 | "velocity_x": 0, 111 | "velocity_y": 0 112 | }, 113 | 114 | "collision": {}, 115 | 116 | "on_collision_remove": false, 117 | 118 | "on_collision_damage": { 119 | "damage": 0 120 | }, 121 | 122 | "on_collision_explosion": { 123 | "power": 0, 124 | "distance": 0 125 | }, 126 | 127 | "play_fx_on_remove": { 128 | "fx_url": "" 129 | }, 130 | 131 | "acceleration": { 132 | "value": 0 133 | }, 134 | 135 | "shooter_controller": { 136 | "bullet_prefab_id": "", 137 | "is_auto_shoot": false, 138 | "spread_angle": 0, 139 | "damage": 0, 140 | "bullet_speed": 2000, 141 | "fire_rate": 0, 142 | "fire_rate_timer": 0, 143 | "burst_count": 5, 144 | "burst_count_current": 0, 145 | "burst_rate": 1, 146 | "bullets_per_shoot": 1 147 | }, 148 | 149 | "on_target_count_command": { 150 | "amount": 0, 151 | "command": false 152 | }, 153 | 154 | "target": false 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /resources/animations/health_visual_circle.json: -------------------------------------------------------------------------------- 1 | {"data":{"animations":[{"animation_id":"health","animation_keys":[{"easing":"outsine","end_value":-32,"key_type":"tween","node_id":"health","property_id":"position_y"},{"easing":"outsine","key_type":"tween","node_id":"health#sprite","property_id":"size_y","start_value":64},{"duration":0.5,"easing":"linear","end_value":-16,"key_type":"tween","node_id":"health","property_id":"position_y","start_value":-32},{"duration":0.5,"easing":"linear","end_value":32,"key_type":"tween","node_id":"health#sprite","property_id":"size_y"},{"duration":1,"easing":"linear","end_value":64,"key_type":"tween","node_id":"health#sprite","property_id":"slice9_bottom"}],"duration":1},{"animation_id":"on_damage","animation_keys":[{"easing":"outsine","end_value":0.5,"key_type":"tween","node_id":"root#impact","property_id":"color_a","start_value":1},{"duration":0.3,"easing":"insine","key_type":"tween","node_id":"root#impact","property_id":"color_a","start_value":0.5}],"duration":0.3}],"metadata":{"fps":60,"gizmo_steps":[],"settings":{"font_size":40}},"nodes":[{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"inherit_alpha":true,"node_id":"root","node_index":1,"node_type":"box","pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":4,"size_y":4},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":0.567,"color_g":0.567,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"root#sprite","node_index":2,"node_type":"box","outer_bounds":"ellipse","parent":"root","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"auto","size_x":64,"size_y":64,"text_leading":1,"texture":"game/ui_circle_64","visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"health","node_index":3,"node_type":"box","outer_bounds":"ellipse","parent":"root","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":4,"size_y":4,"text_leading":1},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"health#sprite","node_index":4,"node_type":"box","outer_bounds":"ellipse","parent":"health","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":64,"size_y":64,"text_leading":1,"texture":"game/ui_circle_64","visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"root#impact","node_index":5,"node_type":"box","outer_bounds":"ellipse","parent":"root","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":64,"size_y":64,"text_leading":1,"texture":"game/ui_circle_64","visible":true}]},"format":"json","type":"animation_editor","version":1} -------------------------------------------------------------------------------- /systems/physics/physics.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local physics_command = require("systems.physics.physics_command") 4 | 5 | ---@class entity 6 | ---@field physics component.physics|nil 7 | 8 | ---@class entity.physics: entity 9 | ---@field physics component.physics 10 | ---@field game_object component.game_object 11 | ---@field transform component.transform 12 | 13 | ---@class component.physics 14 | ---@field box2d_body b2Body 15 | ---@field collisionobject_url url 16 | ---@field velocity_x number 17 | ---@field velocity_y number 18 | 19 | ---@class system.physics: system 20 | ---@field entities entity.physics[] 21 | local M = {} 22 | 23 | local TEMP_VECTOR = vmath.vector3() 24 | 25 | ---@static 26 | ---@return system.physics, system.physics_command 27 | function M.create_system() 28 | local system = setmetatable(ecs.processingSystem(), { __index = M }) 29 | system.filter = ecs.requireAll("physics", "game_object", "transform") 30 | system.id = "physics" 31 | 32 | return system, physics_command.create_system(system) 33 | end 34 | 35 | 36 | ---@param entity entity.physics 37 | function M:onAdd(entity) 38 | local collisionobject_url = msg.url(nil, entity.game_object.root, "collisionobject") 39 | entity.physics.collisionobject_url = collisionobject_url 40 | entity.physics.box2d_body = b2d.get_body(collisionobject_url) 41 | 42 | local body = entity.physics.box2d_body 43 | TEMP_VECTOR.x = entity.physics.velocity_x 44 | TEMP_VECTOR.y = entity.physics.velocity_y 45 | b2d.body.set_linear_velocity(body, TEMP_VECTOR) 46 | end 47 | 48 | 49 | function M:onRemove(entity) 50 | entity.physics.box2d_body = nil 51 | entity.physics.collisionobject_url = nil 52 | end 53 | 54 | 55 | ---@param entity entity.physics 56 | ---@param dt number 57 | function M:process(entity, dt) 58 | local body = entity.physics.box2d_body 59 | local is_awake = b2d.body.is_awake(body) 60 | if not is_awake then 61 | return 62 | end 63 | 64 | local position = b2d.body.get_position(body) 65 | local transform = entity.transform 66 | if position.x ~= transform.position_x or position.y ~= transform.position_y then 67 | transform.position_x = position.x 68 | transform.position_y = position.y 69 | ---@type component.transform_event 70 | local transform_event = { 71 | entity = entity, 72 | is_position_changed = true 73 | } 74 | self.world:addEntity({ transform_event = transform_event }) 75 | end 76 | 77 | local velocity = b2d.body.get_linear_velocity(body) 78 | entity.physics.velocity_x = velocity.x 79 | entity.physics.velocity_y = velocity.y 80 | end 81 | 82 | 83 | ---@param entity entity.physics 84 | ---@param force_x number 85 | ---@param force_y number 86 | function M:add_force(entity, force_x, force_y) 87 | if not self.indices[entity] then 88 | return 89 | end 90 | 91 | local body = entity.physics.box2d_body 92 | 93 | TEMP_VECTOR.x = force_x or 0 94 | TEMP_VECTOR.y = force_y or 0 95 | TEMP_VECTOR.z = 0 96 | b2d.body.apply_force_to_center(body, TEMP_VECTOR) 97 | end 98 | 99 | 100 | return M 101 | -------------------------------------------------------------------------------- /resources/animations/health_visual_rectangle.json: -------------------------------------------------------------------------------- 1 | {"data":{"animations":[{"animation_id":"health","animation_keys":[{"easing":"outsine","end_value":-32,"key_type":"tween","node_id":"health","property_id":"position_y"},{"easing":"outsine","key_type":"tween","node_id":"health#sprite","property_id":"size_y","start_value":64},{"duration":0.5,"easing":"linear","end_value":-16,"key_type":"tween","node_id":"health","property_id":"position_y","start_value":-32},{"duration":0.5,"easing":"linear","end_value":32,"key_type":"tween","node_id":"health#sprite","property_id":"size_y"},{"duration":1,"easing":"linear","end_value":64,"key_type":"tween","node_id":"health#sprite","property_id":"slice9_bottom"}],"duration":1},{"animation_id":"on_damage","animation_keys":[{"easing":"outsine","end_value":0.6,"key_type":"tween","node_id":"root#impact","property_id":"color_a","start_value":1},{"duration":0.3,"easing":"insine","key_type":"tween","node_id":"root#impact","property_id":"color_a","start_value":0.6}],"duration":0.3}],"metadata":{"fps":60,"gizmo_steps":[],"settings":{"font_size":40}},"nodes":[{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"inherit_alpha":true,"node_id":"root","node_index":1,"node_type":"box","pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":4,"size_y":4},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":0.567,"color_g":0.567,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"root#sprite","node_index":2,"node_type":"box","outer_bounds":"ellipse","parent":"root","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":128,"size_y":64,"text_leading":1,"texture":"game/enemy_rectangle","visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"health","node_index":3,"node_type":"box","outer_bounds":"ellipse","parent":"root","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":4,"size_y":4,"text_leading":1},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"health#sprite","node_index":4,"node_type":"box","outer_bounds":"ellipse","parent":"health","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":128,"size_y":64,"text_leading":1,"texture":"game/enemy_rectangle","visible":true},{"blend_mode":"alpha","clipping_mode":"none","clipping_visible":true,"color_a":1,"color_b":1,"color_g":1,"color_r":1,"enabled":true,"fill_angle":360,"inherit_alpha":true,"node_id":"root#impact","node_index":5,"node_type":"box","outer_bounds":"ellipse","parent":"root","perimeter_vertices":10,"pivot":"pivot_center","scale_x":1,"scale_y":1,"scale_z":1,"size_mode":"manual","size_x":128,"size_y":64,"text_leading":1,"texture":"game/enemy_rectangle","visible":true}]},"format":"json","type":"animation_editor","version":1} -------------------------------------------------------------------------------- /systems/panthera/panthera_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | local panthera = require("panthera.panthera") 3 | 4 | ---@class entity 5 | ---@field panthera_command component.panthera_command|nil 6 | 7 | ---@class entity.panthera_command: entity 8 | ---@field panthera_command component.panthera_command 9 | 10 | ---@class component.panthera_command 11 | ---@field entity entity 12 | ---@field animation_id string|nil 13 | ---@field speed number|nil 14 | ---@field is_loop boolean|nil 15 | ---@field detached boolean|nil 16 | ---@field progress number|nil 17 | 18 | ---@class system.panthera_command: system 19 | ---@field entities entity.panthera_command[] 20 | ---@field panthera system.panthera 21 | local M = {} 22 | 23 | ---@static 24 | ---@return system.panthera_command 25 | function M.create_system(panthera) 26 | local system = setmetatable(ecs.system(), { __index = M }) 27 | system.filter = ecs.requireAny("panthera_command", "window_event") 28 | system.id = "panthera_command" 29 | 30 | system.panthera = panthera 31 | 32 | return system 33 | end 34 | 35 | 36 | ---@param entity entity.panthera_command 37 | function M:onAdd(entity) 38 | local command = entity.panthera_command 39 | if command and self.panthera.indices[command.entity] then 40 | self:process_command(command) 41 | end 42 | 43 | local window_event = entity.window_event 44 | if window_event then 45 | if window_event.is_focus_gained then 46 | panthera.reload_animation() 47 | end 48 | end 49 | 50 | self.world:removeEntity(entity) 51 | end 52 | 53 | 54 | ---@param command component.panthera_command 55 | function M:process_command(command) 56 | local entity = command.entity --[[@as entity.panthera]] 57 | local p = entity.panthera 58 | 59 | if command.animation_id then 60 | local animation_state = p.animation_state 61 | if command.detached then 62 | animation_state = panthera.clone_state(p.animation_state) 63 | end 64 | 65 | if not command.progress then 66 | if command.detached then 67 | table.insert(p.detached_animations, animation_state) 68 | end 69 | 70 | panthera.play(animation_state, command.animation_id, { 71 | is_loop = command.is_loop or false, 72 | speed = command.speed or 1, 73 | callback = function(animation_path) 74 | if p.default_animation and p.default_animation ~= "" then 75 | panthera.play(p.animation_state, p.default_animation, { 76 | is_loop = p.is_loop or false, 77 | speed = p.speed or 1 78 | }) 79 | end 80 | if command.detached then 81 | for index = 1, #p.detached_animations do 82 | if p.detached_animations[index] == animation_state then 83 | table.remove(p.detached_animations, index) 84 | break 85 | end 86 | end 87 | end 88 | end 89 | }) 90 | else 91 | if animation_state then 92 | local progress = command.progress 93 | local time = panthera.get_duration(animation_state, command.animation_id) 94 | panthera.set_time(animation_state, command.animation_id, time * progress) 95 | end 96 | end 97 | end 98 | end 99 | 100 | 101 | return M 102 | -------------------------------------------------------------------------------- /resources/animations/gui_main.json: -------------------------------------------------------------------------------- 1 | {"data":{"animations":[{"animation_id":"default","animation_keys":[],"duration":1},{"animation_id":"level_completed","animation_keys":[{"duration":0.42,"easing":"inback","end_value":-1300,"key_type":"tween","node_id":"W_Anchor","property_id":"position_x","start_value":-960},{"duration":0.5,"easing":"outsine","end_value":1.3,"key_type":"tween","node_id":"button_right/root","property_id":"scale_x","start_value":1},{"duration":0.5,"easing":"outsine","end_value":1.3,"key_type":"tween","node_id":"button_right/root","property_id":"scale_y","start_value":1},{"duration":0.37,"easing":"outsine","key_type":"tween","node_id":"W_Anchor","property_id":"color_a","start_time":0.06,"start_value":1},{"duration":0.59,"easing":"outsine","end_value":-305,"key_type":"tween","node_id":"group_time","property_id":"position_y","start_time":0.12,"start_value":-100},{"duration":0.52,"easing":"outsine","end_value":1.3,"key_type":"tween","node_id":"group_time","property_id":"scale_x","start_time":0.24,"start_value":1},{"duration":0.52,"easing":"insine","end_value":1.3,"key_type":"tween","node_id":"group_time","property_id":"scale_y","start_time":0.24,"start_value":1},{"duration":0.34,"easing":"outsine","end_value":-174.46,"key_type":"tween","node_id":"text_timer","property_id":"position_y","start_time":0.54,"start_value":-136},{"duration":0.29,"easing":"outback","end_value":2.5,"key_type":"tween","node_id":"text_timer","property_id":"scale_x","start_time":0.57,"start_value":2},{"duration":0.29,"easing":"outback","end_value":2.5,"key_type":"tween","node_id":"text_timer","property_id":"scale_y","start_time":0.57,"start_value":2}],"duration":1},{"animation_id":"level_start","animation_keys":[{"duration":0.5,"easing":"outsine","end_value":-960,"key_type":"tween","node_id":"W_Anchor","property_id":"position_x","start_value":-960},{"duration":0.5,"easing":"outsine","end_value":-136,"key_type":"tween","node_id":"text_timer","property_id":"position_y","start_value":-136},{"duration":0.5,"easing":"outsine","end_value":-100,"key_type":"tween","node_id":"group_time","property_id":"position_y","start_value":-100},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"W_Anchor","property_id":"color_a","start_value":1},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"button_right/root","property_id":"scale_x","start_value":1},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"button_right/root","property_id":"scale_y","start_value":1},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"group_time","property_id":"scale_x","start_value":1},{"duration":0.5,"easing":"outsine","end_value":1,"key_type":"tween","node_id":"group_time","property_id":"scale_y","start_value":1},{"duration":0.5,"easing":"outsine","end_value":2,"key_type":"tween","node_id":"text_timer","property_id":"scale_x","start_value":2},{"duration":0.5,"easing":"outsine","end_value":2,"key_type":"tween","node_id":"text_timer","property_id":"scale_y","start_value":2}],"duration":1}],"metadata":{"fps":30,"gizmo_steps":{"time":0.033333},"gui_path":"/gui/gui_main/gui_main.gui","is_frame_view":true,"layers":[{"color":"73E84C","name":"game"},{"color":"90D2F6","name":"text"}],"settings":{"font_size":30}},"nodes":[]},"format":"json","type":"animation_editor","version":1} -------------------------------------------------------------------------------- /systems/panthera/panthera_decore.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | local panthera = require("panthera.panthera") 3 | 4 | local panthera_command = require("systems.panthera.panthera_command") 5 | 6 | ---@class entity 7 | ---@field panthera component.panthera|nil 8 | 9 | ---@class entity.panthera: entity 10 | ---@field panthera component.panthera 11 | ---@field game_object component.game_object 12 | ---@field transform component.transform 13 | 14 | ---@class component.panthera 15 | ---@field animation_path string 16 | ---@field animation_state panthera.animation.state|nil 17 | ---@field default_animation string 18 | ---@field speed number 19 | ---@field is_loop boolean|nil 20 | ---@field play_on_start boolean|nil 21 | ---@field detached_animations panthera.animation.state[] 22 | 23 | ---@class system.panthera: system 24 | ---@field entities entity.panthera[] 25 | local M = {} 26 | 27 | ---@static 28 | ---@return system.panthera, system.panthera_command 29 | function M.create_system() 30 | local system = setmetatable(ecs.system(), { __index = M }) 31 | system.filter = ecs.requireAll("panthera", "game_object", ecs.rejectAll("hidden")) 32 | system.id = "panthera" 33 | 34 | return system, panthera_command.create_system(system) 35 | end 36 | 37 | 38 | ---@param entity entity.panthera 39 | function M:onAdd(entity) 40 | local p = entity.panthera 41 | p.detached_animations = {} 42 | 43 | local animation_state = panthera.create_go(p.animation_path, nil, entity.game_object.object) 44 | 45 | if animation_state then 46 | p.animation_state = animation_state 47 | 48 | -- TODO: This one should be in update 49 | if p.play_on_start then 50 | panthera.play(p.animation_state, p.default_animation, { 51 | is_loop = p.is_loop or false, 52 | speed = p.speed or 1 53 | }) 54 | end 55 | end 56 | end 57 | 58 | 59 | ---@param entity entity.panthera 60 | function M:onRemove(entity) 61 | local p = entity.panthera 62 | if panthera.is_playing(p.animation_state) then 63 | panthera.stop(p.animation_state) 64 | end 65 | 66 | p.animation_state = nil 67 | 68 | for index = 1, #p.detached_animations do 69 | local state = p.detached_animations[index] 70 | if panthera.is_playing(state) then 71 | panthera.stop(state) 72 | end 73 | end 74 | end 75 | 76 | 77 | ---@param template string|nil 78 | ---@param nodes table|nil 79 | ---@return function(node_id: string): hash|url 80 | function M.get_node_fn(template, nodes) 81 | return function(node_id) 82 | if template then 83 | node_id = template .. "/" .. node_id 84 | end 85 | 86 | local split_index = string.find(node_id, "#") 87 | if split_index then 88 | local object_id = string.sub(node_id, 1, split_index - 1) 89 | local fragment_id = string.sub(node_id, split_index + 1) 90 | 91 | ---@type string|hash 92 | local object_path = hash("/" .. object_id) 93 | if nodes then 94 | object_path = nodes[object_path] 95 | end 96 | 97 | return msg.url(nil, object_path, fragment_id) 98 | end 99 | 100 | local object_path = hash("/" .. node_id) 101 | if nodes then 102 | object_path = nodes[object_path] 103 | end 104 | 105 | return object_path 106 | end 107 | end 108 | 109 | 110 | return M 111 | -------------------------------------------------------------------------------- /game/objects/enemy/enemy.collection: -------------------------------------------------------------------------------- 1 | name: "circle" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | children: "health" 6 | data: "components {\n" 7 | " id: \"explosion_enemy\"\n" 8 | " component: \"/game/objects/explosion/explosion_enemy.particlefx\"\n" 9 | "}\n" 10 | "embedded_components {\n" 11 | " id: \"sprite\"\n" 12 | " type: \"sprite\"\n" 13 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 14 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 15 | "attributes {\\n" 16 | " name: \\\"color\\\"\\n" 17 | " double_values {\\n" 18 | " v: 1.0\\n" 19 | " v: 1.0\\n" 20 | " v: 1.0\\n" 21 | " v: 1.0\\n" 22 | " }\\n" 23 | "}\\n" 24 | "textures {\\n" 25 | " sampler: \\\"texture_sampler\\\"\\n" 26 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 27 | "}\\n" 28 | "\"\n" 29 | "}\n" 30 | "embedded_components {\n" 31 | " id: \"collisionobject\"\n" 32 | " type: \"collisionobject\"\n" 33 | " data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n" 34 | "mass: 0.75\\n" 35 | "friction: 0.1\\n" 36 | "restitution: 0.2\\n" 37 | "group: \\\"solid\\\"\\n" 38 | "mask: \\\"solid\\\"\\n" 39 | "mask: \\\"player\\\"\\n" 40 | "mask: \\\"bullet\\\"\\n" 41 | "embedded_collision_shape {\\n" 42 | " shapes {\\n" 43 | " shape_type: TYPE_SPHERE\\n" 44 | " position {\\n" 45 | " }\\n" 46 | " rotation {\\n" 47 | " }\\n" 48 | " index: 0\\n" 49 | " count: 1\\n" 50 | " }\\n" 51 | " data: 33.650795\\n" 52 | "}\\n" 53 | "linear_damping: 0.9\\n" 54 | "locked_rotation: true\\n" 55 | "\"\n" 56 | "}\n" 57 | "embedded_components {\n" 58 | " id: \"impact\"\n" 59 | " type: \"sprite\"\n" 60 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 61 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 62 | "size {\\n" 63 | " x: 64.0\\n" 64 | " y: 64.0\\n" 65 | "}\\n" 66 | "size_mode: SIZE_MODE_MANUAL\\n" 67 | "attributes {\\n" 68 | " name: \\\"color\\\"\\n" 69 | " double_values {\\n" 70 | " v: 1.0\\n" 71 | " v: 1.0\\n" 72 | " v: 1.0\\n" 73 | " v: 0.0\\n" 74 | " }\\n" 75 | "}\\n" 76 | "textures {\\n" 77 | " sampler: \\\"texture_sampler\\\"\\n" 78 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 79 | "}\\n" 80 | "\"\n" 81 | " position {\n" 82 | " z: 0.002\n" 83 | " }\n" 84 | "}\n" 85 | "" 86 | } 87 | embedded_instances { 88 | id: "health" 89 | data: "embedded_components {\n" 90 | " id: \"sprite\"\n" 91 | " type: \"sprite\"\n" 92 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 93 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 94 | "size {\\n" 95 | " x: 64.0\\n" 96 | " y: 64.0\\n" 97 | "}\\n" 98 | "size_mode: SIZE_MODE_MANUAL\\n" 99 | "attributes {\\n" 100 | " name: \\\"color\\\"\\n" 101 | " double_values {\\n" 102 | " v: 0.933\\n" 103 | " v: 0.435\\n" 104 | " v: 0.435\\n" 105 | " v: 1.0\\n" 106 | " }\\n" 107 | "}\\n" 108 | "textures {\\n" 109 | " sampler: \\\"texture_sampler\\\"\\n" 110 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 111 | "}\\n" 112 | "\"\n" 113 | "}\n" 114 | "" 115 | position { 116 | z: 0.001 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /systems/game_object/game_object.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local game_object_command = require("systems.game_object.game_object_command") 4 | 5 | ---@class entity 6 | ---@field hidden boolean|nil 7 | ---@field game_object component.game_object|nil 8 | 9 | ---@class entity.game_object: entity 10 | ---@field game_object component.game_object 11 | ---@field transform component.transform 12 | ---@field hidden boolean|nil 13 | 14 | ---@class component.game_object 15 | ---@field factory_url string 16 | ---@field root string|hash|url 17 | ---@field object table 18 | ---@field is_slice9 boolean|nil 19 | ---@field remove_delay number|nil 20 | 21 | ---@class system.game_object: system 22 | local M = {} 23 | 24 | local TEMP_VECTOR = vmath.vector3() 25 | local ROOT_URL = hash("/root") 26 | 27 | 28 | ---@static 29 | ---@return system.game_object, system.game_object_command 30 | function M.create_system() 31 | ---@type system.game_object 32 | local system = setmetatable(ecs.system(), { __index = M }) 33 | system.filter = ecs.requireAll("game_object", "transform", ecs.rejectAll("hidden")) 34 | system.id = "game_object" 35 | 36 | return system, game_object_command.create_system(system) 37 | end 38 | 39 | 40 | ---@param entity entity.game_object 41 | function M:onAdd(entity) 42 | local object = self:create_object(entity) 43 | local root = object[ROOT_URL] 44 | 45 | entity.game_object.root = root 46 | entity.game_object.object = object 47 | 48 | if entity.game_object.is_slice9 then 49 | TEMP_VECTOR.x = entity.transform.size_x 50 | TEMP_VECTOR.y = entity.transform.size_y 51 | TEMP_VECTOR.z = 0 52 | local sprite_url = msg.url(nil, root, "sprite") 53 | go.set(sprite_url, "size", TEMP_VECTOR) 54 | 55 | -- Set scale to initial 1 56 | TEMP_VECTOR.x = 1 57 | TEMP_VECTOR.y = 1 58 | TEMP_VECTOR.z = 1 59 | go.set(root, "scale", TEMP_VECTOR) 60 | else 61 | TEMP_VECTOR.x = entity.transform.scale_x 62 | TEMP_VECTOR.y = entity.transform.scale_y 63 | TEMP_VECTOR.z = entity.transform.scale_x -- X to keep uniform for physics 64 | go.set(root, "scale", TEMP_VECTOR) 65 | end 66 | 67 | go.set(root, "euler.z", entity.transform.rotation) 68 | end 69 | 70 | 71 | ---@param entity entity.game_object 72 | function M:onRemove(entity) 73 | local remove_delay = entity.game_object.remove_delay 74 | 75 | if not remove_delay then 76 | for _, object in pairs(entity.game_object.object) do 77 | go.delete(object) 78 | end 79 | else 80 | timer.delay(remove_delay, false, function() 81 | for _, object in pairs(entity.game_object.object) do 82 | go.delete(object) 83 | end 84 | end) 85 | end 86 | end 87 | 88 | 89 | ---@param entity entity.game_object 90 | ---@return table 91 | function M:create_object(entity) 92 | TEMP_VECTOR.x = entity.transform.position_x 93 | TEMP_VECTOR.y = entity.transform.position_y 94 | TEMP_VECTOR.z = self:get_position_z(entity.transform) 95 | 96 | return collectionfactory.create(entity.game_object.factory_url, TEMP_VECTOR, nil, nil, entity.transform.scale_x) 97 | end 98 | 99 | 100 | ---@param t component.transform 101 | ---@return number 102 | function M:get_position_z(t) 103 | return -t.position_y / 10000 + t.position_x / 100000 + t.position_z / 10 104 | end 105 | 106 | 107 | return M 108 | -------------------------------------------------------------------------------- /game/objects/enemy/enemy_big.collection: -------------------------------------------------------------------------------- 1 | name: "circle" 2 | scale_along_z: 0 3 | embedded_instances { 4 | id: "root" 5 | children: "health" 6 | data: "components {\n" 7 | " id: \"explosion_enemy\"\n" 8 | " component: \"/game/objects/explosion/explosion_enemy.particlefx\"\n" 9 | "}\n" 10 | "embedded_components {\n" 11 | " id: \"sprite\"\n" 12 | " type: \"sprite\"\n" 13 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 14 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 15 | "attributes {\\n" 16 | " name: \\\"color\\\"\\n" 17 | " double_values {\\n" 18 | " v: 1.0\\n" 19 | " v: 1.0\\n" 20 | " v: 1.0\\n" 21 | " v: 1.0\\n" 22 | " }\\n" 23 | "}\\n" 24 | "textures {\\n" 25 | " sampler: \\\"texture_sampler\\\"\\n" 26 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 27 | "}\\n" 28 | "\"\n" 29 | "}\n" 30 | "embedded_components {\n" 31 | " id: \"collisionobject\"\n" 32 | " type: \"collisionobject\"\n" 33 | " data: \"type: COLLISION_OBJECT_TYPE_DYNAMIC\\n" 34 | "mass: 3.0\\n" 35 | "friction: 0.1\\n" 36 | "restitution: 0.2\\n" 37 | "group: \\\"solid\\\"\\n" 38 | "mask: \\\"solid\\\"\\n" 39 | "mask: \\\"player\\\"\\n" 40 | "mask: \\\"bullet\\\"\\n" 41 | "embedded_collision_shape {\\n" 42 | " shapes {\\n" 43 | " shape_type: TYPE_SPHERE\\n" 44 | " position {\\n" 45 | " }\\n" 46 | " rotation {\\n" 47 | " }\\n" 48 | " index: 0\\n" 49 | " count: 1\\n" 50 | " }\\n" 51 | " data: 33.650795\\n" 52 | "}\\n" 53 | "linear_damping: 0.95\\n" 54 | "locked_rotation: true\\n" 55 | "\"\n" 56 | "}\n" 57 | "embedded_components {\n" 58 | " id: \"impact\"\n" 59 | " type: \"sprite\"\n" 60 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 61 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 62 | "blend_mode: BLEND_MODE_ADD\\n" 63 | "size {\\n" 64 | " x: 64.0\\n" 65 | " y: 64.0\\n" 66 | "}\\n" 67 | "size_mode: SIZE_MODE_MANUAL\\n" 68 | "attributes {\\n" 69 | " name: \\\"color\\\"\\n" 70 | " double_values {\\n" 71 | " v: 1.0\\n" 72 | " v: 1.0\\n" 73 | " v: 1.0\\n" 74 | " v: 0.0\\n" 75 | " }\\n" 76 | "}\\n" 77 | "textures {\\n" 78 | " sampler: \\\"texture_sampler\\\"\\n" 79 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 80 | "}\\n" 81 | "\"\n" 82 | " position {\n" 83 | " z: 0.002\n" 84 | " }\n" 85 | "}\n" 86 | "" 87 | } 88 | embedded_instances { 89 | id: "health" 90 | data: "embedded_components {\n" 91 | " id: \"sprite\"\n" 92 | " type: \"sprite\"\n" 93 | " data: \"default_animation: \\\"ui_circle_64\\\"\\n" 94 | "material: \\\"/panthera/materials/sprite.material\\\"\\n" 95 | "size {\\n" 96 | " x: 64.0\\n" 97 | " y: 64.0\\n" 98 | "}\\n" 99 | "size_mode: SIZE_MODE_MANUAL\\n" 100 | "attributes {\\n" 101 | " name: \\\"color\\\"\\n" 102 | " double_values {\\n" 103 | " v: 0.933\\n" 104 | " v: 0.435\\n" 105 | " v: 0.435\\n" 106 | " v: 1.0\\n" 107 | " }\\n" 108 | "}\\n" 109 | "textures {\\n" 110 | " sampler: \\\"texture_sampler\\\"\\n" 111 | " texture: \\\"/assets/atlases/game_shooting_circle.atlas\\\"\\n" 112 | "}\\n" 113 | "\"\n" 114 | "}\n" 115 | "" 116 | position { 117 | z: 0.001 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /systems/color/color.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | local color_command = require("systems.color.color_command") 4 | local color_event = require("systems.color.color_event") 5 | 6 | ---@class entity 7 | ---@field color component.color|nil 8 | 9 | ---@class entity.color: entity 10 | ---@field color component.color 11 | ---@field game_object component.game_object 12 | 13 | ---@class component.color 14 | ---@field hex_color string 15 | ---@field color vector4|nil 16 | ---@field sprite_url string @"/root#sprite" or "/root#sprite,/root#sprite2" 17 | 18 | ---@class system.color: system 19 | ---@field entities entity.color[] 20 | local M = {} 21 | 22 | 23 | ---@static 24 | ---@return system.color, system.color_command, system.color_event 25 | function M.create_system() 26 | local system = setmetatable(ecs.system(), { __index = M }) 27 | system.filter = ecs.requireAll("color", "game_object") 28 | system.id = "color" 29 | 30 | return system, color_command.create_system(system), color_event.create_system() 31 | end 32 | 33 | 34 | ---@param entity entity.color 35 | function M:onAdd(entity) 36 | local hex_color = entity.color.hex_color 37 | if hex_color then 38 | local r, g, b, a = M.hex2rgb(entity.color.hex_color, 1) 39 | local color = vmath.vector4(r, g, b, a) 40 | self:apply_color(entity, color, entity.color.sprite_url) 41 | end 42 | end 43 | 44 | 45 | ---@param entity entity.color 46 | ---@param color vector4|nil 47 | ---@param sprite_url string|nil "/root#sprite,/root#sprite2" 48 | function M:apply_color(entity, color, sprite_url) 49 | if not color or not sprite_url then 50 | return 51 | end 52 | 53 | local splitted_sprites = M.split(sprite_url, ",") 54 | for index = 1, #splitted_sprites do 55 | local target = splitted_sprites[index] 56 | 57 | local splitted = M.split(target, "#") 58 | local object_id, component_id = splitted[1], splitted[2] or "sprite" 59 | 60 | local object = entity.game_object.object 61 | if object and object[object_id] then 62 | local sprite_url = msg.url(nil, object[object_id], component_id) 63 | go.set(sprite_url, "color", color) -- vertex attribute 64 | end 65 | end 66 | 67 | entity.color.color = color 68 | end 69 | 70 | 71 | ---Split string by separator 72 | ---@param s string 73 | ---@param sep string 74 | function M.split(s, sep) 75 | sep = sep or "%s" 76 | local t = {} 77 | local i = 1 78 | for str in string.gmatch(s, "([^" .. sep .. "]+)") do 79 | t[i] = str 80 | i = i + 1 81 | end 82 | return t 83 | end 84 | 85 | 86 | ---@param hex string 87 | ---@param alpha number|nil 88 | ---@return number, number, number, number 89 | function M.hex2rgb(hex, alpha) 90 | alpha = alpha or 1 91 | if alpha > 1 then 92 | alpha = alpha / 100 93 | end 94 | 95 | -- Remove leading # 96 | if string.sub(hex, 1, 1) == "#" then 97 | hex = string.sub(hex, 2) 98 | end 99 | 100 | -- Expand 3-digit hex codes to 6 digits 101 | if #hex == 3 then 102 | hex = string.rep(string.sub(hex, 1, 1), 2) .. 103 | string.rep(string.sub(hex, 2, 2), 2) .. 104 | string.rep(string.sub(hex, 3, 3), 2) 105 | end 106 | 107 | local r = tonumber("0x" .. string.sub(hex, 1, 2)) / 255 108 | local g = tonumber("0x" .. string.sub(hex, 3, 4)) / 255 109 | local b = tonumber("0x" .. string.sub(hex, 5, 6)) / 255 110 | return r, g, b, alpha 111 | end 112 | 113 | 114 | return M 115 | -------------------------------------------------------------------------------- /decore/decore_internal.lua: -------------------------------------------------------------------------------- 1 | local TYPE_STRING = "string" 2 | 3 | local M = {} 4 | 5 | --- Use empty function to save a bit of memory 6 | local EMPTY_FUNCTION = function(_, message, context) end 7 | 8 | ---@type decore.logger 9 | M.empty_logger = { 10 | trace = EMPTY_FUNCTION, 11 | debug = EMPTY_FUNCTION, 12 | info = EMPTY_FUNCTION, 13 | warn = EMPTY_FUNCTION, 14 | error = EMPTY_FUNCTION, 15 | } 16 | 17 | ---@type decore.logger 18 | M.logger = M.empty_logger 19 | 20 | 21 | ---Split string by separator 22 | ---@param s string 23 | ---@param sep string 24 | function M.split(s, sep) 25 | sep = sep or "%s" 26 | local t = {} 27 | local i = 1 28 | for str in string.gmatch(s, "([^" .. sep .. "]+)") do 29 | t[i] = str 30 | i = i + 1 31 | end 32 | return t 33 | end 34 | 35 | 36 | ---Create a copy of lua table 37 | ---@param orig table The table to copy 38 | ---@return table 39 | function M.deepcopy(orig) 40 | local copy = orig 41 | 42 | if type(orig) == "table" then 43 | -- It's faster than copying or JSON serialization 44 | copy = sys.deserialize(sys.serialize(orig)) 45 | end 46 | 47 | return copy 48 | end 49 | 50 | 51 | --- Merge one table into another recursively 52 | ---@param t1 table 53 | ---@param t2 any 54 | function M.merge_tables(t1, t2) 55 | for k, v in pairs(t2) do 56 | if type(v) == "table" and type(t1[k]) == "table" then 57 | M.merge_tables(t1[k], v) 58 | else 59 | t1[k] = v 60 | end 61 | end 62 | end 63 | 64 | 65 | ---Load JSON file from game resources folder (by relative path to game.project) 66 | ---Return nil if file not found or error 67 | ---@param json_path string 68 | ---@return table|nil 69 | function M.load_json(json_path) 70 | local resource, is_error = sys.load_resource(json_path) 71 | if is_error or not resource then 72 | return nil 73 | end 74 | 75 | return json.decode(resource) 76 | end 77 | 78 | 79 | ---@param data_or_path table|string 80 | ---@return table|nil 81 | function M.get_data_if_path(data_or_path) 82 | if type(data_or_path) == TYPE_STRING then 83 | local entities_path = data_or_path --[[@as string]] 84 | local entities_data = M.load_json(entities_path) 85 | if not entities_data then 86 | M.logger:error("Can't load entities pack", data_or_path) 87 | return nil 88 | end 89 | 90 | return entities_data 91 | end 92 | 93 | return data_or_path --[[@as table]] 94 | end 95 | 96 | 97 | ---Remove the value from the array table by value 98 | ---@param t table 99 | ---@param v any 100 | ---@return boolean @true if value was removed 101 | function M.remove_by_value(t, v) 102 | for index = 1, #t do 103 | if t[index] == v then 104 | table.remove(t, index) 105 | return true 106 | end 107 | end 108 | 109 | return false 110 | end 111 | 112 | 113 | ---@param world world 114 | ---@param component_id string 115 | ---@param component_value any 116 | ---@return entity[] 117 | function M.find_entities_by_component_value(world, component_id, component_value) 118 | local entities = {} 119 | 120 | for index = 1, #world.entities do 121 | local entity = world.entities[index] 122 | if entity[component_id] and entity[component_id] == component_value then 123 | table.insert(entities, entity) 124 | end 125 | end 126 | 127 | for index = 1, #world.entitiesToChange do 128 | local entity = world.entitiesToChange[index] 129 | 130 | if entity[component_id] and entity[component_id] == component_value then 131 | table.insert(entities, entity) 132 | end 133 | end 134 | 135 | return entities 136 | end 137 | 138 | 139 | return M 140 | -------------------------------------------------------------------------------- /systems/transform/transform_command.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field transform_command component.transform_command|nil 5 | 6 | ---@class entity.transform_command: entity 7 | ---@field transform_command component.transform_command 8 | 9 | ---@class component.transform_command 10 | ---@field entity entity @The entity to apply the transform to. 11 | ---@field position_x number|nil @Position x in pixels. 12 | ---@field position_y number|nil @Position y in pixels. 13 | ---@field position_z number|nil @Position z in pixels. 14 | ---@field scale_x number|nil @Scale x in pixels. 15 | ---@field scale_y number|nil @Scale y in pixels. 16 | ---@field scale_z number|nil @Scale z in pixels. 17 | ---@field size_x number|nil @Size x in pixels. 18 | ---@field size_y number|nil @Size y in pixels. 19 | ---@field size_z number|nil @Size z in pixels. 20 | ---@field rotation number|nil @Rotation around x axis in degrees. 21 | ---@field animate_time number|nil @If true will animate the transform over time. 22 | ---@field easing userdata|nil @The easing function to use for the animation. 23 | ---@field relative boolean|nil @If true, the values are relative to the current values. 24 | 25 | ---@class system.transform_command: system 26 | ---@field entities entity.transform_command[] 27 | ---@field transform system.transform 28 | local M = {} 29 | 30 | 31 | ---@static 32 | ---@return system.transform_command 33 | function M.create_system(transform) 34 | local system = setmetatable(ecs.system(), { __index = M }) 35 | system.filter = ecs.requireAny("transform_command") 36 | system.transform = transform 37 | system.id = "transform_command" 38 | 39 | return system 40 | end 41 | 42 | 43 | ---@param entity entity.transform_command 44 | function M:onAdd(entity) 45 | local command = entity.transform_command 46 | if command then 47 | self:process_command(command) 48 | self.world:removeEntity(entity) 49 | end 50 | end 51 | 52 | 53 | ---@param command component.transform_command 54 | function M:process_command(command) 55 | local entity = command.entity --[[@as entity.transform]] 56 | local t = entity.transform 57 | 58 | local is_position_changed = command.position_x ~= nil or command.position_y ~= nil or command.position_z ~= nil 59 | t.position_x = command.position_x or t.position_x 60 | t.position_y = command.position_y or t.position_y 61 | t.position_z = command.position_z or t.position_z 62 | 63 | local is_scale_changed = command.scale_x ~= nil or command.scale_y ~= nil or command.scale_z ~= nil 64 | t.scale_x = command.scale_x or t.scale_x 65 | t.scale_y = command.scale_y or t.scale_y 66 | t.scale_z = command.scale_z or t.scale_z 67 | 68 | local is_size_changed = command.size_x ~= nil or command.size_y ~= nil or command.size_z ~= nil 69 | t.size_x = command.size_x or t.size_x 70 | t.size_y = command.size_y or t.size_y 71 | t.size_z = command.size_z or t.size_z 72 | 73 | local is_rotation_changed = command.rotation ~= nil 74 | t.rotation = command.rotation or t.rotation 75 | 76 | local is_any_changed = is_position_changed or is_scale_changed or is_rotation_changed or is_size_changed 77 | 78 | if is_any_changed then 79 | ---@type entity.transform_event 80 | local transform_event = { transform_event = { 81 | entity = entity, 82 | is_position_changed = is_position_changed, 83 | is_scale_changed = is_scale_changed, 84 | is_size_changed = is_size_changed, 85 | is_rotation_changed = is_rotation_changed, 86 | animate_time = command.animate_time, 87 | easing = command.easing 88 | }} 89 | 90 | self.world:addEntity(transform_event) 91 | end 92 | end 93 | 94 | 95 | return M 96 | -------------------------------------------------------------------------------- /game/misc/game.appmanifest: -------------------------------------------------------------------------------- 1 | platforms: 2 | armv7-ios: 3 | context: 4 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 5 | excludeSymbols: [] 6 | symbols: [] 7 | libs: [physics_2d, record_null, liveupdate_null] 8 | frameworks: [] 9 | linkFlags: [] 10 | arm64-ios: 11 | context: 12 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 13 | excludeSymbols: [] 14 | symbols: [] 15 | libs: [physics_2d, record_null, liveupdate_null] 16 | frameworks: [] 17 | linkFlags: [] 18 | x86_64-ios: 19 | context: 20 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 21 | excludeSymbols: [] 22 | symbols: [] 23 | libs: [physics_2d, record_null, liveupdate_null] 24 | frameworks: [] 25 | linkFlags: [] 26 | armv7-android: 27 | context: 28 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 29 | excludeJars: [] 30 | excludeSymbols: [] 31 | symbols: [] 32 | libs: [physics_2d, record_null, liveupdate_null] 33 | linkFlags: [] 34 | jetifier: true 35 | arm64-android: 36 | context: 37 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 38 | excludeJars: [] 39 | excludeSymbols: [] 40 | symbols: [] 41 | libs: [physics_2d, record_null, liveupdate_null] 42 | linkFlags: [] 43 | jetifier: true 44 | arm64-osx: 45 | context: 46 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 47 | excludeSymbols: [] 48 | symbols: [] 49 | libs: [physics_2d, record_null, liveupdate_null] 50 | frameworks: [] 51 | linkFlags: [] 52 | x86_64-osx: 53 | context: 54 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 55 | excludeSymbols: [] 56 | symbols: [] 57 | libs: [physics_2d, record_null, liveupdate_null] 58 | frameworks: [] 59 | linkFlags: [] 60 | x86_64-linux: 61 | context: 62 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 63 | excludeSymbols: [] 64 | symbols: [] 65 | libs: [physics_2d, record_null, liveupdate_null] 66 | linkFlags: [] 67 | x86-win32: 68 | context: 69 | excludeLibs: [libphysics, libLinearMath, libBulletDynamics, libBulletCollision, librecord, vpx, libliveupdate] 70 | excludeSymbols: [] 71 | symbols: [] 72 | libs: [libphysics_2d.lib, librecord_null.lib, libliveupdate_null.lib] 73 | linkFlags: [] 74 | x86_64-win32: 75 | context: 76 | excludeLibs: [libphysics, libLinearMath, libBulletDynamics, libBulletCollision, librecord, vpx, libliveupdate] 77 | excludeSymbols: [] 78 | symbols: [] 79 | libs: [libphysics_2d.lib, librecord_null.lib, libliveupdate_null.lib] 80 | linkFlags: [] 81 | js-web: 82 | context: 83 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 84 | excludeJsLibs: [] 85 | excludeSymbols: [] 86 | symbols: [] 87 | libs: [physics_2d, record_null, liveupdate_null] 88 | linkFlags: [] 89 | wasm-web: 90 | context: 91 | excludeLibs: [physics, LinearMath, BulletDynamics, BulletCollision, record, vpx, liveupdate] 92 | excludeJsLibs: [] 93 | excludeSymbols: [] 94 | symbols: [] 95 | libs: [physics_2d, record_null, liveupdate_null] 96 | linkFlags: [] 97 | -------------------------------------------------------------------------------- /detiled/annotations.lua: -------------------------------------------------------------------------------- 1 | ---Download Defold annotations from here: https://github.com/astrochili/defold-annotations/releases/ 2 | 3 | ---@class detiled.tileset 4 | ---@field class string 5 | ---@field columns number 6 | ---@field fillmode string 7 | ---@field grid detiled.tileset.grid 8 | ---@field margin number 9 | ---@field name string 10 | ---@field objectalignment string 11 | ---@field spacing number 12 | ---@field tilecount number 13 | ---@field tiledversion string 14 | ---@field tileheight number 15 | ---@field tiles detiled.tileset.tile[] 16 | ---@field tilewidth number 17 | ---@field transformations detiled.tileset.transformations 18 | ---@field type string 19 | ---@field version string @Example: "1.9" 20 | 21 | ---@class detiled.tileset.grid 22 | ---@field height number 23 | ---@field orientation string 24 | ---@field width number 25 | 26 | ---@class detiled.tileset.tile 27 | ---@field class string 28 | ---@field id number 29 | ---@field image string 30 | ---@field imageheight number 31 | ---@field imagewidth number 32 | ---@field objectgroup detiled.tileset.objectgroup|nil 33 | ---@field properties detiled.map.property[] 34 | 35 | ---@class detiled.tileset.objectgroup 36 | ---@field visible boolean 37 | ---@field id number 38 | ---@field name string 39 | ---@field draworder string 40 | ---@field opacity number 41 | ---@field type string 42 | ---@field x number 43 | ---@field y number 44 | ---@field objects detiled.tileset.objectgroup.object[] 45 | 46 | ---@class detiled.tileset.objectgroup.object 47 | ---@field visible boolean 48 | ---@field id number 49 | ---@field name string 50 | ---@field point boolean|nil 51 | ---@field width number 52 | ---@field height number 53 | ---@field x number 54 | ---@field y number 55 | ---@field class string 56 | ---@field rotation number 57 | 58 | ---@class detiled.tileset.transformations 59 | ---@field hflip boolean 60 | ---@field preferuntransformed boolean 61 | ---@field rotate boolean 62 | ---@field vflip boolean 63 | 64 | ---@class detiled.map 65 | ---@field class string 66 | ---@field compressionlevel number 67 | ---@field height number 68 | ---@field infinite boolean 69 | ---@field layers detiled.map.layer[] 70 | ---@field nextlayerid number 71 | ---@field nextobjectid number 72 | ---@field orientation string 73 | ---@field renderorder string 74 | ---@field tiledversion string 75 | ---@field tileheight number 76 | ---@field tilesets detiled.map.tileset[] 77 | ---@field tilewidth number 78 | ---@field type string 79 | ---@field version string 80 | ---@field width number 81 | 82 | ---@class detiled.map.layer 83 | ---@field data number[] @global tile id 84 | ---@field height number 85 | ---@field id number 86 | ---@field name string 87 | ---@field offsetx number 88 | ---@field offsety number 89 | ---@field opacity number 90 | ---@field properties detiled.map.property[] 91 | ---@field type string 92 | ---@field visible boolean 93 | ---@field width number 94 | ---@field x number 95 | ---@field y number 96 | ---@field draworder string 97 | ---@field objects detiled.map.object[] 98 | 99 | ---@class detiled.map.property 100 | ---@field name string 101 | ---@field type string 102 | ---@field value string|table 103 | ---@field propertytype string|nil 104 | 105 | ---@class detiled.map.object 106 | ---@field class string 107 | ---@field gid number 108 | ---@field height number 109 | ---@field id number 110 | ---@field name string 111 | ---@field rotation number 112 | ---@field visible boolean 113 | ---@field width number 114 | ---@field properties detiled.map.property[] 115 | ---@field objectgroup any 116 | ---@field x number 117 | ---@field y number 118 | 119 | ---@class detiled.map.tileset 120 | ---@field firstgid number 121 | ---@field source string 122 | -------------------------------------------------------------------------------- /annotations/defold_annotations/factory.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Generated with github.com/astrochili/defold-annotations 3 | Defold 1.8.1 4 | 5 | Factory API documentation 6 | 7 | Functions for controlling factory components which are used to 8 | dynamically spawn game objects into the runtime. 9 | --]] 10 | 11 | ---@diagnostic disable: lowercase-global 12 | ---@diagnostic disable: missing-return 13 | ---@diagnostic disable: duplicate-doc-param 14 | ---@diagnostic disable: duplicate-set-field 15 | 16 | ---@class defold_api.factory 17 | factory = {} 18 | 19 | ---loaded 20 | factory.STATUS_LOADED = nil 21 | 22 | ---loading 23 | factory.STATUS_LOADING = nil 24 | 25 | ---unloaded 26 | factory.STATUS_UNLOADED = nil 27 | 28 | ---The URL identifies which factory should create the game object. 29 | ---If the game object is created inside of the frame (e.g. from an update callback), the game object will be created instantly, but none of its component will be updated in the same frame. 30 | ---Properties defined in scripts in the created game object can be overridden through the properties-parameter below. 31 | ---See go.property for more information on script properties. 32 | --- Calling factory.create on a factory that is marked as dynamic without having loaded resources 33 | ---using factory.load will synchronously load and create resources which may affect application performance. 34 | ---@param url string|hash|url the factory that should create a game object. 35 | ---@param position vector3|nil the position of the new game object, the position of the game object calling factory.create() is used by default, or if the value is nil. 36 | ---@param rotation quaternion|nil the rotation of the new game object, the rotation of the game object calling factory.create() is used by default, or if the value is nil. 37 | ---@param properties table|nil the properties defined in a script attached to the new game object. 38 | ---@param scale number|vector3|nil the scale of the new game object (must be greater than 0), the scale of the game object containing the factory is used by default, or if the value is nil 39 | ---@return hash id the global id of the spawned game object 40 | function factory.create(url, position, rotation, properties, scale) end 41 | 42 | ---This returns status of the factory. 43 | ---Calling this function when the factory is not marked as dynamic loading always returns 44 | ---factory.STATUS_LOADED. 45 | ---@param url string|hash|url|nil the factory component to get status from 46 | ---@return constant status status of the factory component 47 | --- 48 | ---factory.STATUS_UNLOADED 49 | ---factory.STATUS_LOADING 50 | ---factory.STATUS_LOADED 51 | --- 52 | function factory.get_status(url) end 53 | 54 | ---Resources are referenced by the factory component until the existing (parent) collection is destroyed or factory.unload is called. 55 | ---Calling this function when the factory is not marked as dynamic loading does nothing. 56 | ---@param url string|hash|url|nil the factory component to load 57 | ---@param complete_function fun(self, url, result)|nil function to call when resources are loaded. 58 | --- 59 | ---self 60 | ---object The current object. 61 | ---url 62 | ---url url of the factory component 63 | ---result 64 | ---boolean True if resources were loaded successfully 65 | --- 66 | function factory.load(url, complete_function) end 67 | 68 | ---Changes the prototype for the factory. 69 | ---@param url string|hash|url|nil the factory component 70 | ---@param prototype string|nil the path to the new prototype, or nil 71 | function factory.set_prototype(url, prototype) end 72 | 73 | ---This decreases the reference count for each resource loaded with factory.load. If reference is zero, the resource is destroyed. 74 | ---Calling this function when the factory is not marked as dynamic loading does nothing. 75 | ---@param url string|hash|url|nil the factory component to unload 76 | function factory.unload(url) end 77 | 78 | return factory -------------------------------------------------------------------------------- /gui/gui_main/gui_main.gui_script: -------------------------------------------------------------------------------- 1 | local event = require("event.event") 2 | local panthera = require("panthera.panthera") 3 | 4 | local bindings = require("gui.bindings") 5 | 6 | local BUTTON_ANIMATION = "/resources/animations/button.json" 7 | local GUI_MAIN_ANIMATION = "/resources/animations/gui_main.json" 8 | local HASH_TOUCH = hash("touch") 9 | local HASH_LEFT = hash("key_left") 10 | local HASH_RIGHT = hash("key_right") 11 | 12 | ---@class gui.main 13 | ---@field bindings gui.main.bindings 14 | ---@field text_node hash 15 | ---@field text_timer hash 16 | ---@field button_left hash 17 | ---@field button_right hash 18 | ---@field button_left_animation panthera.animation.state 19 | ---@field button_right_animation panthera.animation.state 20 | ---@field gui_main_animation panthera.animation.state 21 | ---@field is_running boolean 22 | ---@field current_timer number 23 | 24 | ---@class gui.main.bindings 25 | ---@field on_left event 26 | ---@field on_right event 27 | ---@field show_text event 28 | ---@field level_completed event 29 | 30 | 31 | local function on_left(self, callback) 32 | panthera.play(self.button_left_animation, "click") 33 | self.bindings.on_left:trigger() 34 | end 35 | 36 | 37 | local function on_right(self, callback) 38 | panthera.play(self.button_right_animation, "click") 39 | self.bindings.on_right:trigger() 40 | end 41 | 42 | 43 | ---@param self gui.main 44 | function init(self) 45 | self.bindings = bindings.set({ 46 | on_left = event.create(), 47 | on_right = event.create(), 48 | show_text = event.create(), 49 | level_completed = event.create() 50 | }) 51 | 52 | self.text_node = gui.get_node("text_current_level") 53 | self.text_timer = gui.get_node("text_timer") 54 | self.button_left = gui.get_node("button_left/root") 55 | self.button_right = gui.get_node("button_right/root") 56 | 57 | self.gui_main_animation = panthera.create_gui(GUI_MAIN_ANIMATION) --[[@as panthera.animation.state]] 58 | self.button_left_animation = panthera.create_gui(BUTTON_ANIMATION, "button_left") --[[@as panthera.animation.state]] 59 | self.button_right_animation = panthera.create_gui(BUTTON_ANIMATION, "button_right") --[[@as panthera.animation.state]] 60 | 61 | self.is_running = false 62 | self.current_timer = 0 63 | 64 | self.bindings.show_text:subscribe(function(text) 65 | self.is_running = true 66 | self.current_timer = 0 67 | gui.set_text(self.text_node, text) 68 | 69 | panthera.play(self.gui_main_animation, "level_start", { 70 | is_skip_init = true 71 | }) 72 | end) 73 | 74 | self.bindings.level_completed:subscribe(function() 75 | self.is_running = false 76 | 77 | panthera.play(self.gui_main_animation, "level_completed") 78 | end) 79 | 80 | msg.post(".", "acquire_input_focus") 81 | end 82 | 83 | 84 | ---@param self gui.main 85 | ---@param action_id hash 86 | ---@param action action 87 | function on_input(self, action_id, action) 88 | local is_consumed = false 89 | 90 | if action_id == HASH_TOUCH then 91 | if gui.pick_node(self.button_left, action.x, action.y) then 92 | is_consumed = true 93 | if action.pressed then 94 | on_left(self) 95 | end 96 | elseif gui.pick_node(self.button_right, action.x, action.y) then 97 | is_consumed = true 98 | if action.pressed then 99 | on_right(self) 100 | end 101 | end 102 | end 103 | 104 | if action_id == HASH_LEFT then 105 | is_consumed = true 106 | if action.pressed then 107 | on_left(self) 108 | end 109 | end 110 | 111 | if action_id == HASH_RIGHT then 112 | is_consumed = true 113 | if action.pressed then 114 | on_right(self) 115 | end 116 | end 117 | 118 | return is_consumed 119 | end 120 | 121 | 122 | function update(self, dt) 123 | if self.is_running then 124 | self.current_timer = self.current_timer + dt 125 | gui.set_text(self.text_timer, string.format("%.1f", self.current_timer)) 126 | end 127 | end -------------------------------------------------------------------------------- /systems/damage_number/damage_number.lua: -------------------------------------------------------------------------------- 1 | local ecs = require("decore.ecs") 2 | 3 | ---@class entity 4 | ---@field damage_number component.damage_number|nil 5 | 6 | ---@class entity.damage_number: entity 7 | ---@field damage_number component.damage_number 8 | ---@field game_object component.game_object 9 | ---@field transform component.transform 10 | 11 | ---@class component.damage_number 12 | ---@field damage number 13 | ---@field label_url url 14 | 15 | ---@class system.damage_number: system 16 | ---@field entities entity.damage_number[] 17 | local M = {} 18 | 19 | 20 | ---@static 21 | ---@return system.damage_number 22 | function M.create_system() 23 | local system = setmetatable(ecs.system(), { __index = M }) 24 | system.filter = ecs.requireAll("damage_number", "game_object") 25 | system.id = "damage_number" 26 | 27 | return system 28 | end 29 | 30 | 31 | ---@param entity entity.damage_number 32 | function M:onAdd(entity) 33 | local time_1 = 0.4 34 | local time_2 = 0.7 35 | local offset_x = math.random(-100, 100) 36 | local offset_y = math.random(-50, 0) 37 | local height = math.random(50, 100) 38 | 39 | -- Set damage text 40 | entity.damage_number.label_url = self:get_component_url(entity.game_object.object, "/root#label") 41 | label.set_text(entity.damage_number.label_url, tostring(entity.damage_number.damage)) 42 | local root = entity.game_object.root 43 | 44 | local t = entity.transform 45 | go.set(root, "position.x", t.position_x + offset_x) 46 | go.animate(root, "position.x", go.PLAYBACK_ONCE_FORWARD, t.position_x + offset_x + offset_x/2, go.EASING_OUTSINE, time_1, 0, function() 47 | go.animate(root, "position.x", go.PLAYBACK_ONCE_FORWARD, t.position_x + offset_x, go.EASING_INSINE, time_2, 0) 48 | end) 49 | 50 | go.set(root, "position.y", t.position_y + offset_y) 51 | go.animate(root, "position.y", go.PLAYBACK_ONCE_FORWARD, t.position_y + height + offset_y, go.EASING_OUTSINE, time_1 + time_2, 0, function() 52 | self.world:removeEntity(entity) 53 | end) 54 | 55 | local z = self:get_position_z(t) 56 | go.set(root, "position.z", z) 57 | go.animate(root, "position.z", go.PLAYBACK_ONCE_FORWARD, z - 1, go.EASING_LINEAR, time_1 + time_2) 58 | 59 | go.animate(root, "scale.x", go.PLAYBACK_ONCE_FORWARD, 1.2, go.EASING_OUTSINE, time_1, 0, function() 60 | go.animate(root, "scale.x", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_INSINE, time_2, 0) 61 | end) 62 | 63 | go.animate(root, "scale.y", go.PLAYBACK_ONCE_FORWARD, 1.2, go.EASING_OUTSINE, time_1, 0, function() 64 | go.animate(root, "scale.y", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_INSINE, time_2, 0) 65 | end) 66 | 67 | go.animate(entity.damage_number.label_url, "color.w", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_OUTSINE, time_2 + time_1) 68 | go.animate(entity.damage_number.label_url, "outline.w", go.PLAYBACK_ONCE_FORWARD, 0, go.EASING_OUTSINE, time_2 + time_1) 69 | end 70 | 71 | 72 | ---@param object table @Table from game_object.object 73 | ---@param component_url string @ Example: "/root#sprite" 74 | ---@return url 75 | function M:get_component_url(object, component_url) 76 | local path_ids = M.split(component_url, "#") 77 | local object_id = path_ids[1] 78 | local fragment_id = path_ids[2] 79 | 80 | local object_url = msg.url(object[hash(object_id)]) 81 | object_url.fragment = fragment_id 82 | 83 | return object_url 84 | end 85 | 86 | 87 | ---@param t component.transform 88 | ---@return number 89 | function M:get_position_z(t) 90 | return -t.position_y / 10000 + t.position_x / 100000 + t.position_z / 10 + 5 91 | end 92 | 93 | 94 | ---Split string by separator 95 | ---@param s string 96 | ---@param sep string 97 | function M.split(s, sep) 98 | sep = sep or "%s" 99 | local t = {} 100 | local i = 1 101 | for str in string.gmatch(s, "([^" .. sep .. "]+)") do 102 | t[i] = str 103 | i = i + 1 104 | end 105 | return t 106 | end 107 | 108 | 109 | 110 | return M 111 | --------------------------------------------------------------------------------