├── .gitattributes ├── .gitignore ├── README.md ├── art ├── Makefile ├── background.aseprite ├── background_forest.aseprite ├── export_all.cmd ├── export_all.command ├── player.aseprite └── tileset.aseprite └── game ├── addons └── ldtk2godot4 │ ├── .gitattributes │ ├── LICENSE │ ├── ldtk2godot4.gd │ ├── ldtk2godot4_import.gd │ ├── ldtk_level.gd │ ├── ldtk_map.gd │ ├── ldtk_resource_loader.gd │ ├── ldtk_tilemap.gd │ ├── ldtk_world.gd │ └── plugin.cfg ├── assets ├── background_forest_back_hills.png ├── background_forest_back_hills.png.import ├── background_forest_clouds.png ├── background_forest_clouds.png.import ├── background_forest_hills.png ├── background_forest_hills.png.import ├── background_forest_sea.png ├── background_forest_sea.png.import ├── background_forest_sky.png ├── background_forest_sky.png.import ├── pixel.png ├── pixel.png.import ├── player.png ├── player.png.import ├── star_particles.png ├── star_particles.png.import ├── tileset.png └── tileset.png.import ├── base └── main │ ├── main.gd │ └── main.tscn ├── entities ├── actor.gd ├── character_body_2d_entity.gd ├── coin │ ├── coin.gd │ ├── coin.tscn │ ├── coin_reward.gd │ └── coin_reward.tscn ├── cucumber │ ├── cucumber.gd │ └── cucumber.tscn ├── node_2d_entity.gd ├── player │ ├── player.gd │ ├── player.tscn │ └── states │ │ ├── player_attack.gd │ │ ├── player_attack_platform.gd │ │ ├── player_cutscene.gd │ │ ├── player_dead.gd │ │ ├── player_fall.gd │ │ ├── player_hit.gd │ │ ├── player_idle.gd │ │ ├── player_jump.gd │ │ └── player_run.gd ├── slime │ ├── sliCD8.tmp │ ├── slime.gd │ └── slime.tscn ├── trooper │ ├── trooper.gd │ └── trooper.tscn └── vfx │ ├── puff.tscn │ ├── star_particles.gd │ ├── star_particles.tscn │ └── stars.tscn ├── general_purpose ├── damage │ ├── deal_damage_area.gd │ └── rcv_damage_area.gd ├── fade_layer │ ├── fade_layer.gd │ ├── fade_layer.gdshader │ └── fade_layer.material ├── fsm │ ├── fsm.gd │ ├── fsm_anim.gd │ ├── fsm_state.gd │ ├── stacked_fsm.gd │ └── stacked_fsm_state.gd ├── ldtk_minimap │ ├── generate_ldtk_minimap.gd │ └── generate_ldtk_minimap.tscn ├── menu │ ├── menu.gd │ └── menu_item.gd ├── sfx │ ├── random_pitch_stream_multiplayer.gd │ └── random_pitch_stream_player.gd ├── shake_camera │ ├── base_shake_camera.gd │ └── shake_camera.gd └── utils │ ├── anim.gd │ └── utils.gd ├── icon.svg ├── icon.svg.import ├── project.godot ├── resources ├── decorations_tileset.tres ├── map.ldtk ├── map.ldtk.import ├── map │ ├── level_0.ldtkl │ ├── level_0.ldtkl.import │ ├── level_1.ldtkl │ ├── level_1.ldtkl.import │ ├── level_2.ldtkl │ ├── level_2.ldtkl.import │ ├── level_3.ldtkl │ ├── level_3.ldtkl.import │ └── png │ │ ├── level_0.png │ │ ├── level_0.png.import │ │ ├── level_1.png │ │ ├── level_1.png.import │ │ ├── level_2.png │ │ ├── level_2.png.import │ │ ├── level_3.png │ │ └── level_3.png.import └── walls_tileset.tres ├── singletons ├── game.gd ├── params.gd └── sigmgr.gd └── world ├── backgrounds └── forest_background.tscn ├── base_level ├── background.gd ├── base_level.gd ├── base_level.tscn ├── destructibles.gd ├── hazards.gd └── walls.gd ├── map_images.gd ├── world.gd └── world.tscn /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot-specific ignores 2 | .import/ 3 | export.cfg 4 | export_presets.cfg 5 | .godot/ 6 | 7 | # Imported translations (automatically generated from CSV files) 8 | *.translation 9 | 10 | # Mono-specific ignores 11 | .mono/ 12 | data_*/ 13 | 14 | exports/ 15 | media/ 16 | game/resources/map/backups 17 | 18 | # MAC OSX 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | An example of how I use LDtk 1.4.1 and Godot 4.2.1 2 | 3 | The license for the code is MIT but the license for the assets is whatever the author determines as described here: https://opengameart.org/content/arcade-platformer-assets 4 | 5 | 6 | https://github.com/securas/LDtk_With_Godot4_Example/assets/2881755/976d2b4d-6e4a-4013-9027-93185ad52d1f 7 | 8 | This also includes a bunch of extra stuff like: 9 | - automatically transitioning between rooms 10 | - (trying to ) load the entire game and entities 11 | - load level pngs generated by LDtk for ease of use 12 | - place the player node in world.tscn to start at any point in the map 13 | - player control 14 | - zero enemy AI... cause I'm lazy 15 | - camera controller (more or less behaved pixel perfect) 16 | - a simple FSM 17 | - code to manage damage 18 | - a simple menu 19 | - saving and loading encrypted game files 20 | -------------------------------------------------------------------------------- /art/Makefile: -------------------------------------------------------------------------------- 1 | 2 | TARGET := ../game/assets/ 3 | 4 | .PHONY = all 5 | 6 | ifeq ($(OS),Windows_NT) 7 | ASEPRITE := C:/Program Files (x86)/Steam/steamapps/common/Aseprite/Aseprite.exe 8 | else 9 | # this is only for my compiled version of aseprite on Mac OSX 10 | ASEPRITE := /Users/rluis/Library/Application Support/Steam/steamapps/common/Aseprite/Aseprite.app/Contents/MacOS/aseprite 11 | endif 12 | 13 | 14 | 15 | # TODO: add spikes, swinging lights 16 | all: \ 17 | $(TARGET)tileset.png \ 18 | $(TARGET)player.png \ 19 | $(TARGET)background_forest_sky.png 20 | 21 | 22 | 23 | 24 | $(TARGET)tileset.png: tileset.aseprite 25 | "$(ASEPRITE)" -b tileset.aseprite --ignore-layer "Background" -save-as $(TARGET)tileset.png 26 | 27 | $(TARGET)player.png: player.aseprite 28 | "$(ASEPRITE)" -b player.aseprite -sheet $(TARGET)player.png --sheet-width 256 29 | 30 | 31 | $(TARGET)background_forest_sky.png : background_forest.aseprite 32 | "$(ASEPRITE)" -b background_forest.aseprite -save-as $(TARGET)background_forest_{layer}.png 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /art/background.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/art/background.aseprite -------------------------------------------------------------------------------- /art/background_forest.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/art/background_forest.aseprite -------------------------------------------------------------------------------- /art/export_all.cmd: -------------------------------------------------------------------------------- 1 | make 2 | rem pause 3 | exit 4 | -------------------------------------------------------------------------------- /art/export_all.command: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | dir=${0%/*} 3 | if [ "$dir" = "$0" ]; then 4 | dir="." 5 | fi 6 | cd "$dir" 7 | 8 | make 9 | 10 | exit 0 11 | 12 | -------------------------------------------------------------------------------- /art/player.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/art/player.aseprite -------------------------------------------------------------------------------- /art/tileset.aseprite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/art/tileset.aseprite -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 securas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/ldtk2godot4.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | 4 | var ldtk2godot4_import 5 | 6 | func _enter_tree(): 7 | ldtk2godot4_import = preload( "ldtk2godot4_import.gd" ).new() 8 | add_import_plugin( ldtk2godot4_import ) 9 | 10 | 11 | func _exit_tree(): 12 | remove_import_plugin( ldtk2godot4_import ) 13 | ldtk2godot4_import = null 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/ldtk2godot4_import.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorImportPlugin 3 | 4 | enum Presets { DEFAULT } 5 | 6 | var debug := true 7 | 8 | func _get_importer_name() -> String: 9 | return "LDtk2Godot4" 10 | 11 | func _get_visible_name() -> String: 12 | return "LDtk_Map" 13 | 14 | func _get_recognized_extensions() -> PackedStringArray: 15 | return PackedStringArray( [ "ldtk", "ldtkl" ] ) 16 | 17 | func _get_save_extension() -> String: 18 | return "res" 19 | 20 | func _get_resource_type() -> String: 21 | return "Resource" 22 | 23 | func _get_preset_count() -> int: 24 | return Presets.size() 25 | 26 | func _get_preset_name( idx : int ) -> String: 27 | match idx: 28 | Presets.DEFAULT: 29 | return "default" 30 | _: 31 | return "Unknown" 32 | 33 | func _get_import_options( _path : String, _preset_idx : int ) -> Array[Dictionary]: 34 | return [] 35 | 36 | func _get_option_visibility( _path : String, _option_name : StringName, _options : Dictionary) -> bool: 37 | return true 38 | 39 | func _get_priority() -> float: 40 | return 1.0 41 | 42 | func _get_import_order() -> int: 43 | return 0 44 | 45 | func _import( source_file : String, save_path : String, _options : Dictionary, _platform_variants : Array[String], _gen_files : Array[ String ] ) -> Error: 46 | if debug: print( "importing %s" % [ source_file ] ) 47 | 48 | # read json data from file 49 | var file := FileAccess.open( source_file, FileAccess.READ ) 50 | var json_string : String = file.get_as_text() 51 | file.close() 52 | file = null 53 | 54 | # parse json 55 | var json := JSON.new() 56 | var error := json.parse(json_string) 57 | if error != OK: 58 | print( "LDtk Import: JSON Parse Error: ", json.get_error_message(), " in ", json_string, " at line ", json.get_error_line() ) 59 | return error 60 | 61 | var ldtkmap = LDtkMap.new() 62 | ldtkmap.single_level = not json.data.has( "levels" ) 63 | if ldtkmap.single_level: ldtkmap.single_level_name = json.data.identifier 64 | ldtkmap.cell_data = _process_json_celldata( json.data ).duplicate( true ) 65 | ldtkmap.entity_data = _process_json_entities( json.data ).duplicate( true ) 66 | ldtkmap.world_data = _process_json_leveldata( json.data ).duplicate( true ) 67 | 68 | var filename = save_path + "." + _get_save_extension() 69 | # print( "importer levels: ", ldtkmap.cell_data ) 70 | return ResourceSaver.save( ldtkmap, filename ) 71 | 72 | 73 | func _process_json_leveldata( json : Dictionary ) -> Dictionary: 74 | var data := {} 75 | var json_levels = [] 76 | if json.has( "levels" ): 77 | json_levels = json.levels 78 | else: 79 | # this is a partial level file 80 | json_levels = [json] 81 | for source_level in json_levels: 82 | data[source_level.identifier] = {} 83 | data[source_level.identifier]["level_rect"] = Rect2( 84 | Vector2( source_level.worldX, source_level.worldY ), 85 | Vector2( source_level.pxWid, source_level.pxHei ) 86 | ) 87 | data[source_level.identifier]["external_resource"] = source_level.externalRelPath 88 | var level_params = {} 89 | if source_level.has( "fieldInstances" ): 90 | for field_instance in source_level.fieldInstances: 91 | level_params[field_instance.__identifier] = field_instance.__value 92 | data[source_level.identifier]["level_params"] = level_params 93 | return data 94 | 95 | func _process_json_celldata( json : Dictionary ) -> Dictionary: 96 | # print( " <<<< Importing LDtk Data >>>>") 97 | var data := {} 98 | var json_levels = [] 99 | if json.has( "levels" ): 100 | # a full levels LDtk file 101 | json_levels = json.levels 102 | else: 103 | # this is a partial level file 104 | json_levels = [json] 105 | 106 | for source_level in json_levels: 107 | if source_level.has( "externalRelPath" ) and source_level.externalRelPath: 108 | print( "Cells: This level is under a different file..." ) 109 | continue 110 | 111 | data[source_level.identifier] = {} 112 | for source_layer in source_level.layerInstances: 113 | if source_layer.__type == "Entities": continue 114 | data[source_level.identifier][source_layer.__identifier] = {} 115 | data[source_level.identifier][source_layer.__identifier].cell_size = source_layer.__gridSize 116 | var tilemap_position = PackedVector2Array() 117 | var autotile_coord = PackedVector2Array() 118 | var flip_x : Array[bool] = [] 119 | var flip_y : Array[bool]= [] 120 | var transpose : Array[bool] = [] 121 | 122 | var cell_size = source_layer.__gridSize 123 | 124 | var source_layer_tiles = source_layer.autoLayerTiles 125 | if not source_layer_tiles: source_layer_tiles = source_layer.gridTiles 126 | for source_cell in source_layer_tiles:#source_layer.autoLayerTiles: 127 | tilemap_position.append( Vector2( source_cell.px[0] / cell_size, source_cell.px[1] / cell_size ) ) 128 | autotile_coord.append( Vector2( source_cell.src[0], source_cell.src[1] ) / cell_size ) 129 | flip_x.append( int( source_cell.f ) & 1 == 1 ) 130 | flip_y.append( int( source_cell.f ) & 2 == 2 ) 131 | transpose.append( false ) 132 | data[source_level.identifier][source_layer.__identifier].tilemap_position = tilemap_position 133 | data[source_level.identifier][source_layer.__identifier].autotile_coord = autotile_coord 134 | data[source_level.identifier][source_layer.__identifier].flip_x = flip_x 135 | data[source_level.identifier][source_layer.__identifier].flip_y = flip_y 136 | data[source_level.identifier][source_layer.__identifier].transpose = transpose 137 | return data 138 | 139 | 140 | 141 | 142 | func _process_json_entities( json : Dictionary ) -> Dictionary: 143 | var data := {} 144 | 145 | var json_levels = [] 146 | if json.has( "levels" ): 147 | json_levels = json.levels 148 | else: 149 | json_levels = [json] 150 | 151 | for source_level in json_levels: 152 | if source_level.has( "externalRelPath" ) and source_level.externalRelPath: 153 | if debug: print( "Entities: This level is under a different file..." ) 154 | continue 155 | data[source_level.identifier] = {} 156 | for source_layer in source_level.layerInstances: 157 | if source_layer.__type != "Entities": continue 158 | data[source_level.identifier][source_layer.__identifier] = [] 159 | var cell_size : int = source_layer.__gridSize 160 | var layer_defid = source_layer.layerDefUid 161 | for entity in source_layer.entityInstances: 162 | var new_entity : Dictionary 163 | new_entity.id = entity.__identifier 164 | new_entity.position_px = Vector2( entity.px[0], entity.px[1] ) 165 | new_entity.iid = entity.iid 166 | new_entity.size = Vector2( entity.width, entity.height ) 167 | new_entity.parameters = {} 168 | for parameter in entity.fieldInstances: 169 | new_entity.parameters[parameter.__identifier] = parameter.__value 170 | data[source_level.identifier][source_layer.__identifier].append( new_entity ) 171 | return data 172 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/ldtk_level.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name LDtkLevel extends Node2D 3 | 4 | 5 | signal player_leaving_level( player, player_world_position ) 6 | 7 | 8 | @export var _map : LDtkMap = null : set = _set_map 9 | @export var _detect_player := true 10 | @export_flags_2d_physics var _area_collision_mask : int 11 | @export_flags_2d_physics var _player_detection_mask : int 12 | 13 | @export var _player_detection_grace_time : float 14 | 15 | var level_name : String 16 | var rect : Rect2i 17 | var entities : Array[Dictionary] 18 | 19 | var _debug := false 20 | var _level_limits_created := false 21 | 22 | var _detect_width := 8 23 | var _report_offset := 0.0 24 | var _update_ingame := false 25 | var _detection_area : Area2D 26 | 27 | #---------------------------- 28 | # Methods to Override 29 | #---------------------------- 30 | func _activate( _act : bool = true) -> void: 31 | pass 32 | 33 | #---------------------------- 34 | # Setters 35 | #---------------------------- 36 | func _set_map( v : LDtkMap ) -> void: 37 | _map = v 38 | if Engine.is_editor_hint() and name: 39 | _update_map() 40 | 41 | #---------------------------- 42 | # Enter Tree 43 | #---------------------------- 44 | func _enter_tree() -> void: 45 | if _debug: print( name, ": Entering tree" ) 46 | if not Engine.is_editor_hint() and _detect_player: 47 | _activate( false ) # always start the level deactivated 48 | _update_map() 49 | _create_level_limits() 50 | 51 | #---------------------------- 52 | # Update map 53 | #---------------------------- 54 | func _update_map() -> void: 55 | if not _map: 56 | if _debug: print( name, ": Requires a valid single level map." ) 57 | _set_LDtk_tilemaps_recursive( self ) 58 | return 59 | if not _map.single_level: 60 | if _debug: print( name, ": Only supports single level maps." ) 61 | _set_LDtk_tilemaps_recursive( self ) 62 | return 63 | if _debug: print( name, ": Updating map" ) 64 | level_name = _map.single_level_name 65 | rect = _map.world_data[ level_name ].level_rect 66 | if _map.entity_data[ level_name ].has( "entities" ): 67 | entities.assign( _map.entity_data[ level_name ].entities ) 68 | _set_LDtk_tilemaps_recursive( self ) 69 | 70 | func _set_LDtk_tilemaps_recursive( node : Node ) -> void: 71 | for c in node.get_children(): 72 | if c is LDtkTileMap: 73 | c._map = _map 74 | _set_LDtk_tilemaps_recursive( c ) 75 | 76 | #---------------------------- 77 | # Level Limits 78 | #---------------------------- 79 | func force_level_limits()->void: 80 | if rect.size.x == 0 and rect.size.y == 0: return 81 | _level_limits_created = false 82 | if _detection_area: 83 | _detection_area.queue_free() 84 | _create_level_limits() 85 | func _create_level_limits() -> void: 86 | if _level_limits_created: return 87 | if rect.size.x == 0 and rect.size.y == 0: return 88 | _level_limits_created = true 89 | var _ret : int 90 | _detection_area = Area2D.new() 91 | _detection_area.name = "_player_limits" 92 | _detection_area.collision_layer = _area_collision_mask 93 | _detection_area.collision_mask = _player_detection_mask 94 | _detection_area.visible = false 95 | var _detection_area_shape = CollisionShape2D.new() 96 | _detection_area_shape.shape = RectangleShape2D.new() 97 | _detection_area_shape.shape.size = \ 98 | Vector2( rect.size ) - Vector2.ONE * _detect_width * 2 99 | _detection_area_shape.position = rect.size * 0.5; 100 | _detection_area.body_exited.connect( _on_player_exited_detection_area, CONNECT_DEFERRED ) 101 | add_child( _detection_area ) 102 | _detection_area.add_child( _detection_area_shape ) 103 | 104 | 105 | func _on_detect_start_time(): 106 | if _debug: print( name, ": Grace time terminated. Activating border areas") 107 | _activate_detection_areas( true ) 108 | 109 | func _on_player_exited_detection_area( player : Node2D ) -> void: 110 | # find player position with respect to level 111 | if player.global_position.x < _detect_width: 112 | if _debug: print( "LDtkLevel: player leaving left" ) 113 | _report_player_detected( player, rect.position + \ 114 | Vector2i( -_report_offset - _detect_width, player.position.y ) ) 115 | elif player.global_position.x > rect.size.x - _detect_width: 116 | if _debug: print( "LDtkLevel: player leaving right" ) 117 | _report_player_detected( player, rect.position + \ 118 | Vector2i( rect.size.x + _detect_width + _report_offset, player.position.y ) ) 119 | elif player.global_position.y < _detect_width: 120 | if _debug: print( "LDtkLevel: player leaving above" ) 121 | _report_player_detected( player, rect.position + \ 122 | Vector2i( player.position.x, -_report_offset - _detect_width ) ) 123 | else: 124 | if _debug: print( "LDtkLevel: player leaving bottom" ) 125 | _report_player_detected( player, rect.position + \ 126 | Vector2i( player.position.x, player.position.y + _report_offset + _detect_width ) ) 127 | 128 | func _report_player_detected( player : Node2D, player_world_position : Vector2 ) -> void: 129 | if not _detect_player: return 130 | player_leaving_level.emit( player, player_world_position ) 131 | 132 | 133 | #------------------------------------------- 134 | # Useful functions 135 | #------------------------------------------- 136 | func get_node_worldpos( node : Node2D ) -> Vector2i: 137 | return Vector2i( node.global_position ) + rect.position 138 | 139 | func local_to_world_position( localpos : Vector2 ) -> Vector2i: 140 | return Vector2i( localpos ) + rect.position 141 | 142 | func get_worldpos_px() -> Vector2i: 143 | if not _map: return Vector2i.ZERO 144 | return rect.position 145 | 146 | func get_worldsize_px() -> Vector2i: 147 | if not _map: return Vector2i.ZERO 148 | return rect.size 149 | 150 | func _activate_detection_areas( v : bool = true ) -> void: 151 | var p = not v 152 | if not _detection_area: return 153 | _detection_area.get_child(0).call_deferred( "set_disabled", p ) 154 | 155 | func get_entities() -> Array[Dictionary]: 156 | return entities 157 | 158 | func get_level_params() -> Dictionary: 159 | if not _map or not _map.single_level: return {} 160 | if not _map.world_data[_map.single_level_name].has( "level_params" ): 161 | return {} 162 | return _map.world_data[_map.single_level_name].level_params 163 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/ldtk_map.gd: -------------------------------------------------------------------------------- 1 | class_name LDtkMap extends Resource 2 | 3 | # Note: All variables to be saved have to be exported! 4 | @export var single_level := false 5 | @export var single_level_name : String 6 | @export var world_data : Dictionary 7 | @export var cell_data : Dictionary 8 | @export var entity_data : Dictionary 9 | 10 | 11 | #------------------------------------------- 12 | # Useful functions 13 | #------------------------------------------- 14 | func get_level_names() -> PackedStringArray: 15 | return PackedStringArray( world_data.keys() ) 16 | 17 | func get_level_name_at( world_position : Vector2 ) -> StringName: 18 | for level_name in get_level_names(): 19 | var r = world_data[level_name].level_rect as Rect2 20 | if r.has_point( world_position ): 21 | return StringName( level_name ) 22 | return &"" 23 | 24 | func get_level_data( level_name : String ) -> Dictionary: 25 | if single_level: 26 | return world_data[ single_level_name ] 27 | if world_data.has( level_name ): 28 | return world_data[ level_name ] 29 | return {} 30 | 31 | 32 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/ldtk_resource_loader.gd: -------------------------------------------------------------------------------- 1 | class_name LDtkResourceLoader extends RefCounted 2 | 3 | signal loading_complete() 4 | 5 | const _hidden_level_position : Vector2 = Vector2.ONE * 10000 6 | const _hidden_entity_position : Vector2 = Vector2( 0, 10000 ) 7 | 8 | var _map : LDtkMap 9 | var _base_level_scene : String 10 | var _base_level_folder : String 11 | var _debug := false 12 | var _entity_types : Dictionary 13 | var _parent : Node2D 14 | 15 | var levels : Array[LDtkLevel] 16 | var entities : Array[Node2D] 17 | var entity_data : Array[Dictionary] 18 | 19 | func _init( parent : Node2D, map : LDtkMap, base_level_scene : String, base_level_folder : String ) -> void: 20 | _parent = parent 21 | _map = map 22 | _base_level_scene = base_level_scene 23 | _base_level_folder = base_level_folder 24 | 25 | func start_loading() -> void: 26 | _generate_ldtk_levels.call_deferred() 27 | 28 | # Ok for small games but maybe too much for large games 29 | # This will simply load all levels, hide them and place them far away 30 | func _generate_ldtk_levels() -> void: 31 | #-------------------------------- 32 | # Generate levels 33 | #-------------------------------- 34 | ## list files in folder 35 | #if _base_level_folder: 36 | #var dir = DirAccess.open( _base_level_folder ) 37 | #if dir: 38 | #dir.list_dir_begin() 39 | #var file_name = dir.get_next() 40 | #while file_name != "": 41 | #if dir.current_is_dir(): 42 | #print("Found directory: " + file_name) 43 | #else: 44 | #print("Found file: " + file_name) 45 | #file_name = dir.get_next() 46 | #else: 47 | #print("An error occurred when trying to access the path.") 48 | 49 | var level_names = _map.get_level_names() 50 | levels.clear() 51 | _entity_types = {} 52 | for level_name in level_names: 53 | if _debug: print( "LDtkResourceLoader: Preparing resource for level - ", level_name ) 54 | # get resource for this level 55 | var resource_path : String = "%s/%s" % [ 56 | _map.resource_path.substr( 0, _map.resource_path.rfind( "/" ) ), 57 | _map.world_data[level_name].external_resource ] 58 | var new_map : LDtkMap = load( resource_path ) 59 | 60 | # check if file exists. If not, generate level at runtime 61 | var filename = "%s/%s.tscn" % [ _base_level_folder, level_name ] 62 | var exported_filename = "%s.remap" % [ filename ] 63 | #var dir = DirAccess.open( _base_level_folder ) 64 | if _debug: print( "LDtkResourceLoader: Searching for level file - ", filename ) 65 | #if dir and dir.file_exists( "%s.tscn" % [ level_name ] ): 66 | if FileAccess.file_exists( filename ) or FileAccess.file_exists( exported_filename ): 67 | if _debug: print( "LDtkResourceLoader: Preloading level - ", filename ) 68 | var new_level : LDtkLevel = load( filename ).instantiate() 69 | new_level.hide() 70 | new_level.position = _hidden_level_position 71 | levels.append( new_level ) 72 | new_level._map = new_map 73 | _parent.add_child( new_level ) 74 | else: 75 | if _debug: print( "LDtkResourceLoader: Initializing level - ", resource_path ) 76 | if not _base_level_scene: 77 | printerr( "LDtkResourceLoader: Unable to find base level scene - ", _base_level_scene ) 78 | continue 79 | var new_level : LDtkLevel = load( _base_level_scene ).instantiate() 80 | new_level.hide() 81 | new_level.position = _hidden_level_position 82 | _parent.add_child( new_level ) 83 | new_level.name = level_name 84 | new_level._map = new_map 85 | new_level._update_map() 86 | new_level.force_level_limits() 87 | levels.append( new_level ) 88 | 89 | # count entities for this level 90 | var ldtk_entities := [] 91 | if new_map.entity_data[level_name].has( "entities" ) and new_map.entity_data[level_name].entities: 92 | #print( ">>>", new_map.entity_data[level_name].entities ) 93 | ldtk_entities = new_map.entity_data[level_name].entities 94 | var _level_entity_counts : Dictionary 95 | for ldtk_entity in ldtk_entities: 96 | if not ldtk_entity.parameters.has( "resource" ): 97 | printerr( "Unable to find resource in entity with id: ", ldtk_entity.id ) 98 | continue 99 | var entity_id : String = ldtk_entity.id 100 | if _level_entity_counts.has( entity_id ): 101 | _level_entity_counts[entity_id].count += 1 102 | else: 103 | _level_entity_counts[entity_id] = { 104 | "count" : 1, 105 | "res" : ldtk_entity.parameters.resource 106 | } 107 | # increment entities 108 | for entity_id in _level_entity_counts: 109 | if _entity_types.has( entity_id ): 110 | _entity_types[entity_id].count = maxi( 111 | _entity_types[entity_id].count, 112 | _level_entity_counts[entity_id].count 113 | ) 114 | else: 115 | # _entity_types[entity_id] = _level_entity_counts[entity_id].duplicate( true ) 116 | _entity_types[entity_id] = { 117 | "count" : _level_entity_counts[entity_id].count, 118 | "res" : _level_entity_counts[entity_id].res 119 | } 120 | #-------------------------------- 121 | # Generate required entities 122 | #-------------------------------- 123 | entities.clear() 124 | entity_data.clear() 125 | for entity_id in _entity_types: 126 | var entity_scn : PackedScene = load( _entity_types[entity_id].res ) 127 | for entity_count in range( _entity_types[entity_id].count ): 128 | var new_entity = entity_scn.instantiate() 129 | new_entity._entity_activate( false ) 130 | new_entity.hide() 131 | new_entity.position = _hidden_entity_position 132 | _parent.add_child( new_entity ) 133 | new_entity._entity_activate( false ) 134 | entities.append( new_entity ) 135 | new_entity.owner = _parent 136 | var new_entity_data = { 137 | "id" : entity_id, 138 | "reserved" : false 139 | } 140 | entity_data.append( new_entity_data ) 141 | 142 | # All done 143 | loading_complete.emit() 144 | 145 | 146 | #---------------------------------------- 147 | # Utilities 148 | #---------------------------------------- 149 | func get_level( level_name : String ) -> LDtkLevel: 150 | for level in levels: 151 | if level.name == level_name: 152 | return level 153 | return null 154 | 155 | func reserve_entity( entity_id : String ) -> Node2D: 156 | for idx in range( entities.size() ): 157 | if not entity_data[idx].reserved and entity_data[idx].id == entity_id: 158 | entity_data[idx].reserved = true 159 | return entities[idx] 160 | return null 161 | 162 | func release_entity( entity : Node2D ) -> void: 163 | var pos = entities.find( entity ) 164 | if pos == -1: 165 | printerr( "LDtkResourceLoader : Unable to release entity" ) 166 | entity_data[pos].reserved = false 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/ldtk_tilemap.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name LDtkTileMap extends TileMap 3 | 4 | signal finished_populating_map 5 | 6 | @export var _map : LDtkMap = null : set = _set_map 7 | @export var __levels : Array[String] : set = _set_levels 8 | @export var _layers : Array[String] : set = _set_layers 9 | @export var tilemap_layer : int = 0 10 | @export var tilemap_source_id : int = 0 11 | 12 | var _debug := false 13 | var _levels : Array[String] 14 | 15 | 16 | #---------------------------- 17 | # Setters 18 | #---------------------------- 19 | func _set_map( v : LDtkMap ) -> void: 20 | if _debug: print( name, ": Setting Map" ) 21 | _map = v 22 | # if Engine.is_editor_hint() and name: _update_ldtk_map() 23 | _update_ldtk_map() 24 | 25 | func _set_levels( v : Array[String] ) -> void: 26 | if _debug: print( name, ": Setting Levels" ) 27 | _levels = v 28 | __levels = _levels 29 | if Engine.is_editor_hint() and name: _update_ldtk_map() 30 | # _update_ldtk_map() 31 | 32 | func _set_layers( v : Array[String] ) -> void: 33 | if _debug: print( name, ": Setting Layers" ) 34 | _layers = v 35 | if Engine.is_editor_hint() and name: _update_ldtk_map() 36 | # _update_ldtk_map() 37 | 38 | #---------------------------- 39 | # Enter Tree 40 | #---------------------------- 41 | func _enter_tree() -> void: 42 | if _debug: print( name, ": Entering tree" ) 43 | # if Engine.is_editor_hint(): # Not sure about this 44 | # _update_ldtk_map() 45 | _update_ldtk_map() 46 | 47 | 48 | #---------------------------- 49 | # Update Map 50 | #---------------------------- 51 | func _update_ldtk_map() -> void: 52 | __update_ldtk_map() 53 | 54 | 55 | func __update_ldtk_map() -> void: 56 | if _debug: print( name, ": Updating tilemap" ) 57 | clear() 58 | if not _map: 59 | if _debug: print( name, ": No available LDtk map." ) 60 | return 61 | if _map.single_level: 62 | if _debug: print( name, ": Single level map. Setting level to ", _map.single_level_name ) 63 | _levels = [ _map.single_level_name ] 64 | if not _levels: 65 | if _debug: print( name, ": No levels defined." ) 66 | return 67 | if not _layers: 68 | if _debug: print( name, ": No layers defined." ) 69 | return 70 | 71 | for cur_level_name in _levels: 72 | if _debug: print( name, ": Updating level - ", cur_level_name ) 73 | if not _map.cell_data.has( cur_level_name ): 74 | if _debug: print( name, ": Could not find data for level ", cur_level_name ) 75 | continue 76 | var cur_level : Dictionary = _map.cell_data[ cur_level_name ] 77 | for cur_layer_name in _layers: 78 | if _debug: print( name, ": Updating layer - ", cur_layer_name ) 79 | if not cur_level.has( cur_layer_name ): 80 | if _debug: print( name, ": Could not find data for layer ", cur_layer_name ) 81 | continue 82 | var cur_layer : Dictionary = cur_level[ cur_layer_name ] 83 | _write_layer_to_map( cur_layer ) 84 | finished_populating_map.emit() 85 | 86 | func _write_layer_to_map( cur_layer : Dictionary ) -> void: 87 | var ntiles : int = cur_layer.tilemap_position.size() 88 | if _debug: print( name, ": Setting ", ntiles," tiles." ) 89 | for idx in range( ntiles ): 90 | set_cell( 91 | tilemap_layer, 92 | cur_layer.tilemap_position[idx], 93 | tilemap_source_id, 94 | cur_layer.autotile_coord[idx], 95 | 0 96 | ) 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/ldtk_world.gd: -------------------------------------------------------------------------------- 1 | class_name LDtkWorld extends Node2D 2 | 3 | signal world_ready 4 | signal level_ready( level : LDtkLevel ) 5 | 6 | @export var map : LDtkMap 7 | @export var use_resource_loader : bool = true 8 | @export_file( "*.tscn" ) var base_level_scene : String 9 | @export_dir var base_level_folder : String 10 | 11 | 12 | var _debug := false 13 | var _resload : LDtkResourceLoader 14 | var _cur_level : LDtkLevel 15 | var _cur_entities : Array[Node2D] 16 | 17 | func _ready() -> void: 18 | if use_resource_loader: 19 | _resload = LDtkResourceLoader.new( self, map, base_level_scene, base_level_folder ) 20 | _resload.loading_complete.connect( _on_finished_loading ) 21 | _resload.start_loading() 22 | 23 | 24 | 25 | func _on_finished_loading() -> void: 26 | world_ready.emit() 27 | 28 | func load_level( level_name : String ) -> void: 29 | call_deferred( "_load_level", level_name ) 30 | 31 | func _load_level( level_name : String ) -> void: 32 | # deactivate current entities 33 | for entity in _cur_entities: 34 | entity.hide() 35 | entity._entity_activate( false ) 36 | _resload.release_entity( entity ) 37 | entity.entity_iid = "" 38 | entity.position = LDtkResourceLoader._hidden_entity_position 39 | _cur_entities.clear() 40 | 41 | # deactivate current level 42 | if _cur_level: 43 | _cur_level.hide() 44 | _cur_level._activate( false ) 45 | _cur_level.position = LDtkResourceLoader._hidden_level_position 46 | _cur_level.player_leaving_level.disconnect( _on_player_leaving_level ) 47 | _cur_level = null 48 | 49 | # position and connect new level 50 | _cur_level = _resload.get_level( level_name ) 51 | if not _cur_level: 52 | printerr( name, ": Unable to find level in resource loader - ", level_name ) 53 | _cur_level.player_leaving_level.connect( _on_player_leaving_level ) 54 | _cur_level.position *= 0 55 | 56 | # position and initialize new entities 57 | if _cur_level._map.entity_data[level_name].has( "entities" ): 58 | for entity_data in _cur_level._map.entity_data[level_name].entities: 59 | var entity = _resload.reserve_entity( entity_data.id ) 60 | if not entity: 61 | printerr( name, ": Unable to find entity in resource loader - ", entity_data.id ) 62 | entity.entity_iid = entity_data.iid 63 | entity.position = entity_data.position_px.round() 64 | var aux_parameters = entity_data.parameters 65 | aux_parameters.position = entity.position 66 | aux_parameters.size = entity_data.size 67 | entity._entity_initialize( aux_parameters ) 68 | entity._entity_activate( true ) 69 | _cur_entities.append( entity ) 70 | 71 | # show level and entities 72 | _cur_level._activate( true ) 73 | _cur_level.show() 74 | for entity in _cur_entities: 75 | entity.show() 76 | # all done 77 | level_ready.emit( _cur_level ) 78 | 79 | 80 | # This part of the code should not be here 81 | # it needs to be overriden by the world class 82 | func _on_player_leaving_level( player : Node2D, player_world_position : Vector2 ) -> void: 83 | return 84 | if _debug: print( "Player leaving level" ) 85 | var new_level_name = map.get_level_name_at( player_world_position ) 86 | if _debug: print( "New level: ", new_level_name ) 87 | if not new_level_name: 88 | printerr( "Unable to find level" ) 89 | return 90 | player._entity_activate( false ) 91 | load_level( new_level_name ) 92 | await level_ready 93 | player.position = player_world_position - Vector2( _cur_level.get_worldpos_px() ) 94 | player._entity_activate( true ) 95 | 96 | 97 | -------------------------------------------------------------------------------- /game/addons/ldtk2godot4/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="ldtk2godot4" 4 | description="" 5 | author="Securas" 6 | version="0.1" 7 | script="ldtk2godot4.gd" 8 | -------------------------------------------------------------------------------- /game/assets/background_forest_back_hills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/background_forest_back_hills.png -------------------------------------------------------------------------------- /game/assets/background_forest_back_hills.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://qu7whdpq1gho" 6 | path="res://.godot/imported/background_forest_back_hills.png-7cf8619224f7883434b9220289f4906d.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/background_forest_back_hills.png" 14 | dest_files=["res://.godot/imported/background_forest_back_hills.png-7cf8619224f7883434b9220289f4906d.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/background_forest_clouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/background_forest_clouds.png -------------------------------------------------------------------------------- /game/assets/background_forest_clouds.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dt3nea0r3swkg" 6 | path="res://.godot/imported/background_forest_clouds.png-16b5e5bf98496c3cc97008aa049a111a.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/background_forest_clouds.png" 14 | dest_files=["res://.godot/imported/background_forest_clouds.png-16b5e5bf98496c3cc97008aa049a111a.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/background_forest_hills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/background_forest_hills.png -------------------------------------------------------------------------------- /game/assets/background_forest_hills.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://so6gg48g01k7" 6 | path="res://.godot/imported/background_forest_hills.png-a647388dba1d9e070ce2f864358fe173.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/background_forest_hills.png" 14 | dest_files=["res://.godot/imported/background_forest_hills.png-a647388dba1d9e070ce2f864358fe173.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/background_forest_sea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/background_forest_sea.png -------------------------------------------------------------------------------- /game/assets/background_forest_sea.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dxp0vt8kplrdb" 6 | path="res://.godot/imported/background_forest_sea.png-e7cbc1a6fca06379795cddef5abe2dda.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/background_forest_sea.png" 14 | dest_files=["res://.godot/imported/background_forest_sea.png-e7cbc1a6fca06379795cddef5abe2dda.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/background_forest_sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/background_forest_sky.png -------------------------------------------------------------------------------- /game/assets/background_forest_sky.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b76leqevv8pdx" 6 | path="res://.godot/imported/background_forest_sky.png-a4c3623aec381d2dd72ba7c0827b86c1.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/background_forest_sky.png" 14 | dest_files=["res://.godot/imported/background_forest_sky.png-a4c3623aec381d2dd72ba7c0827b86c1.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/pixel.png -------------------------------------------------------------------------------- /game/assets/pixel.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://d0f705ki11pq6" 6 | path="res://.godot/imported/pixel.png-06c909df9bfbe4a6f5498f339ed69b7c.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/pixel.png" 14 | dest_files=["res://.godot/imported/pixel.png-06c909df9bfbe4a6f5498f339ed69b7c.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/player.png -------------------------------------------------------------------------------- /game/assets/player.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b8qhuy7q6146h" 6 | path="res://.godot/imported/player.png-be2216fcaabb5c62aa2466cd9a5726a8.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/player.png" 14 | dest_files=["res://.godot/imported/player.png-be2216fcaabb5c62aa2466cd9a5726a8.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/star_particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/star_particles.png -------------------------------------------------------------------------------- /game/assets/star_particles.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://4fg2tggofxf1" 6 | path="res://.godot/imported/star_particles.png-eeef96f8b00441bc06c330f0d4db8cbf.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/star_particles.png" 14 | dest_files=["res://.godot/imported/star_particles.png-eeef96f8b00441bc06c330f0d4db8cbf.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/assets/tileset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/assets/tileset.png -------------------------------------------------------------------------------- /game/assets/tileset.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cmxgl1shdb5uq" 6 | path="res://.godot/imported/tileset.png-c50288acd069fa293d7940a084811264.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://assets/tileset.png" 14 | dest_files=["res://.godot/imported/tileset.png-c50288acd069fa293d7940a084811264.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/base/main/main.gd: -------------------------------------------------------------------------------- 1 | class_name Main extends Node2D 2 | 3 | var start_menu : Node2D 4 | var world : Node2D 5 | var credits : Node2D 6 | 7 | func _ready() -> void: 8 | var _ret : int 9 | _ret = sigmgr.load_screen.connect( _on_load_screen ) 10 | #sigmgr.set_music.connect( _on_set_music ) 11 | 12 | sigmgr.load_screen.emit( "res://world/world.tscn" ) 13 | 14 | 15 | 16 | func _on_load_screen( scnfilename : String ) -> void: 17 | for c : Node2D in $stage.get_children(): 18 | c.queue_free() 19 | var s : Node2D = load( scnfilename ).instantiate() 20 | $stage.add_child( s ) 21 | 22 | 23 | #enum MusicStreams { NORMAL, LAVA, BOSS, CREDITS, NONE } 24 | #var streams : Array[AudioStreamWAV] = [ 25 | #preload( "res://assets/music/normal.wav" ), 26 | #preload( "res://assets/music/lava.wav" ), 27 | #preload( "res://assets/music/boss.wav" ), 28 | #preload( "res://assets/music/end_song.wav" ), 29 | #] 30 | #var cur_stream : int = -1 31 | # 32 | #func _on_set_music( stream_no : MusicStreams, restart : bool = false ) -> void: 33 | #if stream_no == MusicStreams.NONE: 34 | #$music.stop() 35 | #cur_stream = stream_no 36 | #return 37 | # 38 | #if restart: 39 | #cur_stream = stream_no 40 | #$music.stream = streams[stream_no] 41 | #$music.play(0.0) 42 | #else: 43 | #if cur_stream == stream_no: 44 | #return 45 | #_on_set_music( stream_no, true ) 46 | -------------------------------------------------------------------------------- /game/base/main/main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://v8ean2f3d54t"] 2 | 3 | [ext_resource type="Script" path="res://base/main/main.gd" id="1_gae4d"] 4 | 5 | [node name="main" type="Node2D"] 6 | script = ExtResource("1_gae4d") 7 | metadata/_edit_lock_ = true 8 | 9 | [node name="stage" type="Node2D" parent="."] 10 | metadata/_edit_lock_ = true 11 | -------------------------------------------------------------------------------- /game/entities/actor.gd: -------------------------------------------------------------------------------- 1 | class_name Actor extends CharacterBody2D_Entity 2 | 3 | 4 | 5 | 6 | func gravity( delta : float, multiplier : float = 1.0 ) -> void: 7 | velocity.y = min( velocity.y + Params.GRAVITY * delta * multiplier, Params.TERM_VEL ) 8 | 9 | -------------------------------------------------------------------------------- /game/entities/character_body_2d_entity.gd: -------------------------------------------------------------------------------- 1 | class_name CharacterBody2D_Entity extends CharacterBody2D 2 | 3 | var entity_iid : String 4 | 5 | func _entity_activate( _a : bool ) -> void: 6 | pass 7 | 8 | func _entity_initialize( _params : Dictionary ) -> void: 9 | pass 10 | -------------------------------------------------------------------------------- /game/entities/coin/coin.gd: -------------------------------------------------------------------------------- 1 | extends Node2D_Entity 2 | 3 | 4 | 5 | func _entity_activate( activate : bool ) -> void: 6 | if activate: 7 | $detect_player/collision.disabled = false 8 | else: 9 | $detect_player/collision.disabled = true 10 | 11 | func _entity_initialize( _params : Dictionary ) -> void: 12 | pass 13 | 14 | 15 | func _on_detect_player_body_entered( _body : Node2D ) -> void: 16 | $detect_player/collision.disabled = true 17 | game.state.coins += 1 18 | sigmgr.gamestate_changed.emit() 19 | hide() 20 | var x : Node2D = preload( "res://entities/vfx/stars.tscn" ).instantiate() 21 | x.position = position + Vector2( 0, -8 ) 22 | get_parent().add_child( x ) 23 | sigmgr.captured_coin.emit() 24 | 25 | 26 | -------------------------------------------------------------------------------- /game/entities/coin/coin.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://tapoebh5dv47"] 2 | 3 | [ext_resource type="Script" path="res://entities/coin/coin.gd" id="1_5tg8l"] 4 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="1_u2ll3"] 5 | 6 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_qftvv"] 7 | size = Vector2(10, 10) 8 | 9 | [sub_resource type="Animation" id="Animation_o6lwy"] 10 | length = 0.001 11 | tracks/0/type = "value" 12 | tracks/0/imported = false 13 | tracks/0/enabled = true 14 | tracks/0/path = NodePath("coin:region_rect") 15 | tracks/0/interp = 1 16 | tracks/0/loop_wrap = true 17 | tracks/0/keys = { 18 | "times": PackedFloat32Array(0), 19 | "transitions": PackedFloat32Array(1), 20 | "update": 0, 21 | "values": [Rect2(112, 16, 16, 16)] 22 | } 23 | 24 | [sub_resource type="Animation" id="Animation_i4d65"] 25 | resource_name = "cycle" 26 | length = 0.4 27 | loop_mode = 1 28 | tracks/0/type = "value" 29 | tracks/0/imported = false 30 | tracks/0/enabled = true 31 | tracks/0/path = NodePath("coin:region_rect") 32 | tracks/0/interp = 1 33 | tracks/0/loop_wrap = true 34 | tracks/0/keys = { 35 | "times": PackedFloat32Array(0, 0.1, 0.2, 0.3), 36 | "transitions": PackedFloat32Array(1, 1, 1, 1), 37 | "update": 1, 38 | "values": [Rect2(112, 16, 16, 16), Rect2(128, 16, 16, 16), Rect2(144, 16, 16, 16), Rect2(160, 16, 16, 16)] 39 | } 40 | 41 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_jwr2d"] 42 | _data = { 43 | "RESET": SubResource("Animation_o6lwy"), 44 | "cycle": SubResource("Animation_i4d65") 45 | } 46 | 47 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_gay4e"] 48 | size = Vector2(12, 14) 49 | 50 | [node name="coin" type="Node2D"] 51 | z_index = -5 52 | script = ExtResource("1_5tg8l") 53 | metadata/_edit_lock_ = true 54 | 55 | [node name="coin_collision" type="CollisionShape2D" parent="."] 56 | visible = false 57 | position = Vector2(0, -5) 58 | shape = SubResource("RectangleShape2D_qftvv") 59 | metadata/_edit_lock_ = true 60 | 61 | [node name="coin" type="Sprite2D" parent="."] 62 | position = Vector2(0, -8) 63 | texture = ExtResource("1_u2ll3") 64 | region_enabled = true 65 | region_rect = Rect2(112, 16, 16, 16) 66 | metadata/_edit_lock_ = true 67 | 68 | [node name="anim" type="AnimationPlayer" parent="."] 69 | libraries = { 70 | "": SubResource("AnimationLibrary_jwr2d") 71 | } 72 | autoplay = "cycle" 73 | 74 | [node name="detect_player" type="Area2D" parent="."] 75 | collision_layer = 0 76 | collision_mask = 16 77 | metadata/_edit_lock_ = true 78 | 79 | [node name="collision" type="CollisionShape2D" parent="detect_player"] 80 | position = Vector2(0, -8) 81 | shape = SubResource("RectangleShape2D_gay4e") 82 | 83 | [connection signal="body_entered" from="detect_player" to="." method="_on_detect_player_body_entered" flags=3] 84 | -------------------------------------------------------------------------------- /game/entities/coin/coin_reward.gd: -------------------------------------------------------------------------------- 1 | extends Actor 2 | 3 | enum COIN_STATES { NORMAL, FINISHING } 4 | 5 | var coin_timer : float 6 | var active_timer : float 7 | var state : int = COIN_STATES.NORMAL 8 | 9 | func _ready() -> void: 10 | velocity.y = -300 11 | coin_timer = 10.0 12 | active_timer = 0.2 13 | $detect_player/collision.disabled = true 14 | 15 | func _physics_process( delta : float ) -> void: 16 | if active_timer > 0: 17 | active_timer -= delta 18 | if active_timer <= 0: 19 | $detect_player/collision.disabled = false 20 | match state: 21 | COIN_STATES.NORMAL: 22 | coin_timer -= delta 23 | if coin_timer <= 0: 24 | coin_timer = 1.0 25 | state = COIN_STATES.FINISHING 26 | COIN_STATES.FINISHING: 27 | coin_timer -= delta 28 | if coin_timer <= 0: 29 | queue_free() 30 | 31 | gravity( delta ) 32 | var coldata : KinematicCollision2D = move_and_collide( velocity * delta ) 33 | if coldata: 34 | velocity = velocity.bounce( coldata.get_normal() ) 35 | 36 | 37 | var cought := false 38 | func _on_detect_player_body_entered( _body : Node2D ) -> void: 39 | if cought: return 40 | cought = true 41 | game.state.coins += 1 42 | sigmgr.gamestate_changed.emit() 43 | queue_free() 44 | var x : Node2D = preload( "res://entities/vfx/stars.tscn" ).instantiate() 45 | x.position = position + Vector2( 0, -8 ) 46 | get_parent().add_child( x ) 47 | sigmgr.captured_coin.emit() 48 | -------------------------------------------------------------------------------- /game/entities/coin/coin_reward.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://dutwxq2ej0acu"] 2 | 3 | [ext_resource type="Script" path="res://entities/coin/coin_reward.gd" id="1_p1hkx"] 4 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="2_7rfaw"] 5 | 6 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_vr3mx"] 7 | size = Vector2(16, 16) 8 | 9 | [sub_resource type="Animation" id="Animation_o6lwy"] 10 | length = 0.001 11 | tracks/0/type = "value" 12 | tracks/0/imported = false 13 | tracks/0/enabled = true 14 | tracks/0/path = NodePath("coin:region_rect") 15 | tracks/0/interp = 1 16 | tracks/0/loop_wrap = true 17 | tracks/0/keys = { 18 | "times": PackedFloat32Array(0), 19 | "transitions": PackedFloat32Array(1), 20 | "update": 0, 21 | "values": [Rect2(112, 16, 16, 16)] 22 | } 23 | 24 | [sub_resource type="Animation" id="Animation_i4d65"] 25 | resource_name = "cycle" 26 | length = 0.4 27 | loop_mode = 1 28 | tracks/0/type = "value" 29 | tracks/0/imported = false 30 | tracks/0/enabled = true 31 | tracks/0/path = NodePath("coin:region_rect") 32 | tracks/0/interp = 1 33 | tracks/0/loop_wrap = true 34 | tracks/0/keys = { 35 | "times": PackedFloat32Array(0, 0.1, 0.2, 0.3), 36 | "transitions": PackedFloat32Array(1, 1, 1, 1), 37 | "update": 1, 38 | "values": [Rect2(112, 16, 16, 16), Rect2(128, 16, 16, 16), Rect2(144, 16, 16, 16), Rect2(160, 16, 16, 16)] 39 | } 40 | 41 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_jwr2d"] 42 | _data = { 43 | "RESET": SubResource("Animation_o6lwy"), 44 | "cycle": SubResource("Animation_i4d65") 45 | } 46 | 47 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_xoh5s"] 48 | size = Vector2(12, 14) 49 | 50 | [node name="coin_reward" type="CharacterBody2D"] 51 | z_index = -5 52 | collision_layer = 0 53 | script = ExtResource("1_p1hkx") 54 | metadata/_edit_lock_ = true 55 | 56 | [node name="coin_collision" type="CollisionShape2D" parent="."] 57 | position = Vector2(0, -8) 58 | shape = SubResource("RectangleShape2D_vr3mx") 59 | metadata/_edit_lock_ = true 60 | 61 | [node name="coin" type="Sprite2D" parent="."] 62 | position = Vector2(0, -8) 63 | texture = ExtResource("2_7rfaw") 64 | region_enabled = true 65 | region_rect = Rect2(112, 16, 16, 16) 66 | metadata/_edit_lock_ = true 67 | 68 | [node name="anim" type="AnimationPlayer" parent="."] 69 | libraries = { 70 | "": SubResource("AnimationLibrary_jwr2d") 71 | } 72 | autoplay = "cycle" 73 | 74 | [node name="detect_player" type="Area2D" parent="."] 75 | collision_layer = 0 76 | collision_mask = 16 77 | metadata/_edit_lock_ = true 78 | 79 | [node name="collision" type="CollisionShape2D" parent="detect_player"] 80 | position = Vector2(0, -8) 81 | shape = SubResource("RectangleShape2D_xoh5s") 82 | 83 | [connection signal="body_entered" from="detect_player" to="." method="_on_detect_player_body_entered" flags=3] 84 | -------------------------------------------------------------------------------- /game/entities/cucumber/cucumber.gd: -------------------------------------------------------------------------------- 1 | extends Node2D_Entity 2 | 3 | enum ENEMY_STATES { PATROL, BREAK, CHASE, DEAD, RESTART } 4 | 5 | var _state : int = ENEMY_STATES.PATROL 6 | var _params : Dictionary 7 | 8 | @onready var anim : AnimationPlayer = $anim 9 | @onready var rot : Node2D = $rotate 10 | 11 | func _ready() -> void: 12 | set_physics_process( false ) 13 | 14 | func _entity_activate( activate : bool ) -> void: 15 | if activate: 16 | set_physics_process( true ) 17 | $DealDamageArea/damage_collision.disabled = false 18 | $DealDamageArea/damage_collision_break.disabled = true 19 | $RcvDamageArea/rcv_damage_collision.disabled = false 20 | $RcvDamageArea/rcv_damage_collision_break.disabled = true 21 | else: 22 | set_physics_process( false ) 23 | $DealDamageArea/damage_collision.disabled = true 24 | $DealDamageArea/damage_collision_break.disabled = true 25 | $RcvDamageArea/rcv_damage_collision.disabled = true 26 | $RcvDamageArea/rcv_damage_collision_break.disabled = true 27 | 28 | func _entity_initialize( params : Dictionary ) -> void: 29 | _params = params 30 | _prepare_enemy() 31 | 32 | 33 | func _prepare_enemy() -> void: 34 | _state = ENEMY_STATES.PATROL 35 | position = _params.position 36 | _params.path_px = [ position ] 37 | _params.path_px.append( Vector2( _params.path.cx, _params.path.cy ) * 16 + Vector2( 8, 16 ) ) 38 | _params.path_rect = Rect2( 39 | Vector2( 40 | min( _params.path_px[0].x, _params.path_px[1].x ), 41 | min( _params.path_px[0].y, _params.path_px[1].y ) 42 | ) + Vector2( -8, -16 ), 43 | Vector2( 44 | abs( _params.path_px[0].x - _params.path_px[1].x ), 45 | abs( _params.path_px[0].y - _params.path_px[1].y ) 46 | ) + Vector2( 16, 16 ) 47 | ) 48 | _params.path_idx = 1 49 | _params.dead_timer = 0.5 50 | _params.restart_timer = 5.0 51 | _params.break_timer = 1.5 52 | 53 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 54 | if abs( dir.x ) > 0: 55 | rot.scale.x = sign( dir.x ) 56 | 57 | 58 | func _physics_process( delta : float ) -> void: 59 | match _state: 60 | ENEMY_STATES.PATROL: 61 | anim.set_anim( "patrol" ) 62 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 63 | #print( dir ) 64 | position += dir * Params.TROOPER_WALK_VEL * delta 65 | if( position - _params.path_px[_params.path_idx] ).length() < ( 2.0 * Params.SLIME_WALK_VEL / 60.0 ): 66 | position = _params.path_px[_params.path_idx] 67 | _params.path_idx= ( _params.path_idx + 1 ) % _params.path_px.size() 68 | dir = ( _params.path_px[_params.path_idx] - position ).normalized() 69 | if abs( dir.x ) > 0: 70 | rot.scale.x = sign( dir.x ) 71 | #if _player_in_sight(): 72 | #_params.sight_timer = 0.5 73 | #_state = ENEMY_STATES.SIGHT 74 | ENEMY_STATES.BREAK: 75 | anim.set_anim( "break" ) 76 | _params.break_timer -= delta 77 | if _params.break_timer <= 0: 78 | _state = ENEMY_STATES.CHASE 79 | $DealDamageArea/damage_collision.disabled = true 80 | $DealDamageArea/damage_collision_break.disabled = false 81 | $RcvDamageArea/rcv_damage_collision.disabled = true 82 | $RcvDamageArea/rcv_damage_collision_break.disabled = false 83 | ENEMY_STATES.CHASE: 84 | anim.set_anim( "chase" ) 85 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 86 | #print( dir ) 87 | position += dir * Params.TROOPER_CHASE_VEL * delta 88 | if( position - _params.path_px[_params.path_idx] ).length() < ( 2.0 * Params.SLIME_WALK_VEL / 60.0 ): 89 | position = _params.path_px[_params.path_idx] 90 | _params.path_idx= ( _params.path_idx + 1 ) % _params.path_px.size() 91 | dir = ( _params.path_px[_params.path_idx] - position ).normalized() 92 | if abs( dir.x ) > 0: 93 | rot.scale.x = sign( dir.x ) 94 | ENEMY_STATES.DEAD: 95 | if _params.dead_timer > 0: 96 | _params.dead_timer -= delta 97 | if _params.dead_timer <= 0: 98 | hide() 99 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 100 | x.position = position + Vector2( 0, -4 ) 101 | get_parent().add_child( x ) 102 | sigmgr.camera_shake.emit( 0.1, 1, 60 ) 103 | if randf() < 0.1: 104 | var c : Node2D = preload( "res://entities/coin/coin_reward.tscn" ).instantiate() 105 | c.position = position + Vector2( 0, -4 ) 106 | c.velocity.x = randf_range( -30, 30 ) 107 | get_parent().add_child( c ) 108 | _state = ENEMY_STATES.RESTART 109 | _params.restart_timer = 4.0 110 | position = _params.position 111 | ENEMY_STATES.RESTART: 112 | _params.restart_timer -= delta 113 | if _params.restart_timer <= 0: 114 | var dist : Vector2 = game.player.global_position - global_position 115 | if dist.length() > Params.MIN_DISTANCE_RESPAWN: 116 | # restarting 117 | _prepare_enemy() 118 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 119 | x.position = position + Vector2( 0, -4 ) 120 | get_parent().add_child( x ) 121 | show() 122 | $DealDamageArea/damage_collision.disabled = false 123 | $DealDamageArea/damage_collision_break.disabled = true 124 | $RcvDamageArea/rcv_damage_collision.disabled = false 125 | $RcvDamageArea/rcv_damage_collision_break.disabled = true 126 | else: 127 | _params.restart_timer = 1.0 128 | 129 | 130 | func _on_receiving_damage( player : Player, _damage : int ) -> void: 131 | if _state == ENEMY_STATES.DEAD or _state == ENEMY_STATES.RESTART or _state == ENEMY_STATES.BREAK: return 132 | if player.velocity.y <= 0: return # player must be falling down 133 | if player.global_position.y >= global_position.y - 8: return 134 | player._fsm.states.jump.begin_small_jump() 135 | if _state == ENEMY_STATES.PATROL: 136 | _state = ENEMY_STATES.BREAK 137 | $DealDamageArea/damage_collision.set_disabled.call_deferred( true ) 138 | $DealDamageArea/damage_collision_break.set_disabled.call_deferred( true ) 139 | $RcvDamageArea/rcv_damage_collision.set_disabled.call_deferred( true ) 140 | $RcvDamageArea/rcv_damage_collision_break.set_disabled.call_deferred( true ) 141 | else: 142 | _state = ENEMY_STATES.DEAD 143 | _set_dead() 144 | 145 | 146 | func _set_dead() -> void: 147 | $DealDamageArea/damage_collision.set_disabled.call_deferred( true ) 148 | $DealDamageArea/damage_collision_break.set_disabled.call_deferred( true ) 149 | $RcvDamageArea/rcv_damage_collision.set_disabled.call_deferred( true ) 150 | $RcvDamageArea/rcv_damage_collision_break.set_disabled.call_deferred( true ) 151 | anim.set_anim( "dead" ) 152 | 153 | func _player_in_sight() -> bool: 154 | var playerpos : Vector2 = game.player.position + Vector2( 0, -8 ) 155 | var dist : Vector2 = playerpos - ( position + Vector2( 0, -4 ) ) 156 | if sign( dist.x ) != sign( rot.scale.x ): 157 | return false 158 | if _params.path_rect.has_point( playerpos): 159 | return true 160 | return false 161 | 162 | 163 | func _break_blast() -> void: 164 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 165 | x.position = position + Vector2( 30 * rot.scale.x, -4 ) 166 | get_parent().add_child( x ) 167 | -------------------------------------------------------------------------------- /game/entities/cucumber/cucumber.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=17 format=3 uid="uid://on6jkoekl6ei"] 2 | 3 | [ext_resource type="Script" path="res://entities/cucumber/cucumber.gd" id="1_1spqs"] 4 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="2_0peok"] 5 | [ext_resource type="Script" path="res://general_purpose/utils/anim.gd" id="3_us7lj"] 6 | [ext_resource type="Script" path="res://general_purpose/damage/deal_damage_area.gd" id="4_ib1g3"] 7 | [ext_resource type="Script" path="res://general_purpose/damage/rcv_damage_area.gd" id="5_044ft"] 8 | 9 | [sub_resource type="Animation" id="Animation_ysbpd"] 10 | length = 0.001 11 | tracks/0/type = "value" 12 | tracks/0/imported = false 13 | tracks/0/enabled = true 14 | tracks/0/path = NodePath("rotate/cucumber:region_rect") 15 | tracks/0/interp = 1 16 | tracks/0/loop_wrap = true 17 | tracks/0/keys = { 18 | "times": PackedFloat32Array(0), 19 | "transitions": PackedFloat32Array(1), 20 | "update": 0, 21 | "values": [Rect2(48, 40, 16, 24)] 22 | } 23 | tracks/1/type = "value" 24 | tracks/1/imported = false 25 | tracks/1/enabled = true 26 | tracks/1/path = NodePath("rotate/cucumber:scale") 27 | tracks/1/interp = 1 28 | tracks/1/loop_wrap = true 29 | tracks/1/keys = { 30 | "times": PackedFloat32Array(0), 31 | "transitions": PackedFloat32Array(1), 32 | "update": 0, 33 | "values": [Vector2(1, 1)] 34 | } 35 | tracks/2/type = "value" 36 | tracks/2/imported = false 37 | tracks/2/enabled = true 38 | tracks/2/path = NodePath("rotate/top:visible") 39 | tracks/2/interp = 1 40 | tracks/2/loop_wrap = true 41 | tracks/2/keys = { 42 | "times": PackedFloat32Array(0), 43 | "transitions": PackedFloat32Array(1), 44 | "update": 1, 45 | "values": [false] 46 | } 47 | tracks/3/type = "value" 48 | tracks/3/imported = false 49 | tracks/3/enabled = true 50 | tracks/3/path = NodePath("rotate/top:position") 51 | tracks/3/interp = 1 52 | tracks/3/loop_wrap = true 53 | tracks/3/keys = { 54 | "times": PackedFloat32Array(0), 55 | "transitions": PackedFloat32Array(1), 56 | "update": 0, 57 | "values": [Vector2(0, -4)] 58 | } 59 | tracks/4/type = "value" 60 | tracks/4/imported = false 61 | tracks/4/enabled = true 62 | tracks/4/path = NodePath("rotate/top:rotation") 63 | tracks/4/interp = 1 64 | tracks/4/loop_wrap = true 65 | tracks/4/keys = { 66 | "times": PackedFloat32Array(0), 67 | "transitions": PackedFloat32Array(1), 68 | "update": 0, 69 | "values": [0.0] 70 | } 71 | 72 | [sub_resource type="Animation" id="Animation_ijj0r"] 73 | resource_name = "chase" 74 | length = 0.2 75 | loop_mode = 1 76 | tracks/0/type = "value" 77 | tracks/0/imported = false 78 | tracks/0/enabled = true 79 | tracks/0/path = NodePath("rotate/cucumber:region_rect") 80 | tracks/0/interp = 1 81 | tracks/0/loop_wrap = true 82 | tracks/0/keys = { 83 | "times": PackedFloat32Array(0, 0.1), 84 | "transitions": PackedFloat32Array(1, 1), 85 | "update": 1, 86 | "values": [Rect2(96, 40, 16, 24), Rect2(112, 40, 16, 24)] 87 | } 88 | tracks/1/type = "value" 89 | tracks/1/imported = false 90 | tracks/1/enabled = true 91 | tracks/1/path = NodePath("rotate/cucumber:scale") 92 | tracks/1/interp = 1 93 | tracks/1/loop_wrap = true 94 | tracks/1/keys = { 95 | "times": PackedFloat32Array(0), 96 | "transitions": PackedFloat32Array(1), 97 | "update": 0, 98 | "values": [Vector2(1, 1)] 99 | } 100 | 101 | [sub_resource type="Animation" id="Animation_vsccc"] 102 | resource_name = "dead" 103 | length = 0.4 104 | tracks/0/type = "value" 105 | tracks/0/imported = false 106 | tracks/0/enabled = true 107 | tracks/0/path = NodePath("rotate/cucumber:region_rect") 108 | tracks/0/interp = 1 109 | tracks/0/loop_wrap = true 110 | tracks/0/keys = { 111 | "times": PackedFloat32Array(0), 112 | "transitions": PackedFloat32Array(1), 113 | "update": 0, 114 | "values": [Rect2(128, 40, 16, 24)] 115 | } 116 | tracks/1/type = "value" 117 | tracks/1/imported = false 118 | tracks/1/enabled = true 119 | tracks/1/path = NodePath("rotate/cucumber:scale") 120 | tracks/1/interp = 1 121 | tracks/1/loop_wrap = true 122 | tracks/1/keys = { 123 | "times": PackedFloat32Array(0, 0.1), 124 | "transitions": PackedFloat32Array(1, 1), 125 | "update": 0, 126 | "values": [Vector2(1.2, 0.8), Vector2(1, 1)] 127 | } 128 | 129 | [sub_resource type="Animation" id="Animation_6vdvg"] 130 | resource_name = "patrol" 131 | length = 0.4 132 | loop_mode = 1 133 | tracks/0/type = "value" 134 | tracks/0/imported = false 135 | tracks/0/enabled = true 136 | tracks/0/path = NodePath("rotate/cucumber:region_rect") 137 | tracks/0/interp = 1 138 | tracks/0/loop_wrap = true 139 | tracks/0/keys = { 140 | "times": PackedFloat32Array(0, 0.2), 141 | "transitions": PackedFloat32Array(1, 1), 142 | "update": 1, 143 | "values": [Rect2(48, 40, 16, 24), Rect2(64, 40, 16, 24)] 144 | } 145 | tracks/1/type = "value" 146 | tracks/1/imported = false 147 | tracks/1/enabled = true 148 | tracks/1/path = NodePath("rotate/cucumber:scale") 149 | tracks/1/interp = 1 150 | tracks/1/loop_wrap = true 151 | tracks/1/keys = { 152 | "times": PackedFloat32Array(0), 153 | "transitions": PackedFloat32Array(1), 154 | "update": 0, 155 | "values": [Vector2(1, 1)] 156 | } 157 | 158 | [sub_resource type="Animation" id="Animation_2gfko"] 159 | resource_name = "sight" 160 | length = 0.2 161 | tracks/0/type = "value" 162 | tracks/0/imported = false 163 | tracks/0/enabled = true 164 | tracks/0/path = NodePath("rotate/cucumber:region_rect") 165 | tracks/0/interp = 1 166 | tracks/0/loop_wrap = true 167 | tracks/0/keys = { 168 | "times": PackedFloat32Array(0), 169 | "transitions": PackedFloat32Array(1), 170 | "update": 0, 171 | "values": [Rect2(208, 0, 16, 16)] 172 | } 173 | tracks/1/type = "value" 174 | tracks/1/imported = false 175 | tracks/1/enabled = true 176 | tracks/1/path = NodePath("rotate/cucumber:scale") 177 | tracks/1/interp = 1 178 | tracks/1/loop_wrap = true 179 | tracks/1/keys = { 180 | "times": PackedFloat32Array(0, 0.2), 181 | "transitions": PackedFloat32Array(1, 1), 182 | "update": 0, 183 | "values": [Vector2(0.9, 1.3), Vector2(1, 1)] 184 | } 185 | 186 | [sub_resource type="Animation" id="Animation_au3ia"] 187 | resource_name = "break" 188 | tracks/0/type = "value" 189 | tracks/0/imported = false 190 | tracks/0/enabled = true 191 | tracks/0/path = NodePath("rotate/cucumber:region_rect") 192 | tracks/0/interp = 1 193 | tracks/0/loop_wrap = true 194 | tracks/0/keys = { 195 | "times": PackedFloat32Array(0, 0.2), 196 | "transitions": PackedFloat32Array(1, 1), 197 | "update": 1, 198 | "values": [Rect2(80, 40, 16, 24), Rect2(128, 40, 16, 24)] 199 | } 200 | tracks/1/type = "value" 201 | tracks/1/imported = false 202 | tracks/1/enabled = true 203 | tracks/1/path = NodePath("rotate/cucumber:scale") 204 | tracks/1/interp = 1 205 | tracks/1/loop_wrap = true 206 | tracks/1/keys = { 207 | "times": PackedFloat32Array(0, 0.1), 208 | "transitions": PackedFloat32Array(1, 1), 209 | "update": 0, 210 | "values": [Vector2(1.2, 0.8), Vector2(1, 1)] 211 | } 212 | tracks/2/type = "value" 213 | tracks/2/imported = false 214 | tracks/2/enabled = true 215 | tracks/2/path = NodePath("rotate/top:visible") 216 | tracks/2/interp = 1 217 | tracks/2/loop_wrap = true 218 | tracks/2/keys = { 219 | "times": PackedFloat32Array(0, 0.2, 1), 220 | "transitions": PackedFloat32Array(1, 1, 1), 221 | "update": 1, 222 | "values": [false, true, false] 223 | } 224 | tracks/3/type = "value" 225 | tracks/3/imported = false 226 | tracks/3/enabled = true 227 | tracks/3/path = NodePath("rotate/top:position") 228 | tracks/3/interp = 1 229 | tracks/3/loop_wrap = true 230 | tracks/3/keys = { 231 | "times": PackedFloat32Array(0, 0.2, 0.3, 0.5, 0.7), 232 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1), 233 | "update": 0, 234 | "values": [Vector2(0, -4), Vector2(2, -7), Vector2(8, -12), Vector2(17, -4), Vector2(19, -5)] 235 | } 236 | tracks/4/type = "value" 237 | tracks/4/imported = false 238 | tracks/4/enabled = true 239 | tracks/4/path = NodePath("rotate/top:rotation") 240 | tracks/4/interp = 1 241 | tracks/4/loop_wrap = true 242 | tracks/4/keys = { 243 | "times": PackedFloat32Array(0, 0.2, 0.3, 0.5, 0.7), 244 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1), 245 | "update": 0, 246 | "values": [0.0, 0.523935, 0.880675, 1.14727, 1.5708] 247 | } 248 | tracks/5/type = "method" 249 | tracks/5/imported = false 250 | tracks/5/enabled = true 251 | tracks/5/path = NodePath(".") 252 | tracks/5/interp = 1 253 | tracks/5/loop_wrap = true 254 | tracks/5/keys = { 255 | "times": PackedFloat32Array(1), 256 | "transitions": PackedFloat32Array(1), 257 | "values": [{ 258 | "args": [], 259 | "method": &"_break_blast" 260 | }] 261 | } 262 | 263 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_f5oka"] 264 | _data = { 265 | "RESET": SubResource("Animation_ysbpd"), 266 | "break": SubResource("Animation_au3ia"), 267 | "chase": SubResource("Animation_ijj0r"), 268 | "dead": SubResource("Animation_vsccc"), 269 | "patrol": SubResource("Animation_6vdvg"), 270 | "sight": SubResource("Animation_2gfko") 271 | } 272 | 273 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_tdxk1"] 274 | size = Vector2(6, 17) 275 | 276 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_0rbln"] 277 | size = Vector2(6, 8) 278 | 279 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_3hga6"] 280 | size = Vector2(12, 4) 281 | 282 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_5fknu"] 283 | size = Vector2(12, 4) 284 | 285 | [node name="cucumber" type="Node2D"] 286 | z_index = -1 287 | script = ExtResource("1_1spqs") 288 | 289 | [node name="rotate" type="Node2D" parent="."] 290 | metadata/_edit_lock_ = true 291 | 292 | [node name="cucumber" type="Sprite2D" parent="rotate"] 293 | texture = ExtResource("2_0peok") 294 | offset = Vector2(0, -11) 295 | region_enabled = true 296 | region_rect = Rect2(48, 40, 16, 24) 297 | metadata/_edit_lock_ = true 298 | 299 | [node name="top" type="Sprite2D" parent="rotate"] 300 | visible = false 301 | position = Vector2(0, -4) 302 | texture = ExtResource("2_0peok") 303 | offset = Vector2(0, -11) 304 | region_enabled = true 305 | region_rect = Rect2(80, 40, 16, 16) 306 | 307 | [node name="anim" type="AnimationPlayer" parent="."] 308 | libraries = { 309 | "": SubResource("AnimationLibrary_f5oka") 310 | } 311 | script = ExtResource("3_us7lj") 312 | 313 | [node name="DealDamageArea" type="Area2D" parent="."] 314 | collision_layer = 32 315 | collision_mask = 0 316 | script = ExtResource("4_ib1g3") 317 | metadata/_edit_lock_ = true 318 | 319 | [node name="damage_collision" type="CollisionShape2D" parent="DealDamageArea"] 320 | visible = false 321 | position = Vector2(0, -8.5) 322 | shape = SubResource("RectangleShape2D_tdxk1") 323 | metadata/_edit_lock_ = true 324 | 325 | [node name="damage_collision_break" type="CollisionShape2D" parent="DealDamageArea"] 326 | position = Vector2(0, -4) 327 | shape = SubResource("RectangleShape2D_0rbln") 328 | disabled = true 329 | metadata/_edit_lock_ = true 330 | 331 | [node name="RcvDamageArea" type="Area2D" parent="."] 332 | collision_layer = 0 333 | collision_mask = 1024 334 | script = ExtResource("5_044ft") 335 | 336 | [node name="rcv_damage_collision" type="CollisionShape2D" parent="RcvDamageArea"] 337 | visible = false 338 | position = Vector2(0, -20) 339 | shape = SubResource("RectangleShape2D_3hga6") 340 | 341 | [node name="rcv_damage_collision_break" type="CollisionShape2D" parent="RcvDamageArea"] 342 | position = Vector2(0, -12) 343 | shape = SubResource("RectangleShape2D_5fknu") 344 | disabled = true 345 | -------------------------------------------------------------------------------- /game/entities/node_2d_entity.gd: -------------------------------------------------------------------------------- 1 | class_name Node2D_Entity extends Node2D 2 | 3 | 4 | var entity_iid : String 5 | #var confirm_activation : bool = true 6 | # 7 | #var _restart_timer : Timer 8 | #var _restart_parameters : Dictionary 9 | #var _is_restarting : bool = false 10 | 11 | func _entity_activate( _a : bool ) -> void: 12 | pass 13 | 14 | func _entity_initialize( _params : Dictionary ) -> void: 15 | pass 16 | 17 | 18 | # 19 | # 20 | #func _initiate_restart() -> void: 21 | #_is_restarting = true 22 | #position = _restart_parameters.position 23 | #if not _restart_timer: 24 | #_restart_timer = Timer.new() 25 | #_restart_timer.autostart = false 26 | #_restart_timer.one_shot = true 27 | #_restart_timer.wait_time = 1.0 28 | #add_child( _restart_timer ) 29 | #_restart_timer.timeout.connect( _on_restart_timer_timeout.bind( Params.ENEMY_RESPAWN_DELAY ) ) 30 | #_restart_timer.call_deferred( "start" ) 31 | # 32 | # 33 | #func _test_timer( a : int ): 34 | #print( "TIMER CALLED ", name, " ", a ) 35 | # 36 | #func _on_restart_timer_timeout( counter : int ) -> void: 37 | #if not _is_restarting: 38 | #return 39 | #if _restart_timer.timeout.is_connected( _on_restart_timer_timeout ): 40 | #_restart_timer.timeout.disconnect( _on_restart_timer_timeout ) 41 | #if not game.player: return 42 | #var dist : float = ( global_position - game.player.global_position ).length() 43 | #if dist < Params.ENEMY_RESPAWN_DISTANCE: 44 | #_restart_timer.timeout.connect( _on_restart_timer_timeout.bind( Params.ENEMY_RESPAWN_DELAY ) ) 45 | #_restart_timer.start() 46 | #return 47 | #var new_count : int = counter - 1 48 | #if new_count > 0: 49 | #_restart_timer.timeout.connect( _on_restart_timer_timeout.bind( new_count ) ) 50 | #_restart_timer.start() 51 | #return 52 | #else: 53 | #_is_restarting = false 54 | #_entity_initialize( _restart_parameters ) 55 | #_entity_activate( true ) 56 | #show() 57 | ##var x = preload( "res://entities/vfx/small_blast.tscn" ).instantiate() 58 | ##x.position = position + Vector2( 0, -4 ) 59 | ##x.rotation = randf() * TAU 60 | ##get_parent().add_child( x ) 61 | # 62 | #func _stop_restart() -> void: 63 | #if _is_restarting: 64 | #_is_restarting = false 65 | #if _restart_timer: 66 | #_restart_timer.stop() 67 | #if _restart_timer.timeout.is_connected( _on_restart_timer_timeout ): 68 | #_restart_timer.timeout.disconnect( _on_restart_timer_timeout ) 69 | -------------------------------------------------------------------------------- /game/entities/player/player.gd: -------------------------------------------------------------------------------- 1 | class_name Player extends Actor 2 | 3 | signal player_dead 4 | 5 | enum DustTypes { RUN, JUMP, LAND } 6 | 7 | 8 | 9 | 10 | var _fsm : FSM 11 | var dir_cur : int = 0 12 | var dir_nxt : int = 1 13 | var is_dead := false 14 | var is_hit := false 15 | var is_invulnerable := false 16 | var invulnerable_timer := 0.0 17 | var control : Dictionary = { 18 | is_moving = false, 19 | dir = 0.0, 20 | is_jump = false, 21 | is_just_jump = false, 22 | is_down = false, 23 | is_just_down = false, 24 | is_fire = false, 25 | is_just_fire = false, 26 | is_interact = false, 27 | is_just_interact = false, 28 | } 29 | 30 | 31 | var cur_level_worldpos : Vector2i 32 | 33 | @onready var _rotate : Node2D = $rotate 34 | @onready var _anim : FSM_Anim = $anim 35 | @onready var anim_fx : AnimationPlayer = $anim_fx 36 | @onready var _detect_hazards : Area2D = $detect_hazards 37 | 38 | #------------------------------------------------- 39 | # Entity 40 | #------------------------------------------------- 41 | func _entity_activate( a : bool ) -> void: 42 | if a: 43 | set_physics_process( true ) 44 | $detect_hazards/hazards_collision.disabled = false 45 | $RcvDamageArea/damage_collision.disabled = false 46 | if _anim.assigned_animation: 47 | _anim.play() 48 | $rotate/player.show() 49 | else: 50 | set_physics_process( false ) 51 | $detect_hazards/hazards_collision.disabled = true 52 | $RcvDamageArea/damage_collision.disabled = true 53 | $DealDamageArea/damage_collision.disabled = true 54 | _anim.pause() 55 | 56 | 57 | #------------------------------------------------- 58 | # Setup 59 | #------------------------------------------------- 60 | func _ready() -> void: 61 | game.player = self 62 | _fsm = FSM.new( self, $states, $states/idle, _anim ) 63 | #$rotate/DealDamageArea.control_node = self 64 | 65 | 66 | #------------------------------------------------- 67 | # Process 68 | #------------------------------------------------- 69 | func _physics_process( delta : float ) -> void: 70 | _update_control() 71 | _fsm.run_machine( delta ) 72 | _anim.update_anim() 73 | _update_direction() 74 | _update_invulnerable( delta ) 75 | _update_hazards() 76 | 77 | func _update_control() -> void: 78 | control.dir = Input.get_action_strength( "btn_right" ) - Input.get_action_strength( "btn_left" ) 79 | if abs( control.dir ) > 0.1: 80 | control.is_moving = true 81 | else: 82 | control.is_moving = false 83 | control.dir = 0.0 84 | control.is_jump = Input.is_action_pressed( "btn_jump" ) 85 | control.is_just_jump = Input.is_action_just_pressed( "btn_jump" ) 86 | control.is_down = Input.is_action_pressed( "btn_down" ) 87 | control.is_just_down = Input.is_action_just_pressed( "btn_down" ) 88 | #control.is_fire = Input.is_action_pressed( "btn_fire" ) 89 | #control.is_just_fire = Input.is_action_just_pressed( "btn_fire" ) 90 | #control.is_interact = Input.is_action_pressed( "btn_interact" ) 91 | #control.is_just_interact = Input.is_action_just_pressed( "btn_interact" ) 92 | 93 | func _update_direction() -> void: 94 | if dir_cur != dir_nxt: 95 | dir_cur = dir_nxt 96 | _rotate.scale.x = dir_cur 97 | 98 | #------------------------------------------------- 99 | # Cutscenes 100 | #------------------------------------------------- 101 | func set_cutscene( anim_nxt : String, duration : float = -1.0 ) -> void: 102 | _set_cutscene.call_deferred( anim_nxt, duration ) 103 | 104 | func _set_cutscene( anim_nxt : String, duration : float ) -> void: 105 | _anim.anim_nxt = anim_nxt 106 | _fsm.states.cutscene.cutscene_timer = duration 107 | _fsm.state_nxt = _fsm.states.cutscene 108 | 109 | 110 | func clear_cutscene() -> void: 111 | _fsm.state_nxt = _fsm.states.idle 112 | 113 | 114 | 115 | #------------------------------------------------- 116 | # Damage 117 | #------------------------------------------------- 118 | func _on_receiving_damage( from : Node2D, damage : int ) -> void: 119 | if is_dead or is_hit or is_invulnerable: return 120 | if _fsm.is_state( _fsm.states.cutscene ): return 121 | game.state.energy = int( max( game.state.energy - damage, 0 ) ) 122 | sigmgr.gamestate_changed.emit() 123 | #$hit.play() 124 | var hit_dir : Vector2 = Vector2.ZERO 125 | if from: 126 | hit_dir = global_position + Vector2( 0, -6 ) - from.global_position 127 | if game.state.energy > 0: 128 | _fsm.state_nxt = _fsm.states.hit 129 | is_hit = true 130 | is_invulnerable = true 131 | invulnerable_timer = 0.4 132 | sigmgr.camera_shake.emit( 0.2, 2, 60 ) 133 | velocity = hit_dir.normalized() * Params.PLAYER_HAZARD_THROWBACK_VEL * 0.75 134 | else: 135 | _fsm.state_nxt = _fsm.states.dead 136 | is_dead = true 137 | sigmgr.camera_shake.emit( 0.1, 2, 60 ) 138 | velocity = hit_dir.normalized() * Params.PLAYER_HAZARD_THROWBACK_VEL * 0.75 139 | 140 | func death() -> void: 141 | $rotate/player.hide() 142 | velocity *= 0 143 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 144 | x.position = position + Vector2( 0, -8 ) 145 | get_parent().add_child( x ) 146 | sigmgr.camera_shake.emit( 0.4, 2, 60 ) 147 | 148 | func _update_hazards() -> void: 149 | if is_dead or is_hit or is_invulnerable: return 150 | if _fsm.is_state( _fsm.states.cutscene ): return 151 | var has_hazard_body : bool = not _detect_hazards.get_overlapping_bodies().is_empty() 152 | var has_hazard_area : bool = not _detect_hazards.get_overlapping_areas().is_empty() 153 | if not has_hazard_body and not has_hazard_area: return 154 | # find where the hazard is 155 | var hazard_dir : Vector2 = Vector2.ZERO 156 | if ( has_hazard_body and not $test_hazards/test_hazards_1.get_overlapping_bodies().is_empty() ) or \ 157 | ( has_hazard_area and not $test_hazards/test_hazards_1.get_overlapping_areas().is_empty() ): 158 | hazard_dir += Vector2.RIGHT 159 | if ( has_hazard_body and not $test_hazards/test_hazards_2.get_overlapping_bodies().is_empty() ) or \ 160 | ( has_hazard_area and not $test_hazards/test_hazards_2.get_overlapping_areas().is_empty() ): 161 | hazard_dir += Vector2.UP 162 | if ( has_hazard_body and not $test_hazards/test_hazards_3.get_overlapping_bodies().is_empty() ) or \ 163 | ( has_hazard_area and not $test_hazards/test_hazards_3.get_overlapping_areas().is_empty() ): 164 | hazard_dir += Vector2.LEFT 165 | if ( has_hazard_body and not $test_hazards/test_hazards_4.get_overlapping_bodies().is_empty() ) or \ 166 | ( has_hazard_area and not $test_hazards/test_hazards_4.get_overlapping_areas().is_empty() ): 167 | hazard_dir += Vector2.DOWN 168 | 169 | #$hit.play() 170 | var damage : int = 1 171 | game.state.energy = int( max( game.state.energy - damage, 0 ) ) 172 | sigmgr.gamestate_changed.emit() 173 | if game.state.energy > 0: 174 | _fsm.state_nxt = _fsm.states.hit 175 | is_hit = true 176 | is_invulnerable = true 177 | invulnerable_timer = 0.4 178 | sigmgr.camera_shake.emit( 0.2, 2, 60 ) 179 | velocity = -hazard_dir.normalized() * Params.PLAYER_HAZARD_THROWBACK_VEL 180 | else: 181 | _fsm.state_nxt = _fsm.states.dead 182 | is_dead = true 183 | velocity = -hazard_dir.normalized() * Params.PLAYER_HAZARD_THROWBACK_VEL * 0.75 184 | 185 | func _update_invulnerable( delta : float ) -> void: 186 | if is_invulnerable and invulnerable_timer > 0: 187 | invulnerable_timer -= delta 188 | if invulnerable_timer <= 0: 189 | is_invulnerable = false 190 | 191 | 192 | #func _on_check_level_type_timer_timeout() -> void: 193 | #if owner and owner._cur_level: 194 | #var cur_level_type : int = owner._cur_level.type 195 | #if cur_level_type == 1: 196 | #if not game.state.heat_resistance: 197 | #_on_receiving_damage( null, 1 ) 198 | 199 | 200 | #func _on_dealing_damage( _to : Node ) -> void: 201 | #game.state.energy_charger += 1 202 | #if game.state.energy_charger >= game.state.max_energy_charger: 203 | #game.state.energy_charger = 0 204 | #game.state.energy += 1 205 | #if game.state.energy > game.state.max_energy: game.state.energy = game.state.max_energy 206 | #sigmgr.gamestate_changed.emit() 207 | #$enemy_dead.play() 208 | 209 | 210 | #------------------------------------------------- 211 | # World checks 212 | #------------------------------------------------- 213 | #func is_on_elevator() -> bool: 214 | #return false 215 | # 216 | #func is_on_conveyor_belt() -> bool: 217 | #return false 218 | # 219 | #func get_walls_material() -> int: 220 | #return -1 221 | 222 | #------------------------------------------------- 223 | # VFX 224 | #------------------------------------------------- 225 | #func _whip_particles() -> void: 226 | #$rotate/whip_particles.rotation = TAU * randf() 227 | #$rotate/whip_particles/anim.play( "cycle" ) 228 | #$rotate/whip_particles/anim.queue( "RESET" ) 229 | 230 | 231 | func dust( _type : int = DustTypes.RUN ) -> void: 232 | pass 233 | #var _cur_dust : Sprite2D 234 | #for d in $dust.get_children(): 235 | #if not d.get_node( "anim" ).is_playing(): 236 | #cur_dust = d 237 | #cur_dust.top_level = true 238 | #break 239 | #if not cur_dust: 240 | #return 241 | #cur_dust.global_position = global_position 242 | #cur_dust.scale.x = dir_cur 243 | #match type: 244 | #DustTypes.RUN: 245 | #cur_dust.get_node( "anim" ).play( "run" ) 246 | #DustTypes.JUMP: 247 | #cur_dust.get_node( "anim" ).play( "jump" ) 248 | #DustTypes.LAND: 249 | #cur_dust.get_node( "anim" ).play( "land" ) 250 | #if randf() > 0.5: cur_dust.scale.x = -dir_cur 251 | #cur_dust.get_node( "anim" ).queue( "RESET" ) 252 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /game/entities/player/player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=31 format=3 uid="uid://bc8ap3kuleqvd"] 2 | 3 | [ext_resource type="Script" path="res://entities/player/player.gd" id="1_rjdal"] 4 | [ext_resource type="Script" path="res://general_purpose/fsm/fsm_anim.gd" id="3_58rvi"] 5 | [ext_resource type="Texture2D" uid="uid://b8qhuy7q6146h" path="res://assets/player.png" id="3_rvjru"] 6 | [ext_resource type="Script" path="res://general_purpose/damage/deal_damage_area.gd" id="3_w432v"] 7 | [ext_resource type="Script" path="res://entities/player/states/player_cutscene.gd" id="4_pjh81"] 8 | [ext_resource type="Script" path="res://entities/player/states/player_idle.gd" id="5_guaq2"] 9 | [ext_resource type="Script" path="res://entities/player/states/player_run.gd" id="6_npntt"] 10 | [ext_resource type="Script" path="res://entities/player/states/player_jump.gd" id="7_2ieit"] 11 | [ext_resource type="Script" path="res://entities/player/states/player_fall.gd" id="8_j6j6w"] 12 | [ext_resource type="Script" path="res://entities/player/states/player_hit.gd" id="9_81uy3"] 13 | [ext_resource type="Script" path="res://entities/player/states/player_dead.gd" id="10_bpeev"] 14 | [ext_resource type="Script" path="res://general_purpose/damage/rcv_damage_area.gd" id="11_mx0lk"] 15 | [ext_resource type="PackedScene" uid="uid://bovwniyfgotb3" path="res://entities/vfx/star_particles.tscn" id="13_yiwu2"] 16 | 17 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_xqpgq"] 18 | size = Vector2(12, 15) 19 | 20 | [sub_resource type="Animation" id="Animation_tvlq5"] 21 | length = 0.001 22 | tracks/0/type = "value" 23 | tracks/0/imported = false 24 | tracks/0/enabled = true 25 | tracks/0/path = NodePath("rotate/player:frame") 26 | tracks/0/interp = 1 27 | tracks/0/loop_wrap = true 28 | tracks/0/keys = { 29 | "times": PackedFloat32Array(0), 30 | "transitions": PackedFloat32Array(1), 31 | "update": 1, 32 | "values": [0] 33 | } 34 | 35 | [sub_resource type="Animation" id="Animation_h1m3e"] 36 | resource_name = "fall" 37 | length = 0.28 38 | loop_mode = 1 39 | step = 0.07 40 | tracks/0/type = "value" 41 | tracks/0/imported = false 42 | tracks/0/enabled = true 43 | tracks/0/path = NodePath("rotate/player:frame") 44 | tracks/0/interp = 1 45 | tracks/0/loop_wrap = true 46 | tracks/0/keys = { 47 | "times": PackedFloat32Array(0, 0.07, 0.14, 0.21), 48 | "transitions": PackedFloat32Array(1, 1, 1, 1), 49 | "update": 1, 50 | "values": [14, 15, 16, 15] 51 | } 52 | 53 | [sub_resource type="Animation" id="Animation_xrtd4"] 54 | resource_name = "hit" 55 | length = 0.1 56 | tracks/0/type = "value" 57 | tracks/0/imported = false 58 | tracks/0/enabled = true 59 | tracks/0/path = NodePath("rotate/player:frame") 60 | tracks/0/interp = 1 61 | tracks/0/loop_wrap = true 62 | tracks/0/keys = { 63 | "times": PackedFloat32Array(0), 64 | "transitions": PackedFloat32Array(1), 65 | "update": 1, 66 | "values": [17] 67 | } 68 | 69 | [sub_resource type="Animation" id="Animation_6fnsa"] 70 | resource_name = "idle" 71 | length = 6.0 72 | loop_mode = 1 73 | step = 0.2 74 | tracks/0/type = "value" 75 | tracks/0/imported = false 76 | tracks/0/enabled = true 77 | tracks/0/path = NodePath("rotate/player:frame") 78 | tracks/0/interp = 1 79 | tracks/0/loop_wrap = true 80 | tracks/0/keys = { 81 | "times": PackedFloat32Array(0, 0.2, 0.4, 0.6, 0.8, 1, 1.2, 1.4, 1.6, 1.8, 2, 2.2, 2.4, 2.6, 2.8, 3, 3.2, 3.4, 3.6, 3.8, 4, 4.2, 4.4, 4.6, 4.8, 5, 5.2, 5.4, 5.6, 5.8), 82 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1), 83 | "update": 1, 84 | "values": [0, 1, 2, 0, 1, 2, 0, 1, 2, 3, 1, 2, 0, 1, 2, 0, 4, 2, 3, 1, 2, 0, 1, 2, 6, 7, 8, 6, 7, 8] 85 | } 86 | 87 | [sub_resource type="Animation" id="Animation_jqaxq"] 88 | resource_name = "jump" 89 | length = 0.1 90 | tracks/0/type = "value" 91 | tracks/0/imported = false 92 | tracks/0/enabled = true 93 | tracks/0/path = NodePath("rotate/player:frame") 94 | tracks/0/interp = 1 95 | tracks/0/loop_wrap = true 96 | tracks/0/keys = { 97 | "times": PackedFloat32Array(0), 98 | "transitions": PackedFloat32Array(1), 99 | "update": 1, 100 | "values": [13] 101 | } 102 | 103 | [sub_resource type="Animation" id="Animation_75trf"] 104 | resource_name = "run" 105 | length = 0.3 106 | loop_mode = 1 107 | step = 0.05 108 | tracks/0/type = "value" 109 | tracks/0/imported = false 110 | tracks/0/enabled = true 111 | tracks/0/path = NodePath("rotate/player:frame") 112 | tracks/0/interp = 1 113 | tracks/0/loop_wrap = true 114 | tracks/0/keys = { 115 | "times": PackedFloat32Array(0, 0.1, 0.15, 0.25), 116 | "transitions": PackedFloat32Array(1, 1, 1, 1), 117 | "update": 1, 118 | "values": [10, 11, 12, 9] 119 | } 120 | 121 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_4gody"] 122 | _data = { 123 | "RESET": SubResource("Animation_tvlq5"), 124 | "fall": SubResource("Animation_h1m3e"), 125 | "hit": SubResource("Animation_xrtd4"), 126 | "idle": SubResource("Animation_6fnsa"), 127 | "jump": SubResource("Animation_jqaxq"), 128 | "run": SubResource("Animation_75trf") 129 | } 130 | 131 | [sub_resource type="Animation" id="Animation_26avg"] 132 | length = 0.001 133 | tracks/0/type = "value" 134 | tracks/0/imported = false 135 | tracks/0/enabled = true 136 | tracks/0/path = NodePath("rotate/player:scale") 137 | tracks/0/interp = 1 138 | tracks/0/loop_wrap = true 139 | tracks/0/keys = { 140 | "times": PackedFloat32Array(0), 141 | "transitions": PackedFloat32Array(1), 142 | "update": 0, 143 | "values": [Vector2(1, 1)] 144 | } 145 | tracks/1/type = "value" 146 | tracks/1/imported = false 147 | tracks/1/enabled = true 148 | tracks/1/path = NodePath("rotate/player:modulate") 149 | tracks/1/interp = 1 150 | tracks/1/loop_wrap = true 151 | tracks/1/keys = { 152 | "times": PackedFloat32Array(0), 153 | "transitions": PackedFloat32Array(1), 154 | "update": 0, 155 | "values": [Color(1, 1, 1, 1)] 156 | } 157 | 158 | [sub_resource type="Animation" id="Animation_e3mjb"] 159 | resource_name = "hit" 160 | length = 0.5 161 | tracks/0/type = "value" 162 | tracks/0/imported = false 163 | tracks/0/enabled = true 164 | tracks/0/path = NodePath("rotate/player:modulate") 165 | tracks/0/interp = 1 166 | tracks/0/loop_wrap = true 167 | tracks/0/keys = { 168 | "times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5), 169 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), 170 | "update": 1, 171 | "values": [Color(100, 100, 100, 1), Color(1, 1, 1, 1), Color(100, 100, 100, 1), Color(1, 1, 1, 1), Color(100, 100, 100, 1), Color(1, 1, 1, 1)] 172 | } 173 | 174 | [sub_resource type="Animation" id="Animation_orenk"] 175 | resource_name = "jump" 176 | length = 0.2 177 | tracks/0/type = "value" 178 | tracks/0/imported = false 179 | tracks/0/enabled = true 180 | tracks/0/path = NodePath("rotate/player:scale") 181 | tracks/0/interp = 1 182 | tracks/0/loop_wrap = true 183 | tracks/0/keys = { 184 | "times": PackedFloat32Array(0, 0.2), 185 | "transitions": PackedFloat32Array(1, 1), 186 | "update": 0, 187 | "values": [Vector2(0.8, 1.4), Vector2(1, 1)] 188 | } 189 | tracks/1/type = "value" 190 | tracks/1/imported = false 191 | tracks/1/enabled = true 192 | tracks/1/path = NodePath("rotate/player:modulate") 193 | tracks/1/interp = 1 194 | tracks/1/loop_wrap = true 195 | tracks/1/keys = { 196 | "times": PackedFloat32Array(0), 197 | "transitions": PackedFloat32Array(1), 198 | "update": 0, 199 | "values": [Color(1, 1, 1, 1)] 200 | } 201 | 202 | [sub_resource type="Animation" id="Animation_xoe4g"] 203 | resource_name = "land" 204 | length = 0.2 205 | tracks/0/type = "value" 206 | tracks/0/imported = false 207 | tracks/0/enabled = true 208 | tracks/0/path = NodePath("rotate/player:scale") 209 | tracks/0/interp = 1 210 | tracks/0/loop_wrap = true 211 | tracks/0/keys = { 212 | "times": PackedFloat32Array(0, 0.2), 213 | "transitions": PackedFloat32Array(1, 1), 214 | "update": 0, 215 | "values": [Vector2(1.4, 0.8), Vector2(1, 1)] 216 | } 217 | tracks/1/type = "value" 218 | tracks/1/imported = false 219 | tracks/1/enabled = true 220 | tracks/1/path = NodePath("rotate/player:modulate") 221 | tracks/1/interp = 1 222 | tracks/1/loop_wrap = true 223 | tracks/1/keys = { 224 | "times": PackedFloat32Array(0), 225 | "transitions": PackedFloat32Array(1), 226 | "update": 0, 227 | "values": [Color(1, 1, 1, 1)] 228 | } 229 | 230 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_fos6b"] 231 | _data = { 232 | "RESET": SubResource("Animation_26avg"), 233 | "hit": SubResource("Animation_e3mjb"), 234 | "jump": SubResource("Animation_orenk"), 235 | "land": SubResource("Animation_xoe4g") 236 | } 237 | 238 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_yjq2y"] 239 | size = Vector2(10, 12) 240 | 241 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_od1j0"] 242 | size = Vector2(2, 2) 243 | 244 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_dvbiq"] 245 | size = Vector2(10, 9) 246 | 247 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_wfeq5"] 248 | size = Vector2(8, 2) 249 | 250 | [node name="player" type="CharacterBody2D"] 251 | collision_layer = 16 252 | script = ExtResource("1_rjdal") 253 | 254 | [node name="collision" type="CollisionShape2D" parent="."] 255 | visible = false 256 | position = Vector2(0, -7.5) 257 | shape = SubResource("RectangleShape2D_xqpgq") 258 | metadata/_edit_lock_ = true 259 | 260 | [node name="rotate" type="Node2D" parent="."] 261 | metadata/_edit_lock_ = true 262 | 263 | [node name="player" type="Sprite2D" parent="rotate"] 264 | texture = ExtResource("3_rvjru") 265 | offset = Vector2(0, -13) 266 | hframes = 8 267 | vframes = 3 268 | metadata/_edit_lock_ = true 269 | 270 | [node name="anim" type="AnimationPlayer" parent="."] 271 | libraries = { 272 | "": SubResource("AnimationLibrary_4gody") 273 | } 274 | script = ExtResource("3_58rvi") 275 | 276 | [node name="anim_fx" type="AnimationPlayer" parent="."] 277 | libraries = { 278 | "": SubResource("AnimationLibrary_fos6b") 279 | } 280 | 281 | [node name="states" type="Node" parent="."] 282 | 283 | [node name="cutscene" type="Node" parent="states"] 284 | script = ExtResource("4_pjh81") 285 | 286 | [node name="idle" type="Node" parent="states"] 287 | script = ExtResource("5_guaq2") 288 | 289 | [node name="run" type="Node" parent="states"] 290 | script = ExtResource("6_npntt") 291 | 292 | [node name="jump" type="Node" parent="states"] 293 | script = ExtResource("7_2ieit") 294 | 295 | [node name="fall" type="Node" parent="states"] 296 | script = ExtResource("8_j6j6w") 297 | 298 | [node name="hit" type="Node" parent="states"] 299 | script = ExtResource("9_81uy3") 300 | 301 | [node name="dead" type="Node" parent="states"] 302 | script = ExtResource("10_bpeev") 303 | 304 | [node name="detect_hazards" type="Area2D" parent="."] 305 | visible = false 306 | collision_layer = 0 307 | collision_mask = 128 308 | metadata/_edit_lock_ = true 309 | 310 | [node name="hazards_collision" type="CollisionShape2D" parent="detect_hazards"] 311 | position = Vector2(0, -7) 312 | shape = SubResource("RectangleShape2D_yjq2y") 313 | metadata/_edit_lock_ = true 314 | 315 | [node name="test_hazards" type="Node2D" parent="."] 316 | visible = false 317 | metadata/_edit_lock_ = true 318 | 319 | [node name="test_hazards_1" type="Area2D" parent="test_hazards"] 320 | position = Vector2(6, -7) 321 | collision_layer = 0 322 | collision_mask = 128 323 | metadata/_edit_lock_ = true 324 | 325 | [node name="hazards_collision" type="CollisionShape2D" parent="test_hazards/test_hazards_1"] 326 | shape = SubResource("RectangleShape2D_od1j0") 327 | metadata/_edit_lock_ = true 328 | 329 | [node name="test_hazards_2" type="Area2D" parent="test_hazards"] 330 | position = Vector2(0, -14) 331 | collision_layer = 0 332 | collision_mask = 128 333 | metadata/_edit_lock_ = true 334 | 335 | [node name="hazards_collision" type="CollisionShape2D" parent="test_hazards/test_hazards_2"] 336 | shape = SubResource("RectangleShape2D_od1j0") 337 | metadata/_edit_lock_ = true 338 | 339 | [node name="test_hazards_3" type="Area2D" parent="test_hazards"] 340 | position = Vector2(-6, -7) 341 | collision_layer = 0 342 | collision_mask = 128 343 | metadata/_edit_lock_ = true 344 | 345 | [node name="hazards_collision" type="CollisionShape2D" parent="test_hazards/test_hazards_3"] 346 | shape = SubResource("RectangleShape2D_od1j0") 347 | metadata/_edit_lock_ = true 348 | 349 | [node name="test_hazards_4" type="Area2D" parent="test_hazards"] 350 | collision_layer = 0 351 | collision_mask = 128 352 | metadata/_edit_lock_ = true 353 | 354 | [node name="hazards_collision" type="CollisionShape2D" parent="test_hazards/test_hazards_4"] 355 | shape = SubResource("RectangleShape2D_od1j0") 356 | metadata/_edit_lock_ = true 357 | 358 | [node name="RcvDamageArea" type="Area2D" parent="."] 359 | visible = false 360 | collision_layer = 0 361 | collision_mask = 32 362 | script = ExtResource("11_mx0lk") 363 | metadata/_edit_lock_ = true 364 | 365 | [node name="damage_collision" type="CollisionShape2D" parent="RcvDamageArea"] 366 | position = Vector2(0, -8.5) 367 | shape = SubResource("RectangleShape2D_dvbiq") 368 | metadata/_edit_lock_ = true 369 | 370 | [node name="DealDamageArea" type="Area2D" parent="."] 371 | visible = false 372 | collision_layer = 1024 373 | collision_mask = 0 374 | script = ExtResource("3_w432v") 375 | metadata/_edit_lock_ = true 376 | 377 | [node name="damage_collision" type="CollisionShape2D" parent="DealDamageArea"] 378 | position = Vector2(0, 1) 379 | shape = SubResource("RectangleShape2D_wfeq5") 380 | disabled = true 381 | metadata/_edit_lock_ = true 382 | 383 | [node name="star_particles" parent="." instance=ExtResource("13_yiwu2")] 384 | position = Vector2(0, -7) 385 | -------------------------------------------------------------------------------- /game/entities/player/states/player_attack.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | var _attack_timer : float 4 | 5 | func _initialize() -> void: 6 | anim.anim_nxt = "attack" 7 | _attack_timer = 0.5 8 | obj.velocity *= 0 9 | 10 | 11 | 12 | func _run( delta : float ) -> void: 13 | if _attack_timer > 0: 14 | _attack_timer -= delta 15 | if _attack_timer <= 0: 16 | fsm.state_nxt = fsm.state_lst 17 | obj.move_and_slide() 18 | -------------------------------------------------------------------------------- /game/entities/player/states/player_attack_platform.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | var _attack_timer : float 4 | 5 | func _initialize() -> void: 6 | anim.anim_nxt = "attack_paralize" 7 | _attack_timer = 0.5 8 | obj.velocity *= 0 9 | obj.get_node( "rotate/DealDamageArea" ).damage_dealt = -1 10 | 11 | func _run( delta : float ) -> void: 12 | if _attack_timer > 0: 13 | _attack_timer -= delta 14 | if _attack_timer <= 0: 15 | fsm.state_nxt = fsm.state_lst 16 | obj.move_and_slide() 17 | 18 | func _terminate() -> void: 19 | obj.get_node( "rotate/DealDamageArea" ).damage_dealt = game.state.whip_damage 20 | -------------------------------------------------------------------------------- /game/entities/player/states/player_cutscene.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | var cutscene_timer : float 4 | var stop_moving : bool 5 | 6 | func _initialize() -> void: 7 | obj.is_invulnerable = true 8 | obj.invulnerable_timer = -1.0 9 | obj.velocity *= 0 10 | stop_moving = false 11 | 12 | func _run( delta : float ) -> void: 13 | if not stop_moving: 14 | obj.gravity( delta, 0.1 ) 15 | var _ret : bool = obj.move_and_slide() 16 | if obj.is_on_floor(): 17 | stop_moving = true 18 | 19 | if cutscene_timer > 0: 20 | cutscene_timer -= delta 21 | if cutscene_timer <= 0: 22 | fsm.state_nxt = fsm.states.idle 23 | 24 | func _terminate() -> void: 25 | obj.is_invulnerable = false 26 | -------------------------------------------------------------------------------- /game/entities/player/states/player_dead.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | var hit_timer : float 4 | var dead_timer : float 5 | var state : int 6 | 7 | func _initialize() -> void: 8 | anim.anim_nxt = "hit" 9 | hit_timer = 0.5 10 | dead_timer = 1.0 11 | state = 0 12 | 13 | func _run( delta : float ) -> void: 14 | match state: 15 | 0: 16 | var _ret : bool = obj.move_and_slide() 17 | obj.velocity *= 0.75 18 | hit_timer -= delta 19 | if hit_timer <= 0: 20 | state = 1 21 | obj.death() 22 | 1: 23 | dead_timer -= delta 24 | if dead_timer <= 0: 25 | obj.player_dead.emit() 26 | fsm.state_nxt = fsm.states.idle 27 | 28 | 29 | 30 | func _terminate() -> void: 31 | obj.is_hit = false 32 | obj.is_dead = false 33 | -------------------------------------------------------------------------------- /game/entities/player/states/player_fall.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | var rejump_timer : float 4 | var coyote_timer : float 5 | var fall_timer : float 6 | 7 | func _initialize() -> void: 8 | fall_timer = 0.0 9 | rejump_timer = 0.0 10 | #if fsm.state_lst != fsm.states.jump and \ 11 | #fsm.state_lst != fsm.states.attack and \ 12 | #fsm.state_lst != fsm.states.attack_platform: 13 | if fsm.state_lst != fsm.states.jump: 14 | coyote_timer = Params.PLAYER_COYOTE_MARGIN 15 | else: 16 | coyote_timer = -1.0 17 | anim.anim_nxt = "fall" 18 | obj.get_node( "DealDamageArea/damage_collision" ).disabled = false 19 | 20 | func _run( delta : float ) -> void: 21 | if coyote_timer > 0: 22 | coyote_timer -= delta 23 | if obj.control.is_just_jump: 24 | fsm.states.jump.begin_jump() 25 | return 26 | 27 | if obj.control.is_just_fire and game.state.whip: 28 | fsm.state_nxt = fsm.states.attack 29 | return 30 | if obj.control.is_just_interact and game.state.paralize_whip: 31 | fsm.state_nxt = fsm.states.attack_platform 32 | return 33 | 34 | #if obj.is_on_elevator(): 35 | ##obj.elevator( delta ) 36 | #pass 37 | #else: 38 | #obj.gravity( delta ) 39 | obj.gravity( delta ) 40 | 41 | if obj.control.is_moving: 42 | var dir : float = sign( obj.control.dir ) 43 | obj.velocity.x = lerp( obj.velocity.x, dir * Params.PLAYER_MAX_VEL, Params.PLAYER_AIR_ACCEL * delta ) 44 | obj.dir_nxt = int( dir ) 45 | else: 46 | obj.velocity.x = lerp( obj.velocity.x, 0.0, Params.PLAYER_AIR_DECEL * delta ) 47 | var _ret : bool = obj.move_and_slide() 48 | 49 | rejump_timer -= delta 50 | if obj.control.is_just_jump: 51 | rejump_timer = 0.2 52 | 53 | fall_timer += delta 54 | if obj.is_on_floor(): 55 | obj.dust( Player.DustTypes.LAND ) 56 | if rejump_timer >= 0: 57 | fsm.states.jump.begin_jump() 58 | return 59 | else: 60 | #if abs( obj.velocity.x ) < 15: 61 | #obj.get_node( "step" ).play() 62 | if fall_timer > 0.15: 63 | obj.anim_fx.play( "land" ) 64 | if abs( obj.velocity.x ) > Params.PLAYER_MAX_VEL * 0.25: 65 | fsm.state_nxt = fsm.states.run 66 | else: 67 | fsm.state_nxt = fsm.states.idle 68 | 69 | func _terminate() -> void: 70 | obj.get_node( "DealDamageArea/damage_collision" ).disabled = true 71 | -------------------------------------------------------------------------------- /game/entities/player/states/player_hit.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | var hit_timer : float 4 | 5 | func _initialize() -> void: 6 | anim.anim_nxt = "hit" 7 | obj.anim_fx.play( "hit" ) 8 | obj.anim_fx.queue( "RESET" ) 9 | hit_timer = 0.15 10 | 11 | func _run( delta : float ) -> void: 12 | obj.gravity( delta ) 13 | var _ret : bool = obj.move_and_slide() 14 | 15 | if obj.is_on_floor(): 16 | obj.velocity.x *= 0.95 17 | 18 | hit_timer -= delta 19 | if hit_timer <= 0: 20 | fsm.state_nxt = fsm.states.idle 21 | 22 | 23 | 24 | func _terminate() -> void: 25 | obj.is_hit = false 26 | -------------------------------------------------------------------------------- /game/entities/player/states/player_idle.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | func _initialize() -> void: 4 | anim.anim_nxt = "idle" 5 | obj.position = obj.position.round() 6 | 7 | func _run( delta : float ) -> void: 8 | if obj.control.is_just_jump: 9 | fsm.states.jump.begin_jump() 10 | return 11 | 12 | if obj.control.is_just_fire and game.state.whip: 13 | fsm.state_nxt = fsm.states.attack 14 | return 15 | if obj.control.is_just_interact and game.state.paralize_whip: 16 | fsm.state_nxt = fsm.states.attack_platform 17 | return 18 | 19 | obj.velocity.x = 0 20 | 21 | if obj.control.is_moving: 22 | fsm.state_nxt = fsm.states.run 23 | 24 | obj.gravity( delta, 0.1 ) 25 | var _ret : bool = obj.move_and_slide() 26 | 27 | if obj.is_on_floor(): 28 | if obj.control.is_down and _check_drop( delta ): 29 | obj.position.y += 1 30 | pass 31 | else: 32 | fsm.state_nxt = fsm.states.fall 33 | 34 | 35 | func _check_drop( delta : float ) -> bool: 36 | var tm : bool = obj.test_move( obj.get_transform().translated( Vector2.DOWN ), Params.GRAVITY * delta * Vector2.DOWN ) 37 | return not tm 38 | 39 | #func _terminate(): 40 | #var _ret = obj.check_conveyor_belt(false) 41 | -------------------------------------------------------------------------------- /game/entities/player/states/player_jump.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | var hold_timer : float 4 | 5 | 6 | func begin_jump() -> void: 7 | obj.dust( Player.DustTypes.JUMP ) 8 | obj.velocity.y = -Params.PLAYER_JUMP_VEL 9 | var _ret : bool = obj.move_and_slide() 10 | obj.anim_fx.play( "jump" ) 11 | hold_timer = Params.PLAYER_VARIABLE_JUMP_MARGIN 12 | anim.anim_nxt = "jump" 13 | fsm.state_nxt = fsm.states.jump 14 | 15 | func begin_small_jump() -> void: 16 | obj.dust( Player.DustTypes.JUMP ) 17 | if obj.control.is_jump: 18 | obj.velocity.y = -Params.PLAYER_JUMP_VEL 19 | else: 20 | obj.velocity.y = -Params.PLAYER_JUMP_VEL * 0.75 21 | var _ret : bool = obj.move_and_slide() 22 | obj.anim_fx.play( "jump" ) 23 | hold_timer = Params.PLAYER_VARIABLE_JUMP_MARGIN 24 | anim.anim_nxt = "jump" 25 | fsm.state_nxt = fsm.states.jump 26 | 27 | 28 | func _run( delta : float ) -> void: 29 | if obj.control.is_just_fire and game.state.whip: 30 | fsm.state_nxt = fsm.states.attack 31 | return 32 | if obj.control.is_just_interact and game.state.paralize_whip: 33 | fsm.state_nxt = fsm.states.attack_platform 34 | return 35 | 36 | if hold_timer > 0: 37 | hold_timer -= delta 38 | if obj.velocity.y >= 0: 39 | hold_timer = 0 40 | elif hold_timer <= 0 or not obj.control.is_jump: 41 | hold_timer = 0 42 | obj.velocity.y *= 0.5 43 | 44 | #if obj.is_on_elevator(): 45 | ##obj.elevator( delta ) 46 | #pass 47 | #else: 48 | #obj.gravity( delta ) 49 | obj.gravity( delta ) 50 | 51 | if obj.control.is_moving: 52 | var dir : float = sign( obj.control.dir ) 53 | obj.velocity.x = lerp( obj.velocity.x, dir * Params.PLAYER_MAX_VEL, Params.PLAYER_AIR_ACCEL * delta ) 54 | obj.dir_nxt = int( dir ) 55 | else: 56 | obj.velocity.x = lerp( obj.velocity.x, 0.0, Params.PLAYER_AIR_DECEL * delta ) 57 | var _ret : bool = obj.move_and_slide() 58 | 59 | if obj.velocity.y > 0: 60 | fsm.state_nxt = fsm.states.fall 61 | return 62 | 63 | if obj.is_on_floor(): 64 | fsm.state_nxt = fsm.states.idle 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | -------------------------------------------------------------------------------- /game/entities/player/states/player_run.gd: -------------------------------------------------------------------------------- 1 | extends FSM_State 2 | 3 | func _initialize() -> void: 4 | anim.anim_nxt = "run" 5 | 6 | func _run( delta : float ) -> void: 7 | if obj.control.is_just_jump: 8 | fsm.states.jump.begin_jump() 9 | return 10 | 11 | if obj.control.is_just_fire and game.state.whip: 12 | fsm.state_nxt = fsm.states.attack 13 | return 14 | if obj.control.is_just_interact and game.state.paralize_whip: 15 | fsm.state_nxt = fsm.states.attack_platform 16 | return 17 | 18 | obj.gravity( delta, 0.1 ) 19 | if obj.control.is_moving: 20 | var dir : float = sign( obj.control.dir ) 21 | obj.velocity.x = dir * Params.PLAYER_MAX_VEL 22 | obj.dir_nxt = int( dir ) 23 | else: 24 | obj.velocity.x = 0 25 | fsm.state_nxt = fsm.states.idle 26 | return 27 | var _ret : bool = obj.move_and_slide() 28 | 29 | if not obj.is_on_floor(): 30 | fsm.state_nxt = fsm.states.fall 31 | 32 | -------------------------------------------------------------------------------- /game/entities/slime/sliCD8.tmp: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://bqnvsveodmguh"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="1_3rs8e"] 4 | [ext_resource type="Script" path="res://entities/slime/slime.gd" id="1_etr8h"] 5 | 6 | [sub_resource type="Animation" id="Animation_6vdvg"] 7 | resource_name = "cycle" 8 | length = 0.4 9 | loop_mode = 1 10 | tracks/0/type = "value" 11 | tracks/0/imported = false 12 | tracks/0/enabled = true 13 | tracks/0/path = NodePath("rotate/slime:region_rect") 14 | tracks/0/interp = 1 15 | tracks/0/loop_wrap = true 16 | tracks/0/keys = { 17 | "times": PackedFloat32Array(0, 0.2), 18 | "transitions": PackedFloat32Array(1, 1), 19 | "update": 1, 20 | "values": [Rect2(176, 0, 16, 16), Rect2(192, 0, 16, 16)] 21 | } 22 | 23 | [sub_resource type="Animation" id="Animation_ysbpd"] 24 | length = 0.001 25 | tracks/0/type = "value" 26 | tracks/0/imported = false 27 | tracks/0/enabled = true 28 | tracks/0/path = NodePath("rotate/slime:region_rect") 29 | tracks/0/interp = 1 30 | tracks/0/loop_wrap = true 31 | tracks/0/keys = { 32 | "times": PackedFloat32Array(0), 33 | "transitions": PackedFloat32Array(1), 34 | "update": 0, 35 | "values": [Rect2(176, 0, 16, 16)] 36 | } 37 | 38 | [sub_resource type="Animation" id="Animation_vsccc"] 39 | resource_name = "dead" 40 | length = 0.1 41 | tracks/0/type = "value" 42 | tracks/0/imported = false 43 | tracks/0/enabled = true 44 | tracks/0/path = NodePath("rotate/slime:region_rect") 45 | tracks/0/interp = 1 46 | tracks/0/loop_wrap = true 47 | tracks/0/keys = { 48 | "times": PackedFloat32Array(0), 49 | "transitions": PackedFloat32Array(1), 50 | "update": 0, 51 | "values": [Rect2(160, 0, 16, 16)] 52 | } 53 | 54 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_f5oka"] 55 | _data = { 56 | "RESET": SubResource("Animation_ysbpd"), 57 | "cycle": SubResource("Animation_6vdvg"), 58 | "dead": SubResource("Animation_vsccc") 59 | } 60 | 61 | [node name="slime" type="Node2D"] 62 | z_index = -1 63 | script = ExtResource("1_etr8h") 64 | 65 | [node name="rotate" type="Node2D" parent="."] 66 | metadata/_edit_lock_ = true 67 | 68 | [node name="slime" type="Sprite2D" parent="rotate"] 69 | texture = ExtResource("1_3rs8e") 70 | offset = Vector2(0, -7) 71 | region_enabled = true 72 | region_rect = Rect2(176, 0, 16, 16) 73 | metadata/_edit_lock_ = true 74 | 75 | [node name="anim" type="AnimationPlayer" parent="."] 76 | libraries = { 77 | "": SubResource("AnimationLibrary_f5oka") 78 | } 79 | -------------------------------------------------------------------------------- /game/entities/slime/slime.gd: -------------------------------------------------------------------------------- 1 | extends Node2D_Entity 2 | 3 | enum ENEMY_STATES { PATROL, DEAD, RESTART } 4 | 5 | var _state : int = ENEMY_STATES.PATROL 6 | var _params : Dictionary 7 | 8 | @onready var anim : AnimationPlayer = $anim 9 | 10 | func _ready() -> void: 11 | set_physics_process( false ) 12 | 13 | func _entity_activate( activate : bool ) -> void: 14 | if activate: 15 | set_physics_process( true ) 16 | $DealDamageArea/damage_collision.disabled = false 17 | $RcvDamageArea/rcv_damage_collision.disabled = false 18 | else: 19 | set_physics_process( false ) 20 | $DealDamageArea/damage_collision.disabled = true 21 | $RcvDamageArea/rcv_damage_collision.disabled = true 22 | 23 | func _entity_initialize( params : Dictionary ) -> void: 24 | _params = params 25 | _prepare_enemy() 26 | 27 | 28 | func _prepare_enemy() -> void: 29 | _state = ENEMY_STATES.PATROL 30 | position = _params.position 31 | _params.path_px = [ position ] 32 | _params.path_px.append( Vector2( _params.path.cx, _params.path.cy ) * 16 + Vector2( 8, 16 ) ) 33 | _params.path_idx = 1 34 | _params.dead_timer = 0.5 35 | _params.restart_timer = 5.0 36 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 37 | if abs( dir.x ) > 0: 38 | $rotate.scale.x = sign( dir.x ) 39 | 40 | 41 | func _physics_process( delta : float ) -> void: 42 | match _state: 43 | ENEMY_STATES.PATROL: 44 | anim.set_anim( "patrol" ) 45 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 46 | #print( dir ) 47 | position += dir * Params.SLIME_WALK_VEL * delta 48 | if( position - _params.path_px[_params.path_idx] ).length() < ( 2.0 * Params.SLIME_WALK_VEL / 60.0 ): 49 | position = _params.path_px[_params.path_idx] 50 | _params.path_idx= ( _params.path_idx + 1 ) % _params.path_px.size() 51 | dir = ( _params.path_px[_params.path_idx] - position ).normalized() 52 | if abs( dir.x ) > 0: 53 | $rotate.scale.x = sign( dir.x ) 54 | ENEMY_STATES.DEAD: 55 | if _params.dead_timer > 0: 56 | _params.dead_timer -= delta 57 | if _params.dead_timer <= 0: 58 | hide() 59 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 60 | x.position = position + Vector2( 0, -4 ) 61 | get_parent().add_child( x ) 62 | sigmgr.camera_shake.emit( 0.1, 1, 60 ) 63 | if randf() < 0.1: 64 | var c : Node2D = preload( "res://entities/coin/coin_reward.tscn" ).instantiate() 65 | c.position = position + Vector2( 0, -4 ) 66 | c.velocity.x = randf_range( -30, 30 ) 67 | get_parent().add_child( c ) 68 | _state = ENEMY_STATES.RESTART 69 | _params.restart_timer = 4.0 70 | position = _params.position 71 | ENEMY_STATES.RESTART: 72 | _params.restart_timer -= delta 73 | if _params.restart_timer <= 0: 74 | var dist : Vector2 = game.player.global_position - global_position 75 | #print( "Distance to player: ", dist ) 76 | if dist.length() > Params.MIN_DISTANCE_RESPAWN: 77 | # restarting 78 | _prepare_enemy() 79 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 80 | x.position = position + Vector2( 0, -4 ) 81 | get_parent().add_child( x ) 82 | show() 83 | $DealDamageArea/damage_collision.call_deferred( "set_disabled", false ) 84 | else: 85 | _params.restart_timer = 1.0 86 | 87 | 88 | func _on_receiving_damage( player : Player, _damage : int ) -> void: 89 | if _state == ENEMY_STATES.DEAD or _state == ENEMY_STATES.RESTART: return 90 | if player.velocity.y <= 0: return # player must be falling down 91 | if player.global_position.y >= global_position.y - 8: return 92 | _state = ENEMY_STATES.DEAD 93 | #player.velocity.y = -Params.PLAYER_JUMP_VEL 94 | player._fsm.states.jump.begin_small_jump() 95 | _set_dead() 96 | 97 | 98 | func _set_dead() -> void: 99 | $DealDamageArea/damage_collision.call_deferred( "set_disabled", true ) 100 | anim.set_anim( "dead" ) 101 | -------------------------------------------------------------------------------- /game/entities/slime/slime.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=12 format=3 uid="uid://bqnvsveodmguh"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="1_3rs8e"] 4 | [ext_resource type="Script" path="res://entities/slime/slime.gd" id="1_etr8h"] 5 | [ext_resource type="Script" path="res://general_purpose/utils/anim.gd" id="3_5gaun"] 6 | [ext_resource type="Script" path="res://general_purpose/damage/deal_damage_area.gd" id="4_1sfqn"] 7 | [ext_resource type="Script" path="res://general_purpose/damage/rcv_damage_area.gd" id="5_6mws2"] 8 | 9 | [sub_resource type="Animation" id="Animation_ysbpd"] 10 | length = 0.001 11 | tracks/0/type = "value" 12 | tracks/0/imported = false 13 | tracks/0/enabled = true 14 | tracks/0/path = NodePath("rotate/slime:region_rect") 15 | tracks/0/interp = 1 16 | tracks/0/loop_wrap = true 17 | tracks/0/keys = { 18 | "times": PackedFloat32Array(0), 19 | "transitions": PackedFloat32Array(1), 20 | "update": 0, 21 | "values": [Rect2(176, 0, 16, 16)] 22 | } 23 | tracks/1/type = "value" 24 | tracks/1/imported = false 25 | tracks/1/enabled = true 26 | tracks/1/path = NodePath("rotate/slime:scale") 27 | tracks/1/interp = 1 28 | tracks/1/loop_wrap = true 29 | tracks/1/keys = { 30 | "times": PackedFloat32Array(0), 31 | "transitions": PackedFloat32Array(1), 32 | "update": 0, 33 | "values": [Vector2(1, 1)] 34 | } 35 | 36 | [sub_resource type="Animation" id="Animation_vsccc"] 37 | resource_name = "dead" 38 | length = 0.4 39 | tracks/0/type = "value" 40 | tracks/0/imported = false 41 | tracks/0/enabled = true 42 | tracks/0/path = NodePath("rotate/slime:region_rect") 43 | tracks/0/interp = 1 44 | tracks/0/loop_wrap = true 45 | tracks/0/keys = { 46 | "times": PackedFloat32Array(0), 47 | "transitions": PackedFloat32Array(1), 48 | "update": 0, 49 | "values": [Rect2(160, 0, 16, 16)] 50 | } 51 | tracks/1/type = "value" 52 | tracks/1/imported = false 53 | tracks/1/enabled = true 54 | tracks/1/path = NodePath("rotate/slime:scale") 55 | tracks/1/interp = 1 56 | tracks/1/loop_wrap = true 57 | tracks/1/keys = { 58 | "times": PackedFloat32Array(0, 0.1), 59 | "transitions": PackedFloat32Array(1, 1), 60 | "update": 0, 61 | "values": [Vector2(1.5, 0.5), Vector2(1, 1)] 62 | } 63 | 64 | [sub_resource type="Animation" id="Animation_6vdvg"] 65 | resource_name = "patrol" 66 | length = 0.4 67 | loop_mode = 1 68 | tracks/0/type = "value" 69 | tracks/0/imported = false 70 | tracks/0/enabled = true 71 | tracks/0/path = NodePath("rotate/slime:region_rect") 72 | tracks/0/interp = 1 73 | tracks/0/loop_wrap = true 74 | tracks/0/keys = { 75 | "times": PackedFloat32Array(0, 0.2), 76 | "transitions": PackedFloat32Array(1, 1), 77 | "update": 1, 78 | "values": [Rect2(176, 0, 16, 16), Rect2(192, 0, 16, 16)] 79 | } 80 | tracks/1/type = "value" 81 | tracks/1/imported = false 82 | tracks/1/enabled = true 83 | tracks/1/path = NodePath("rotate/slime:scale") 84 | tracks/1/interp = 1 85 | tracks/1/loop_wrap = true 86 | tracks/1/keys = { 87 | "times": PackedFloat32Array(0), 88 | "transitions": PackedFloat32Array(1), 89 | "update": 0, 90 | "values": [Vector2(1, 1)] 91 | } 92 | 93 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_f5oka"] 94 | _data = { 95 | "RESET": SubResource("Animation_ysbpd"), 96 | "dead": SubResource("Animation_vsccc"), 97 | "patrol": SubResource("Animation_6vdvg") 98 | } 99 | 100 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_6sckj"] 101 | size = Vector2(6, 8) 102 | 103 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_x0ynt"] 104 | size = Vector2(14, 4) 105 | 106 | [node name="slime" type="Node2D"] 107 | z_index = -1 108 | script = ExtResource("1_etr8h") 109 | 110 | [node name="rotate" type="Node2D" parent="."] 111 | metadata/_edit_lock_ = true 112 | 113 | [node name="slime" type="Sprite2D" parent="rotate"] 114 | texture = ExtResource("1_3rs8e") 115 | offset = Vector2(0, -7) 116 | region_enabled = true 117 | region_rect = Rect2(176, 0, 16, 16) 118 | metadata/_edit_lock_ = true 119 | 120 | [node name="anim" type="AnimationPlayer" parent="."] 121 | libraries = { 122 | "": SubResource("AnimationLibrary_f5oka") 123 | } 124 | script = ExtResource("3_5gaun") 125 | 126 | [node name="DealDamageArea" type="Area2D" parent="."] 127 | visible = false 128 | collision_layer = 32 129 | collision_mask = 0 130 | script = ExtResource("4_1sfqn") 131 | metadata/_edit_lock_ = true 132 | 133 | [node name="damage_collision" type="CollisionShape2D" parent="DealDamageArea"] 134 | position = Vector2(0, -4) 135 | shape = SubResource("RectangleShape2D_6sckj") 136 | metadata/_edit_lock_ = true 137 | 138 | [node name="RcvDamageArea" type="Area2D" parent="."] 139 | collision_layer = 0 140 | collision_mask = 1024 141 | script = ExtResource("5_6mws2") 142 | 143 | [node name="rcv_damage_collision" type="CollisionShape2D" parent="RcvDamageArea"] 144 | position = Vector2(0, -8) 145 | shape = SubResource("RectangleShape2D_x0ynt") 146 | -------------------------------------------------------------------------------- /game/entities/trooper/trooper.gd: -------------------------------------------------------------------------------- 1 | extends Node2D_Entity 2 | 3 | enum ENEMY_STATES { PATROL, SIGHT, CHASE, DEAD, RESTART } 4 | 5 | var _state : int = ENEMY_STATES.PATROL 6 | var _params : Dictionary 7 | 8 | @onready var anim : AnimationPlayer = $anim 9 | @onready var rot : Node2D = $rotate 10 | 11 | func _ready() -> void: 12 | set_physics_process( false ) 13 | 14 | func _entity_activate( activate : bool ) -> void: 15 | if activate: 16 | set_physics_process( true ) 17 | $DealDamageArea/damage_collision.disabled = false 18 | $RcvDamageArea/rcv_damage_collision.disabled = false 19 | else: 20 | set_physics_process( false ) 21 | $DealDamageArea/damage_collision.disabled = true 22 | $RcvDamageArea/rcv_damage_collision.disabled = true 23 | 24 | func _entity_initialize( params : Dictionary ) -> void: 25 | _params = params 26 | _prepare_enemy() 27 | 28 | 29 | func _prepare_enemy() -> void: 30 | _state = ENEMY_STATES.PATROL 31 | position = _params.position 32 | _params.path_px = [ position ] 33 | _params.path_px.append( Vector2( _params.path.cx, _params.path.cy ) * 16 + Vector2( 8, 16 ) ) 34 | _params.path_rect = Rect2( 35 | Vector2( 36 | min( _params.path_px[0].x, _params.path_px[1].x ), 37 | min( _params.path_px[0].y, _params.path_px[1].y ) 38 | ) + Vector2( -8, -16 ), 39 | Vector2( 40 | abs( _params.path_px[0].x - _params.path_px[1].x ), 41 | abs( _params.path_px[0].y - _params.path_px[1].y ) 42 | ) + Vector2( 16, 16 ) 43 | ) 44 | _params.path_idx = 1 45 | _params.dead_timer = 0.5 46 | _params.restart_timer = 5.0 47 | _params.sight_timer = 0.25 48 | 49 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 50 | if abs( dir.x ) > 0: 51 | rot.scale.x = sign( dir.x ) 52 | 53 | 54 | func _physics_process( delta : float ) -> void: 55 | match _state: 56 | ENEMY_STATES.PATROL: 57 | anim.set_anim( "patrol" ) 58 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 59 | #print( dir ) 60 | position += dir * Params.TROOPER_WALK_VEL * delta 61 | if( position - _params.path_px[_params.path_idx] ).length() < ( 2.0 * Params.SLIME_WALK_VEL / 60.0 ): 62 | position = _params.path_px[_params.path_idx] 63 | _params.path_idx= ( _params.path_idx + 1 ) % _params.path_px.size() 64 | dir = ( _params.path_px[_params.path_idx] - position ).normalized() 65 | if abs( dir.x ) > 0: 66 | rot.scale.x = sign( dir.x ) 67 | if _player_in_sight(): 68 | _params.sight_timer = 0.5 69 | _state = ENEMY_STATES.SIGHT 70 | ENEMY_STATES.SIGHT: 71 | anim.set_anim( "sight" ) 72 | _params.sight_timer -= delta 73 | if _params.sight_timer <= 0: 74 | _state = ENEMY_STATES.CHASE 75 | ENEMY_STATES.CHASE: 76 | anim.set_anim( "chase" ) 77 | var dir : Vector2 = ( _params.path_px[_params.path_idx] - position ).normalized() 78 | #print( dir ) 79 | position += dir * Params.TROOPER_CHASE_VEL * delta 80 | if( position - _params.path_px[_params.path_idx] ).length() < ( 2.0 * Params.SLIME_WALK_VEL / 60.0 ): 81 | position = _params.path_px[_params.path_idx] 82 | _params.path_idx= ( _params.path_idx + 1 ) % _params.path_px.size() 83 | dir = ( _params.path_px[_params.path_idx] - position ).normalized() 84 | if abs( dir.x ) > 0: 85 | rot.scale.x = sign( dir.x ) 86 | _state = ENEMY_STATES.PATROL 87 | ENEMY_STATES.DEAD: 88 | if _params.dead_timer > 0: 89 | _params.dead_timer -= delta 90 | if _params.dead_timer <= 0: 91 | hide() 92 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 93 | x.position = position + Vector2( 0, -4 ) 94 | get_parent().add_child( x ) 95 | sigmgr.camera_shake.emit( 0.1, 1, 60 ) 96 | if randf() < 0.1: 97 | var c : Node2D = preload( "res://entities/coin/coin_reward.tscn" ).instantiate() 98 | c.position = position + Vector2( 0, -4 ) 99 | c.velocity.x = randf_range( -30, 30 ) 100 | get_parent().add_child( c ) 101 | _state = ENEMY_STATES.RESTART 102 | _params.restart_timer = 4.0 103 | position = _params.position 104 | ENEMY_STATES.RESTART: 105 | _params.restart_timer -= delta 106 | if _params.restart_timer <= 0: 107 | var dist : Vector2 = game.player.global_position - global_position 108 | if dist.length() > Params.MIN_DISTANCE_RESPAWN: 109 | # restarting 110 | _prepare_enemy() 111 | var x : Sprite2D = preload( "res://entities/vfx/puff.tscn" ).instantiate() 112 | x.position = position + Vector2( 0, -4 ) 113 | get_parent().add_child( x ) 114 | show() 115 | $DealDamageArea/damage_collision.call_deferred( "set_disabled", false ) 116 | else: 117 | _params.restart_timer = 1.0 118 | 119 | 120 | func _on_receiving_damage( player : Player, _damage : int ) -> void: 121 | if _state == ENEMY_STATES.DEAD or _state == ENEMY_STATES.RESTART: return 122 | if player.velocity.y <= 0: return # player must be falling down 123 | if player.global_position.y >= global_position.y - 8: return 124 | _state = ENEMY_STATES.DEAD 125 | #player.velocity.y = -Params.PLAYER_JUMP_VEL 126 | player._fsm.states.jump.begin_small_jump() 127 | _set_dead() 128 | 129 | 130 | func _set_dead() -> void: 131 | $DealDamageArea/damage_collision.call_deferred( "set_disabled", true ) 132 | anim.set_anim( "dead" ) 133 | 134 | func _player_in_sight() -> bool: 135 | var playerpos : Vector2 = game.player.position + Vector2( 0, -8 ) 136 | var dist : Vector2 = playerpos - ( position + Vector2( 0, -4 ) ) 137 | if sign( dist.x ) != sign( rot.scale.x ): 138 | return false 139 | if _params.path_rect.has_point( playerpos): 140 | return true 141 | return false 142 | 143 | -------------------------------------------------------------------------------- /game/entities/trooper/trooper.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=14 format=3 uid="uid://bty1fxmcyhuj0"] 2 | 3 | [ext_resource type="Script" path="res://entities/trooper/trooper.gd" id="1_ovlxo"] 4 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="2_dffjv"] 5 | [ext_resource type="Script" path="res://general_purpose/utils/anim.gd" id="3_r8s7k"] 6 | [ext_resource type="Script" path="res://general_purpose/damage/deal_damage_area.gd" id="4_50xjg"] 7 | [ext_resource type="Script" path="res://general_purpose/damage/rcv_damage_area.gd" id="5_736y7"] 8 | 9 | [sub_resource type="Animation" id="Animation_ysbpd"] 10 | length = 0.001 11 | tracks/0/type = "value" 12 | tracks/0/imported = false 13 | tracks/0/enabled = true 14 | tracks/0/path = NodePath("rotate/trooper:region_rect") 15 | tracks/0/interp = 1 16 | tracks/0/loop_wrap = true 17 | tracks/0/keys = { 18 | "times": PackedFloat32Array(0), 19 | "transitions": PackedFloat32Array(1), 20 | "update": 0, 21 | "values": [Rect2(208, 0, 16, 16)] 22 | } 23 | tracks/1/type = "value" 24 | tracks/1/imported = false 25 | tracks/1/enabled = true 26 | tracks/1/path = NodePath("rotate/trooper:scale") 27 | tracks/1/interp = 1 28 | tracks/1/loop_wrap = true 29 | tracks/1/keys = { 30 | "times": PackedFloat32Array(0), 31 | "transitions": PackedFloat32Array(1), 32 | "update": 0, 33 | "values": [Vector2(1, 1)] 34 | } 35 | 36 | [sub_resource type="Animation" id="Animation_ijj0r"] 37 | resource_name = "chase" 38 | length = 0.2 39 | loop_mode = 1 40 | tracks/0/type = "value" 41 | tracks/0/imported = false 42 | tracks/0/enabled = true 43 | tracks/0/path = NodePath("rotate/trooper:region_rect") 44 | tracks/0/interp = 1 45 | tracks/0/loop_wrap = true 46 | tracks/0/keys = { 47 | "times": PackedFloat32Array(0, 0.1), 48 | "transitions": PackedFloat32Array(1, 1), 49 | "update": 1, 50 | "values": [Rect2(224, 0, 16, 16), Rect2(208, 0, 16, 16)] 51 | } 52 | tracks/1/type = "value" 53 | tracks/1/imported = false 54 | tracks/1/enabled = true 55 | tracks/1/path = NodePath("rotate/trooper:scale") 56 | tracks/1/interp = 1 57 | tracks/1/loop_wrap = true 58 | tracks/1/keys = { 59 | "times": PackedFloat32Array(0), 60 | "transitions": PackedFloat32Array(1), 61 | "update": 0, 62 | "values": [Vector2(1, 1)] 63 | } 64 | 65 | [sub_resource type="Animation" id="Animation_vsccc"] 66 | resource_name = "dead" 67 | length = 0.4 68 | tracks/0/type = "value" 69 | tracks/0/imported = false 70 | tracks/0/enabled = true 71 | tracks/0/path = NodePath("rotate/trooper:region_rect") 72 | tracks/0/interp = 1 73 | tracks/0/loop_wrap = true 74 | tracks/0/keys = { 75 | "times": PackedFloat32Array(0), 76 | "transitions": PackedFloat32Array(1), 77 | "update": 0, 78 | "values": [Rect2(240, 0, 16, 16)] 79 | } 80 | tracks/1/type = "value" 81 | tracks/1/imported = false 82 | tracks/1/enabled = true 83 | tracks/1/path = NodePath("rotate/trooper:scale") 84 | tracks/1/interp = 1 85 | tracks/1/loop_wrap = true 86 | tracks/1/keys = { 87 | "times": PackedFloat32Array(0, 0.1), 88 | "transitions": PackedFloat32Array(1, 1), 89 | "update": 0, 90 | "values": [Vector2(1.2, 0.8), Vector2(1, 1)] 91 | } 92 | 93 | [sub_resource type="Animation" id="Animation_6vdvg"] 94 | resource_name = "patrol" 95 | length = 0.4 96 | loop_mode = 1 97 | tracks/0/type = "value" 98 | tracks/0/imported = false 99 | tracks/0/enabled = true 100 | tracks/0/path = NodePath("rotate/trooper:region_rect") 101 | tracks/0/interp = 1 102 | tracks/0/loop_wrap = true 103 | tracks/0/keys = { 104 | "times": PackedFloat32Array(0, 0.2), 105 | "transitions": PackedFloat32Array(1, 1), 106 | "update": 1, 107 | "values": [Rect2(224, 0, 16, 16), Rect2(208, 0, 16, 16)] 108 | } 109 | tracks/1/type = "value" 110 | tracks/1/imported = false 111 | tracks/1/enabled = true 112 | tracks/1/path = NodePath("rotate/trooper:scale") 113 | tracks/1/interp = 1 114 | tracks/1/loop_wrap = true 115 | tracks/1/keys = { 116 | "times": PackedFloat32Array(0), 117 | "transitions": PackedFloat32Array(1), 118 | "update": 0, 119 | "values": [Vector2(1, 1)] 120 | } 121 | 122 | [sub_resource type="Animation" id="Animation_2gfko"] 123 | resource_name = "sight" 124 | length = 0.2 125 | tracks/0/type = "value" 126 | tracks/0/imported = false 127 | tracks/0/enabled = true 128 | tracks/0/path = NodePath("rotate/trooper:region_rect") 129 | tracks/0/interp = 1 130 | tracks/0/loop_wrap = true 131 | tracks/0/keys = { 132 | "times": PackedFloat32Array(0), 133 | "transitions": PackedFloat32Array(1), 134 | "update": 0, 135 | "values": [Rect2(208, 0, 16, 16)] 136 | } 137 | tracks/1/type = "value" 138 | tracks/1/imported = false 139 | tracks/1/enabled = true 140 | tracks/1/path = NodePath("rotate/trooper:scale") 141 | tracks/1/interp = 1 142 | tracks/1/loop_wrap = true 143 | tracks/1/keys = { 144 | "times": PackedFloat32Array(0, 0.2), 145 | "transitions": PackedFloat32Array(1, 1), 146 | "update": 0, 147 | "values": [Vector2(0.9, 1.3), Vector2(1, 1)] 148 | } 149 | 150 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_f5oka"] 151 | _data = { 152 | "RESET": SubResource("Animation_ysbpd"), 153 | "chase": SubResource("Animation_ijj0r"), 154 | "dead": SubResource("Animation_vsccc"), 155 | "patrol": SubResource("Animation_6vdvg"), 156 | "sight": SubResource("Animation_2gfko") 157 | } 158 | 159 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_6sckj"] 160 | size = Vector2(6, 8) 161 | 162 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_x0ynt"] 163 | size = Vector2(12, 4) 164 | 165 | [node name="trooper" type="Node2D"] 166 | z_index = -1 167 | script = ExtResource("1_ovlxo") 168 | 169 | [node name="rotate" type="Node2D" parent="."] 170 | metadata/_edit_lock_ = true 171 | 172 | [node name="trooper" type="Sprite2D" parent="rotate"] 173 | texture = ExtResource("2_dffjv") 174 | offset = Vector2(0, -7) 175 | region_enabled = true 176 | region_rect = Rect2(208, 0, 16, 16) 177 | metadata/_edit_lock_ = true 178 | 179 | [node name="anim" type="AnimationPlayer" parent="."] 180 | libraries = { 181 | "": SubResource("AnimationLibrary_f5oka") 182 | } 183 | script = ExtResource("3_r8s7k") 184 | 185 | [node name="DealDamageArea" type="Area2D" parent="."] 186 | visible = false 187 | collision_layer = 32 188 | collision_mask = 0 189 | script = ExtResource("4_50xjg") 190 | metadata/_edit_lock_ = true 191 | 192 | [node name="damage_collision" type="CollisionShape2D" parent="DealDamageArea"] 193 | position = Vector2(0, -4) 194 | shape = SubResource("RectangleShape2D_6sckj") 195 | metadata/_edit_lock_ = true 196 | 197 | [node name="RcvDamageArea" type="Area2D" parent="."] 198 | collision_layer = 0 199 | collision_mask = 1024 200 | script = ExtResource("5_736y7") 201 | 202 | [node name="rcv_damage_collision" type="CollisionShape2D" parent="RcvDamageArea"] 203 | position = Vector2(0, -8) 204 | shape = SubResource("RectangleShape2D_x0ynt") 205 | -------------------------------------------------------------------------------- /game/entities/vfx/puff.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://uim7ae5tsk83"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="1_b5rw4"] 4 | 5 | [sub_resource type="Animation" id="Animation_1o6si"] 6 | resource_name = "cycle" 7 | length = 0.5 8 | tracks/0/type = "value" 9 | tracks/0/imported = false 10 | tracks/0/enabled = true 11 | tracks/0/path = NodePath(".:region_rect") 12 | tracks/0/interp = 1 13 | tracks/0/loop_wrap = true 14 | tracks/0/keys = { 15 | "times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4), 16 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1), 17 | "update": 1, 18 | "values": [Rect2(0, 16, 16, 16), Rect2(16, 16, 16, 16), Rect2(32, 16, 16, 16), Rect2(48, 16, 16, 16), Rect2(64, 16, 16, 16)] 19 | } 20 | tracks/1/type = "method" 21 | tracks/1/imported = false 22 | tracks/1/enabled = true 23 | tracks/1/path = NodePath(".") 24 | tracks/1/interp = 1 25 | tracks/1/loop_wrap = true 26 | tracks/1/keys = { 27 | "times": PackedFloat32Array(0.5), 28 | "transitions": PackedFloat32Array(1), 29 | "values": [{ 30 | "args": [], 31 | "method": &"queue_free" 32 | }] 33 | } 34 | 35 | [sub_resource type="Animation" id="Animation_pun21"] 36 | length = 0.001 37 | tracks/0/type = "value" 38 | tracks/0/imported = false 39 | tracks/0/enabled = true 40 | tracks/0/path = NodePath(".:region_rect") 41 | tracks/0/interp = 1 42 | tracks/0/loop_wrap = true 43 | tracks/0/keys = { 44 | "times": PackedFloat32Array(0), 45 | "transitions": PackedFloat32Array(1), 46 | "update": 0, 47 | "values": [Rect2(0, 16, 16, 16)] 48 | } 49 | 50 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_mn8c4"] 51 | _data = { 52 | "RESET": SubResource("Animation_pun21"), 53 | "cycle": SubResource("Animation_1o6si") 54 | } 55 | 56 | [node name="puff" type="Sprite2D"] 57 | z_index = 10 58 | texture = ExtResource("1_b5rw4") 59 | region_enabled = true 60 | region_rect = Rect2(0, 16, 16, 16) 61 | 62 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 63 | libraries = { 64 | "": SubResource("AnimationLibrary_mn8c4") 65 | } 66 | autoplay = "cycle" 67 | speed_scale = 2.0 68 | -------------------------------------------------------------------------------- /game/entities/vfx/star_particles.gd: -------------------------------------------------------------------------------- 1 | extends GPUParticles2D 2 | 3 | @export var continuous : bool = false 4 | 5 | func _ready() -> void: 6 | var _ret : int = sigmgr.captured_coin.connect( _on_captured_coin ) 7 | 8 | func _on_captured_coin() -> void: 9 | if continuous: return 10 | emitting = true 11 | $timer.start() 12 | 13 | func _on_timer_timeout() -> void: 14 | emitting = false 15 | 16 | 17 | -------------------------------------------------------------------------------- /game/entities/vfx/star_particles.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://bovwniyfgotb3"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://4fg2tggofxf1" path="res://assets/star_particles.png" id="1_atv28"] 4 | [ext_resource type="Script" path="res://entities/vfx/star_particles.gd" id="2_m5oa6"] 5 | 6 | [sub_resource type="CanvasItemMaterial" id="CanvasItemMaterial_onlsp"] 7 | particles_animation = true 8 | particles_anim_h_frames = 5 9 | particles_anim_v_frames = 1 10 | particles_anim_loop = false 11 | 12 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_k63sc"] 13 | particle_flag_disable_z = true 14 | gravity = Vector3(0, -10, 0) 15 | anim_speed_min = 0.8 16 | anim_speed_max = 1.2 17 | 18 | [node name="star_particles" type="GPUParticles2D"] 19 | z_index = -1 20 | material = SubResource("CanvasItemMaterial_onlsp") 21 | emitting = false 22 | process_material = SubResource("ParticleProcessMaterial_k63sc") 23 | texture = ExtResource("1_atv28") 24 | randomness = 0.5 25 | script = ExtResource("2_m5oa6") 26 | 27 | [node name="timer" type="Timer" parent="."] 28 | wait_time = 0.7 29 | one_shot = true 30 | 31 | [connection signal="timeout" from="timer" to="." method="_on_timer_timeout" flags=3] 32 | -------------------------------------------------------------------------------- /game/entities/vfx/stars.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://cw5k4k138no8v"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="1_vemsy"] 4 | 5 | [sub_resource type="Animation" id="Animation_pun21"] 6 | length = 0.001 7 | tracks/0/type = "value" 8 | tracks/0/imported = false 9 | tracks/0/enabled = true 10 | tracks/0/path = NodePath(".:region_rect") 11 | tracks/0/interp = 1 12 | tracks/0/loop_wrap = true 13 | tracks/0/keys = { 14 | "times": PackedFloat32Array(0), 15 | "transitions": PackedFloat32Array(1), 16 | "update": 0, 17 | "values": [Rect2(176, 16, 16, 16)] 18 | } 19 | 20 | [sub_resource type="Animation" id="Animation_1o6si"] 21 | resource_name = "cycle" 22 | length = 0.4 23 | tracks/0/type = "value" 24 | tracks/0/imported = false 25 | tracks/0/enabled = true 26 | tracks/0/path = NodePath(".:region_rect") 27 | tracks/0/interp = 1 28 | tracks/0/loop_wrap = true 29 | tracks/0/keys = { 30 | "times": PackedFloat32Array(0, 0.1, 0.2, 0.3), 31 | "transitions": PackedFloat32Array(1, 1, 1, 1), 32 | "update": 1, 33 | "values": [Rect2(176, 16, 16, 16), Rect2(192, 16, 16, 16), Rect2(208, 16, 16, 16), Rect2(224, 16, 16, 16)] 34 | } 35 | tracks/1/type = "method" 36 | tracks/1/imported = false 37 | tracks/1/enabled = true 38 | tracks/1/path = NodePath(".") 39 | tracks/1/interp = 1 40 | tracks/1/loop_wrap = true 41 | tracks/1/keys = { 42 | "times": PackedFloat32Array(0.4), 43 | "transitions": PackedFloat32Array(1), 44 | "values": [{ 45 | "args": [], 46 | "method": &"queue_free" 47 | }] 48 | } 49 | 50 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_mn8c4"] 51 | _data = { 52 | "RESET": SubResource("Animation_pun21"), 53 | "cycle": SubResource("Animation_1o6si") 54 | } 55 | 56 | [node name="stars" type="Sprite2D"] 57 | z_index = 10 58 | texture = ExtResource("1_vemsy") 59 | region_enabled = true 60 | region_rect = Rect2(176, 16, 16, 16) 61 | 62 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 63 | libraries = { 64 | "": SubResource("AnimationLibrary_mn8c4") 65 | } 66 | autoplay = "cycle" 67 | speed_scale = 2.0 68 | -------------------------------------------------------------------------------- /game/general_purpose/damage/deal_damage_area.gd: -------------------------------------------------------------------------------- 1 | class_name DealDamageArea extends Area2D 2 | 3 | 4 | @export var damage_dealt : int = 1 5 | var control_node : Node = null 6 | 7 | func _ready() -> void: 8 | monitorable = true 9 | monitoring = false 10 | if not control_node: 11 | control_node = get_parent() 12 | 13 | func _on_dealing_damage( area : Area2D ) -> void: 14 | if control_node and control_node.has_method( "_on_dealing_damage" ): 15 | control_node._on_dealing_damage( area.control_node ) 16 | -------------------------------------------------------------------------------- /game/general_purpose/damage/rcv_damage_area.gd: -------------------------------------------------------------------------------- 1 | class_name RcvDamageArea extends Area2D 2 | 3 | 4 | @export var active : bool = true 5 | var control_node : Node = null 6 | 7 | func _ready() -> void: 8 | var _ret : int = connect( "area_entered", self._on_deal_damage_area_entered ) 9 | monitorable = false 10 | monitoring = true 11 | if not control_node: 12 | control_node = get_parent() 13 | 14 | 15 | func _on_deal_damage_area_entered( area : Area2D ) -> void: 16 | if not active: return 17 | if not area is DealDamageArea: 18 | printerr( name, ": Failed to interact with non DealDamageArea" ) 19 | return 20 | 21 | # alert parent immediately about damage 22 | if control_node and control_node.has_method( "_on_receiving_damage" ): 23 | control_node._on_receiving_damage( area.control_node, area.damage_dealt ) 24 | 25 | # report interaction to dealdamagearea 26 | area._on_dealing_damage( self ) 27 | 28 | -------------------------------------------------------------------------------- /game/general_purpose/fade_layer/fade_layer.gd: -------------------------------------------------------------------------------- 1 | extends CanvasLayer 2 | 3 | signal fade_complete 4 | 5 | @export_color_no_alpha var pixel_color := Color.BLACK 6 | @export_range( 0, 9 ) var transition_type : int = 0 7 | @export_range( 0, 2 ) var transition_time : float = 0.3 8 | 9 | var pixel : Sprite2D 10 | 11 | func _ready() -> void: 12 | _create_fade() 13 | 14 | func _create_fade() -> void: 15 | var img : Image = Image.create( 1, 1, false, Image.FORMAT_RGBA8 ) 16 | img.set_pixel( 0, 0, pixel_color ) 17 | pixel = Sprite2D.new() 18 | pixel.texture = ImageTexture.create_from_image( img ) 19 | pixel.scale = get_viewport().get_visible_rect().size 20 | pixel.centered = false 21 | pixel.material = preload( "fade_layer.material" ) 22 | #pixel.modulate = pixel_color 23 | add_child( pixel ) 24 | 25 | func fade_in( instant : bool = false ) -> void: 26 | pixel.material.set_shader_parameter( "type", transition_type ) 27 | if instant: 28 | pixel.material.set_shader_parameter( "r", 0.0 ) 29 | return 30 | var fade_tween : Tween = create_tween() 31 | var _ret : MethodTweener = fade_tween.tween_method( _set_fade, \ 32 | pixel.material.get_shader_parameter( "r" ), 0.0, transition_time ) 33 | var _xret : int = fade_tween.finished.connect( _on_fade_finished ) 34 | 35 | func fade_out( instant : bool = false ) -> void: 36 | pixel.material.set_shader_parameter( "type", transition_type ) 37 | if instant: 38 | pixel.material.set_shader_parameter( "r", 1.0 ) 39 | return 40 | var fade_tween : Tween = create_tween() 41 | var _ret : MethodTweener = fade_tween.tween_method( _set_fade, \ 42 | pixel.material.get_shader_parameter( "r" ), 1.0, transition_time ) 43 | var _xret : int = fade_tween.finished.connect( _on_fade_finished ) 44 | # 45 | func _on_fade_finished() -> void: 46 | fade_complete.emit() 47 | # 48 | func _set_fade( r : float ) -> void: 49 | pixel.material.set_shader_parameter( "r", r ) 50 | -------------------------------------------------------------------------------- /game/general_purpose/fade_layer/fade_layer.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | uniform int type = 9; 3 | uniform float r = 1.0; 4 | 5 | void fragment() 6 | { 7 | const vec3 color0 = vec3( 0.0, 0.0, 0.0 ); 8 | if( type == 0 ) 9 | { 10 | //COLOR = vec4( color0, step( UV.x, r ) ); 11 | COLOR.a = step( UV.x, r ); 12 | } 13 | else if( type == 1 ) 14 | { 15 | //COLOR = vec4( color0, step( UV.y, r ) ); 16 | COLOR.a = step( UV.y, r ); 17 | } 18 | else if( type == 2 ) 19 | { 20 | //COLOR = vec4( color0, step( 1.0 - r, abs( UV.x - 0.5 ) ) ); 21 | COLOR.a = step( 1.0 - r, abs( UV.x - 0.5 ) ); 22 | } 23 | else if( type == 3 ) 24 | { 25 | //COLOR = vec4( color0, step( 1.0 - r, abs( UV.y - 0.5 ) ) ); 26 | COLOR.a = step( 1.0 - r, abs( UV.y - 0.5 ) ); 27 | } 28 | else if( type == 4 ) 29 | { 30 | float d = length( UV * vec2( 1.77778, 1.0 ) - vec2( 0.88889, 0.5 ) ); 31 | //COLOR = vec4( color0, step( r, d ) ); 32 | COLOR.a = step( r, d ); 33 | } 34 | else if( type == 5 ) 35 | { 36 | //COLOR = vec4( color0, step( mod( UV.x * 20.0, 1.0 ) + UV.x, r * 2.0 ) ); 37 | COLOR.a = step( mod( UV.x * 20.0, 1.0 ) + UV.x, r * 2.0 ); 38 | } 39 | else if( type == 6 ) 40 | { 41 | //COLOR = vec4( color0, step( mod( ( UV.x + UV.y * 0.5625 ) * 10.0, 1.0 ) + UV.y + UV.x, r * 3.0 ) ); 42 | COLOR.a = step( mod( ( UV.x + UV.y * 0.5625 ) * 10.0, 1.0 ) + UV.y + UV.x, r * 3.0 ); 43 | } 44 | else if( type == 7 ) 45 | { 46 | vec2 uv = mod( UV * vec2( 1.77778, 1.0 ) * 10.0, 1.0 ); 47 | float d = length( uv - vec2( 0.5, 0.5 ) ); 48 | //COLOR = vec4( color0, step( 1.0 - r, d ) ); 49 | COLOR.a = step( 1.0 - r, d ); 50 | } 51 | else if( type == 8 ) 52 | { 53 | //COLOR = vec4( color0, step( 1.0 - r, abs( UV.x + abs( UV.y - 0.5 ) - 0.75 ) ) ); 54 | COLOR.a = step( 1.0 - r, abs( UV.x + abs( UV.y - 0.5 ) - 0.75 ) ); 55 | } 56 | else if( type == 9 ) 57 | { 58 | vec2 uv = UV - vec2( 0.5 ); 59 | float a = atan( uv.x / uv.y ); 60 | //COLOR = vec4( color0, step( 1.0 - r, mod( a * 3.1415, 1.0 ) ) ); 61 | COLOR.a = step( 1.0 - r, mod( a * 3.1415, 1.0 ) ); 62 | } 63 | } -------------------------------------------------------------------------------- /game/general_purpose/fade_layer/fade_layer.material: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/general_purpose/fade_layer/fade_layer.material -------------------------------------------------------------------------------- /game/general_purpose/fsm/fsm.gd: -------------------------------------------------------------------------------- 1 | class_name FSM extends RefCounted 2 | 3 | 4 | var debug : bool = false 5 | var states : Dictionary = {} 6 | var state_cur : FSM_State = null 7 | var state_nxt : FSM_State = null 8 | var state_lst : FSM_State = null 9 | var obj : CharacterBody2D = null 10 | var anim : FSM_Anim = null 11 | 12 | 13 | # warning-ignore:shadowed_variable 14 | func _init( _obj : CharacterBody2D, _states_parent_node : Node, \ 15 | _initial_state : FSM_State, _anim : FSM_Anim = null, _debug : bool = false ) -> void: 16 | obj = _obj 17 | debug = _debug 18 | anim = _anim 19 | _set_states_parent_node( _states_parent_node ) 20 | state_nxt = _initial_state 21 | 22 | func _set_states_parent_node( pnode : Node ) -> void: 23 | if debug: print( "Found ", pnode.get_child_count(), " states" ) 24 | if pnode.get_child_count() == 0: 25 | return 26 | var state_nodes : Array = pnode.get_children() 27 | for state_node : Node in state_nodes: 28 | if not state_node is FSM_State: continue 29 | if debug: print( "adding state: ", state_node.name ) 30 | states[ state_node.name ] = state_node 31 | state_node.fsm = self 32 | state_node.obj = self.obj 33 | state_node.anim = self.anim 34 | 35 | func run_machine( delta : float ) -> void: 36 | if state_nxt != state_cur: 37 | if state_cur != null: 38 | if debug: 39 | print( "(", obj.get_tree().get_frame(), ") ", obj.name, ": changing from state ", state_cur.name, " to ", state_nxt.name ) 40 | state_cur._terminate() 41 | elif debug: 42 | print( "(", obj.get_tree().get_frame(), ") ", obj.name, ": starting with state ", state_nxt.name ) 43 | state_lst = state_cur 44 | state_cur = state_nxt 45 | state_cur._initialize() 46 | # run state 47 | state_cur._run( delta ) 48 | 49 | func is_state( check : FSM_State ) -> bool: 50 | if state_cur == check or state_nxt == check: 51 | return true 52 | return false 53 | 54 | -------------------------------------------------------------------------------- /game/general_purpose/fsm/fsm_anim.gd: -------------------------------------------------------------------------------- 1 | class_name FSM_Anim extends AnimationPlayer 2 | 3 | 4 | var anim_nxt : String 5 | var anim_cur : String 6 | 7 | func update_anim() -> void: 8 | if anim_cur != anim_nxt: 9 | anim_cur = anim_nxt 10 | play( anim_cur ) 11 | 12 | 13 | -------------------------------------------------------------------------------- /game/general_purpose/fsm/fsm_state.gd: -------------------------------------------------------------------------------- 1 | class_name FSM_State extends Node 2 | 3 | 4 | # warning-ignore:unused_class_variable 5 | var fsm : FSM 6 | # warning-ignore:unused_class_variable 7 | var obj : CharacterBody2D 8 | ## warning-ignore:unused_class_variable 9 | var anim : FSM_Anim 10 | 11 | 12 | func is_current() -> bool: 13 | if not fsm: return false 14 | return fsm.is_state( self ) 15 | 16 | func _initialize() -> void: 17 | pass 18 | 19 | func _terminate() -> void: 20 | pass 21 | 22 | func _run( _delta : float ) -> void: 23 | pass 24 | -------------------------------------------------------------------------------- /game/general_purpose/fsm/stacked_fsm.gd: -------------------------------------------------------------------------------- 1 | extends RefCounted 2 | class_name StackedFSM 3 | 4 | var debug = false 5 | var states = {} 6 | var obj : CharacterBody2D = null 7 | 8 | var _stack : Array 9 | var _state_cur : StackedFSM_State = null 10 | var _state_nxt : StackedFSM_State = null 11 | var _state_lst : StackedFSM_State = null 12 | var anim : FSM_Anim 13 | 14 | func _init( _obj, _states_parent_node, _initial_state, _anim = null, _debug = false ): 15 | obj = _obj 16 | debug = _debug 17 | _set_states_parent_node( _states_parent_node, _anim ) 18 | _stack.push_back( _initial_state ) 19 | anim = _anim 20 | 21 | 22 | 23 | func _set_states_parent_node( pnode, _anim ) -> void: 24 | if debug: print( "Found ", pnode.get_child_count(), " states" ) 25 | if pnode.get_child_count() == 0: 26 | return 27 | var state_nodes = pnode.get_children() 28 | for state_node in state_nodes: 29 | if not state_node is StackedFSM_State: continue 30 | if debug: print( "adding state: ", state_node.name ) 31 | states[ state_node.name ] = state_node 32 | state_node.fsm = self 33 | state_node.obj = self.obj 34 | state_node.anim = _anim 35 | 36 | func push( state : StackedFSM_State ) -> void: 37 | _stack.push_back( state ) 38 | 39 | func pop() -> StackedFSM_State: 40 | return _stack.pop_back() 41 | 42 | func run_machine( delta ): 43 | _state_nxt = _stack[-1] 44 | if _state_nxt != _state_cur: 45 | if _state_cur != null: 46 | if debug: 47 | print( "(", obj.get_tree().get_frame(), ") ", obj.name, ": changing from state ", _state_cur.name, " to ", _state_nxt.name ) 48 | _state_cur._terminate() 49 | elif debug: 50 | print( "(", obj.get_tree().get_frame(), ") ", obj.name, ": starting with state ", _state_nxt.name ) 51 | _state_lst = _state_cur 52 | _state_cur = _state_nxt 53 | _state_cur._initialize() 54 | # run state 55 | _state_cur._run( delta ) 56 | 57 | 58 | func is_state( state ): 59 | return _state_cur == state or _state_nxt == state 60 | -------------------------------------------------------------------------------- /game/general_purpose/fsm/stacked_fsm_state.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | class_name StackedFSM_State 3 | 4 | # warning-ignore:unused_signal 5 | signal state_event( state, evt_name ) 6 | 7 | 8 | # warning-ignore:unused_class_variable 9 | var fsm : StackedFSM = null 10 | # warning-ignore:unused_class_variable 11 | var obj : CharacterBody2D 12 | # warning-ignore:unused_class_variable 13 | var anim : FSM_Anim 14 | 15 | 16 | func is_current() -> bool: 17 | if not fsm: return false 18 | return fsm.is_state( self ) 19 | 20 | func _initialize(): 21 | pass 22 | 23 | func _terminate(): 24 | pass 25 | 26 | func _run( _delta ): 27 | pass 28 | 29 | -------------------------------------------------------------------------------- /game/general_purpose/ldtk_minimap/generate_ldtk_minimap.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var map : LDtkMap 4 | var layer_name : String = "minimap_walls" 5 | var cell_size : int = 8 6 | 7 | func _ready() -> void: 8 | if map: 9 | call_deferred( "_generate_minimap" ) 10 | 11 | 12 | func _generate_minimap() -> void: 13 | var level_names = map.get_level_names() 14 | var levels : Dictionary = {} 15 | var maxpos : Vector2 = -100000 * Vector2.ONE 16 | var minpos : Vector2 = 100000 * Vector2.ONE 17 | for level_name in level_names: 18 | var pos = map.resource_path.find( ".ldtk" ) 19 | var filename = map.resource_path.substr( 0, pos ) 20 | filename += "/%s.ldtkl" % [ level_name ] 21 | var level : LDtkMap = load( filename ) as LDtkMap 22 | levels[level_name] = level 23 | var cur_minpos = level.world_data[level_name].level_rect.position 24 | var cur_maxpos = cur_minpos + level.world_data[level_name].level_rect.size 25 | if cur_minpos.x < minpos.x: minpos.x = cur_minpos.x 26 | if cur_minpos.y < minpos.y: minpos.y = cur_minpos.y 27 | if cur_maxpos.x > maxpos.x: maxpos.x = cur_maxpos.x 28 | if cur_maxpos.y > maxpos.y: maxpos.y = cur_maxpos.y 29 | var world_minpos : Vector2i = Vector2i( minpos ) 30 | var world_maxpos : Vector2i = Vector2i( maxpos ) 31 | var world_size : Vector2i = world_maxpos - world_minpos 32 | var world_cellsize : Vector2i = world_size / cell_size 33 | # create world image 34 | var img : Image 35 | img = Image.create( world_cellsize.x, world_cellsize.y, false, Image.FORMAT_RGBA8 ) 36 | 37 | 38 | for level_name in level_names: 39 | var level : LDtkMap = levels[level_name] 40 | var world_data = level.world_data[level_name] 41 | var cell_data = level.cell_data[level_name] 42 | var pos_ref : Vector2i = Vector2i( world_data.level_rect.position / cell_size ) 43 | pos_ref -= world_minpos / cell_size 44 | for c in cell_data[layer_name].tilemap_position: 45 | var pixel_pos : Vector2 = Vector2( pos_ref ) + Vector2( c ) 46 | pixel_pos = ( pixel_pos * 0.49 ).floor() 47 | #print( pixel_pos ) 48 | img.set_pixelv( pixel_pos, Color.WHITE ) 49 | #break 50 | 51 | var texture = ImageTexture.create_from_image( img ) 52 | $minimap_texture.texture = texture 53 | $minimap_texture.position = Vector2( world_minpos / cell_size ).floor() 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /game/general_purpose/ldtk_minimap/generate_ldtk_minimap.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=3 uid="uid://c6yusm010rajp"] 2 | 3 | [ext_resource type="Script" path="res://general_purpose/ldtk_minimap/generate_ldtk_minimap.gd" id="1_clv2e"] 4 | [ext_resource type="Resource" uid="uid://buho8muhsvty6" path="res://resources/map.ldtk" id="2_qvmgv"] 5 | 6 | [node name="generate_ldtk_minimap" type="Node2D"] 7 | script = ExtResource("1_clv2e") 8 | map = ExtResource("2_qvmgv") 9 | metadata/_edit_lock_ = true 10 | 11 | [node name="minimap_texture" type="Sprite2D" parent="."] 12 | centered = false 13 | metadata/_edit_lock_ = true 14 | -------------------------------------------------------------------------------- /game/general_purpose/menu/menu.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | class_name Menu 3 | 4 | signal selected_item( item ) 5 | # warning-ignore:unused_signal 6 | signal return_menu 7 | signal activated 8 | signal deactivated 9 | signal highlighted_item( itemno ) 10 | 11 | @export var is_active : bool = false : set = _set_active 12 | @export var vertical : bool = true 13 | @export var horizontal : bool = true 14 | @export var stop_after_select : bool = true 15 | @export var circulate : bool = true 16 | var items : Array = [] 17 | var cur_item : int = 0 18 | 19 | 20 | func _ready() -> void: 21 | for c in get_children(): 22 | if c is MenuItem: 23 | items.append( c ) 24 | c.set_highlight( false ) 25 | update_menu() 26 | set_physics_process( is_active ) 27 | 28 | func _set_active( v : bool ) -> void: 29 | is_active = v 30 | set_physics_process( is_active ) 31 | if is_active: 32 | emit_signal("activated") 33 | else: 34 | emit_signal("deactivated") 35 | 36 | func _physics_process( _delta : float ) -> void: 37 | var selection_changed := false 38 | 39 | if vertical: 40 | if Input.is_action_just_pressed( "btn_down" ): 41 | advance_cur_item(1) 42 | selection_changed = true 43 | if Input.is_action_just_pressed( "btn_up" ): 44 | advance_cur_item(-1) 45 | selection_changed = true 46 | if horizontal: 47 | if Input.is_action_just_pressed( "btn_right" ): 48 | advance_cur_item(1) 49 | selection_changed = true 50 | if Input.is_action_just_pressed( "btn_left" ): 51 | advance_cur_item(-1) 52 | selection_changed = true 53 | if selection_changed: 54 | update_menu() 55 | 56 | if Input.is_action_just_pressed( "btn_action" ): 57 | items[cur_item].call_deferred( "execute" ) 58 | if stop_after_select: 59 | is_active = false 60 | emit_signal( "selected_item", items[cur_item] ) 61 | 62 | 63 | 64 | func advance_cur_item( direction : int ) -> void: 65 | var found_valid_item : bool = false 66 | var candidate_item : int = cur_item 67 | while not found_valid_item: 68 | if circulate: 69 | candidate_item = ( candidate_item + direction ) % items.size() 70 | if candidate_item < 0: candidate_item = items.size() - 1 71 | else: 72 | candidate_item += direction 73 | if candidate_item >= items.size() - 1: candidate_item = items.size() - 1 74 | if candidate_item < 0: candidate_item = 0 75 | if items[candidate_item].selectable: 76 | found_valid_item = true 77 | cur_item = candidate_item 78 | emit_signal( "highlighted_item", cur_item ) 79 | 80 | func update_menu() -> void: 81 | items[cur_item].highlight = true 82 | for idx : int in range( items.size() ): 83 | if idx != cur_item: 84 | items[idx].highlight = false 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /game/general_purpose/menu/menu_item.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | class_name MenuItem 3 | 4 | # warning-ignore:unused_class_variable 5 | @export var selectable : bool = true : set = _set_selectable 6 | var highlight : = false : set = _set_highlight 7 | 8 | 9 | 10 | func _set_highlight( v ): 11 | highlight = v 12 | set_highlight( highlight ) 13 | if not selectable: _set_selectable( selectable ) 14 | 15 | func _set_selectable( v ): 16 | selectable = v 17 | set_selectable( selectable ) 18 | 19 | #------------------------------------- 20 | # methods to override 21 | #------------------------------------- 22 | @warning_ignore("unused_parameter") 23 | func set_highlight( v ): 24 | pass 25 | 26 | @warning_ignore("unused_parameter") 27 | func set_selectable( v ): 28 | pass 29 | 30 | func execute(): 31 | pass 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /game/general_purpose/sfx/random_pitch_stream_multiplayer.gd: -------------------------------------------------------------------------------- 1 | extends AudioStreamPlayer 2 | class_name RandomPitchStreamMultiPlayer 3 | 4 | @export var no_players : int = 3 5 | @export var range_extent : float = 0.2 6 | @export var stream_1 : AudioStream 7 | @export var stream_2: AudioStream 8 | @export var stream_3 : AudioStream 9 | 10 | var streams : Array 11 | 12 | var players = [] 13 | var cur_player = 0 14 | var cur_pitch = 1.0 15 | var base_pitch : float 16 | func _ready(): 17 | base_pitch = pitch_scale 18 | if stream_1: 19 | streams.append( stream_1 ) 20 | if stream_2: 21 | streams.append( stream_2 ) 22 | if stream_3: 23 | streams.append( stream_3 ) 24 | if not streams: 25 | streams.append( stream ) 26 | players.append( self ) 27 | for _n in range( no_players - 1 ): 28 | var a = AudioStreamPlayer.new() 29 | players.append( a ) 30 | add_child( a ) 31 | a.bus = self.bus 32 | a.volume_db = self.volume_db 33 | 34 | func get_player(): 35 | cur_player = ( cur_player + 1 ) % no_players 36 | return players[ cur_player ] 37 | 38 | func set_pitch( p ): 39 | cur_pitch = p 40 | 41 | @warning_ignore("native_method_override") 42 | func play( from_position : float = 0.0 ): 43 | var p = get_player() 44 | p.pitch_scale = base_pitch + randf_range( -range_extent, range_extent ) 45 | p.stream = streams[ randi() % streams.size() ] 46 | #print( "playing on: ", p, " with ", p.pitch_scale ) 47 | if p == self: 48 | super.play( from_position ) 49 | else: 50 | p.play( from_position ) 51 | 52 | @warning_ignore("native_method_override") 53 | func stop(): 54 | for p in players: 55 | if p != self: 56 | p.stop() 57 | super.stop() 58 | 59 | -------------------------------------------------------------------------------- /game/general_purpose/sfx/random_pitch_stream_player.gd: -------------------------------------------------------------------------------- 1 | extends AudioStreamPlayer 2 | class_name RandomPitchStreamPlayer 3 | 4 | @export var range_extent : float = 0.2 5 | @export var stream_1 : AudioStream 6 | @export var stream_2: AudioStream 7 | @export var stream_3 : AudioStream 8 | var streams : Array 9 | 10 | func _ready(): 11 | if stream_1: 12 | streams.append( stream_1 ) 13 | if stream_2: 14 | streams.append( stream_2 ) 15 | if stream_3: 16 | streams.append( stream_3 ) 17 | if not streams: 18 | streams.append( stream ) 19 | 20 | 21 | 22 | @warning_ignore("native_method_override") 23 | func play( from_position : float = 0.0 ): 24 | pitch_scale = 1 + randf_range( -range_extent, range_extent ) 25 | stream = streams[ randi() % streams.size() ] 26 | super.play( from_position ) 27 | 28 | -------------------------------------------------------------------------------- /game/general_purpose/shake_camera/base_shake_camera.gd: -------------------------------------------------------------------------------- 1 | class_name BaseShakeCamera extends Camera2D 2 | 3 | var parent : Node2D 4 | var target_position : Vector2 5 | var smooth_position : Vector2 6 | var _shake_noise : FastNoiseLite 7 | var _noise_coordinate : float = 0.0 8 | var _is_shaking : float 9 | var _shaking_magnitude : float 10 | 11 | var _camera_speed = 5.0 12 | 13 | func _ready() -> void: 14 | #top_level = true 15 | parent = get_parent() 16 | _reset_camera() 17 | _shake_noise = FastNoiseLite.new() 18 | _shake_noise.frequency = 20.0 19 | _shake_noise.fractal_type = FastNoiseLite.FRACTAL_FBM 20 | sigmgr.camera_shake.connect( shake ) 21 | 22 | 23 | func _reset_camera() -> void: 24 | target_position = parent.global_position 25 | smooth_position = target_position 26 | reset_smoothing() 27 | 28 | 29 | 30 | func _process_x( delta : float ) -> void: 31 | target_position = parent.global_position 32 | #target_position.x += parent._camera_offset 33 | #smooth_position = lerp( smooth_position, target_position, _camera_speed * delta ) 34 | global_position = target_position.round() 35 | 36 | if _is_shaking > 0: 37 | _is_shaking -= delta 38 | var _offset : Vector2 = Vector2.ZERO 39 | _offset.x = _shake_noise.get_noise_1d( _noise_coordinate ) 40 | _noise_coordinate += delta 41 | _offset.y = _shake_noise.get_noise_1d( _noise_coordinate ) 42 | _offset *= 2.0 43 | _noise_coordinate += delta 44 | offset = _offset * _shaking_magnitude 45 | if _is_shaking <= 0: 46 | offset *= 0 47 | 48 | func shake( duration : float, magnitude : float, frequency : float = 60.0 ) -> void: 49 | _is_shaking = duration 50 | _shaking_magnitude = magnitude 51 | _shake_noise.frequency = frequency 52 | -------------------------------------------------------------------------------- /game/general_purpose/shake_camera/shake_camera.gd: -------------------------------------------------------------------------------- 1 | class_name ShakeCamera extends Camera2D 2 | 3 | var parent : Node2D 4 | var target_position : Vector2 5 | var smooth_position : Vector2 6 | var _shake_noise : FastNoiseLite 7 | var _noise_coordinate : float = 0.0 8 | var _is_shaking : float 9 | var _shaking_magnitude : float 10 | 11 | var _camera_speed : float = 5.0 12 | 13 | func _ready() -> void: 14 | top_level = true 15 | parent = get_parent() 16 | _reset_camera() 17 | _shake_noise = FastNoiseLite.new() 18 | _shake_noise.frequency = 20.0 19 | _shake_noise.fractal_type = FastNoiseLite.FRACTAL_FBM 20 | var _ret : int = sigmgr.camera_shake.connect( shake ) 21 | 22 | 23 | func _reset_camera() -> void: 24 | target_position = parent.global_position 25 | smooth_position = target_position 26 | reset_smoothing() 27 | 28 | 29 | 30 | func _process( delta : float ) -> void: 31 | target_position = parent.global_position 32 | #target_position.x += parent._camera_offset 33 | smooth_position = lerp( smooth_position, target_position, _camera_speed * delta ) 34 | global_position = smooth_position.round() 35 | 36 | if _is_shaking > 0: 37 | _is_shaking -= delta 38 | var _offset : Vector2 = Vector2.ZERO 39 | _offset.x = _shake_noise.get_noise_1d( _noise_coordinate ) 40 | _noise_coordinate += delta 41 | _offset.y = _shake_noise.get_noise_1d( _noise_coordinate ) 42 | _offset *= 2.0 43 | _noise_coordinate += delta 44 | offset = _offset * _shaking_magnitude 45 | if _is_shaking <= 0: 46 | offset *= 0 47 | 48 | func shake( duration : float, magnitude : float, frequency : float = 60.0 ) -> void: 49 | _is_shaking = duration 50 | _shaking_magnitude = magnitude 51 | _shake_noise.frequency = frequency 52 | -------------------------------------------------------------------------------- /game/general_purpose/utils/anim.gd: -------------------------------------------------------------------------------- 1 | extends AnimationPlayer 2 | 3 | var anim_cur : String 4 | 5 | func set_anim( anim_nxt : String ) -> void: 6 | if anim_cur != anim_nxt: 7 | anim_cur = anim_nxt 8 | play( anim_cur ) 9 | -------------------------------------------------------------------------------- /game/general_purpose/utils/utils.gd: -------------------------------------------------------------------------------- 1 | class_name Utils extends RefCounted 2 | 3 | 4 | static func on_screen( obj : Node2D, margin : float = 16.0 ) -> bool: 5 | var screen_rect : Rect2 = obj.get_viewport_rect() 6 | screen_rect.position -= Vector2.ONE * margin 7 | screen_rect.size += Vector2.ONE * margin * 2.0 8 | var screen_pos : Vector2 = screen_position( obj ) 9 | return screen_rect.has_point( screen_pos ) 10 | 11 | static func screen_position( obj : Node2D ) -> Vector2: 12 | return obj.get_global_transform_with_canvas().origin 13 | 14 | static func get_unique_id( obj : Node ) -> String: 15 | var id : String = obj.owner.filename + "_" + obj.owner.get_path_to( obj ) 16 | return id 17 | 18 | static func load_encrypted_file( filename : String ) -> Dictionary: 19 | if FileAccess.file_exists( filename ): 20 | var faccess : FileAccess = FileAccess.open_encrypted_with_pass( \ 21 | filename, FileAccess.READ, OS.get_unique_id() ) 22 | if not faccess: 23 | return {} 24 | var data : Dictionary = faccess.get_var( true ) 25 | faccess.close() 26 | return data 27 | else: 28 | return {} 29 | 30 | static func save_encrypted_file( filename : String, data : Dictionary ) -> bool: 31 | var faccess := FileAccess.open_encrypted_with_pass( \ 32 | filename, FileAccess.WRITE, OS.get_unique_id() ) 33 | if faccess: 34 | faccess.store_var( data, true ) 35 | faccess.close() 36 | return true 37 | else: 38 | faccess.close() 39 | return false 40 | 41 | #static func target_in_sight( sourcenode : Node2D, sourcepos : Vector2, targetpos : Vector2, \ 42 | #max_dist : float, view_direction : float, view_angle : float, exclude : Array, colmask : int ) -> bool: 43 | #var dist = targetpos - sourcepos 44 | ## check maximum distance 45 | #if max_dist > 0 and dist.length() > max_dist: 46 | #return false 47 | ## check if direction dependent view in the horizontal axis 48 | #if abs( dist.x ) > 0.0 and abs( view_direction) > 0.0: 49 | #if sign( dist.x ) != sign( view_direction ): 50 | #return false 51 | ## if there is a view direction, then also checks the view angle 52 | #if abs( dist.angle_to( Vector2.RIGHT * sign( dist.x ) ) ) > view_angle: 53 | #return false 54 | ## check the view angle 55 | #var space_state = sourcenode.get_world_2d().direct_space_state 56 | #var result = space_state.intersect_ray( \ 57 | #sourcepos, targetpos, exclude, colmask ) 58 | #if result.empty(): return true 59 | ## print( "obstacle: ", result.collider.name ) 60 | #return false 61 | -------------------------------------------------------------------------------- /game/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /game/icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://hxckwhbou6g" 6 | path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://icon.svg" 14 | dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | svg/scale=1.0 36 | editor/scale_with_editor_scale=false 37 | editor/convert_colors_with_editor_theme=false 38 | -------------------------------------------------------------------------------- /game/project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Game In A Week" 14 | run/main_scene="res://base/main/main.tscn" 15 | config/features=PackedStringArray("4.2", "GL Compatibility") 16 | config/icon="res://icon.svg" 17 | 18 | [autoload] 19 | 20 | sigmgr="*res://singletons/sigmgr.gd" 21 | game="*res://singletons/game.gd" 22 | 23 | [debug] 24 | 25 | gdscript/warnings/untyped_declaration=1 26 | gdscript/warnings/return_value_discarded=1 27 | 28 | [display] 29 | 30 | window/size/viewport_width=480 31 | window/size/viewport_height=240 32 | window/size/window_width_override=960 33 | window/size/window_height_override=480 34 | window/stretch/mode="viewport" 35 | 36 | [editor_plugins] 37 | 38 | enabled=PackedStringArray("res://addons/ldtk2godot4/plugin.cfg") 39 | 40 | [input] 41 | 42 | btn_left={ 43 | "deadzone": 0.5, 44 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":65,"key_label":0,"unicode":97,"echo":false,"script":null) 45 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194319,"key_label":0,"unicode":0,"echo":false,"script":null) 46 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":13,"pressure":0.0,"pressed":true,"script":null) 47 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":-1.0,"script":null) 48 | ] 49 | } 50 | btn_right={ 51 | "deadzone": 0.5, 52 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":68,"key_label":0,"unicode":100,"echo":false,"script":null) 53 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194321,"key_label":0,"unicode":0,"echo":false,"script":null) 54 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":14,"pressure":0.0,"pressed":true,"script":null) 55 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":0,"axis_value":1.0,"script":null) 56 | ] 57 | } 58 | btn_up={ 59 | "deadzone": 0.5, 60 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"echo":false,"script":null) 61 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194320,"key_label":0,"unicode":0,"echo":false,"script":null) 62 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":11,"pressure":0.0,"pressed":true,"script":null) 63 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":-1.0,"script":null) 64 | ] 65 | } 66 | btn_down={ 67 | "deadzone": 0.5, 68 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"echo":false,"script":null) 69 | , Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194322,"key_label":0,"unicode":0,"echo":false,"script":null) 70 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":12,"pressure":0.0,"pressed":true,"script":null) 71 | , Object(InputEventJoypadMotion,"resource_local_to_scene":false,"resource_name":"","device":-1,"axis":1,"axis_value":1.0,"script":null) 72 | ] 73 | } 74 | btn_jump={ 75 | "deadzone": 0.5, 76 | "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":32,"key_label":0,"unicode":32,"echo":false,"script":null) 77 | , Object(InputEventJoypadButton,"resource_local_to_scene":false,"resource_name":"","device":-1,"button_index":0,"pressure":0.0,"pressed":true,"script":null) 78 | ] 79 | } 80 | 81 | [layer_names] 82 | 83 | 2d_physics/layer_1="walls" 84 | 2d_physics/layer_5="player" 85 | 2d_physics/layer_6="player_hitbox" 86 | 2d_physics/layer_7="player_dead" 87 | 2d_physics/layer_8="player_hazards" 88 | 2d_physics/layer_10="enemy" 89 | 2d_physics/layer_11="enemy_hitbox" 90 | 2d_physics/layer_12="enemy_dead" 91 | 92 | [rendering] 93 | 94 | textures/canvas_textures/default_texture_filter=0 95 | renderer/rendering_method="gl_compatibility" 96 | renderer/rendering_method.mobile="gl_compatibility" 97 | -------------------------------------------------------------------------------- /game/resources/decorations_tileset.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="TileSet" load_steps=3 format=3 uid="uid://sac6nlwkbmi4"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://cmxgl1shdb5uq" path="res://assets/tileset.png" id="1_ngro6"] 4 | 5 | [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_7dvf7"] 6 | texture = ExtResource("1_ngro6") 7 | 0:0/0 = 0 8 | 1:0/0 = 0 9 | 2:0/0 = 0 10 | 3:0/0 = 0 11 | 4:0/0 = 0 12 | 5:0/0 = 0 13 | 6:0/0 = 0 14 | 7:0/0 = 0 15 | 8:0/0 = 0 16 | 9:0/0 = 0 17 | 10:0/0 = 0 18 | 11:0/0 = 0 19 | 12:0/0 = 0 20 | 13:0/0 = 0 21 | 14:0/0 = 0 22 | 15:0/0 = 0 23 | 14:1/0 = 0 24 | 13:1/0 = 0 25 | 12:1/0 = 0 26 | 11:1/0 = 0 27 | 10:1/0 = 0 28 | 9:1/0 = 0 29 | 8:1/0 = 0 30 | 7:1/0 = 0 31 | 6:1/0 = 0 32 | 5:1/0 = 0 33 | 4:1/0 = 0 34 | 3:1/0 = 0 35 | 2:1/0 = 0 36 | 1:1/0 = 0 37 | 0:1/0 = 0 38 | 15:1/0 = 0 39 | 15:2/0 = 0 40 | 14:2/0 = 0 41 | 13:2/0 = 0 42 | 12:2/0 = 0 43 | 11:2/0 = 0 44 | 10:2/0 = 0 45 | 9:2/0 = 0 46 | 8:2/0 = 0 47 | 7:2/0 = 0 48 | 6:2/0 = 0 49 | 5:2/0 = 0 50 | 4:2/0 = 0 51 | 3:2/0 = 0 52 | 2:2/0 = 0 53 | 1:2/0 = 0 54 | 0:2/0 = 0 55 | 0:3/0 = 0 56 | 1:3/0 = 0 57 | 2:3/0 = 0 58 | 3:3/0 = 0 59 | 4:3/0 = 0 60 | 5:3/0 = 0 61 | 6:3/0 = 0 62 | 7:3/0 = 0 63 | 8:3/0 = 0 64 | 9:3/0 = 0 65 | 10:3/0 = 0 66 | 11:3/0 = 0 67 | 12:3/0 = 0 68 | 13:3/0 = 0 69 | 14:3/0 = 0 70 | 15:3/0 = 0 71 | 15:4/0 = 0 72 | 14:4/0 = 0 73 | 13:4/0 = 0 74 | 12:4/0 = 0 75 | 11:4/0 = 0 76 | 10:4/0 = 0 77 | 9:4/0 = 0 78 | 8:4/0 = 0 79 | 7:4/0 = 0 80 | 6:4/0 = 0 81 | 5:4/0 = 0 82 | 4:4/0 = 0 83 | 3:4/0 = 0 84 | 2:4/0 = 0 85 | 1:4/0 = 0 86 | 0:4/0 = 0 87 | 0:5/0 = 0 88 | 1:5/0 = 0 89 | 2:5/0 = 0 90 | 3:5/0 = 0 91 | 4:5/0 = 0 92 | 5:5/0 = 0 93 | 6:5/0 = 0 94 | 7:5/0 = 0 95 | 8:5/0 = 0 96 | 9:5/0 = 0 97 | 10:5/0 = 0 98 | 11:5/0 = 0 99 | 12:5/0 = 0 100 | 13:5/0 = 0 101 | 14:5/0 = 0 102 | 15:5/0 = 0 103 | 15:6/0 = 0 104 | 14:6/0 = 0 105 | 13:6/0 = 0 106 | 12:6/0 = 0 107 | 11:6/0 = 0 108 | 10:6/0 = 0 109 | 9:6/0 = 0 110 | 8:6/0 = 0 111 | 7:6/0 = 0 112 | 6:6/0 = 0 113 | 5:6/0 = 0 114 | 4:6/0 = 0 115 | 3:6/0 = 0 116 | 2:6/0 = 0 117 | 1:6/0 = 0 118 | 0:6/0 = 0 119 | 0:7/0 = 0 120 | 1:7/0 = 0 121 | 2:7/0 = 0 122 | 3:7/0 = 0 123 | 4:7/0 = 0 124 | 5:7/0 = 0 125 | 6:7/0 = 0 126 | 7:7/0 = 0 127 | 8:7/0 = 0 128 | 9:7/0 = 0 129 | 10:7/0 = 0 130 | 11:7/0 = 0 131 | 12:7/0 = 0 132 | 13:7/0 = 0 133 | 14:7/0 = 0 134 | 15:7/0 = 0 135 | 15:8/0 = 0 136 | 14:8/0 = 0 137 | 13:8/0 = 0 138 | 12:8/0 = 0 139 | 11:8/0 = 0 140 | 10:8/0 = 0 141 | 9:8/0 = 0 142 | 8:8/0 = 0 143 | 7:8/0 = 0 144 | 6:8/0 = 0 145 | 5:8/0 = 0 146 | 4:8/0 = 0 147 | 3:8/0 = 0 148 | 2:8/0 = 0 149 | 1:8/0 = 0 150 | 0:8/0 = 0 151 | 0:9/0 = 0 152 | 1:9/0 = 0 153 | 2:9/0 = 0 154 | 3:9/0 = 0 155 | 4:9/0 = 0 156 | 5:9/0 = 0 157 | 6:9/0 = 0 158 | 7:9/0 = 0 159 | 8:9/0 = 0 160 | 9:9/0 = 0 161 | 10:9/0 = 0 162 | 11:9/0 = 0 163 | 12:9/0 = 0 164 | 13:9/0 = 0 165 | 14:9/0 = 0 166 | 15:9/0 = 0 167 | 15:10/0 = 0 168 | 14:10/0 = 0 169 | 13:10/0 = 0 170 | 12:10/0 = 0 171 | 11:10/0 = 0 172 | 10:10/0 = 0 173 | 9:10/0 = 0 174 | 8:10/0 = 0 175 | 7:10/0 = 0 176 | 6:10/0 = 0 177 | 5:10/0 = 0 178 | 4:10/0 = 0 179 | 3:10/0 = 0 180 | 2:10/0 = 0 181 | 1:10/0 = 0 182 | 0:10/0 = 0 183 | 0:11/0 = 0 184 | 1:11/0 = 0 185 | 2:11/0 = 0 186 | 3:11/0 = 0 187 | 4:11/0 = 0 188 | 5:11/0 = 0 189 | 6:11/0 = 0 190 | 7:11/0 = 0 191 | 8:11/0 = 0 192 | 9:11/0 = 0 193 | 10:11/0 = 0 194 | 11:11/0 = 0 195 | 12:11/0 = 0 196 | 13:11/0 = 0 197 | 14:11/0 = 0 198 | 15:11/0 = 0 199 | 15:12/0 = 0 200 | 14:12/0 = 0 201 | 13:12/0 = 0 202 | 12:12/0 = 0 203 | 11:12/0 = 0 204 | 10:12/0 = 0 205 | 9:12/0 = 0 206 | 8:12/0 = 0 207 | 7:12/0 = 0 208 | 6:12/0 = 0 209 | 5:12/0 = 0 210 | 4:12/0 = 0 211 | 3:12/0 = 0 212 | 2:12/0 = 0 213 | 1:12/0 = 0 214 | 0:12/0 = 0 215 | 0:13/0 = 0 216 | 1:13/0 = 0 217 | 2:13/0 = 0 218 | 3:13/0 = 0 219 | 4:13/0 = 0 220 | 5:13/0 = 0 221 | 6:13/0 = 0 222 | 7:13/0 = 0 223 | 8:13/0 = 0 224 | 15:13/0 = 0 225 | 14:13/0 = 0 226 | 13:13/0 = 0 227 | 12:13/0 = 0 228 | 11:13/0 = 0 229 | 10:13/0 = 0 230 | 9:13/0 = 0 231 | 8:14/0 = 0 232 | 7:14/0 = 0 233 | 6:14/0 = 0 234 | 5:14/0 = 0 235 | 4:14/0 = 0 236 | 3:14/0 = 0 237 | 2:14/0 = 0 238 | 1:14/0 = 0 239 | 1:15/0 = 0 240 | 0:15/0 = 0 241 | 0:14/0 = 0 242 | 2:15/0 = 0 243 | 3:15/0 = 0 244 | 4:15/0 = 0 245 | 5:15/0 = 0 246 | 6:15/0 = 0 247 | 7:15/0 = 0 248 | 8:15/0 = 0 249 | 9:15/0 = 0 250 | 10:15/0 = 0 251 | 11:15/0 = 0 252 | 12:15/0 = 0 253 | 13:15/0 = 0 254 | 14:15/0 = 0 255 | 15:15/0 = 0 256 | 15:14/0 = 0 257 | 14:14/0 = 0 258 | 13:14/0 = 0 259 | 12:14/0 = 0 260 | 11:14/0 = 0 261 | 10:14/0 = 0 262 | 9:14/0 = 0 263 | 264 | [resource] 265 | sources/0 = SubResource("TileSetAtlasSource_7dvf7") 266 | -------------------------------------------------------------------------------- /game/resources/map.ldtk.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="LDtk2Godot4" 4 | type="Resource" 5 | uid="uid://c23adbbwev2pw" 6 | path="res://.godot/imported/map.ldtk-56edfbcaae56ea440e53e4d514ad4bbd.res" 7 | 8 | [deps] 9 | 10 | source_file="res://resources/map.ldtk" 11 | dest_files=["res://.godot/imported/map.ldtk-56edfbcaae56ea440e53e4d514ad4bbd.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /game/resources/map/level_0.ldtkl.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="LDtk2Godot4" 4 | type="Resource" 5 | uid="uid://kbd17kdty62u" 6 | path="res://.godot/imported/level_0.ldtkl-0124c5bb559be442b1ec677debb3d405.res" 7 | 8 | [deps] 9 | 10 | source_file="res://resources/map/level_0.ldtkl" 11 | dest_files=["res://.godot/imported/level_0.ldtkl-0124c5bb559be442b1ec677debb3d405.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /game/resources/map/level_1.ldtkl.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="LDtk2Godot4" 4 | type="Resource" 5 | uid="uid://ctnw80giqi61" 6 | path="res://.godot/imported/level_1.ldtkl-b0de1af8d11cc59f4bb7788ca0f0ebb0.res" 7 | 8 | [deps] 9 | 10 | source_file="res://resources/map/level_1.ldtkl" 11 | dest_files=["res://.godot/imported/level_1.ldtkl-b0de1af8d11cc59f4bb7788ca0f0ebb0.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /game/resources/map/level_2.ldtkl.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="LDtk2Godot4" 4 | type="Resource" 5 | uid="uid://dj8l5wt5egv3r" 6 | path="res://.godot/imported/level_2.ldtkl-125aafb927fbcf68e9b2396f73e2cfaa.res" 7 | 8 | [deps] 9 | 10 | source_file="res://resources/map/level_2.ldtkl" 11 | dest_files=["res://.godot/imported/level_2.ldtkl-125aafb927fbcf68e9b2396f73e2cfaa.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /game/resources/map/level_3.ldtkl.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="LDtk2Godot4" 4 | type="Resource" 5 | uid="uid://b7gr3mol60y3w" 6 | path="res://.godot/imported/level_3.ldtkl-ee4f359ea7d9fc3d90c5d1468130fba0.res" 7 | 8 | [deps] 9 | 10 | source_file="res://resources/map/level_3.ldtkl" 11 | dest_files=["res://.godot/imported/level_3.ldtkl-ee4f359ea7d9fc3d90c5d1468130fba0.res"] 12 | 13 | [params] 14 | 15 | -------------------------------------------------------------------------------- /game/resources/map/png/level_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/resources/map/png/level_0.png -------------------------------------------------------------------------------- /game/resources/map/png/level_0.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dlj1xr6o336qy" 6 | path="res://.godot/imported/level_0.png-f180789eeb6f12282e15668e88b1d07a.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://resources/map/png/level_0.png" 14 | dest_files=["res://.godot/imported/level_0.png-f180789eeb6f12282e15668e88b1d07a.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/resources/map/png/level_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/resources/map/png/level_1.png -------------------------------------------------------------------------------- /game/resources/map/png/level_1.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://qvcu4p3pxii4" 6 | path="res://.godot/imported/level_1.png-2be816c68d50bab48a5ada8113b23094.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://resources/map/png/level_1.png" 14 | dest_files=["res://.godot/imported/level_1.png-2be816c68d50bab48a5ada8113b23094.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/resources/map/png/level_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/resources/map/png/level_2.png -------------------------------------------------------------------------------- /game/resources/map/png/level_2.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ca2ysm6aom1xx" 6 | path="res://.godot/imported/level_2.png-10da1c3463ccbb09269c3e10369611d5.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://resources/map/png/level_2.png" 14 | dest_files=["res://.godot/imported/level_2.png-10da1c3463ccbb09269c3e10369611d5.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/resources/map/png/level_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/securas/LDtk_With_Godot4_Example/b2951bfb36d0c88c801e42c6b8b312fb78f793a9/game/resources/map/png/level_3.png -------------------------------------------------------------------------------- /game/resources/map/png/level_3.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bkxw4plisu5nv" 6 | path="res://.godot/imported/level_3.png-80c05ceaad73919e8b0f8ee90c03e702.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://resources/map/png/level_3.png" 14 | dest_files=["res://.godot/imported/level_3.png-80c05ceaad73919e8b0f8ee90c03e702.ctex"] 15 | 16 | [params] 17 | 18 | compress/mode=0 19 | compress/high_quality=false 20 | compress/lossy_quality=0.7 21 | compress/hdr_compression=1 22 | compress/normal_map=0 23 | compress/channel_pack=0 24 | mipmaps/generate=false 25 | mipmaps/limit=-1 26 | roughness/mode=0 27 | roughness/src_normal="" 28 | process/fix_alpha_border=true 29 | process/premult_alpha=false 30 | process/normal_map_invert_y=false 31 | process/hdr_as_srgb=false 32 | process/hdr_clamp_exposure=false 33 | process/size_limit=0 34 | detect_3d/compress_to=1 35 | -------------------------------------------------------------------------------- /game/singletons/game.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | const SAVEFILENAME = "user://gamesave.dat" 4 | 5 | var debug := false 6 | @warning_ignore("untyped_declaration") 7 | var player : set = _set_player, get = _get_player 8 | var state : Dictionary : set = _set_state, get = _get_state 9 | var _state : Dictionary 10 | 11 | func _set_player( v : Player ) -> void: 12 | player = weakref( v ) 13 | 14 | func _get_player() -> Player: 15 | if not player: return null 16 | if player.get_ref(): 17 | return player.get_ref() 18 | else: 19 | return null 20 | 21 | func _set_state( v : Dictionary ) -> void: 22 | state = v 23 | _state = v 24 | sigmgr.gamestate_changed.emit() 25 | 26 | func _get_state() -> Dictionary: 27 | return _state 28 | 29 | func _ready() -> void: 30 | _initialize_state() 31 | 32 | 33 | func _initialize_state() -> void: 34 | _state = { 35 | events = [], 36 | worldpos = Vector2i( 100, 14 ), 37 | coins = 0, 38 | energy = 1, 39 | max_energy = 1, 40 | } 41 | if debug: 42 | _initialize_debug_state() 43 | 44 | func _initialize_debug_state() -> void: 45 | pass 46 | 47 | func reset_state() -> void: 48 | _state.energy = _state.max_energy 49 | sigmgr.gamestate_changed.emit() 50 | 51 | 52 | func save_gamestate() -> void: 53 | var _res : bool = Utils.save_encrypted_file( SAVEFILENAME, state ) 54 | 55 | func load_gamestate() -> bool: 56 | var new_state : Dictionary = Utils.load_encrypted_file( SAVEFILENAME ) 57 | if new_state: 58 | state = new_state.duplicate( true ) 59 | return true 60 | return false 61 | 62 | 63 | 64 | #----------------------- 65 | # Manage events 66 | #----------------------- 67 | func is_event( evt_name : String ) -> bool: 68 | if state.events.find( evt_name ) > -1: 69 | return true 70 | return false 71 | 72 | func add_event( evt_name : String ) -> bool: 73 | if is_event( evt_name ): return false 74 | state.events.append( evt_name ) 75 | return true 76 | -------------------------------------------------------------------------------- /game/singletons/params.gd: -------------------------------------------------------------------------------- 1 | class_name Params extends RefCounted 2 | 3 | 4 | const GRAVITY = 750 5 | const TERM_VEL = 250 6 | 7 | const PLAYER_MAX_VEL = 85 8 | const PLAYER_JUMP_VEL = 310 9 | const PLAYER_AIR_ACCEL = 10 10 | const PLAYER_AIR_DECEL = 5 11 | const PLAYER_VARIABLE_JUMP_MARGIN = 0.2 12 | const PLAYER_COYOTE_MARGIN = 0.15 13 | const PLAYER_HAZARD_THROWBACK_VEL = 600 14 | 15 | #const ENEMY_FREEZE_TIME = 5.0 16 | #const ENEMY_RESPAWN_DELAY = 5 17 | #const ENEMY_RESPAWN_DISTANCE = 32 18 | 19 | const MIN_DISTANCE_RESPAWN = 128 20 | const SLIME_WALK_VEL = 25 21 | 22 | const TROOPER_WALK_VEL = 30 23 | const TROOPER_CHASE_VEL = 75 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /game/singletons/sigmgr.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | 4 | signal gamestate_changed 5 | 6 | #@warning_ignore("untyped_declaration") 7 | signal camera_shake( duration : float, magnitude : float, frequency : float ) 8 | 9 | signal load_screen( scn_filename : String ) 10 | 11 | #signal set_music( stream_no : Main.MusicStreams ) 12 | signal captured_coin 13 | -------------------------------------------------------------------------------- /game/world/backgrounds/forest_background.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=3 uid="uid://cd82l6xhggjen"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://b76leqevv8pdx" path="res://assets/background_forest_sky.png" id="1_21sos"] 4 | [ext_resource type="Texture2D" uid="uid://dt3nea0r3swkg" path="res://assets/background_forest_clouds.png" id="2_q6mfh"] 5 | [ext_resource type="Texture2D" uid="uid://qu7whdpq1gho" path="res://assets/background_forest_back_hills.png" id="3_6qsp5"] 6 | [ext_resource type="Texture2D" uid="uid://so6gg48g01k7" path="res://assets/background_forest_hills.png" id="4_xhx3p"] 7 | [ext_resource type="Texture2D" uid="uid://dxp0vt8kplrdb" path="res://assets/background_forest_sea.png" id="5_xhx2p"] 8 | 9 | [node name="forest_background" type="ParallaxBackground"] 10 | 11 | [node name="sky" type="ParallaxLayer" parent="."] 12 | motion_scale = Vector2(0, 0) 13 | motion_mirroring = Vector2(480, 0) 14 | metadata/_edit_lock_ = true 15 | 16 | [node name="BackgroundForestSky" type="Sprite2D" parent="sky"] 17 | texture = ExtResource("1_21sos") 18 | centered = false 19 | metadata/_edit_lock_ = true 20 | 21 | [node name="clouds" type="ParallaxLayer" parent="."] 22 | motion_scale = Vector2(0.125, 0) 23 | motion_mirroring = Vector2(480, 0) 24 | metadata/_edit_lock_ = true 25 | 26 | [node name="BackgroundForestClouds" type="Sprite2D" parent="clouds"] 27 | texture = ExtResource("2_q6mfh") 28 | centered = false 29 | metadata/_edit_lock_ = true 30 | 31 | [node name="back_hills" type="ParallaxLayer" parent="."] 32 | motion_scale = Vector2(0.25, 0) 33 | motion_mirroring = Vector2(480, 0) 34 | metadata/_edit_lock_ = true 35 | 36 | [node name="BackgroundForestBackHills" type="Sprite2D" parent="back_hills"] 37 | texture = ExtResource("3_6qsp5") 38 | centered = false 39 | metadata/_edit_lock_ = true 40 | 41 | [node name="hills" type="ParallaxLayer" parent="."] 42 | motion_scale = Vector2(0.5, 0) 43 | motion_mirroring = Vector2(480, 0) 44 | metadata/_edit_lock_ = true 45 | 46 | [node name="BackgroundForestHills" type="Sprite2D" parent="hills"] 47 | texture = ExtResource("4_xhx3p") 48 | centered = false 49 | metadata/_edit_lock_ = true 50 | 51 | [node name="sea" type="ParallaxLayer" parent="."] 52 | motion_scale = Vector2(0.5, 0) 53 | motion_mirroring = Vector2(480, 0) 54 | metadata/_edit_lock_ = true 55 | 56 | [node name="BackgroundForestSea" type="Sprite2D" parent="sea"] 57 | texture = ExtResource("5_xhx2p") 58 | centered = false 59 | -------------------------------------------------------------------------------- /game/world/base_level/background.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends LDtkTileMap 3 | -------------------------------------------------------------------------------- /game/world/base_level/base_level.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name BaseLevel extends LDtkLevel 3 | 4 | var type : int = 0 5 | 6 | 7 | # Called before finish activating 8 | func _activate( _act : bool = true) -> void: 9 | var level_params : Dictionary = get_level_params() 10 | if level_params: 11 | type = int( level_params.type ) 12 | #if act: 13 | ## setup particles 14 | #$particles.process_material.set_shader_parameter( "level_size", Vector2( get_worldsize_px() ) ) 15 | #$particles.amount = 40 * int( get_worldsize_px().x * get_worldsize_px().y / ( 240 * 135.0 ) ) 16 | #$particles.visibility_rect = Rect2( 17 | #Vector2.ZERO, Vector2( get_worldsize_px() ) 18 | #) 19 | #$particles.emitting = true 20 | # 21 | ## setup lava levels 22 | #if type == 1: 23 | #$particles.modulate = Color( "ff004d" ) 24 | #$heat_shader.region_rect.size = Vector2( get_worldsize_px() ) 25 | #$heat_shader.show() 26 | # 27 | ## music 28 | #if type == 1: 29 | #sigmgr.set_music.emit( Main.MusicStreams.LAVA ) 30 | #else: 31 | #sigmgr.set_music.emit( Main.MusicStreams.NORMAL ) 32 | #else: 33 | #$particles.emitting = false 34 | #$heat_shader.hide() 35 | 36 | -------------------------------------------------------------------------------- /game/world/base_level/base_level.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://dqtkatm2wdjuk"] 2 | 3 | [ext_resource type="Script" path="res://world/base_level/base_level.gd" id="1_c4xlb"] 4 | [ext_resource type="TileSet" uid="uid://djglcshpq81mi" path="res://resources/walls_tileset.tres" id="2_8hgcm"] 5 | [ext_resource type="Script" path="res://world/base_level/walls.gd" id="2_uq1c3"] 6 | [ext_resource type="TileSet" uid="uid://sac6nlwkbmi4" path="res://resources/decorations_tileset.tres" id="4_tok5n"] 7 | [ext_resource type="Script" path="res://addons/ldtk2godot4/ldtk_tilemap.gd" id="5_7nerj"] 8 | [ext_resource type="PackedScene" uid="uid://cd82l6xhggjen" path="res://world/backgrounds/forest_background.tscn" id="6_i7obo"] 9 | 10 | [node name="base_level" type="Node2D"] 11 | script = ExtResource("1_c4xlb") 12 | _player_detection_mask = 16 13 | metadata/_edit_lock_ = true 14 | 15 | [node name="walls" type="TileMap" parent="."] 16 | z_index = -5 17 | tile_set = ExtResource("2_8hgcm") 18 | format = 2 19 | script = ExtResource("2_uq1c3") 20 | _layers = Array[String](["walls"]) 21 | metadata/_edit_lock_ = true 22 | 23 | [node name="decorations" type="TileMap" parent="."] 24 | z_index = -5 25 | tile_set = ExtResource("4_tok5n") 26 | format = 2 27 | script = ExtResource("5_7nerj") 28 | _layers = Array[String](["decorations"]) 29 | 30 | [node name="auto_decorations" type="TileMap" parent="."] 31 | z_index = -5 32 | tile_set = ExtResource("4_tok5n") 33 | format = 2 34 | script = ExtResource("5_7nerj") 35 | _layers = Array[String](["auto_decorations"]) 36 | 37 | [node name="forest_background" parent="." instance=ExtResource("6_i7obo")] 38 | -------------------------------------------------------------------------------- /game/world/base_level/destructibles.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends LDtkTileMap 3 | 4 | var dscn := preload( "res://entities/props/destructibles/destructible.tscn" ) 5 | 6 | func _ready(): 7 | if Engine.is_editor_hint(): return 8 | call_deferred( "_set_destructibles" ) 9 | 10 | func _set_destructibles() -> void: 11 | for cell in get_used_cells( 0 ): 12 | var coords = get_cell_atlas_coords( 0, cell ) 13 | var d = dscn.instantiate() 14 | d.position = cell * 8 15 | d.tile = coords 16 | add_child( d ) 17 | set_cell( 0, cell ) 18 | -------------------------------------------------------------------------------- /game/world/base_level/hazards.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends LDtkTileMap 3 | 4 | 5 | -------------------------------------------------------------------------------- /game/world/base_level/walls.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends LDtkTileMap 3 | 4 | # 5 | #var grass_scn : PackedScene = preload( "res://entities/props/wavy_grass/wavy_grass.tscn" ) 6 | #var grass_cells : Array[Vector2i]= [ 7 | #Vector2i( 2, 5 ), Vector2i( 3, 5 ), Vector2i( 4, 5 ), Vector2i( 5, 5 ), Vector2i( 6, 5 ), 8 | #] 9 | # 10 | #func _ready() -> void: 11 | #if Engine.is_editor_hint(): return 12 | #finished_populating_map.connect( _on_finished_populating_map ) 13 | # 14 | # 15 | # 16 | #func _on_finished_populating_map() -> void: 17 | #_set_grass() 18 | # 19 | #func _set_grass() -> void: 20 | #var used_cells : Array[Vector2i] = get_used_cells(0) 21 | #for cell in used_cells: 22 | #var autotile_coord : Vector2i = get_cell_atlas_coords( 0, cell ) 23 | #var pos = grass_cells.find( autotile_coord ) 24 | #if pos == -1: continue 25 | #var g = grass_scn.instantiate() 26 | #g.position = cell * 8 27 | #g.tile_rect = Rect2i( autotile_coord, Vector2i( 1, 1 ) ) 28 | #add_child( g ) 29 | #set_cell( 0, cell ) 30 | 31 | -------------------------------------------------------------------------------- /game/world/map_images.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends Node2D 3 | 4 | @export var map : LDtkMap 5 | 6 | 7 | func _enter_tree() -> void: 8 | if not Engine.is_editor_hint(): return 9 | for c in get_children(): 10 | c.queue_free() 11 | if map: 12 | var pos : int = map.resource_path.find( ".ldtk" ) 13 | var png_path : String = map.resource_path.substr( 0, pos ) + "/png" 14 | var _levels : Array = map.world_data.keys() 15 | for level_name : String in map.world_data.keys(): 16 | var png_filename : String = "%s/%s.png" % [ png_path, level_name ] 17 | var texture : Texture = load( png_filename ) 18 | var s : Sprite2D = Sprite2D.new() 19 | s.texture = texture 20 | s.position = map.world_data[level_name].level_rect.position 21 | s.centered = false 22 | add_child( s ) 23 | 24 | func _ready() -> void: 25 | if not Engine.is_editor_hint(): 26 | queue_free() 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /game/world/world.gd: -------------------------------------------------------------------------------- 1 | extends LDtkWorld 2 | 3 | 4 | @export var world_debug : bool = true 5 | @onready var camera : Camera2D = $player/camera 6 | @onready var player : Player = $player 7 | 8 | func _ready() -> void: 9 | if Engine.is_editor_hint(): 10 | return 11 | 12 | if world_debug: 13 | game.debug = world_debug 14 | game._initialize_state() 15 | #$fade_layer.fade_out( true ) 16 | var _ret : int = player.player_dead.connect( _on_player_dead ) 17 | _ret = world_ready.connect( _on_world_ready ) 18 | super._ready() 19 | 20 | 21 | 22 | func _on_world_ready() -> void: 23 | if world_debug: 24 | game.state.worldpos = Vector2i( $player.global_position ) 25 | _on_player_leaving_level( null, game.state.worldpos ) 26 | 27 | 28 | func _on_player_leaving_level( _player : Node2D, player_world_position : Vector2 ) -> void: 29 | var new_level_name : String = map.get_level_name_at( player_world_position ) 30 | if _debug: print( "New level: ", new_level_name, " at ", player_world_position ) 31 | if not new_level_name: 32 | printerr( "Unable to find level" ) 33 | return 34 | player._entity_activate( false ) 35 | #$hud_layer.set_physics_process( false ) 36 | $fade_layer.fade_out() 37 | await $fade_layer.fade_complete 38 | load_level( new_level_name ) 39 | await level_ready 40 | player.position = player_world_position - Vector2( _cur_level.get_worldpos_px() ) 41 | camera.limit_left = 0 42 | camera.limit_right = _cur_level.get_worldsize_px().x 43 | camera.limit_top = 0 44 | camera.limit_bottom = round( _cur_level.get_worldsize_px().y / 240.0 ) * 240.0 45 | camera._reset_camera() 46 | $fade_layer.fade_in() 47 | await $fade_layer.fade_complete 48 | #$hud_layer.set_physics_process( true ) 49 | player._entity_activate( true ) 50 | player.cur_level_worldpos = _cur_level.get_worldpos_px() 51 | 52 | 53 | func _on_player_dead() -> void: 54 | game.reset_state() 55 | _on_player_leaving_level( $player, game.state.worldpos ) 56 | 57 | 58 | -------------------------------------------------------------------------------- /game/world/world.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://bbgqrdk7xw16k"] 2 | 3 | [ext_resource type="Script" path="res://world/world.gd" id="1_03fup"] 4 | [ext_resource type="Resource" uid="uid://c23adbbwev2pw" path="res://resources/map.ldtk" id="2_c5j0b"] 5 | [ext_resource type="Script" path="res://world/map_images.gd" id="2_wbp3t"] 6 | [ext_resource type="PackedScene" uid="uid://bc8ap3kuleqvd" path="res://entities/player/player.tscn" id="4_a3mf8"] 7 | [ext_resource type="Script" path="res://general_purpose/fade_layer/fade_layer.gd" id="5_1kiee"] 8 | [ext_resource type="Script" path="res://general_purpose/shake_camera/shake_camera.gd" id="5_a54sp"] 9 | 10 | [node name="world" type="Node2D"] 11 | script = ExtResource("1_03fup") 12 | map = ExtResource("2_c5j0b") 13 | base_level_scene = "res://world/base_level/base_level.tscn" 14 | metadata/_edit_lock_ = true 15 | 16 | [node name="map_images" type="Node2D" parent="."] 17 | script = ExtResource("2_wbp3t") 18 | map = ExtResource("2_c5j0b") 19 | metadata/_edit_lock_ = true 20 | 21 | [node name="player" parent="." instance=ExtResource("4_a3mf8")] 22 | position = Vector2(2128, 256) 23 | 24 | [node name="camera" type="Camera2D" parent="player"] 25 | script = ExtResource("5_a54sp") 26 | metadata/_edit_lock_ = true 27 | 28 | [node name="fade_layer" type="CanvasLayer" parent="."] 29 | script = ExtResource("5_1kiee") 30 | --------------------------------------------------------------------------------