├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── addons └── safe_resource_loader │ ├── plugin.cfg │ ├── plugin.gd │ └── safe_resource_loader.gd ├── camera ├── camera.gd └── camera.tscn ├── delayed_detector └── delayed_detector.gd ├── export_presets.cfg ├── fish ├── fish.gd ├── fish.gdshader ├── fish.png ├── fish.png.import ├── fish.tscn ├── saved_fish_data.gd └── shader_control.gd ├── game.gd ├── game.tscn ├── game_over_sign ├── game_over_sign.gd ├── game_over_sign.png ├── game_over_sign.png.import ├── game_over_sign.tscn └── scale_container.gd ├── godotneers-ensuring-saved-game-compatibility.pdf ├── icon.svg ├── icon.svg.import ├── level_exit ├── exit_sign.png ├── exit_sign.png.import ├── level_exit.gd └── level_exit.tscn ├── levels ├── level.gd ├── level_0.tscn ├── level_1.tscn ├── level_3.tscn └── level_4.tscn ├── light_cone ├── cone.gdshader └── light_cone.tscn ├── movement_particles ├── bubble.png ├── bubble.png.import ├── movement_particles.gd └── movement_particles.tscn ├── pause_screen ├── grayscale_postprocess.gdshader └── pause_screen.tscn ├── plankton ├── plankton.tscn ├── plankton_blob.png └── plankton_blob.png.import ├── player ├── health_indicator.gdshader ├── player.gd ├── player.tscn ├── submarine.png └── submarine.png.import ├── project.godot ├── saved_game ├── saved_data.gd └── saved_game.gd ├── saver_loader └── saver_loader.gd ├── shared_assets ├── art_deco_font.ttf ├── art_deco_font.ttf.import ├── one_pixel.png ├── one_pixel.png.import ├── ui_bg.png └── ui_bg.png.import ├── spawn_point ├── spawn_point.gd └── spawn_point.tscn ├── torpedo ├── saved_torpedo_data.gd ├── torpedo.png ├── torpedo.png.import └── torpedo.tscn ├── tower ├── health_indicator.gdshader ├── saved_tower_data.gd ├── sphere.png ├── sphere.png.import ├── tower.gd └── tower.tscn ├── tower_projectile ├── projectile.gd ├── projectile.png ├── projectile.png.import └── projectile.tscn ├── unused_data └── unused_data.gd ├── utilities └── path_fixer.gd └── world_root └── world_root.gd /.gitattributes: -------------------------------------------------------------------------------- 1 | # Normalize EOL for all files that Git considers text files. 2 | * text=auto eol=lf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: derkork 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jan Thomä 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Saving and loading with Godot 2 | 3 |

4 |

5 | Saving and Loading with Godot, Video on YouTube Cover Image 6 |

7 |

8 | 9 | Hello, Godotneers! Thank you very much for watching this video. This repository contains the little game we used in the video. You can use it to follow along with the video or to experiment with the code. 10 | 11 | ## Using this repository 12 | There is a `start` branch that contains the code as it was at the beginning of the video. You can use this branch to follow along with the video if you'd like. 13 | 14 | The `main` branch contains the code as it was at the end of the video. It also includes some additional features: 15 | 16 | - It adds some things mentioned in the article below, e.g. the `PathFixer` and the handling of deleted or renamed items. 17 | - It adds some additional comments to the code, so it's easier to follow even if you didn't watch the video. 18 | - It adds saving and loading for the towers which are part of the later levels. These were not shown in the video. Check the [`tower.gd`](tower/tower.gd) and[`saved_tower_data.gd`](tower/saved_tower_data.gd) scripts for details. 19 | - It adds saving and loading the current level, so if you save in level 3, you will continue in level 3 when you load the game. This also was not shown in the video. Check the updated [`saver_loader.gd`](saver_loader/saver_loader.gd) script for details. 20 | 21 | If you don't know how to use git, you can also download the code as a zip file using these links: 22 | 23 | - [Download the code as a zip file (main branch, at the end of the video)](https://github.com/godotneers/saving-loading-video/archive/refs/heads/main.zip) 24 | - [Download the code as a zip file (start branch, at the beginning of the video)](https://github.com/godotneers/saving-loading-video/archive/refs/heads/start.zip) 25 | 26 | ## Additional material 27 | 28 | Be sure to also check the [article on saved game compatibility](https://raw.githubusercontent.com/godotneers/saving-loading-video/main/godotneers-ensuring-saved-game-compatibility.pdf), it contains a lot of useful strategies to ensure your saved games are compatible across different versions of your game. 29 | 30 | 31 | ## Support me 32 | 33 | If this video and the example code were helpful to you, please consider supporting me on [Ko-fi](https://ko-fi.com/derkork). Thank you very much! 34 | 35 | 36 | ## License 37 | 38 | The code and all assets except the article in this repository are licensed under the MIT license. See the LICENSE file for more information. The article is licensed under CC BY-SA 4.0. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/4.0/. 39 | -------------------------------------------------------------------------------- /addons/safe_resource_loader/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="Safe Resource Loader" 4 | description="An extension for safely loading resource files from unknown sources." 5 | author="Jan Thomä" 6 | version="0.0.1" 7 | script="plugin.gd" 8 | -------------------------------------------------------------------------------- /addons/safe_resource_loader/plugin.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | extends EditorPlugin 3 | -------------------------------------------------------------------------------- /addons/safe_resource_loader/safe_resource_loader.gd: -------------------------------------------------------------------------------- 1 | class_name SafeResourceLoader 2 | 3 | ## Safely loads resource files (.tres) by scanning them for any 4 | ## embedded GDScript resources. If such resources are found, the 5 | ## loading will be aborted so embedded scripts cannote be executed. 6 | ## If loading fails for any reason, an error message will be printed 7 | ## and this function returns null. 8 | static func load(path:String, type_hint:String = "", \ 9 | cache_mode:ResourceLoader.CacheMode = ResourceLoader.CacheMode.CACHE_MODE_REUSE) -> Resource: 10 | 11 | # We only really support .tres files, so refuse to load anything else. 12 | if not path.ends_with(".tres"): 13 | push_error("This resource loader only supports .tres files.") 14 | return null 15 | 16 | # Also refuse to load anything within res:// as it should be safe and also 17 | # will not be readable after export anyways. 18 | if path.begins_with("res://"): 19 | push_error("This resource loader is intended for loading resources from unsafe " + \ 20 | "origins (e.g. saved games downloaded from the internet). Using it on safe resources " + \ 21 | "inside res:// will just slow down loading for no benefit. In addition it will not work " + \ 22 | "on exported games as resources are packed and no longer readable from the file system.") 23 | return null 24 | 25 | # Check if the file exists. 26 | if not FileAccess.file_exists(path): 27 | push_error("Cannot load resource '" + path + "' because it does not exist or is not accessible.") 28 | return null 29 | 30 | # Load it as text content, only. This will not execute any scripts. 31 | var file = FileAccess.open(path, FileAccess.READ) 32 | var file_as_text = file.get_as_text() 33 | file.close() 34 | 35 | # Use a regex to find any instance of an embedded GDScript resource. 36 | var regex:RegEx = RegEx.new() 37 | regex.compile("type\\s*=\\s*\"GDScript\"\\s*") 38 | 39 | # If we found one, bail out. 40 | if regex.search(file_as_text) != null: 41 | push_warning("Resource '" + path + "' contains inline GDScripts, will not load it.") 42 | return null 43 | 44 | # otherwise use the normal resource loader to load it. 45 | return ResourceLoader.load(path, type_hint, ) 46 | 47 | -------------------------------------------------------------------------------- /camera/camera.gd: -------------------------------------------------------------------------------- 1 | extends Camera2D 2 | 3 | var _target:Node2D 4 | 5 | func _process(_delta): 6 | # try to acquire a target if none is present 7 | if not is_instance_valid(_target): 8 | var targets = get_tree().get_nodes_in_group("camera_target") 9 | if targets.size() == 0: 10 | return 11 | 12 | _target = targets[0] 13 | 14 | position = _target.global_position 15 | 16 | -------------------------------------------------------------------------------- /camera/camera.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://b5qgw8ps41nq2"] 2 | 3 | [ext_resource type="Script" path="res://camera/camera.gd" id="1_2fr1k"] 4 | 5 | [node name="Camera" type="Camera2D"] 6 | position = Vector2(965, 547) 7 | limit_left = 0 8 | limit_top = -200 9 | limit_right = 4200 10 | limit_bottom = 1200 11 | position_smoothing_enabled = true 12 | script = ExtResource("1_2fr1k") 13 | -------------------------------------------------------------------------------- /delayed_detector/delayed_detector.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | 4 | func _ready(): 5 | # delay enabling the detection area to avoid having spurious overlaps with the 6 | # player when loading a new level 7 | await get_tree().physics_frame 8 | await get_tree().physics_frame 9 | monitoring = true 10 | monitorable = true 11 | 12 | -------------------------------------------------------------------------------- /export_presets.cfg: -------------------------------------------------------------------------------- 1 | [preset.0] 2 | 3 | name="Windows Desktop" 4 | platform="Windows Desktop" 5 | runnable=true 6 | dedicated_server=false 7 | custom_features="" 8 | export_filter="all_resources" 9 | include_filter="" 10 | exclude_filter="" 11 | export_path="C:/Users/kork/Desktop/test_export/load_save.exe" 12 | encryption_include_filters="" 13 | encryption_exclude_filters="" 14 | encrypt_pck=false 15 | encrypt_directory=false 16 | 17 | [preset.0.options] 18 | 19 | custom_template/debug="" 20 | custom_template/release="" 21 | debug/export_console_wrapper=1 22 | binary_format/embed_pck=false 23 | texture_format/bptc=true 24 | texture_format/s3tc=true 25 | texture_format/etc=false 26 | texture_format/etc2=false 27 | binary_format/architecture="x86_64" 28 | codesign/enable=false 29 | codesign/timestamp=true 30 | codesign/timestamp_server_url="" 31 | codesign/digest_algorithm=1 32 | codesign/description="" 33 | codesign/custom_options=PackedStringArray() 34 | application/modify_resources=true 35 | application/icon="" 36 | application/console_wrapper_icon="" 37 | application/icon_interpolation=4 38 | application/file_version="" 39 | application/product_version="" 40 | application/company_name="" 41 | application/product_name="" 42 | application/file_description="" 43 | application/copyright="" 44 | application/trademarks="" 45 | ssh_remote_deploy/enabled=false 46 | ssh_remote_deploy/host="user@host_ip" 47 | ssh_remote_deploy/port="22" 48 | ssh_remote_deploy/extra_args_ssh="" 49 | ssh_remote_deploy/extra_args_scp="" 50 | ssh_remote_deploy/run_script="Expand-Archive -LiteralPath '{temp_dir}\\{archive_name}' -DestinationPath '{temp_dir}' 51 | $action = New-ScheduledTaskAction -Execute '{temp_dir}\\{exe_name}' -Argument '{cmd_args}' 52 | $trigger = New-ScheduledTaskTrigger -Once -At 00:00 53 | $settings = New-ScheduledTaskSettingsSet 54 | $task = New-ScheduledTask -Action $action -Trigger $trigger -Settings $settings 55 | Register-ScheduledTask godot_remote_debug -InputObject $task -Force:$true 56 | Start-ScheduledTask -TaskName godot_remote_debug 57 | while (Get-ScheduledTask -TaskName godot_remote_debug | ? State -eq running) { Start-Sleep -Milliseconds 100 } 58 | Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue" 59 | ssh_remote_deploy/cleanup_script="Stop-ScheduledTask -TaskName godot_remote_debug -ErrorAction:SilentlyContinue 60 | Unregister-ScheduledTask -TaskName godot_remote_debug -Confirm:$false -ErrorAction:SilentlyContinue 61 | Remove-Item -Recurse -Force '{temp_dir}'" 62 | dotnet/include_scripts_content=false 63 | dotnet/include_debug_symbols=true 64 | -------------------------------------------------------------------------------- /fish/fish.gd: -------------------------------------------------------------------------------- 1 | extends CharacterBody2D 2 | @export var speed:float = 200 3 | @export var impact_damage:float = 50 4 | 5 | @onready var _sprite:Sprite2D = %Sprite2D 6 | @onready var _animation_player = %AnimationPlayer 7 | 8 | var _target:Node2D 9 | var _dying:bool = false 10 | 11 | func on_save_game(saved_data:Array[SavedData]): 12 | if _dying: 13 | return 14 | 15 | var my_data = SavedFishData.new() 16 | my_data.position = global_position 17 | my_data.scene_path = scene_file_path 18 | my_data.scale = scale 19 | saved_data.append(my_data) 20 | 21 | 22 | func on_before_load_game(): 23 | get_parent().remove_child(self) 24 | queue_free() 25 | 26 | 27 | func on_load_game(saved_data:SavedData): 28 | global_position = saved_data.position 29 | 30 | if saved_data is SavedFishData: 31 | var my_data = saved_data as SavedFishData 32 | scale = my_data.scale 33 | 34 | 35 | func _physics_process(_delta): 36 | if not is_instance_valid(_target) or _dying: 37 | return 38 | 39 | velocity = (_target.global_position - global_position).normalized() * speed 40 | _sprite.flip_h = velocity.x < 0 41 | move_and_slide() 42 | 43 | var collision_count = get_slide_collision_count() 44 | for i in collision_count: 45 | if _dying: 46 | return 47 | 48 | var collision_info = get_slide_collision(i) 49 | var collider = collision_info.get_collider() 50 | 51 | if collider.has_method("take_damage"): 52 | collider.take_damage(impact_damage) 53 | _die() 54 | 55 | 56 | 57 | func take_damage(_damage:float): 58 | if scale.x < 1: 59 | _die() 60 | else: 61 | scale = Vector2(0.5, 0.5) 62 | 63 | 64 | func _die(): 65 | _dying = true 66 | _animation_player.play("explode") 67 | await _animation_player.animation_finished 68 | visible = false 69 | queue_free() 70 | 71 | 72 | func _on_detection_area_body_entered(body): 73 | if body.is_in_group("player"): 74 | _target = body 75 | -------------------------------------------------------------------------------- /fish/fish.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform vec4 modulate:source_color = vec4(1.0); 4 | uniform float time_scale = 1.0; 5 | 6 | void fragment() { 7 | float scaleY = (0.4 + 0.2 * sin(11. * (0.2 + TIME * time_scale))) * 8 | (1.0 - smoothstep(0., 0.7, UV.x)); 9 | float scaleX = (0.5 + 0.2 * sin(5. * TIME * time_scale)) * 10 | (1.0 - smoothstep(0.2, 0.85, UV.x)); 11 | COLOR = texture(TEXTURE, vec2(UV.x + (UV.x - 0.5) * scaleX, UV.y + (UV.y - 0.5) * scaleY)) * modulate; 12 | } 13 | -------------------------------------------------------------------------------- /fish/fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/fish/fish.png -------------------------------------------------------------------------------- /fish/fish.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dnntm1mgk2r6d" 6 | path="res://.godot/imported/fish.png-c95fae89119f8c6363bf7a78677f5560.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://fish/fish.png" 14 | dest_files=["res://.godot/imported/fish.png-c95fae89119f8c6363bf7a78677f5560.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 | -------------------------------------------------------------------------------- /fish/fish.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=18 format=3 uid="uid://b67uxcsmxaqqi"] 2 | 3 | [ext_resource type="Script" path="res://fish/fish.gd" id="1_6ahu8"] 4 | [ext_resource type="Shader" path="res://fish/fish.gdshader" id="2_elrrs"] 5 | [ext_resource type="Texture2D" uid="uid://dnntm1mgk2r6d" path="res://fish/fish.png" id="2_mjo2s"] 6 | [ext_resource type="Texture2D" uid="uid://bjty65c32jxhq" path="res://movement_particles/bubble.png" id="4_a60dk"] 7 | [ext_resource type="Script" path="res://fish/shader_control.gd" id="4_y68b4"] 8 | [ext_resource type="Script" path="res://delayed_detector/delayed_detector.gd" id="5_fk5ku"] 9 | 10 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_xapgh"] 11 | shader = ExtResource("2_elrrs") 12 | shader_parameter/modulate = Color(1, 1, 1, 1) 13 | shader_parameter/time_scale = 1.0 14 | 15 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_avlye"] 16 | radius = 73.9999 17 | height = 165.997 18 | 19 | [sub_resource type="CircleShape2D" id="CircleShape2D_2egbw"] 20 | radius = 394.138 21 | 22 | [sub_resource type="Animation" id="Animation_wwab5"] 23 | length = 0.001 24 | tracks/0/type = "value" 25 | tracks/0/imported = false 26 | tracks/0/enabled = true 27 | tracks/0/path = NodePath("CollisionShape2D:disabled") 28 | tracks/0/interp = 1 29 | tracks/0/loop_wrap = true 30 | tracks/0/keys = { 31 | "times": PackedFloat32Array(0), 32 | "transitions": PackedFloat32Array(1), 33 | "update": 1, 34 | "values": [false] 35 | } 36 | tracks/1/type = "value" 37 | tracks/1/imported = false 38 | tracks/1/enabled = true 39 | tracks/1/path = NodePath("Sprite2D:scale") 40 | tracks/1/interp = 1 41 | tracks/1/loop_wrap = true 42 | tracks/1/keys = { 43 | "times": PackedFloat32Array(0), 44 | "transitions": PackedFloat32Array(1), 45 | "update": 0, 46 | "values": [Vector2(0.347656, 0.3125)] 47 | } 48 | tracks/2/type = "value" 49 | tracks/2/imported = false 50 | tracks/2/enabled = true 51 | tracks/2/path = NodePath("ExplosionParticles:emitting") 52 | tracks/2/interp = 1 53 | tracks/2/loop_wrap = true 54 | tracks/2/keys = { 55 | "times": PackedFloat32Array(0), 56 | "transitions": PackedFloat32Array(1), 57 | "update": 1, 58 | "values": [false] 59 | } 60 | 61 | [sub_resource type="Animation" id="Animation_cfh4d"] 62 | resource_name = "explode" 63 | length = 5.0 64 | tracks/0/type = "value" 65 | tracks/0/imported = false 66 | tracks/0/enabled = true 67 | tracks/0/path = NodePath("ExplosionParticles:emitting") 68 | tracks/0/interp = 1 69 | tracks/0/loop_wrap = true 70 | tracks/0/keys = { 71 | "times": PackedFloat32Array(0), 72 | "transitions": PackedFloat32Array(1), 73 | "update": 1, 74 | "values": [true] 75 | } 76 | tracks/1/type = "value" 77 | tracks/1/imported = false 78 | tracks/1/enabled = true 79 | tracks/1/path = NodePath("CollisionShape2D:disabled") 80 | tracks/1/interp = 1 81 | tracks/1/loop_wrap = true 82 | tracks/1/keys = { 83 | "times": PackedFloat32Array(0), 84 | "transitions": PackedFloat32Array(1), 85 | "update": 1, 86 | "values": [true] 87 | } 88 | tracks/2/type = "value" 89 | tracks/2/imported = false 90 | tracks/2/enabled = true 91 | tracks/2/path = NodePath("Sprite2D:scale") 92 | tracks/2/interp = 1 93 | tracks/2/loop_wrap = true 94 | tracks/2/keys = { 95 | "times": PackedFloat32Array(0, 0.4), 96 | "transitions": PackedFloat32Array(1, 0.0312499), 97 | "update": 0, 98 | "values": [Vector2(0.347656, 0.3125), Vector2(0, 0)] 99 | } 100 | 101 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_i74i6"] 102 | _data = { 103 | "RESET": SubResource("Animation_wwab5"), 104 | "explode": SubResource("Animation_cfh4d") 105 | } 106 | 107 | [sub_resource type="Gradient" id="Gradient_dfpea"] 108 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 109 | 110 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_4yff5"] 111 | gradient = SubResource("Gradient_dfpea") 112 | 113 | [sub_resource type="Curve" id="Curve_ne7hk"] 114 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 115 | point_count = 2 116 | 117 | [sub_resource type="CurveTexture" id="CurveTexture_svkju"] 118 | curve = SubResource("Curve_ne7hk") 119 | 120 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_5kgx0"] 121 | lifetime_randomness = 0.33 122 | particle_flag_disable_z = true 123 | direction = Vector3(0, 0, 0) 124 | spread = 180.0 125 | gravity = Vector3(0, 0, 0) 126 | initial_velocity_min = 62.63 127 | initial_velocity_max = 138.59 128 | angular_velocity_min = -181.89 129 | angular_velocity_max = 227.37 130 | orbit_velocity_min = 0.0 131 | orbit_velocity_max = 0.0 132 | scale_min = 0.3 133 | scale_max = 0.5 134 | scale_curve = SubResource("CurveTexture_svkju") 135 | color_ramp = SubResource("GradientTexture1D_4yff5") 136 | 137 | [node name="Fish" type="CharacterBody2D" groups=["enemy", "game_events"]] 138 | collision_layer = 4 139 | collision_mask = 3 140 | script = ExtResource("1_6ahu8") 141 | speed = 150.0 142 | 143 | [node name="Sprite2D" type="Sprite2D" parent="."] 144 | unique_name_in_owner = true 145 | process_mode = 3 146 | material = SubResource("ShaderMaterial_xapgh") 147 | scale = Vector2(0.347656, 0.3125) 148 | texture = ExtResource("2_mjo2s") 149 | flip_h = true 150 | script = ExtResource("4_y68b4") 151 | 152 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 153 | rotation = 1.56926 154 | shape = SubResource("CapsuleShape2D_avlye") 155 | 156 | [node name="DetectionArea" type="Area2D" parent="."] 157 | monitoring = false 158 | monitorable = false 159 | script = ExtResource("5_fk5ku") 160 | 161 | [node name="CollisionShape2D" type="CollisionShape2D" parent="DetectionArea"] 162 | shape = SubResource("CircleShape2D_2egbw") 163 | 164 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 165 | unique_name_in_owner = true 166 | libraries = { 167 | "": SubResource("AnimationLibrary_i74i6") 168 | } 169 | 170 | [node name="ExplosionParticles" type="GPUParticles2D" parent="."] 171 | unique_name_in_owner = true 172 | emitting = false 173 | amount = 250 174 | process_material = SubResource("ParticleProcessMaterial_5kgx0") 175 | texture = ExtResource("4_a60dk") 176 | lifetime = 2.0 177 | one_shot = true 178 | explosiveness = 0.88 179 | fixed_fps = 60 180 | 181 | [connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"] 182 | -------------------------------------------------------------------------------- /fish/saved_fish_data.gd: -------------------------------------------------------------------------------- 1 | class_name SavedFishData 2 | extends SavedData 3 | 4 | @export var scale:Vector2 5 | -------------------------------------------------------------------------------- /fish/shader_control.gd: -------------------------------------------------------------------------------- 1 | extends Sprite2D 2 | 3 | func _process(_delta): 4 | material.set_shader_parameter("time_scale", 0 if get_tree().paused else 1) 5 | -------------------------------------------------------------------------------- /game.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @onready var _world_root:WorldRoot = %WorldRoot 4 | @onready var _saver_loader:SaverLoader = %SaverLoader 5 | @onready var _pause_screen = %PauseScreen 6 | 7 | var _paused:bool = false 8 | 9 | 10 | func _ready(): 11 | _world_root.level_exit_reached.connect(_on_level_exit_reached) 12 | 13 | 14 | func _on_level_exit_reached(next_level): 15 | _world_root.load_level_async(next_level) 16 | 17 | 18 | func _process(_delta): 19 | if Input.is_action_just_pressed("pause"): 20 | _pause(not _paused) 21 | 22 | 23 | func _pause(value:bool): 24 | _paused = value 25 | _pause_screen.visible = _paused 26 | get_tree().paused = _paused 27 | 28 | 29 | ## ------------------------------------------------------------------------------------ 30 | 31 | # Called when the save game button is pressed 32 | func _on_save_game(): 33 | print("Save game!") 34 | _saver_loader.save_game() 35 | 36 | # Called when the load game button is pressed 37 | func _on_load_game(): 38 | print("Load game!") 39 | _saver_loader.load_game() 40 | # _pause(true) 41 | 42 | -------------------------------------------------------------------------------- /game.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=3 uid="uid://djr4gxcdxrgc8"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://dt42gugmqq28j" path="res://player/player.tscn" id="1_6b83d"] 4 | [ext_resource type="Script" path="res://game.gd" id="1_l5r35"] 5 | [ext_resource type="Script" path="res://world_root/world_root.gd" id="3_ay4xh"] 6 | [ext_resource type="PackedScene" uid="uid://co3jn3ck5jb1m" path="res://levels/level_1.tscn" id="3_lfwqq"] 7 | [ext_resource type="PackedScene" uid="uid://dk3cdmmwsb57l" path="res://pause_screen/pause_screen.tscn" id="5_7yult"] 8 | [ext_resource type="FontFile" uid="uid://cx27tcro6r2q7" path="res://shared_assets/art_deco_font.ttf" id="5_t1l8g"] 9 | [ext_resource type="Script" path="res://saver_loader/saver_loader.gd" id="7_rrrf1"] 10 | 11 | [node name="Game" type="Node2D"] 12 | process_mode = 3 13 | position = Vector2(-25, -4) 14 | script = ExtResource("1_l5r35") 15 | 16 | [node name="WorldRoot" type="Node2D" parent="."] 17 | unique_name_in_owner = true 18 | process_mode = 1 19 | script = ExtResource("3_ay4xh") 20 | 21 | [node name="Level1" parent="WorldRoot" instance=ExtResource("3_lfwqq")] 22 | 23 | [node name="Player" parent="WorldRoot" groups=["world_root_no_touch"] instance=ExtResource("1_6b83d")] 24 | unique_name_in_owner = true 25 | process_mode = 1 26 | position = Vector2(412, 510) 27 | collision_mask = 6 28 | speed = 300.0 29 | world_root = NodePath("..") 30 | 31 | [node name="UI" type="CanvasLayer" parent="."] 32 | 33 | [node name="MarginContainer" type="MarginContainer" parent="UI"] 34 | anchors_preset = 7 35 | anchor_left = 0.5 36 | anchor_top = 1.0 37 | anchor_right = 0.5 38 | anchor_bottom = 1.0 39 | offset_left = -122.0 40 | offset_top = -109.0 41 | offset_right = 122.0 42 | grow_horizontal = 2 43 | grow_vertical = 0 44 | theme_override_constants/margin_right = 50 45 | theme_override_constants/margin_bottom = 50 46 | 47 | [node name="HBoxContainer" type="HBoxContainer" parent="UI/MarginContainer"] 48 | layout_mode = 2 49 | theme_override_constants/separation = 20 50 | alignment = 1 51 | 52 | [node name="SaveButton" type="Button" parent="UI/MarginContainer/HBoxContainer"] 53 | layout_mode = 2 54 | focus_mode = 0 55 | theme_override_fonts/font = ExtResource("5_t1l8g") 56 | theme_override_font_sizes/font_size = 48 57 | text = "Save" 58 | 59 | [node name="LoadButton" type="Button" parent="UI/MarginContainer/HBoxContainer"] 60 | layout_mode = 2 61 | focus_mode = 0 62 | theme_override_fonts/font = ExtResource("5_t1l8g") 63 | theme_override_font_sizes/font_size = 48 64 | text = "Load" 65 | 66 | [node name="PauseScreen" parent="UI" instance=ExtResource("5_7yult")] 67 | unique_name_in_owner = true 68 | visible = false 69 | 70 | [node name="Utilities" type="Node" parent="."] 71 | 72 | [node name="SaverLoader" type="Node" parent="Utilities"] 73 | unique_name_in_owner = true 74 | script = ExtResource("7_rrrf1") 75 | 76 | [connection signal="exit_reached" from="WorldRoot/Level1" to="." method="_on_level_exit_reached"] 77 | [connection signal="pressed" from="UI/MarginContainer/HBoxContainer/SaveButton" to="." method="_on_save_game"] 78 | [connection signal="pressed" from="UI/MarginContainer/HBoxContainer/LoadButton" to="." method="_on_load_game"] 79 | -------------------------------------------------------------------------------- /game_over_sign/game_over_sign.gd: -------------------------------------------------------------------------------- 1 | class_name GameOverSign 2 | extends MarginContainer 3 | 4 | @onready var _animation_player:AnimationPlayer = %AnimationPlayer 5 | 6 | func reveal(): 7 | visible = true 8 | _animation_player.play("reveal") 9 | 10 | func conceal(): 11 | _animation_player.play("conceal") 12 | 13 | -------------------------------------------------------------------------------- /game_over_sign/game_over_sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/game_over_sign/game_over_sign.png -------------------------------------------------------------------------------- /game_over_sign/game_over_sign.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://60l5di81hbgo" 6 | path="res://.godot/imported/game_over_sign.png-a73c8791c4662f9e4aa41f0fa5ca75dc.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://game_over_sign/game_over_sign.png" 14 | dest_files=["res://.godot/imported/game_over_sign.png-a73c8791c4662f9e4aa41f0fa5ca75dc.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_over_sign/game_over_sign.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=3 uid="uid://ktqdmncsl8vj"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://60l5di81hbgo" path="res://game_over_sign/game_over_sign.png" id="1_242oq"] 4 | [ext_resource type="Script" path="res://game_over_sign/game_over_sign.gd" id="1_e0kk3"] 5 | [ext_resource type="Script" path="res://game_over_sign/scale_container.gd" id="3_d6t2t"] 6 | [ext_resource type="FontFile" uid="uid://cx27tcro6r2q7" path="res://shared_assets/art_deco_font.ttf" id="4_b05xd"] 7 | 8 | [sub_resource type="Animation" id="Animation_m63cr"] 9 | length = 0.001 10 | tracks/0/type = "value" 11 | tracks/0/imported = false 12 | tracks/0/enabled = true 13 | tracks/0/path = NodePath(".:modulate") 14 | tracks/0/interp = 1 15 | tracks/0/loop_wrap = true 16 | tracks/0/keys = { 17 | "times": PackedFloat32Array(0), 18 | "transitions": PackedFloat32Array(1), 19 | "update": 0, 20 | "values": [Color(1, 1, 1, 0)] 21 | } 22 | tracks/1/type = "value" 23 | tracks/1/imported = false 24 | tracks/1/enabled = true 25 | tracks/1/path = NodePath("ScaleContainer/Label:modulate") 26 | tracks/1/interp = 1 27 | tracks/1/loop_wrap = true 28 | tracks/1/keys = { 29 | "times": PackedFloat32Array(0), 30 | "transitions": PackedFloat32Array(1), 31 | "update": 0, 32 | "values": [Color(0, 0, 0, 0)] 33 | } 34 | tracks/2/type = "value" 35 | tracks/2/imported = false 36 | tracks/2/enabled = true 37 | tracks/2/path = NodePath("ScaleContainer:child_scale") 38 | tracks/2/interp = 1 39 | tracks/2/loop_wrap = true 40 | tracks/2/keys = { 41 | "times": PackedFloat32Array(0), 42 | "transitions": PackedFloat32Array(1), 43 | "update": 0, 44 | "values": [Vector2(0, 0)] 45 | } 46 | tracks/3/type = "value" 47 | tracks/3/imported = false 48 | tracks/3/enabled = true 49 | tracks/3/path = NodePath("TextureRect:modulate") 50 | tracks/3/interp = 1 51 | tracks/3/loop_wrap = true 52 | tracks/3/keys = { 53 | "times": PackedFloat32Array(0), 54 | "transitions": PackedFloat32Array(1), 55 | "update": 0, 56 | "values": [Color(1, 1, 1, 0)] 57 | } 58 | 59 | [sub_resource type="Animation" id="Animation_tr7t5"] 60 | resource_name = "conceal" 61 | tracks/0/type = "value" 62 | tracks/0/imported = false 63 | tracks/0/enabled = true 64 | tracks/0/path = NodePath(".:modulate") 65 | tracks/0/interp = 1 66 | tracks/0/loop_wrap = true 67 | tracks/0/keys = { 68 | "times": PackedFloat32Array(0.1, 1), 69 | "transitions": PackedFloat32Array(1, 1), 70 | "update": 0, 71 | "values": [Color(1, 1, 1, 1), Color(1, 1, 1, 0)] 72 | } 73 | 74 | [sub_resource type="Animation" id="Animation_txjca"] 75 | resource_name = "reveal" 76 | length = 5.0 77 | tracks/0/type = "value" 78 | tracks/0/imported = false 79 | tracks/0/enabled = true 80 | tracks/0/path = NodePath(".:modulate") 81 | tracks/0/interp = 1 82 | tracks/0/loop_wrap = true 83 | tracks/0/keys = { 84 | "times": PackedFloat32Array(0), 85 | "transitions": PackedFloat32Array(1), 86 | "update": 0, 87 | "values": [Color(1, 1, 1, 1)] 88 | } 89 | tracks/1/type = "value" 90 | tracks/1/imported = false 91 | tracks/1/enabled = true 92 | tracks/1/path = NodePath("ScaleContainer/Label:modulate") 93 | tracks/1/interp = 1 94 | tracks/1/loop_wrap = true 95 | tracks/1/keys = { 96 | "times": PackedFloat32Array(0, 0.7, 3.4), 97 | "transitions": PackedFloat32Array(1, 1.23114, 1), 98 | "update": 0, 99 | "values": [Color(0, 0, 0, 0), Color(0, 0, 0, 1), Color(1, 1, 1, 1)] 100 | } 101 | tracks/2/type = "value" 102 | tracks/2/imported = false 103 | tracks/2/enabled = true 104 | tracks/2/path = NodePath("ScaleContainer:child_scale") 105 | tracks/2/interp = 1 106 | tracks/2/loop_wrap = true 107 | tracks/2/keys = { 108 | "times": PackedFloat32Array(0, 2.1, 2.5, 4.3), 109 | "transitions": PackedFloat32Array(1, 1, 1, 1), 110 | "update": 0, 111 | "values": [Vector2(0, 0), Vector2(1, 1), Vector2(1.1, 1.1), Vector2(1, 1)] 112 | } 113 | tracks/3/type = "value" 114 | tracks/3/imported = false 115 | tracks/3/enabled = true 116 | tracks/3/path = NodePath("TextureRect:modulate") 117 | tracks/3/interp = 1 118 | tracks/3/loop_wrap = true 119 | tracks/3/keys = { 120 | "times": PackedFloat32Array(1.5, 4.3), 121 | "transitions": PackedFloat32Array(1, 1), 122 | "update": 0, 123 | "values": [Color(1, 1, 1, 0), Color(1, 1, 1, 1)] 124 | } 125 | 126 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_htu50"] 127 | _data = { 128 | "RESET": SubResource("Animation_m63cr"), 129 | "conceal": SubResource("Animation_tr7t5"), 130 | "reveal": SubResource("Animation_txjca") 131 | } 132 | 133 | [node name="GameOverSign" type="MarginContainer"] 134 | modulate = Color(1, 1, 1, 0) 135 | anchors_preset = 15 136 | anchor_right = 1.0 137 | anchor_bottom = 1.0 138 | grow_horizontal = 2 139 | grow_vertical = 2 140 | script = ExtResource("1_e0kk3") 141 | 142 | [node name="TextureRect" type="TextureRect" parent="."] 143 | modulate = Color(1, 1, 1, 0) 144 | layout_mode = 2 145 | texture = ExtResource("1_242oq") 146 | stretch_mode = 3 147 | 148 | [node name="ScaleContainer" type="Container" parent="."] 149 | layout_mode = 2 150 | script = ExtResource("3_d6t2t") 151 | child_scale = Vector2(0, 0) 152 | 153 | [node name="Label" type="Label" parent="ScaleContainer"] 154 | modulate = Color(0, 0, 0, 0) 155 | layout_mode = 2 156 | size_flags_vertical = 1 157 | theme_type_variation = &"GameOverLabel" 158 | theme_override_fonts/font = ExtResource("4_b05xd") 159 | theme_override_font_sizes/font_size = 200 160 | text = "Game Over" 161 | horizontal_alignment = 1 162 | vertical_alignment = 1 163 | 164 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 165 | unique_name_in_owner = true 166 | libraries = { 167 | "": SubResource("AnimationLibrary_htu50") 168 | } 169 | -------------------------------------------------------------------------------- /game_over_sign/scale_container.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | class_name ScaleContainer 3 | extends Container 4 | 5 | @export var child_scale:Vector2 = Vector2.ONE: 6 | set(value): 7 | child_scale = value 8 | queue_sort() 9 | 10 | 11 | func _get_minimum_size(): 12 | var combined_size := Vector2.ZERO 13 | for child in get_children(true): 14 | if not (child is Control): 15 | continue 16 | var child_size = child.get_combined_minimum_size() 17 | combined_size = Vector2(max(combined_size.x, child_size.x), max(combined_size.y, child_size.y)) 18 | 19 | return combined_size 20 | 21 | func _notification(what): 22 | if what == NOTIFICATION_SORT_CHILDREN: 23 | var rect = get_rect() 24 | for child in get_children(true): 25 | if not (child is Control): 26 | continue 27 | 28 | fit_child_in_rect(child, rect) 29 | child.scale = child_scale 30 | 31 | var child_rect = child.get_rect() 32 | 33 | child.position += (rect.size - child_rect.size) / 2 34 | 35 | -------------------------------------------------------------------------------- /godotneers-ensuring-saved-game-compatibility.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/godotneers-ensuring-saved-game-compatibility.pdf -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /icon.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dls1v4hkbich8" 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 | -------------------------------------------------------------------------------- /level_exit/exit_sign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/level_exit/exit_sign.png -------------------------------------------------------------------------------- /level_exit/exit_sign.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cmifejtnmlr67" 6 | path="res://.godot/imported/exit_sign.png-550c741880d3d66a0c28ef57955cb644.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://level_exit/exit_sign.png" 14 | dest_files=["res://.godot/imported/exit_sign.png-550c741880d3d66a0c28ef57955cb644.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 | -------------------------------------------------------------------------------- /level_exit/level_exit.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal exit_reached() 4 | 5 | func _on_detection_area_body_entered(_body): 6 | exit_reached.emit() 7 | -------------------------------------------------------------------------------- /level_exit/level_exit.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=16 format=3 uid="uid://6ydtqgm8mfqb"] 2 | 3 | [ext_resource type="PackedScene" uid="uid://d11vwcdb8m2ko" path="res://light_cone/light_cone.tscn" id="1_2r5cp"] 4 | [ext_resource type="Script" path="res://level_exit/level_exit.gd" id="1_d4hnl"] 5 | [ext_resource type="Shader" path="res://light_cone/cone.gdshader" id="2_0hg5p"] 6 | [ext_resource type="Texture2D" uid="uid://cmifejtnmlr67" path="res://level_exit/exit_sign.png" id="3_khddq"] 7 | [ext_resource type="Script" path="res://delayed_detector/delayed_detector.gd" id="5_osvk3"] 8 | 9 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_vt64g"] 10 | resource_local_to_scene = true 11 | shader = ExtResource("2_0hg5p") 12 | shader_parameter/modulate = Color(1, 1, 1, 1) 13 | 14 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_1phdr"] 15 | resource_local_to_scene = true 16 | shader = ExtResource("2_0hg5p") 17 | shader_parameter/modulate = Color(1, 1, 1, 1) 18 | 19 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_agrg4"] 20 | resource_local_to_scene = true 21 | shader = ExtResource("2_0hg5p") 22 | shader_parameter/modulate = Color(1, 1, 1, 1) 23 | 24 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_53722"] 25 | resource_local_to_scene = true 26 | shader = ExtResource("2_0hg5p") 27 | shader_parameter/modulate = Color(1, 1, 1, 1) 28 | 29 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_0mfx8"] 30 | resource_local_to_scene = true 31 | shader = ExtResource("2_0hg5p") 32 | shader_parameter/modulate = Color(1, 1, 1, 1) 33 | 34 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_1e14y"] 35 | resource_local_to_scene = true 36 | shader = ExtResource("2_0hg5p") 37 | shader_parameter/modulate = Color(1, 1, 1, 1) 38 | 39 | [sub_resource type="Animation" id="Animation_c4cd2"] 40 | length = 0.001 41 | tracks/0/type = "value" 42 | tracks/0/imported = false 43 | tracks/0/enabled = true 44 | tracks/0/path = NodePath("../LightConeRoot2:rotation") 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": [4.40341] 52 | } 53 | 54 | [sub_resource type="Animation" id="Animation_lk08a"] 55 | resource_name = "rotate" 56 | length = 10.0 57 | loop_mode = 1 58 | tracks/0/type = "value" 59 | tracks/0/imported = false 60 | tracks/0/enabled = true 61 | tracks/0/path = NodePath(".:rotation") 62 | tracks/0/interp = 1 63 | tracks/0/loop_wrap = true 64 | tracks/0/keys = { 65 | "times": PackedFloat32Array(0, 10), 66 | "transitions": PackedFloat32Array(1, 1), 67 | "update": 0, 68 | "values": [0.0, 6.28319] 69 | } 70 | tracks/1/type = "value" 71 | tracks/1/imported = false 72 | tracks/1/enabled = true 73 | tracks/1/path = NodePath("../LightConeRoot2:rotation") 74 | tracks/1/interp = 1 75 | tracks/1/loop_wrap = true 76 | tracks/1/keys = { 77 | "times": PackedFloat32Array(0, 10), 78 | "transitions": PackedFloat32Array(1, 1), 79 | "update": 0, 80 | "values": [0.0, -6.28319] 81 | } 82 | 83 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_620q4"] 84 | _data = { 85 | "RESET": SubResource("Animation_c4cd2"), 86 | "rotate": SubResource("Animation_lk08a") 87 | } 88 | 89 | [sub_resource type="CircleShape2D" id="CircleShape2D_3y2f2"] 90 | resource_local_to_scene = true 91 | radius = 139.743 92 | 93 | [node name="LevelExit" type="Node2D" groups=["level_exit"]] 94 | script = ExtResource("1_d4hnl") 95 | 96 | [node name="LightConeRoot" type="Node2D" parent="."] 97 | rotation = 0.628208 98 | 99 | [node name="LightCone" parent="LightConeRoot" instance=ExtResource("1_2r5cp")] 100 | material = SubResource("ShaderMaterial_vt64g") 101 | position = Vector2(139.729, 1.61257) 102 | scale = Vector2(64, 256) 103 | 104 | [node name="LightCone2" parent="LightConeRoot" instance=ExtResource("1_2r5cp")] 105 | material = SubResource("ShaderMaterial_1phdr") 106 | position = Vector2(-71.8767, 113.349) 107 | rotation = 0.541052 108 | scale = Vector2(64, 256) 109 | 110 | [node name="LightCone3" parent="LightConeRoot" instance=ExtResource("1_2r5cp")] 111 | material = SubResource("ShaderMaterial_agrg4") 112 | position = Vector2(-67.2145, -120.342) 113 | rotation = 2.61799 114 | scale = Vector2(64, 256) 115 | 116 | [node name="LightConeRoot2" type="Node2D" parent="."] 117 | rotation = 4.40341 118 | 119 | [node name="LightCone" parent="LightConeRoot2" instance=ExtResource("1_2r5cp")] 120 | material = SubResource("ShaderMaterial_53722") 121 | position = Vector2(85.8969, 2.13569) 122 | scale = Vector2(32, 150) 123 | 124 | [node name="LightCone2" parent="LightConeRoot2" instance=ExtResource("1_2r5cp")] 125 | material = SubResource("ShaderMaterial_0mfx8") 126 | position = Vector2(-49.7137, 73.8475) 127 | rotation = 0.541052 128 | scale = Vector2(32, 150) 129 | 130 | [node name="LightCone3" parent="LightConeRoot2" instance=ExtResource("1_2r5cp")] 131 | material = SubResource("ShaderMaterial_1e14y") 132 | position = Vector2(-42.8128, -74.9953) 133 | rotation = 2.61799 134 | scale = Vector2(32, 150) 135 | 136 | [node name="ExitSign" type="Sprite2D" parent="."] 137 | scale = Vector2(0.238224, 0.238224) 138 | texture = ExtResource("3_khddq") 139 | 140 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 141 | root_node = NodePath("../LightConeRoot") 142 | libraries = { 143 | "": SubResource("AnimationLibrary_620q4") 144 | } 145 | 146 | [node name="DetectionArea" type="Area2D" parent="."] 147 | unique_name_in_owner = true 148 | collision_layer = 0 149 | monitoring = false 150 | monitorable = false 151 | script = ExtResource("5_osvk3") 152 | 153 | [node name="CollisionShape2D" type="CollisionShape2D" parent="DetectionArea"] 154 | shape = SubResource("CircleShape2D_3y2f2") 155 | 156 | [connection signal="ready" from="." to="AnimationPlayer" method="play" binds= ["rotate"]] 157 | [connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"] 158 | -------------------------------------------------------------------------------- /levels/level.gd: -------------------------------------------------------------------------------- 1 | class_name Level 2 | extends Node2D 3 | 4 | signal exit_reached(next_level:String) 5 | 6 | # We don't export a packed scene here to avoid circular references between scenes. 7 | @export_file("*.tscn") var next_level:String 8 | 9 | func _ready(): 10 | if exit_reached.get_connections().size() == 0: 11 | push_warning(get_path(), " exit_reached is not connected, this doesn't look right.") 12 | 13 | var level_exits = get_tree().get_nodes_in_group("level_exit") 14 | for exit in level_exits: 15 | exit.exit_reached.connect(_on_exit_reached) 16 | 17 | func _on_exit_reached(): 18 | exit_reached.emit(next_level) 19 | 20 | -------------------------------------------------------------------------------- /levels/level_0.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=3 uid="uid://bcurds8d7gm8l"] 2 | 3 | [ext_resource type="Script" path="res://levels/level.gd" id="1_efrk8"] 4 | [ext_resource type="Texture2D" uid="uid://ctd44e5ku42cy" path="res://shared_assets/one_pixel.png" id="2_cuyle"] 5 | [ext_resource type="PackedScene" uid="uid://cf0svghvb3d78" path="res://spawn_point/spawn_point.tscn" id="3_stnc2"] 6 | [ext_resource type="PackedScene" uid="uid://mk3lbwd4lpb5" path="res://plankton/plankton.tscn" id="3_uxxkp"] 7 | [ext_resource type="PackedScene" uid="uid://b5qgw8ps41nq2" path="res://camera/camera.tscn" id="4_hvit4"] 8 | [ext_resource type="PackedScene" uid="uid://6ydtqgm8mfqb" path="res://level_exit/level_exit.tscn" id="6_8t02t"] 9 | 10 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_6sotd"] 11 | size = Vector2(4764, 135) 12 | 13 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_8khbb"] 14 | size = Vector2(184, 1279.5) 15 | 16 | [node name="Level0" type="Node2D" groups=["level"]] 17 | script = ExtResource("1_efrk8") 18 | next_level = "res://levels/level_1.tscn" 19 | 20 | [node name="BG" type="Sprite2D" parent="."] 21 | modulate = Color(0.0470588, 0.00392157, 0.117647, 1) 22 | position = Vector2(2005, 586) 23 | scale = Vector2(5000, 2000) 24 | texture = ExtResource("2_cuyle") 25 | metadata/_edit_lock_ = true 26 | 27 | [node name="SpawnPoint" parent="." instance=ExtResource("3_stnc2")] 28 | position = Vector2(230, 563) 29 | 30 | [node name="Plankton" parent="." instance=ExtResource("3_uxxkp")] 31 | visible = true 32 | position = Vector2(1981, 602) 33 | amount = 500 34 | visibility_rect = Rect2(-2500, -750, 5000, 1500) 35 | 36 | [node name="UpperBlock" type="StaticBody2D" parent="."] 37 | collision_layer = 2 38 | collision_mask = 7 39 | 40 | [node name="CollisionShape2D" type="CollisionShape2D" parent="UpperBlock"] 41 | position = Vector2(2001, -57.5) 42 | shape = SubResource("RectangleShape2D_6sotd") 43 | 44 | [node name="LowerBlock" type="StaticBody2D" parent="."] 45 | collision_layer = 2 46 | collision_mask = 7 47 | 48 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LowerBlock"] 49 | position = Vector2(1997, 1150) 50 | shape = SubResource("RectangleShape2D_6sotd") 51 | 52 | [node name="LeftBlock" type="StaticBody2D" parent="."] 53 | collision_layer = 2 54 | collision_mask = 7 55 | 56 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LeftBlock"] 57 | position = Vector2(-101, 566) 58 | shape = SubResource("RectangleShape2D_8khbb") 59 | 60 | [node name="RightBlock" type="StaticBody2D" parent="."] 61 | collision_layer = 2 62 | collision_mask = 7 63 | 64 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RightBlock"] 65 | position = Vector2(1929, 537) 66 | shape = SubResource("RectangleShape2D_8khbb") 67 | 68 | [node name="Camera" parent="." instance=ExtResource("4_hvit4")] 69 | 70 | [node name="LevelExit" parent="." instance=ExtResource("6_8t02t")] 71 | position = Vector2(1584, 550) 72 | -------------------------------------------------------------------------------- /levels/level_1.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=3 uid="uid://co3jn3ck5jb1m"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://ctd44e5ku42cy" path="res://shared_assets/one_pixel.png" id="1_336jh"] 4 | [ext_resource type="Script" path="res://levels/level.gd" id="1_b70ol"] 5 | [ext_resource type="PackedScene" uid="uid://mk3lbwd4lpb5" path="res://plankton/plankton.tscn" id="2_bjax7"] 6 | [ext_resource type="PackedScene" uid="uid://b5qgw8ps41nq2" path="res://camera/camera.tscn" id="3_4ru7x"] 7 | [ext_resource type="PackedScene" uid="uid://cf0svghvb3d78" path="res://spawn_point/spawn_point.tscn" id="3_d0oof"] 8 | [ext_resource type="PackedScene" uid="uid://6ydtqgm8mfqb" path="res://level_exit/level_exit.tscn" id="5_12l3p"] 9 | [ext_resource type="PackedScene" uid="uid://b67uxcsmxaqqi" path="res://fish/fish.tscn" id="7_bywpx"] 10 | 11 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_6sotd"] 12 | size = Vector2(4764, 135) 13 | 14 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_8khbb"] 15 | size = Vector2(184, 1279.5) 16 | 17 | [node name="Level1" type="Node2D" groups=["level"]] 18 | script = ExtResource("1_b70ol") 19 | next_level = "res://levels/level_3.tscn" 20 | 21 | [node name="BG" type="Sprite2D" parent="."] 22 | modulate = Color(0.0470588, 0.00392157, 0.117647, 1) 23 | position = Vector2(2005, 586) 24 | scale = Vector2(5000, 2000) 25 | texture = ExtResource("1_336jh") 26 | metadata/_edit_lock_ = true 27 | 28 | [node name="SpawnPoint" parent="." instance=ExtResource("3_d0oof")] 29 | position = Vector2(230, 563) 30 | 31 | [node name="Plankton" parent="." instance=ExtResource("2_bjax7")] 32 | visible = true 33 | position = Vector2(1981, 602) 34 | amount = 500 35 | visibility_rect = Rect2(-2500, -750, 5000, 1500) 36 | 37 | [node name="UpperBlock" type="StaticBody2D" parent="."] 38 | collision_layer = 2 39 | collision_mask = 7 40 | 41 | [node name="CollisionShape2D" type="CollisionShape2D" parent="UpperBlock"] 42 | position = Vector2(2001, -57.5) 43 | shape = SubResource("RectangleShape2D_6sotd") 44 | 45 | [node name="LowerBlock" type="StaticBody2D" parent="."] 46 | collision_layer = 2 47 | collision_mask = 7 48 | 49 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LowerBlock"] 50 | position = Vector2(1997, 1150) 51 | shape = SubResource("RectangleShape2D_6sotd") 52 | 53 | [node name="LeftBlock" type="StaticBody2D" parent="."] 54 | collision_layer = 2 55 | collision_mask = 7 56 | 57 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LeftBlock"] 58 | position = Vector2(-101, 566) 59 | shape = SubResource("RectangleShape2D_8khbb") 60 | 61 | [node name="RightBlock" type="StaticBody2D" parent="."] 62 | collision_layer = 2 63 | collision_mask = 7 64 | 65 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RightBlock"] 66 | position = Vector2(1929, 537) 67 | shape = SubResource("RectangleShape2D_8khbb") 68 | 69 | [node name="Camera" parent="." instance=ExtResource("3_4ru7x")] 70 | 71 | [node name="LevelExit" parent="." instance=ExtResource("5_12l3p")] 72 | position = Vector2(1584, 550) 73 | 74 | [node name="Fish" parent="." instance=ExtResource("7_bywpx")] 75 | position = Vector2(877, 155) 76 | 77 | [node name="Fish2" parent="." instance=ExtResource("7_bywpx")] 78 | position = Vector2(1562, 888) 79 | -------------------------------------------------------------------------------- /levels/level_3.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=3 uid="uid://bmm7xu23dst2b"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://ctd44e5ku42cy" path="res://shared_assets/one_pixel.png" id="1_336jh"] 4 | [ext_resource type="Script" path="res://levels/level.gd" id="1_b70ol"] 5 | [ext_resource type="PackedScene" uid="uid://mk3lbwd4lpb5" path="res://plankton/plankton.tscn" id="2_bjax7"] 6 | [ext_resource type="PackedScene" uid="uid://b5qgw8ps41nq2" path="res://camera/camera.tscn" id="3_4ru7x"] 7 | [ext_resource type="PackedScene" uid="uid://cf0svghvb3d78" path="res://spawn_point/spawn_point.tscn" id="3_d0oof"] 8 | [ext_resource type="PackedScene" uid="uid://dwxl8elj3d5gj" path="res://tower/tower.tscn" id="4_6h1cn"] 9 | [ext_resource type="PackedScene" uid="uid://6ydtqgm8mfqb" path="res://level_exit/level_exit.tscn" id="5_12l3p"] 10 | 11 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_6sotd"] 12 | size = Vector2(4764, 135) 13 | 14 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_8khbb"] 15 | size = Vector2(184, 1279.5) 16 | 17 | [node name="Level3" type="Node2D" groups=["level"]] 18 | script = ExtResource("1_b70ol") 19 | next_level = "res://levels/level_4.tscn" 20 | 21 | [node name="BG" type="Sprite2D" parent="."] 22 | modulate = Color(0.0470588, 0.00392157, 0.117647, 1) 23 | position = Vector2(2005, 586) 24 | scale = Vector2(5000, 2000) 25 | texture = ExtResource("1_336jh") 26 | metadata/_edit_lock_ = true 27 | 28 | [node name="SpawnPoint" parent="." instance=ExtResource("3_d0oof")] 29 | position = Vector2(252, 501) 30 | 31 | [node name="Plankton" parent="." instance=ExtResource("2_bjax7")] 32 | visible = true 33 | position = Vector2(1981, 602) 34 | amount = 500 35 | visibility_rect = Rect2(-2500, -750, 5000, 1500) 36 | 37 | [node name="UpperBlock" type="StaticBody2D" parent="."] 38 | collision_layer = 2 39 | collision_mask = 7 40 | 41 | [node name="CollisionShape2D" type="CollisionShape2D" parent="UpperBlock"] 42 | position = Vector2(2001, -57.5) 43 | shape = SubResource("RectangleShape2D_6sotd") 44 | 45 | [node name="LowerBlock" type="StaticBody2D" parent="."] 46 | collision_layer = 2 47 | collision_mask = 7 48 | 49 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LowerBlock"] 50 | position = Vector2(1997, 1150) 51 | shape = SubResource("RectangleShape2D_6sotd") 52 | 53 | [node name="LeftBlock" type="StaticBody2D" parent="."] 54 | collision_layer = 2 55 | collision_mask = 7 56 | 57 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LeftBlock"] 58 | position = Vector2(-101, 566) 59 | shape = SubResource("RectangleShape2D_8khbb") 60 | 61 | [node name="RightBlock" type="StaticBody2D" parent="."] 62 | collision_layer = 2 63 | collision_mask = 7 64 | 65 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RightBlock"] 66 | position = Vector2(4125, 577) 67 | shape = SubResource("RectangleShape2D_8khbb") 68 | 69 | [node name="Camera" parent="." instance=ExtResource("3_4ru7x")] 70 | 71 | [node name="Tower2" parent="." instance=ExtResource("4_6h1cn")] 72 | position = Vector2(1070, 807) 73 | rotation = -2.82683 74 | 75 | [node name="Tower3" parent="." instance=ExtResource("4_6h1cn")] 76 | position = Vector2(2390, 215) 77 | rotation = -3.523 78 | 79 | [node name="LevelExit" parent="." instance=ExtResource("5_12l3p")] 80 | position = Vector2(3916, 567) 81 | -------------------------------------------------------------------------------- /levels/level_4.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=3 uid="uid://b5l7tnoo1r7qq"] 2 | 3 | [ext_resource type="Script" path="res://levels/level.gd" id="1_qhwon"] 4 | [ext_resource type="Texture2D" uid="uid://ctd44e5ku42cy" path="res://shared_assets/one_pixel.png" id="2_1r415"] 5 | [ext_resource type="PackedScene" uid="uid://mk3lbwd4lpb5" path="res://plankton/plankton.tscn" id="3_jr5gj"] 6 | [ext_resource type="PackedScene" uid="uid://b5qgw8ps41nq2" path="res://camera/camera.tscn" id="4_j71he"] 7 | [ext_resource type="PackedScene" uid="uid://cf0svghvb3d78" path="res://spawn_point/spawn_point.tscn" id="4_uy45k"] 8 | [ext_resource type="PackedScene" uid="uid://dwxl8elj3d5gj" path="res://tower/tower.tscn" id="5_rtpor"] 9 | [ext_resource type="PackedScene" uid="uid://6ydtqgm8mfqb" path="res://level_exit/level_exit.tscn" id="6_3wqtm"] 10 | 11 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_6sotd"] 12 | size = Vector2(4764, 135) 13 | 14 | [sub_resource type="RectangleShape2D" id="RectangleShape2D_8khbb"] 15 | size = Vector2(184, 1759.38) 16 | 17 | [node name="Level4" type="Node2D" groups=["level"]] 18 | script = ExtResource("1_qhwon") 19 | next_level = "res://levels/level_0.tscn" 20 | 21 | [node name="BG" type="Sprite2D" parent="."] 22 | modulate = Color(0.0470588, 0.0666667, 0, 1) 23 | position = Vector2(2005, 586) 24 | scale = Vector2(5000, 2000) 25 | texture = ExtResource("2_1r415") 26 | metadata/_edit_lock_ = true 27 | 28 | [node name="Plankton" parent="." instance=ExtResource("3_jr5gj")] 29 | visible = true 30 | position = Vector2(1981, 602) 31 | amount = 500 32 | visibility_rect = Rect2(-2500, -750, 5000, 1500) 33 | 34 | [node name="UpperBlock" type="StaticBody2D" parent="."] 35 | collision_layer = 2 36 | collision_mask = 7 37 | 38 | [node name="CollisionShape2D" type="CollisionShape2D" parent="UpperBlock"] 39 | position = Vector2(2005, -358) 40 | shape = SubResource("RectangleShape2D_6sotd") 41 | 42 | [node name="LowerBlock" type="StaticBody2D" parent="."] 43 | collision_layer = 2 44 | collision_mask = 7 45 | 46 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LowerBlock"] 47 | position = Vector2(1997, 1505) 48 | shape = SubResource("RectangleShape2D_6sotd") 49 | 50 | [node name="LeftBlock" type="StaticBody2D" parent="."] 51 | collision_layer = 2 52 | collision_mask = 7 53 | 54 | [node name="CollisionShape2D" type="CollisionShape2D" parent="LeftBlock"] 55 | position = Vector2(-231, 550) 56 | shape = SubResource("RectangleShape2D_8khbb") 57 | 58 | [node name="RightBlock" type="StaticBody2D" parent="."] 59 | collision_layer = 2 60 | collision_mask = 7 61 | 62 | [node name="CollisionShape2D" type="CollisionShape2D" parent="RightBlock"] 63 | position = Vector2(4404, 511) 64 | shape = SubResource("RectangleShape2D_8khbb") 65 | 66 | [node name="SpawnPoint" parent="." instance=ExtResource("4_uy45k")] 67 | position = Vector2(315, 545) 68 | 69 | [node name="Camera" parent="." instance=ExtResource("4_j71he")] 70 | limit_top = -400 71 | limit_right = 4400 72 | limit_bottom = 1500 73 | 74 | [node name="LevelExit" parent="." instance=ExtResource("6_3wqtm")] 75 | position = Vector2(4192, 593) 76 | 77 | [node name="Tower" parent="." instance=ExtResource("5_rtpor")] 78 | position = Vector2(3537, -89) 79 | 80 | [node name="Tower2" parent="." instance=ExtResource("5_rtpor")] 81 | position = Vector2(3209, 243) 82 | 83 | [node name="Tower3" parent="." instance=ExtResource("5_rtpor")] 84 | position = Vector2(1742, 643) 85 | 86 | [node name="Tower4" parent="." instance=ExtResource("5_rtpor")] 87 | position = Vector2(3290, 1110) 88 | 89 | [node name="Tower5" parent="." instance=ExtResource("5_rtpor")] 90 | position = Vector2(3728, 1302) 91 | -------------------------------------------------------------------------------- /light_cone/cone.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform vec4 modulate:source_color = vec4(1.0); 4 | 5 | render_mode blend_add; 6 | 7 | void fragment() { 8 | 9 | float x_mask = 0.5 * UV.y - distance(0.5, UV.x); 10 | float x_component = clamp(smoothstep(0., 0.05, x_mask), 0., 1.); 11 | float y_component = 1. - UV.y; 12 | 13 | float sine_component1 = sin(10. * UV.x + 2.4*TIME); 14 | float sine_component2 = cos(-7.28 * UV.x + 1.3 * TIME); 15 | 16 | float sine_component = sine_component1 + sine_component2; 17 | 18 | 19 | COLOR=vec4(1.,1.,1., x_component * y_component + (0.2 * x_component * y_component * sine_component)) * modulate; 20 | } 21 | -------------------------------------------------------------------------------- /light_cone/light_cone.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=3 uid="uid://d11vwcdb8m2ko"] 2 | 3 | [ext_resource type="Shader" path="res://light_cone/cone.gdshader" id="1_bua8e"] 4 | [ext_resource type="Texture2D" uid="uid://ctd44e5ku42cy" path="res://shared_assets/one_pixel.png" id="2_fy2ly"] 5 | 6 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_gaq3j"] 7 | resource_local_to_scene = true 8 | shader = ExtResource("1_bua8e") 9 | shader_parameter/modulate = Color(1, 1, 1, 1) 10 | 11 | [node name="LightCone" type="Sprite2D"] 12 | material = SubResource("ShaderMaterial_gaq3j") 13 | position = Vector2(234, 0) 14 | rotation = -1.5708 15 | scale = Vector2(256, 512) 16 | texture = ExtResource("2_fy2ly") 17 | -------------------------------------------------------------------------------- /movement_particles/bubble.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/movement_particles/bubble.png -------------------------------------------------------------------------------- /movement_particles/bubble.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bjty65c32jxhq" 6 | path="res://.godot/imported/bubble.png-d11c4649c8c1950f97a956ff50002058.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://movement_particles/bubble.png" 14 | dest_files=["res://.godot/imported/bubble.png-d11c4649c8c1950f97a956ff50002058.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 | -------------------------------------------------------------------------------- /movement_particles/movement_particles.gd: -------------------------------------------------------------------------------- 1 | @tool 2 | 3 | extends GPUParticles2D 4 | 5 | @export var size_min:float = 0.02: 6 | set(value): 7 | size_min = value 8 | _apply_values() 9 | 10 | @export var size_max:float = 0.05: 11 | set(value): 12 | size_max = value 13 | _apply_values() 14 | 15 | 16 | func _ready(): 17 | _apply_values() 18 | 19 | func _apply_values(): 20 | process_material.scale_min = size_min 21 | process_material.scale_max = size_max 22 | -------------------------------------------------------------------------------- /movement_particles/movement_particles.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=10 format=3 uid="uid://dvihgltjn3xty"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://bjty65c32jxhq" path="res://movement_particles/bubble.png" id="1_80w1h"] 4 | [ext_resource type="Script" path="res://movement_particles/movement_particles.gd" id="2_606yj"] 5 | 6 | [sub_resource type="Gradient" id="Gradient_ot4t6"] 7 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 8 | 9 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_p1afh"] 10 | gradient = SubResource("Gradient_ot4t6") 11 | 12 | [sub_resource type="Curve" id="Curve_8yyqn"] 13 | min_value = -200.0 14 | max_value = 200.0 15 | _data = [Vector2(0.00598802, 200), 0.0, -30.2536, 0, 0, Vector2(0.431138, -12.5), 0.0, 0.0, 0, 0] 16 | point_count = 2 17 | 18 | [sub_resource type="CurveTexture" id="CurveTexture_417bp"] 19 | curve = SubResource("Curve_8yyqn") 20 | 21 | [sub_resource type="Curve" id="Curve_gb13t"] 22 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 23 | point_count = 2 24 | 25 | [sub_resource type="CurveTexture" id="CurveTexture_d41hf"] 26 | curve = SubResource("Curve_gb13t") 27 | 28 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_ph3u3"] 29 | resource_local_to_scene = true 30 | lifetime_randomness = 0.33 31 | particle_flag_disable_z = true 32 | direction = Vector3(-1, 0, 0) 33 | spread = 63.23 34 | gravity = Vector3(0, 0, 0) 35 | initial_velocity_min = 10.53 36 | initial_velocity_max = 52.64 37 | angular_velocity_min = -181.89 38 | angular_velocity_max = 227.37 39 | orbit_velocity_min = 0.0 40 | orbit_velocity_max = 0.0 41 | linear_accel_curve = SubResource("CurveTexture_417bp") 42 | scale_min = 0.02 43 | scale_max = 0.05 44 | scale_curve = SubResource("CurveTexture_d41hf") 45 | color_ramp = SubResource("GradientTexture1D_p1afh") 46 | 47 | [node name="MovementParticles" type="GPUParticles2D"] 48 | amount = 50 49 | process_material = SubResource("ParticleProcessMaterial_ph3u3") 50 | texture = ExtResource("1_80w1h") 51 | lifetime = 2.0 52 | fixed_fps = 60 53 | script = ExtResource("2_606yj") 54 | -------------------------------------------------------------------------------- /pause_screen/grayscale_postprocess.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest; 4 | 5 | void fragment() { 6 | // read in the current pixel 7 | vec4 input = texture(screen_texture, SCREEN_UV); 8 | // convert color to grayscale using CIE 1931 linear luminance 9 | // https://en.wikipedia.org/wiki/Grayscale 10 | float col = (input.r * 0.2126 + input.g * 0.7152 + input.b * 0.0722); 11 | // dim color a bit down 12 | col *= 0.7; 13 | // and write new pixel 14 | COLOR = vec4(col, col, col, 1); 15 | } -------------------------------------------------------------------------------- /pause_screen/pause_screen.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=3 uid="uid://dk3cdmmwsb57l"] 2 | 3 | [ext_resource type="Shader" path="res://pause_screen/grayscale_postprocess.gdshader" id="1_djina"] 4 | [ext_resource type="Texture2D" uid="uid://ctd44e5ku42cy" path="res://shared_assets/one_pixel.png" id="2_yuttr"] 5 | [ext_resource type="FontFile" uid="uid://cx27tcro6r2q7" path="res://shared_assets/art_deco_font.ttf" id="3_j685c"] 6 | 7 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_vp03q"] 8 | shader = ExtResource("1_djina") 9 | 10 | [node name="PauseScreen" type="MarginContainer"] 11 | anchors_preset = 15 12 | anchor_right = 1.0 13 | anchor_bottom = 1.0 14 | grow_horizontal = 2 15 | grow_vertical = 2 16 | 17 | [node name="Panel" type="TextureRect" parent="."] 18 | material = SubResource("ShaderMaterial_vp03q") 19 | layout_mode = 2 20 | texture = ExtResource("2_yuttr") 21 | 22 | [node name="PauseLabel" type="Label" parent="."] 23 | unique_name_in_owner = true 24 | layout_mode = 2 25 | theme_override_colors/font_outline_color = Color(0, 0, 0, 1) 26 | theme_override_constants/outline_size = 20 27 | theme_override_fonts/font = ExtResource("3_j685c") 28 | theme_override_font_sizes/font_size = 127 29 | text = "Paused" 30 | horizontal_alignment = 1 31 | -------------------------------------------------------------------------------- /plankton/plankton.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=3 uid="uid://mk3lbwd4lpb5"] 2 | 3 | [ext_resource type="Texture2D" uid="uid://b3y4hicwkv762" path="res://plankton/plankton_blob.png" id="1_l4iwk"] 4 | 5 | [sub_resource type="Gradient" id="Gradient_3xtk0"] 6 | offsets = PackedFloat32Array(0, 0.292593, 1) 7 | colors = PackedColorArray(0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0) 8 | 9 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_nunsy"] 10 | gradient = SubResource("Gradient_3xtk0") 11 | 12 | [sub_resource type="Curve" id="Curve_qfk8w"] 13 | _data = [Vector2(0.0239521, 0.598958), 0.0, 0.0, 0, 0, Vector2(0.311377, 0.953125), 0.0, 0.0, 0, 0, Vector2(0.736527, 0.838542), 0.0, 0.0, 0, 0, Vector2(0.988024, 0.369792), 0.0, 0.0, 0, 0] 14 | point_count = 4 15 | 16 | [sub_resource type="CurveTexture" id="CurveTexture_ekwfx"] 17 | curve = SubResource("Curve_qfk8w") 18 | 19 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_gukr5"] 20 | emission_shape = 3 21 | emission_box_extents = Vector3(5000, 1500, 0) 22 | particle_flag_disable_z = true 23 | gravity = Vector3(0, 0, 0) 24 | initial_velocity_min = -3.0 25 | initial_velocity_max = 8.0 26 | angular_velocity_min = -8.0 27 | angular_velocity_max = 9.19 28 | orbit_velocity_min = -0.01 29 | orbit_velocity_max = 0.01 30 | damping_min = 1.053 31 | damping_max = 8.421 32 | scale_min = 0.4 33 | scale_max = 1.9 34 | scale_curve = SubResource("CurveTexture_ekwfx") 35 | color_ramp = SubResource("GradientTexture1D_nunsy") 36 | turbulence_enabled = true 37 | turbulence_noise_strength = 0.0 38 | turbulence_influence_min = 0.38 39 | turbulence_influence_max = 0.38 40 | turbulence_initial_displacement_min = -6.3 41 | turbulence_initial_displacement_max = 27.4 42 | 43 | [node name="Plankton" type="GPUParticles2D"] 44 | process_mode = 3 45 | visible = false 46 | position = Vector2(961, 545) 47 | amount = 150 48 | process_material = SubResource("ParticleProcessMaterial_gukr5") 49 | texture = ExtResource("1_l4iwk") 50 | lifetime = 13.0 51 | preprocess = 5.0 52 | randomness = 0.62 53 | metadata/_edit_lock_ = true 54 | -------------------------------------------------------------------------------- /plankton/plankton_blob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/plankton/plankton_blob.png -------------------------------------------------------------------------------- /plankton/plankton_blob.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://b3y4hicwkv762" 6 | path="res://.godot/imported/plankton_blob.png-c04ab156edf9d1e3c507962866435344.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://plankton/plankton_blob.png" 14 | dest_files=["res://.godot/imported/plankton_blob.png-c04ab156edf9d1e3c507962866435344.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 | -------------------------------------------------------------------------------- /player/health_indicator.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform float health_percent = 1.0f; 4 | 5 | void fragment() { 6 | vec4 texture_color = texture(TEXTURE, UV); 7 | vec4 damaged_color = texture_color * vec4(1.0, 0.3, 0.3, 0.5 + abs(sin(TIME * 2.0)) * 0.5); 8 | 9 | COLOR = texture_color * (1.0 - step(health_percent, UV.x)) + 10 | damaged_color * (step(health_percent, UV.x)); 11 | } 12 | -------------------------------------------------------------------------------- /player/player.gd: -------------------------------------------------------------------------------- 1 | class_name Player 2 | extends CharacterBody2D 3 | 4 | signal died() 5 | 6 | @export var speed:float = 200 7 | @export var rotation_speed:float = PI 8 | @export var max_health:float = 100 9 | @export var cooldown:float = 0.9 10 | @export var ammo:PackedScene 11 | @export var world_root:NodePath 12 | 13 | @onready var _projectile_spawn_point:Node2D = %ProjectileSpawnPoint 14 | @onready var _sprite:Sprite2D = %Sprite2D 15 | 16 | @onready var health:float = max_health: 17 | set(value): 18 | health = value 19 | _refresh_health() 20 | 21 | var _dying = false 22 | var _cooldown:float = 0 23 | var _world_root:Node2D = null 24 | 25 | func _ready(): 26 | _world_root = get_node(world_root) 27 | _refresh_health() 28 | 29 | func take_damage(damage:float): 30 | if _dying: 31 | return 32 | 33 | health = max(0, health-damage) 34 | 35 | if health <= 0: 36 | _explode() 37 | 38 | func _explode(): 39 | _dying = true 40 | died.emit() 41 | queue_free() 42 | 43 | func _refresh_health(): 44 | if is_instance_valid(_sprite): 45 | _sprite.material.set_shader_parameter("health_percent", health / max_health) 46 | 47 | 48 | func _physics_process(delta): 49 | _cooldown -= delta 50 | 51 | var horizontal = Input.get_axis("left", "right") 52 | var vertical = Input.get_axis("up", "down") 53 | 54 | velocity = Vector2(horizontal, vertical) * speed 55 | move_and_slide() 56 | 57 | if Input.is_action_pressed("fire") and _cooldown <= 0: 58 | # reset cooldown 59 | _cooldown = cooldown 60 | var new_projectile = ammo.instantiate() 61 | new_projectile.global_position = _projectile_spawn_point.global_position 62 | new_projectile.direction = _projectile_spawn_point.global_position - global_position 63 | _world_root.add_child(new_projectile) 64 | 65 | 66 | -------------------------------------------------------------------------------- /player/player.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=16 format=3 uid="uid://dt42gugmqq28j"] 2 | 3 | [ext_resource type="Script" path="res://player/player.gd" id="1_out1h"] 4 | [ext_resource type="Texture2D" uid="uid://dquwarjqwltkk" path="res://player/submarine.png" id="2_jwb4p"] 5 | [ext_resource type="PackedScene" uid="uid://bt11i1i0lkiw3" path="res://player_projectile/projectile.tscn" id="2_ss62b"] 6 | [ext_resource type="Shader" path="res://player/health_indicator.gdshader" id="3_1j2au"] 7 | [ext_resource type="PackedScene" uid="uid://dvihgltjn3xty" path="res://movement_particles/movement_particles.tscn" id="3_h342r"] 8 | [ext_resource type="Script" path="res://fish/shader_control.gd" id="5_qguf3"] 9 | 10 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_y1fc3"] 11 | shader = ExtResource("3_1j2au") 12 | shader_parameter/health_percent = 0.395 13 | 14 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_k227k"] 15 | radius = 49.75 16 | height = 242.5 17 | 18 | [sub_resource type="Gradient" id="Gradient_ot4t6"] 19 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 20 | 21 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_p1afh"] 22 | gradient = SubResource("Gradient_ot4t6") 23 | 24 | [sub_resource type="Curve" id="Curve_8yyqn"] 25 | min_value = -200.0 26 | max_value = 200.0 27 | _data = [Vector2(0.00598802, 200), 0.0, -30.2536, 0, 0, Vector2(0.431138, -12.5), 0.0, 0.0, 0, 0] 28 | point_count = 2 29 | 30 | [sub_resource type="CurveTexture" id="CurveTexture_417bp"] 31 | curve = SubResource("Curve_8yyqn") 32 | 33 | [sub_resource type="Curve" id="Curve_gb13t"] 34 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 35 | point_count = 2 36 | 37 | [sub_resource type="CurveTexture" id="CurveTexture_d41hf"] 38 | curve = SubResource("Curve_gb13t") 39 | 40 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_d053g"] 41 | resource_local_to_scene = true 42 | lifetime_randomness = 0.33 43 | particle_flag_disable_z = true 44 | direction = Vector3(-1, 0, 0) 45 | spread = 63.23 46 | gravity = Vector3(0, 0, 0) 47 | initial_velocity_min = 10.53 48 | initial_velocity_max = 52.64 49 | angular_velocity_min = -181.89 50 | angular_velocity_max = 227.37 51 | orbit_velocity_min = 0.0 52 | orbit_velocity_max = 0.0 53 | linear_accel_curve = SubResource("CurveTexture_417bp") 54 | scale_min = 0.215 55 | scale_max = 0.315 56 | scale_curve = SubResource("CurveTexture_d41hf") 57 | color_ramp = SubResource("GradientTexture1D_p1afh") 58 | 59 | [node name="Player" type="CharacterBody2D" groups=["camera_target", "player"]] 60 | rotation = 1.5708 61 | script = ExtResource("1_out1h") 62 | max_health = 200.0 63 | ammo = ExtResource("2_ss62b") 64 | 65 | [node name="Sprite2D" type="Sprite2D" parent="."] 66 | unique_name_in_owner = true 67 | material = SubResource("ShaderMaterial_y1fc3") 68 | rotation = -1.5708 69 | scale = Vector2(0.272365, 0.272365) 70 | texture = ExtResource("2_jwb4p") 71 | script = ExtResource("5_qguf3") 72 | 73 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 74 | position = Vector2(-0.75, -1.25) 75 | shape = SubResource("CapsuleShape2D_k227k") 76 | 77 | [node name="MovementParticles" parent="." instance=ExtResource("3_h342r")] 78 | position = Vector2(-0.999603, 109) 79 | rotation = -1.57079 80 | amount = 60 81 | process_material = SubResource("ParticleProcessMaterial_d053g") 82 | size_min = 0.215 83 | size_max = 0.315 84 | 85 | [node name="ProjectileSpawnPoint" type="Marker2D" parent="."] 86 | unique_name_in_owner = true 87 | position = Vector2(0, -160) 88 | -------------------------------------------------------------------------------- /player/submarine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/player/submarine.png -------------------------------------------------------------------------------- /player/submarine.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dquwarjqwltkk" 6 | path.s3tc="res://.godot/imported/submarine.png-fa0f17e41d10546b99002125c1f785cf.s3tc.ctex" 7 | metadata={ 8 | "imported_formats": ["s3tc_bptc"], 9 | "vram_texture": true 10 | } 11 | 12 | [deps] 13 | 14 | source_file="res://player/submarine.png" 15 | dest_files=["res://.godot/imported/submarine.png-fa0f17e41d10546b99002125c1f785cf.s3tc.ctex"] 16 | 17 | [params] 18 | 19 | compress/mode=2 20 | compress/high_quality=false 21 | compress/lossy_quality=0.7 22 | compress/hdr_compression=1 23 | compress/normal_map=0 24 | compress/channel_pack=0 25 | mipmaps/generate=true 26 | mipmaps/limit=-1 27 | roughness/mode=0 28 | roughness/src_normal="" 29 | process/fix_alpha_border=true 30 | process/premult_alpha=false 31 | process/normal_map_invert_y=false 32 | process/hdr_as_srgb=false 33 | process/hdr_clamp_exposure=false 34 | process/size_limit=0 35 | detect_3d/compress_to=0 36 | -------------------------------------------------------------------------------- /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="loading_saving_video" 14 | run/main_scene="res://game.tscn" 15 | config/features=PackedStringArray("4.1", "Forward Plus") 16 | config/icon="res://icon.svg" 17 | 18 | [display] 19 | 20 | window/size/viewport_width=1920 21 | window/size/viewport_height=1080 22 | window/stretch/mode="canvas_items" 23 | window/stretch/aspect="expand" 24 | 25 | [dotnet] 26 | 27 | project/assembly_name="loading_saving_video" 28 | 29 | [editor_plugins] 30 | 31 | enabled=PackedStringArray("res://addons/safe_resource_loader/plugin.cfg") 32 | 33 | [input] 34 | 35 | left={ 36 | "deadzone": 0.5, 37 | "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":65,"physical_keycode":65,"key_label":65,"unicode":97,"echo":false,"script":null) 38 | ] 39 | } 40 | right={ 41 | "deadzone": 0.5, 42 | "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) 43 | ] 44 | } 45 | up={ 46 | "deadzone": 0.5, 47 | "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) 48 | ] 49 | } 50 | down={ 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":83,"key_label":0,"unicode":115,"echo":false,"script":null) 53 | ] 54 | } 55 | rotate_left={ 56 | "deadzone": 0.5, 57 | "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":81,"key_label":0,"unicode":113,"echo":false,"script":null) 58 | ] 59 | } 60 | rotate_right={ 61 | "deadzone": 0.5, 62 | "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":69,"key_label":0,"unicode":101,"echo":false,"script":null) 63 | ] 64 | } 65 | fire={ 66 | "deadzone": 0.5, 67 | "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) 68 | ] 69 | } 70 | pause={ 71 | "deadzone": 0.5, 72 | "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":80,"key_label":0,"unicode":112,"echo":false,"script":null) 73 | ] 74 | } 75 | 76 | [layer_names] 77 | 78 | 2d_physics/layer_1="Player" 79 | 2d_physics/layer_2="Boundaries" 80 | 2d_physics/layer_3="Enemies" 81 | -------------------------------------------------------------------------------- /saved_game/saved_data.gd: -------------------------------------------------------------------------------- 1 | class_name SavedData 2 | extends Resource 3 | 4 | @export var position:Vector2 5 | @export var scene_path:String 6 | -------------------------------------------------------------------------------- /saved_game/saved_game.gd: -------------------------------------------------------------------------------- 1 | class_name SavedGame 2 | extends Resource 3 | 4 | ## Path to the level that was loaded when the game was saved 5 | @export var level_path:String 6 | ## Position of the player 7 | @export var player_position:Vector2 8 | ## Health of the player 9 | @export var player_health:float 10 | ## Saved data for all dynamic parts of the level 11 | @export var saved_data:Array[SavedData] = [] 12 | -------------------------------------------------------------------------------- /saver_loader/saver_loader.gd: -------------------------------------------------------------------------------- 1 | class_name SaverLoader 2 | extends Node 3 | 4 | @onready var _player:Player = %Player as Player 5 | @onready var _world_root:WorldRoot = %WorldRoot 6 | 7 | 8 | func save_game(): 9 | var saved_game := SavedGame.new() 10 | 11 | # save the path to the currently loaded level 12 | saved_game.level_path = _world_root.get_current_level_path() 13 | 14 | # store player health and position 15 | saved_game.player_health = _player.health 16 | saved_game.player_position = _player.global_position 17 | 18 | # collect all dynamic game elements 19 | var saved_data:Array[SavedData] = [] 20 | get_tree().call_group("game_events", "on_save_game", saved_data) 21 | 22 | # store them in the savegame 23 | saved_game.saved_data = saved_data 24 | 25 | # write the savegame to disk 26 | ResourceSaver.save(saved_game, "user://savegame.tres") 27 | 28 | 29 | 30 | func load_game(): 31 | 32 | # fix any paths that may be broken after a game update 33 | var fixed_path = PathFixer.fix_paths("user://savegame.tres") 34 | print("Loading from ", fixed_path ) 35 | 36 | # load the savegame securely 37 | var saved_game:SavedGame = SafeResourceLoader.load(fixed_path) as SavedGame 38 | if saved_game == null: 39 | return 40 | 41 | # first restore the level 42 | # this may take a few frames, so we wait with await 43 | await _world_root.load_level_async(saved_game.level_path) 44 | 45 | # clear the stage 46 | get_tree().call_group("game_events", "on_before_load_game") 47 | 48 | # restore player position 49 | _player.global_position = saved_game.player_position 50 | # verify & restore player health 51 | _player.health = min(saved_game.player_health, 200) 52 | 53 | # restore all dynamic game elements 54 | for item in saved_game.saved_data: 55 | # skip over data we don't use anymore 56 | if item is UnusedData: 57 | continue 58 | 59 | # load the scene of the saved item and create a new instance 60 | var scene := load(item.scene_path) as PackedScene 61 | var restored_node = scene.instantiate() 62 | # add it to the world root 63 | _world_root.add_child(restored_node) 64 | # and run any custom load logic 65 | if restored_node.has_method("on_load_game"): 66 | restored_node.on_load_game(item) 67 | 68 | 69 | -------------------------------------------------------------------------------- /shared_assets/art_deco_font.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/shared_assets/art_deco_font.ttf -------------------------------------------------------------------------------- /shared_assets/art_deco_font.ttf.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="font_data_dynamic" 4 | type="FontFile" 5 | uid="uid://cx27tcro6r2q7" 6 | path="res://.godot/imported/art_deco_font.ttf-fd196ffaf49dda30577bb57b02bb3890.fontdata" 7 | 8 | [deps] 9 | 10 | source_file="res://shared_assets/art_deco_font.ttf" 11 | dest_files=["res://.godot/imported/art_deco_font.ttf-fd196ffaf49dda30577bb57b02bb3890.fontdata"] 12 | 13 | [params] 14 | 15 | Rendering=null 16 | antialiasing=1 17 | generate_mipmaps=false 18 | multichannel_signed_distance_field=false 19 | msdf_pixel_range=8 20 | msdf_size=48 21 | allow_system_fallback=true 22 | force_autohinter=false 23 | hinting=1 24 | subpixel_positioning=1 25 | oversampling=0.0 26 | Fallbacks=null 27 | fallbacks=[] 28 | Compress=null 29 | compress=true 30 | preload=[] 31 | language_support={} 32 | script_support={} 33 | opentype_features={} 34 | -------------------------------------------------------------------------------- /shared_assets/one_pixel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/shared_assets/one_pixel.png -------------------------------------------------------------------------------- /shared_assets/one_pixel.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://ctd44e5ku42cy" 6 | path="res://.godot/imported/one_pixel.png-82761f8e2af52106c24a4e6fcd6fa5cc.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://shared_assets/one_pixel.png" 14 | dest_files=["res://.godot/imported/one_pixel.png-82761f8e2af52106c24a4e6fcd6fa5cc.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 | -------------------------------------------------------------------------------- /shared_assets/ui_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/shared_assets/ui_bg.png -------------------------------------------------------------------------------- /shared_assets/ui_bg.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://cbh4neo44fbxb" 6 | path="res://.godot/imported/ui_bg.png-5dbd20327c893588c3e129dc67a30313.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://shared_assets/ui_bg.png" 14 | dest_files=["res://.godot/imported/ui_bg.png-5dbd20327c893588c3e129dc67a30313.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 | -------------------------------------------------------------------------------- /spawn_point/spawn_point.gd: -------------------------------------------------------------------------------- 1 | extends Marker2D 2 | 3 | 4 | func _ready(): 5 | for player in get_tree().get_nodes_in_group("player"): 6 | player.global_position = global_position 7 | 8 | 9 | -------------------------------------------------------------------------------- /spawn_point/spawn_point.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://cf0svghvb3d78"] 2 | 3 | [ext_resource type="Script" path="res://spawn_point/spawn_point.gd" id="1_gfeao"] 4 | 5 | [node name="SpawnPoint" type="Marker2D"] 6 | script = ExtResource("1_gfeao") 7 | -------------------------------------------------------------------------------- /torpedo/saved_torpedo_data.gd: -------------------------------------------------------------------------------- 1 | class_name SavedTorpedoData 2 | extends SavedData 3 | 4 | @export var direction:Vector2 5 | -------------------------------------------------------------------------------- /torpedo/torpedo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/torpedo/torpedo.png -------------------------------------------------------------------------------- /torpedo/torpedo.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://bjxc1tasd5lpt" 6 | path="res://.godot/imported/torpedo.png-d6618dad38c2abaa10c4813ba01d4f43.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://torpedo/torpedo.png" 14 | dest_files=["res://.godot/imported/torpedo.png-d6618dad38c2abaa10c4813ba01d4f43.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 | -------------------------------------------------------------------------------- /torpedo/torpedo.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=20 format=3 uid="uid://bt11i1i0lkiw3"] 2 | 3 | [ext_resource type="Script" path="res://tower_projectile/projectile.gd" id="1_ywxoy"] 4 | [ext_resource type="Texture2D" uid="uid://bjty65c32jxhq" path="res://movement_particles/bubble.png" id="2_r22n7"] 5 | [ext_resource type="Texture2D" uid="uid://bjxc1tasd5lpt" path="res://torpedo/torpedo.png" id="3_pnlwx"] 6 | 7 | [sub_resource type="Gradient" id="Gradient_ot4t6"] 8 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 9 | 10 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_p1afh"] 11 | gradient = SubResource("Gradient_ot4t6") 12 | 13 | [sub_resource type="Curve" id="Curve_8yyqn"] 14 | min_value = -200.0 15 | max_value = 200.0 16 | _data = [Vector2(0.00598802, 200), 0.0, -30.2536, 0, 0, Vector2(0.431138, -12.5), 0.0, 0.0, 0, 0] 17 | point_count = 2 18 | 19 | [sub_resource type="CurveTexture" id="CurveTexture_417bp"] 20 | curve = SubResource("Curve_8yyqn") 21 | 22 | [sub_resource type="Curve" id="Curve_gb13t"] 23 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 24 | point_count = 2 25 | 26 | [sub_resource type="CurveTexture" id="CurveTexture_d41hf"] 27 | curve = SubResource("Curve_gb13t") 28 | 29 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_ph3u3"] 30 | lifetime_randomness = 0.33 31 | particle_flag_disable_z = true 32 | direction = Vector3(-1, 0, 0) 33 | spread = 63.23 34 | gravity = Vector3(0, 0, 0) 35 | initial_velocity_min = 10.53 36 | initial_velocity_max = 52.64 37 | angular_velocity_min = -181.89 38 | angular_velocity_max = 227.37 39 | orbit_velocity_min = 0.0 40 | orbit_velocity_max = 0.0 41 | linear_accel_curve = SubResource("CurveTexture_417bp") 42 | scale_min = 0.05 43 | scale_max = 0.2 44 | scale_curve = SubResource("CurveTexture_d41hf") 45 | color_ramp = SubResource("GradientTexture1D_p1afh") 46 | 47 | [sub_resource type="Animation" id="Animation_h58q7"] 48 | length = 0.001 49 | tracks/0/type = "value" 50 | tracks/0/imported = false 51 | tracks/0/enabled = true 52 | tracks/0/path = NodePath("ExplosionParticles:emitting") 53 | tracks/0/interp = 1 54 | tracks/0/loop_wrap = true 55 | tracks/0/keys = { 56 | "times": PackedFloat32Array(0), 57 | "transitions": PackedFloat32Array(1), 58 | "update": 1, 59 | "values": [false] 60 | } 61 | tracks/1/type = "value" 62 | tracks/1/imported = false 63 | tracks/1/enabled = true 64 | tracks/1/path = NodePath("Ammo:modulate") 65 | tracks/1/interp = 1 66 | tracks/1/loop_wrap = true 67 | tracks/1/keys = { 68 | "times": PackedFloat32Array(0), 69 | "transitions": PackedFloat32Array(1), 70 | "update": 0, 71 | "values": [Color(1, 1, 1, 1)] 72 | } 73 | tracks/2/type = "value" 74 | tracks/2/imported = false 75 | tracks/2/enabled = true 76 | tracks/2/path = NodePath("Ammo:scale") 77 | tracks/2/interp = 1 78 | tracks/2/loop_wrap = true 79 | tracks/2/keys = { 80 | "times": PackedFloat32Array(0), 81 | "transitions": PackedFloat32Array(1), 82 | "update": 0, 83 | "values": [Vector2(0.15, 0.238)] 84 | } 85 | tracks/3/type = "value" 86 | tracks/3/imported = false 87 | tracks/3/enabled = true 88 | tracks/3/path = NodePath("MovementParticles:visible") 89 | tracks/3/interp = 1 90 | tracks/3/loop_wrap = true 91 | tracks/3/keys = { 92 | "times": PackedFloat32Array(0), 93 | "transitions": PackedFloat32Array(1), 94 | "update": 1, 95 | "values": [true] 96 | } 97 | tracks/4/type = "value" 98 | tracks/4/imported = false 99 | tracks/4/enabled = true 100 | tracks/4/path = NodePath("ExplosionParticles:visible") 101 | tracks/4/interp = 1 102 | tracks/4/loop_wrap = true 103 | tracks/4/keys = { 104 | "times": PackedFloat32Array(0), 105 | "transitions": PackedFloat32Array(1), 106 | "update": 1, 107 | "values": [false] 108 | } 109 | 110 | [sub_resource type="Animation" id="Animation_3mbd8"] 111 | resource_name = "explode" 112 | length = 4.0 113 | step = 0.05 114 | tracks/0/type = "value" 115 | tracks/0/imported = false 116 | tracks/0/enabled = true 117 | tracks/0/path = NodePath("ExplosionParticles:emitting") 118 | tracks/0/interp = 1 119 | tracks/0/loop_wrap = true 120 | tracks/0/keys = { 121 | "times": PackedFloat32Array(0), 122 | "transitions": PackedFloat32Array(1), 123 | "update": 1, 124 | "values": [true] 125 | } 126 | tracks/1/type = "value" 127 | tracks/1/imported = false 128 | tracks/1/enabled = true 129 | tracks/1/path = NodePath("Ammo:modulate") 130 | tracks/1/interp = 1 131 | tracks/1/loop_wrap = true 132 | tracks/1/keys = { 133 | "times": PackedFloat32Array(0, 0.1, 0.3, 0.6, 0.9), 134 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1), 135 | "update": 0, 136 | "values": [Color(1, 1, 1, 1), Color(3, 3, 3, 1), Color(1, 1, 1, 1), Color(0, 0, 0, 1), Color(0, 0, 0, 0)] 137 | } 138 | tracks/2/type = "value" 139 | tracks/2/imported = false 140 | tracks/2/enabled = true 141 | tracks/2/path = NodePath("Ammo:scale") 142 | tracks/2/interp = 1 143 | tracks/2/loop_wrap = true 144 | tracks/2/keys = { 145 | "times": PackedFloat32Array(0, 0.4), 146 | "transitions": PackedFloat32Array(1, 1), 147 | "update": 0, 148 | "values": [Vector2(0.15, 0.238), Vector2(0, 0)] 149 | } 150 | tracks/3/type = "value" 151 | tracks/3/imported = false 152 | tracks/3/enabled = true 153 | tracks/3/path = NodePath("MovementParticles:visible") 154 | tracks/3/interp = 1 155 | tracks/3/loop_wrap = true 156 | tracks/3/keys = { 157 | "times": PackedFloat32Array(0), 158 | "transitions": PackedFloat32Array(1), 159 | "update": 1, 160 | "values": [false] 161 | } 162 | tracks/4/type = "value" 163 | tracks/4/imported = false 164 | tracks/4/enabled = true 165 | tracks/4/path = NodePath("ExplosionParticles:visible") 166 | tracks/4/interp = 1 167 | tracks/4/loop_wrap = true 168 | tracks/4/keys = { 169 | "times": PackedFloat32Array(0), 170 | "transitions": PackedFloat32Array(1), 171 | "update": 1, 172 | "values": [true] 173 | } 174 | 175 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_ycy2u"] 176 | _data = { 177 | "RESET": SubResource("Animation_h58q7"), 178 | "explode": SubResource("Animation_3mbd8") 179 | } 180 | 181 | [sub_resource type="Gradient" id="Gradient_5ng1t"] 182 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 183 | 184 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_5b75f"] 185 | gradient = SubResource("Gradient_5ng1t") 186 | 187 | [sub_resource type="Curve" id="Curve_amtu7"] 188 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 189 | point_count = 2 190 | 191 | [sub_resource type="CurveTexture" id="CurveTexture_y6idn"] 192 | curve = SubResource("Curve_amtu7") 193 | 194 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_g7mco"] 195 | lifetime_randomness = 0.33 196 | particle_flag_disable_z = true 197 | direction = Vector3(0, 0, 0) 198 | spread = 180.0 199 | gravity = Vector3(0, 0, 0) 200 | initial_velocity_min = 62.63 201 | initial_velocity_max = 138.59 202 | angular_velocity_min = -181.89 203 | angular_velocity_max = 227.37 204 | orbit_velocity_min = 0.0 205 | orbit_velocity_max = 0.0 206 | scale_min = 0.3 207 | scale_max = 0.5 208 | scale_curve = SubResource("CurveTexture_y6idn") 209 | color_ramp = SubResource("GradientTexture1D_5b75f") 210 | 211 | [sub_resource type="CapsuleShape2D" id="CapsuleShape2D_hxgdy"] 212 | radius = 12.9763 213 | height = 89.9941 214 | 215 | [node name="Ammo" type="Node2D" groups=["game_events"]] 216 | script = ExtResource("1_ywxoy") 217 | 218 | [node name="MovementParticles" type="GPUParticles2D" parent="."] 219 | unique_name_in_owner = true 220 | position = Vector2(-45, 1) 221 | amount = 80 222 | process_material = SubResource("ParticleProcessMaterial_ph3u3") 223 | texture = ExtResource("2_r22n7") 224 | lifetime = 2.0 225 | fixed_fps = 60 226 | 227 | [node name="Ammo" type="Sprite2D" parent="."] 228 | position = Vector2(0, 2) 229 | scale = Vector2(0.15, 0.238) 230 | texture = ExtResource("3_pnlwx") 231 | 232 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 233 | unique_name_in_owner = true 234 | libraries = { 235 | "": SubResource("AnimationLibrary_ycy2u") 236 | } 237 | 238 | [node name="ExplosionParticles" type="GPUParticles2D" parent="."] 239 | unique_name_in_owner = true 240 | visible = false 241 | emitting = false 242 | amount = 250 243 | process_material = SubResource("ParticleProcessMaterial_g7mco") 244 | texture = ExtResource("2_r22n7") 245 | lifetime = 2.0 246 | one_shot = true 247 | explosiveness = 0.88 248 | fixed_fps = 60 249 | 250 | [node name="DetectionArea" type="Area2D" parent="."] 251 | collision_layer = 0 252 | collision_mask = 4 253 | 254 | [node name="CollisionShape2D" type="CollisionShape2D" parent="DetectionArea"] 255 | position = Vector2(0, 1) 256 | rotation = 1.57359 257 | shape = SubResource("CapsuleShape2D_hxgdy") 258 | 259 | [connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"] 260 | -------------------------------------------------------------------------------- /tower/health_indicator.gdshader: -------------------------------------------------------------------------------- 1 | shader_type canvas_item; 2 | 3 | uniform float thickness; 4 | uniform float progress = 1.0; 5 | uniform float opacity = 1.0; 6 | 7 | void fragment() { 8 | float progress_angle_min = progress * PI; 9 | float progress_angle_max = progress * - PI; 10 | float pixel_angle = atan( UV.x - 0.5, UV.y - 0.5); 11 | float pixel_distance = distance(UV, vec2(0.5)); 12 | float outside = 0.5 - 0.003 * sin(50.0 * pixel_angle + TIME); 13 | 14 | float alpha = 1.0; 15 | // everything outside 0.5 is transparent 16 | alpha -= smoothstep(outside-0.01, outside, pixel_distance); 17 | // everything between 0 and 0.5 - thickness is also transparent 18 | alpha -= smoothstep(outside - thickness, outside - 0.01 - thickness, pixel_distance); 19 | // and we only want the pie 20 | if (progress < 1.0) { // this is so we get a fully closed progress bar at 100% 21 | alpha *= 1.0 - smoothstep(0.0, 0.01, pixel_angle - progress_angle_min); 22 | alpha -= 1.0 - smoothstep(0.0, 0.01, pixel_angle - progress_angle_max); 23 | } 24 | 25 | alpha *= 0.2 + smoothstep(outside, outside - thickness, pixel_distance); 26 | alpha *= opacity; 27 | 28 | COLOR.a = alpha; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /tower/saved_tower_data.gd: -------------------------------------------------------------------------------- 1 | class_name SavedTowerData 2 | extends SavedData 3 | 4 | ## the health the tower had when the game was saved 5 | @export var health:float 6 | ## the rotation the tower had when the game was saved 7 | @export var rotation:float 8 | ## the cooldown until the tower can shoot again 9 | @export var cooldown:float 10 | -------------------------------------------------------------------------------- /tower/sphere.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/tower/sphere.png -------------------------------------------------------------------------------- /tower/sphere.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://5hdhq0mnmawx" 6 | path="res://.godot/imported/sphere.png-cd14d50915976d938fcbc04506499d6e.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://tower/sphere.png" 14 | dest_files=["res://.godot/imported/sphere.png-cd14d50915976d938fcbc04506499d6e.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 | -------------------------------------------------------------------------------- /tower/tower.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var ammo:PackedScene 4 | @export var cooldown:float = 2.5 5 | @export var max_health:float = 300 6 | @export var points:int = 25 7 | 8 | @onready var _ammo_spawn_point:Marker2D = %AmmoSpawnPoint 9 | @onready var _health_indicator:Sprite2D = %HealthIndicator 10 | @onready var _animation_player:AnimationPlayer = %AnimationPlayer 11 | @onready var _collision_shape:CollisionShape2D = %CollisionShape 12 | 13 | 14 | @onready var _health:float = max_health: 15 | set(value): 16 | _health = value 17 | _health_indicator.material.set_shader_parameter("progress", _health / max_health) 18 | 19 | 20 | var _cooldown:float = 0 21 | var _dying:bool = false 22 | var _current_enemy:Node2D = null 23 | var _last_enemy_position:Vector2 24 | 25 | 26 | func on_save_game(saved_data:Array[SavedData]): 27 | if _dying: 28 | return 29 | 30 | var my_data = SavedTowerData.new() 31 | my_data.position = global_position 32 | my_data.scene_path = scene_file_path 33 | my_data.rotation = rotation 34 | my_data.health = _health 35 | my_data.cooldown = _cooldown 36 | saved_data.append(my_data) 37 | 38 | 39 | func on_before_load_game(): 40 | get_parent().remove_child(self) 41 | queue_free() 42 | 43 | 44 | func on_load_game(saved_data:SavedData): 45 | global_position = saved_data.position 46 | 47 | if saved_data is SavedTowerData: 48 | var my_data = saved_data as SavedTowerData 49 | _health = my_data.health 50 | rotation = my_data.rotation 51 | _cooldown = my_data.cooldown 52 | # health indicator does not rotate with us 53 | _health_indicator.global_rotation = 0 54 | 55 | func take_damage(amount:float): 56 | if _dying: 57 | return 58 | 59 | _health -= amount 60 | if _health <= 0: 61 | _die() 62 | 63 | func game_ended(): 64 | queue_free() 65 | 66 | 67 | 68 | 69 | func _ready(): 70 | if not is_instance_valid(ammo): 71 | push_error("Tower has no ammo set!") 72 | set_physics_process(false) 73 | 74 | 75 | func _physics_process(delta): 76 | if _dying: 77 | return 78 | 79 | _cooldown -= delta 80 | 81 | if not is_instance_valid(_current_enemy): 82 | return 83 | 84 | var angle = get_angle_to(_current_enemy.global_position) 85 | rotate(PI * angle * delta) 86 | 87 | # health indicator does not rotate with us 88 | _health_indicator.global_rotation = 0 89 | 90 | if _cooldown > 0: 91 | _last_enemy_position = _current_enemy.global_position 92 | return 93 | 94 | if abs(get_angle_to(_current_enemy.global_position)) > deg_to_rad(20): 95 | _last_enemy_position = _current_enemy.global_position 96 | return # only fire when having a suitable angle 97 | 98 | # calculate where the enemy will be 99 | var distance = _ammo_spawn_point.global_position.distance_to(_current_enemy.global_position) 100 | var new_ammo = ammo.instantiate() 101 | 102 | # given the speed of the ammo, determine how long the ammo will travel 103 | var travel_time = distance / new_ammo.speed 104 | 105 | # if the enemy is moving, try to extrapolate where the enemy will be 106 | var enemy_direction = _current_enemy.global_position - _last_enemy_position 107 | var target_point = _current_enemy.global_position 108 | if enemy_direction.length_squared() > 0: 109 | target_point = travel_time * enemy_direction.normalized() + _current_enemy.global_position 110 | 111 | new_ammo.direction = target_point - _ammo_spawn_point.global_position 112 | get_parent().add_child(new_ammo) 113 | new_ammo.global_position = _ammo_spawn_point.global_position 114 | 115 | _cooldown = cooldown 116 | 117 | ## Plays the death animation 118 | func _die(): 119 | _dying = true 120 | _collision_shape.set_deferred("disabled", true) 121 | _animation_player.play("explode") 122 | await _animation_player.animation_finished 123 | queue_free() 124 | 125 | 126 | 127 | func _on_enemy_entered(body): 128 | _current_enemy = body 129 | 130 | 131 | func _on_enemy_exited(_body): 132 | _current_enemy = null 133 | 134 | 135 | func on_before_load(): 136 | queue_free() 137 | -------------------------------------------------------------------------------- /tower/tower.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=22 format=3 uid="uid://dwxl8elj3d5gj"] 2 | 3 | [ext_resource type="Script" path="res://tower/tower.gd" id="1_3eceo"] 4 | [ext_resource type="PackedScene" uid="uid://bb1if1fbcr7f4" path="res://tower_projectile/projectile.tscn" id="2_rjrxu"] 5 | [ext_resource type="Shader" path="res://tower/health_indicator.gdshader" id="3_ipdor"] 6 | [ext_resource type="Texture2D" uid="uid://5hdhq0mnmawx" path="res://tower/sphere.png" id="3_m4lar"] 7 | [ext_resource type="Texture2D" uid="uid://ctd44e5ku42cy" path="res://shared_assets/one_pixel.png" id="4_3lijr"] 8 | [ext_resource type="PackedScene" uid="uid://d11vwcdb8m2ko" path="res://light_cone/light_cone.tscn" id="5_r7nbw"] 9 | [ext_resource type="Shader" path="res://light_cone/cone.gdshader" id="6_ledcd"] 10 | [ext_resource type="Texture2D" uid="uid://bjty65c32jxhq" path="res://movement_particles/bubble.png" id="7_3skka"] 11 | 12 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_ywm8n"] 13 | resource_local_to_scene = true 14 | shader = ExtResource("3_ipdor") 15 | shader_parameter/thickness = 0.055 16 | shader_parameter/progress = 1.0 17 | shader_parameter/opacity = 0.29 18 | 19 | [sub_resource type="CircleShape2D" id="CircleShape2D_6683l"] 20 | radius = 64.0703 21 | 22 | [sub_resource type="ShaderMaterial" id="ShaderMaterial_2jbhr"] 23 | resource_local_to_scene = true 24 | shader = ExtResource("6_ledcd") 25 | shader_parameter/modulate = Color(1, 1, 1, 1) 26 | 27 | [sub_resource type="Gradient" id="Gradient_nuf6p"] 28 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 29 | 30 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_hst4n"] 31 | gradient = SubResource("Gradient_nuf6p") 32 | 33 | [sub_resource type="Curve" id="Curve_og54h"] 34 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 35 | point_count = 2 36 | 37 | [sub_resource type="CurveTexture" id="CurveTexture_wof2h"] 38 | curve = SubResource("Curve_og54h") 39 | 40 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_f7sj2"] 41 | lifetime_randomness = 0.33 42 | particle_flag_disable_z = true 43 | direction = Vector3(0, 0, 0) 44 | spread = 180.0 45 | gravity = Vector3(0, 0, 0) 46 | initial_velocity_min = 20.52 47 | initial_velocity_max = 75.43 48 | angular_velocity_min = -181.89 49 | angular_velocity_max = 227.37 50 | orbit_velocity_min = 0.0 51 | orbit_velocity_max = 0.0 52 | scale_min = 0.6 53 | scale_max = 0.8 54 | scale_curve = SubResource("CurveTexture_wof2h") 55 | color_ramp = SubResource("GradientTexture1D_hst4n") 56 | 57 | [sub_resource type="Animation" id="Animation_fv5sh"] 58 | length = 0.001 59 | tracks/0/type = "value" 60 | tracks/0/imported = false 61 | tracks/0/enabled = true 62 | tracks/0/path = NodePath("LightCone:material:shader_parameter/modulate") 63 | tracks/0/interp = 1 64 | tracks/0/loop_wrap = true 65 | tracks/0/keys = { 66 | "times": PackedFloat32Array(0), 67 | "transitions": PackedFloat32Array(1), 68 | "update": 0, 69 | "values": [Color(1, 1, 1, 1)] 70 | } 71 | tracks/1/type = "value" 72 | tracks/1/imported = false 73 | tracks/1/enabled = true 74 | tracks/1/path = NodePath("Sphere:modulate") 75 | tracks/1/interp = 1 76 | tracks/1/loop_wrap = true 77 | tracks/1/keys = { 78 | "times": PackedFloat32Array(0), 79 | "transitions": PackedFloat32Array(1), 80 | "update": 0, 81 | "values": [Color(1, 1, 1, 1)] 82 | } 83 | tracks/2/type = "value" 84 | tracks/2/imported = false 85 | tracks/2/enabled = true 86 | tracks/2/path = NodePath("Sphere:scale") 87 | tracks/2/interp = 1 88 | tracks/2/loop_wrap = true 89 | tracks/2/keys = { 90 | "times": PackedFloat32Array(0), 91 | "transitions": PackedFloat32Array(1), 92 | "update": 0, 93 | "values": [Vector2(0.5, 0.5)] 94 | } 95 | tracks/3/type = "value" 96 | tracks/3/imported = false 97 | tracks/3/enabled = true 98 | tracks/3/path = NodePath("ExplosionParticles:emitting") 99 | tracks/3/interp = 1 100 | tracks/3/loop_wrap = true 101 | tracks/3/keys = { 102 | "times": PackedFloat32Array(0), 103 | "transitions": PackedFloat32Array(1), 104 | "update": 1, 105 | "values": [false] 106 | } 107 | 108 | [sub_resource type="Animation" id="Animation_0l7em"] 109 | resource_name = "explode" 110 | length = 5.0 111 | tracks/0/type = "value" 112 | tracks/0/imported = false 113 | tracks/0/enabled = true 114 | tracks/0/path = NodePath("LightCone:material:shader_parameter/modulate") 115 | tracks/0/interp = 1 116 | tracks/0/loop_wrap = true 117 | tracks/0/keys = { 118 | "times": PackedFloat32Array(0, 0.1, 0.2, 0.3, 0.4, 0.5), 119 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), 120 | "update": 0, 121 | "values": [Color(1, 1, 1, 1), Color(0, 0, 0, 0), Color(1, 1, 1, 1), Color(0, 0, 0, 0), Color(1, 1, 1, 1), Color(0, 0, 0, 0)] 122 | } 123 | tracks/1/type = "value" 124 | tracks/1/imported = false 125 | tracks/1/enabled = true 126 | tracks/1/path = NodePath("Sphere:modulate") 127 | tracks/1/interp = 1 128 | tracks/1/loop_wrap = true 129 | tracks/1/keys = { 130 | "times": PackedFloat32Array(0.5, 1.4, 3), 131 | "transitions": PackedFloat32Array(1, 1, 1), 132 | "update": 0, 133 | "values": [Color(1, 1, 1, 1), Color(0, 0, 0, 1), Color(0, 0, 0, 0)] 134 | } 135 | tracks/2/type = "value" 136 | tracks/2/imported = false 137 | tracks/2/enabled = true 138 | tracks/2/path = NodePath("Sphere:scale") 139 | tracks/2/interp = 1 140 | tracks/2/loop_wrap = true 141 | tracks/2/keys = { 142 | "times": PackedFloat32Array(0.7, 3), 143 | "transitions": PackedFloat32Array(1, 1), 144 | "update": 0, 145 | "values": [Vector2(0.5, 0.5), Vector2(0, 0)] 146 | } 147 | tracks/3/type = "value" 148 | tracks/3/imported = false 149 | tracks/3/enabled = true 150 | tracks/3/path = NodePath("ExplosionParticles:emitting") 151 | tracks/3/interp = 1 152 | tracks/3/loop_wrap = true 153 | tracks/3/keys = { 154 | "times": PackedFloat32Array(0.5), 155 | "transitions": PackedFloat32Array(1), 156 | "update": 1, 157 | "values": [true] 158 | } 159 | 160 | [sub_resource type="Animation" id="Animation_yl8ow"] 161 | resource_name = "startup" 162 | tracks/0/type = "value" 163 | tracks/0/imported = false 164 | tracks/0/enabled = true 165 | tracks/0/path = NodePath("LightCone:material:shader_parameter/modulate") 166 | tracks/0/interp = 1 167 | tracks/0/loop_wrap = true 168 | tracks/0/keys = { 169 | "times": PackedFloat32Array(0, 0.4, 0.5, 0.6, 0.7, 1), 170 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1, 1), 171 | "update": 0, 172 | "values": [Color(0, 0, 0, 0), Color(1, 1, 1, 1), Color(0, 0, 0, 0), Color(1, 1, 1, 1), Color(0, 0, 0, 0), Color(1, 1, 1, 1)] 173 | } 174 | 175 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_lhm24"] 176 | _data = { 177 | "RESET": SubResource("Animation_fv5sh"), 178 | "explode": SubResource("Animation_0l7em"), 179 | "startup": SubResource("Animation_yl8ow") 180 | } 181 | 182 | [sub_resource type="CircleShape2D" id="CircleShape2D_wo5b2"] 183 | radius = 987.051 184 | 185 | [node name="Tower" type="StaticBody2D" groups=["clean_on_load", "game_events", "influences_score", "tower"]] 186 | collision_layer = 4 187 | script = ExtResource("1_3eceo") 188 | ammo = ExtResource("2_rjrxu") 189 | max_health = 100.0 190 | 191 | [node name="HealthIndicator" type="Sprite2D" parent="."] 192 | unique_name_in_owner = true 193 | material = SubResource("ShaderMaterial_ywm8n") 194 | position = Vector2(-2, 0) 195 | scale = Vector2(169, 169) 196 | texture = ExtResource("4_3lijr") 197 | 198 | [node name="CollisionShape" type="CollisionShape2D" parent="."] 199 | unique_name_in_owner = true 200 | position = Vector2(-2, 0) 201 | shape = SubResource("CircleShape2D_6683l") 202 | metadata/_edit_lock_ = true 203 | 204 | [node name="LightCone" parent="." instance=ExtResource("5_r7nbw")] 205 | material = SubResource("ShaderMaterial_2jbhr") 206 | 207 | [node name="Sphere" type="Sprite2D" parent="."] 208 | scale = Vector2(0.5, 0.5) 209 | texture = ExtResource("3_m4lar") 210 | 211 | [node name="AmmoSpawnPoint" type="Marker2D" parent="."] 212 | unique_name_in_owner = true 213 | position = Vector2(74, -1) 214 | 215 | [node name="ExplosionParticles" type="GPUParticles2D" parent="."] 216 | emitting = false 217 | amount = 100 218 | process_material = SubResource("ParticleProcessMaterial_f7sj2") 219 | texture = ExtResource("7_3skka") 220 | lifetime = 2.5 221 | one_shot = true 222 | explosiveness = 0.28 223 | fixed_fps = 60 224 | 225 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 226 | unique_name_in_owner = true 227 | libraries = { 228 | "": SubResource("AnimationLibrary_lhm24") 229 | } 230 | 231 | [node name="DetectionArea" type="Area2D" parent="."] 232 | collision_layer = 0 233 | 234 | [node name="CollisionShape2D" type="CollisionShape2D" parent="DetectionArea"] 235 | shape = SubResource("CircleShape2D_wo5b2") 236 | 237 | [connection signal="body_entered" from="DetectionArea" to="." method="_on_enemy_entered"] 238 | [connection signal="body_exited" from="DetectionArea" to="." method="_on_enemy_exited"] 239 | -------------------------------------------------------------------------------- /tower_projectile/projectile.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | @export var speed:float = 500 4 | @export var damage:float = 30 5 | @export var lifetime:float = 20 6 | 7 | var direction:Vector2 = Vector2.ONE 8 | 9 | var _dying:bool = false 10 | 11 | 12 | func on_save_game(saved_data:Array[SavedData]): 13 | if _dying: 14 | return 15 | 16 | var my_data = SavedTorpedoData.new() 17 | my_data.position = global_position 18 | my_data.scene_path = scene_file_path 19 | my_data.direction = direction 20 | saved_data.append(my_data) 21 | 22 | 23 | func on_before_load_game(): 24 | get_parent().remove_child(self) 25 | queue_free() 26 | 27 | 28 | func on_load_game(saved_data:SavedData): 29 | var my_data = saved_data as SavedTorpedoData 30 | global_position = my_data.position 31 | direction = my_data.direction 32 | look_at(global_position + direction) 33 | 34 | 35 | func _ready(): 36 | direction = direction.normalized() 37 | look_at(global_position + direction) 38 | 39 | func _physics_process(delta): 40 | if _dying: 41 | return 42 | 43 | lifetime -= delta 44 | if lifetime <= 0: 45 | explode() 46 | 47 | 48 | global_position = global_position + direction * speed * delta 49 | 50 | 51 | func explode(): 52 | _dying = true 53 | %AnimationPlayer.play("explode") 54 | await %AnimationPlayer.animation_finished 55 | visible = false 56 | queue_free() 57 | 58 | 59 | func _on_detection_area_body_entered(body): 60 | if _dying: 61 | return 62 | 63 | if body.has_method("take_damage"): 64 | body.take_damage(damage) 65 | explode() 66 | 67 | 68 | -------------------------------------------------------------------------------- /tower_projectile/projectile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/godotneers/saving-loading-video/efc2da0a63548541ed6d3b53bd82c2a6d1195712/tower_projectile/projectile.png -------------------------------------------------------------------------------- /tower_projectile/projectile.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="CompressedTexture2D" 5 | uid="uid://dn0afohwhxqfq" 6 | path="res://.godot/imported/projectile.png-f610e389cfd34b1fe641f66750e4e8e7.ctex" 7 | metadata={ 8 | "vram_texture": false 9 | } 10 | 11 | [deps] 12 | 13 | source_file="res://tower_projectile/projectile.png" 14 | dest_files=["res://.godot/imported/projectile.png-f610e389cfd34b1fe641f66750e4e8e7.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 | -------------------------------------------------------------------------------- /tower_projectile/projectile.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=20 format=3 uid="uid://bb1if1fbcr7f4"] 2 | 3 | [ext_resource type="Script" path="res://tower_projectile/projectile.gd" id="1_j7pgu"] 4 | [ext_resource type="Texture2D" uid="uid://bjty65c32jxhq" path="res://movement_particles/bubble.png" id="2_t888p"] 5 | [ext_resource type="Texture2D" uid="uid://dn0afohwhxqfq" path="res://tower_projectile/projectile.png" id="3_4xxui"] 6 | 7 | [sub_resource type="Gradient" id="Gradient_ot4t6"] 8 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 9 | 10 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_p1afh"] 11 | gradient = SubResource("Gradient_ot4t6") 12 | 13 | [sub_resource type="Curve" id="Curve_8yyqn"] 14 | min_value = -200.0 15 | max_value = 200.0 16 | _data = [Vector2(0.00598802, 200), 0.0, -30.2536, 0, 0, Vector2(0.431138, -12.5), 0.0, 0.0, 0, 0] 17 | point_count = 2 18 | 19 | [sub_resource type="CurveTexture" id="CurveTexture_417bp"] 20 | curve = SubResource("Curve_8yyqn") 21 | 22 | [sub_resource type="Curve" id="Curve_gb13t"] 23 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 24 | point_count = 2 25 | 26 | [sub_resource type="CurveTexture" id="CurveTexture_d41hf"] 27 | curve = SubResource("Curve_gb13t") 28 | 29 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_ph3u3"] 30 | lifetime_randomness = 0.33 31 | particle_flag_disable_z = true 32 | direction = Vector3(-1, 0, 0) 33 | spread = 63.23 34 | gravity = Vector3(0, 0, 0) 35 | initial_velocity_min = 10.53 36 | initial_velocity_max = 52.64 37 | angular_velocity_min = -181.89 38 | angular_velocity_max = 227.37 39 | orbit_velocity_min = 0.0 40 | orbit_velocity_max = 0.0 41 | linear_accel_curve = SubResource("CurveTexture_417bp") 42 | scale_min = 0.05 43 | scale_max = 0.2 44 | scale_curve = SubResource("CurveTexture_d41hf") 45 | color_ramp = SubResource("GradientTexture1D_p1afh") 46 | 47 | [sub_resource type="Animation" id="Animation_h58q7"] 48 | length = 0.001 49 | tracks/0/type = "value" 50 | tracks/0/imported = false 51 | tracks/0/enabled = true 52 | tracks/0/path = NodePath("ExplosionParticles:emitting") 53 | tracks/0/interp = 1 54 | tracks/0/loop_wrap = true 55 | tracks/0/keys = { 56 | "times": PackedFloat32Array(0), 57 | "transitions": PackedFloat32Array(1), 58 | "update": 1, 59 | "values": [false] 60 | } 61 | tracks/1/type = "value" 62 | tracks/1/imported = false 63 | tracks/1/enabled = true 64 | tracks/1/path = NodePath("Ammo:modulate") 65 | tracks/1/interp = 1 66 | tracks/1/loop_wrap = true 67 | tracks/1/keys = { 68 | "times": PackedFloat32Array(0), 69 | "transitions": PackedFloat32Array(1), 70 | "update": 0, 71 | "values": [Color(1, 1, 1, 1)] 72 | } 73 | tracks/2/type = "value" 74 | tracks/2/imported = false 75 | tracks/2/enabled = true 76 | tracks/2/path = NodePath("Ammo:scale") 77 | tracks/2/interp = 1 78 | tracks/2/loop_wrap = true 79 | tracks/2/keys = { 80 | "times": PackedFloat32Array(0), 81 | "transitions": PackedFloat32Array(1), 82 | "update": 0, 83 | "values": [Vector2(0.238, 0.238)] 84 | } 85 | tracks/3/type = "value" 86 | tracks/3/imported = false 87 | tracks/3/enabled = true 88 | tracks/3/path = NodePath("MovementParticles:visible") 89 | tracks/3/interp = 1 90 | tracks/3/loop_wrap = true 91 | tracks/3/keys = { 92 | "times": PackedFloat32Array(0), 93 | "transitions": PackedFloat32Array(1), 94 | "update": 1, 95 | "values": [true] 96 | } 97 | tracks/4/type = "value" 98 | tracks/4/imported = false 99 | tracks/4/enabled = true 100 | tracks/4/path = NodePath("ExplosionParticles:visible") 101 | tracks/4/interp = 1 102 | tracks/4/loop_wrap = true 103 | tracks/4/keys = { 104 | "times": PackedFloat32Array(0), 105 | "transitions": PackedFloat32Array(1), 106 | "update": 1, 107 | "values": [false] 108 | } 109 | 110 | [sub_resource type="Animation" id="Animation_3mbd8"] 111 | resource_name = "explode" 112 | length = 4.0 113 | step = 0.05 114 | tracks/0/type = "value" 115 | tracks/0/imported = false 116 | tracks/0/enabled = true 117 | tracks/0/path = NodePath("ExplosionParticles:emitting") 118 | tracks/0/interp = 1 119 | tracks/0/loop_wrap = true 120 | tracks/0/keys = { 121 | "times": PackedFloat32Array(0), 122 | "transitions": PackedFloat32Array(1), 123 | "update": 1, 124 | "values": [true] 125 | } 126 | tracks/1/type = "value" 127 | tracks/1/imported = false 128 | tracks/1/enabled = true 129 | tracks/1/path = NodePath("Ammo:modulate") 130 | tracks/1/interp = 1 131 | tracks/1/loop_wrap = true 132 | tracks/1/keys = { 133 | "times": PackedFloat32Array(0, 0.1, 0.3, 0.6, 0.9), 134 | "transitions": PackedFloat32Array(1, 1, 1, 1, 1), 135 | "update": 0, 136 | "values": [Color(1, 1, 1, 1), Color(3, 3, 3, 1), Color(1, 1, 1, 1), Color(0, 0, 0, 1), Color(0, 0, 0, 0)] 137 | } 138 | tracks/2/type = "value" 139 | tracks/2/imported = false 140 | tracks/2/enabled = true 141 | tracks/2/path = NodePath("Ammo:scale") 142 | tracks/2/interp = 1 143 | tracks/2/loop_wrap = true 144 | tracks/2/keys = { 145 | "times": PackedFloat32Array(0, 0.1, 0.35, 0.9), 146 | "transitions": PackedFloat32Array(1, 1, 1, 1), 147 | "update": 0, 148 | "values": [Vector2(0.238, 0.238), Vector2(0.4, 0.4), Vector2(0.238, 0.238), Vector2(0, 0)] 149 | } 150 | tracks/3/type = "value" 151 | tracks/3/imported = false 152 | tracks/3/enabled = true 153 | tracks/3/path = NodePath("MovementParticles:visible") 154 | tracks/3/interp = 1 155 | tracks/3/loop_wrap = true 156 | tracks/3/keys = { 157 | "times": PackedFloat32Array(0), 158 | "transitions": PackedFloat32Array(1), 159 | "update": 1, 160 | "values": [false] 161 | } 162 | tracks/4/type = "value" 163 | tracks/4/imported = false 164 | tracks/4/enabled = true 165 | tracks/4/path = NodePath("ExplosionParticles:visible") 166 | tracks/4/interp = 1 167 | tracks/4/loop_wrap = true 168 | tracks/4/keys = { 169 | "times": PackedFloat32Array(0), 170 | "transitions": PackedFloat32Array(1), 171 | "update": 1, 172 | "values": [true] 173 | } 174 | 175 | [sub_resource type="AnimationLibrary" id="AnimationLibrary_ycy2u"] 176 | _data = { 177 | "RESET": SubResource("Animation_h58q7"), 178 | "explode": SubResource("Animation_3mbd8") 179 | } 180 | 181 | [sub_resource type="Gradient" id="Gradient_5ng1t"] 182 | colors = PackedColorArray(1, 1, 1, 1, 1, 1, 1, 0) 183 | 184 | [sub_resource type="GradientTexture1D" id="GradientTexture1D_4yff5"] 185 | gradient = SubResource("Gradient_5ng1t") 186 | 187 | [sub_resource type="Curve" id="Curve_amtu7"] 188 | _data = [Vector2(0, 0.380208), 0.0, 0.0, 0, 0, Vector2(1, 1), 0.0, 0.0, 0, 0] 189 | point_count = 2 190 | 191 | [sub_resource type="CurveTexture" id="CurveTexture_svkju"] 192 | curve = SubResource("Curve_amtu7") 193 | 194 | [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_g7mco"] 195 | lifetime_randomness = 0.33 196 | particle_flag_disable_z = true 197 | direction = Vector3(0, 0, 0) 198 | spread = 180.0 199 | gravity = Vector3(0, 0, 0) 200 | initial_velocity_min = 62.63 201 | initial_velocity_max = 138.59 202 | angular_velocity_min = -181.89 203 | angular_velocity_max = 227.37 204 | orbit_velocity_min = 0.0 205 | orbit_velocity_max = 0.0 206 | scale_min = 0.3 207 | scale_max = 0.5 208 | scale_curve = SubResource("CurveTexture_svkju") 209 | color_ramp = SubResource("GradientTexture1D_4yff5") 210 | 211 | [sub_resource type="CircleShape2D" id="CircleShape2D_2kdoc"] 212 | radius = 24.5153 213 | 214 | [node name="Ammo" type="Node2D" groups=["game_events"]] 215 | script = ExtResource("1_j7pgu") 216 | 217 | [node name="MovementParticles" type="GPUParticles2D" parent="."] 218 | unique_name_in_owner = true 219 | amount = 50 220 | process_material = SubResource("ParticleProcessMaterial_ph3u3") 221 | texture = ExtResource("2_t888p") 222 | lifetime = 2.0 223 | fixed_fps = 60 224 | 225 | [node name="Ammo" type="Sprite2D" parent="."] 226 | rotation = 1.5708 227 | scale = Vector2(0.238, 0.238) 228 | texture = ExtResource("3_4xxui") 229 | 230 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 231 | unique_name_in_owner = true 232 | libraries = { 233 | "": SubResource("AnimationLibrary_ycy2u") 234 | } 235 | 236 | [node name="ExplosionParticles" type="GPUParticles2D" parent="."] 237 | unique_name_in_owner = true 238 | visible = false 239 | emitting = false 240 | amount = 250 241 | process_material = SubResource("ParticleProcessMaterial_g7mco") 242 | texture = ExtResource("2_t888p") 243 | lifetime = 2.0 244 | one_shot = true 245 | explosiveness = 0.88 246 | fixed_fps = 60 247 | 248 | [node name="DetectionArea" type="Area2D" parent="."] 249 | collision_layer = 0 250 | 251 | [node name="CollisionShape2D" type="CollisionShape2D" parent="DetectionArea"] 252 | position = Vector2(1, 0) 253 | shape = SubResource("CircleShape2D_2kdoc") 254 | 255 | [connection signal="body_entered" from="DetectionArea" to="." method="_on_detection_area_body_entered"] 256 | -------------------------------------------------------------------------------- /unused_data/unused_data.gd: -------------------------------------------------------------------------------- 1 | class_name UnusedData 2 | extends SavedData 3 | 4 | -------------------------------------------------------------------------------- /utilities/path_fixer.gd: -------------------------------------------------------------------------------- 1 | class_name PathFixer 2 | 3 | ## This holds a dictionary of paths to replace in the input file 4 | const Mappings = { 5 | } 6 | 7 | ## Fixes paths referenced in the input file. Writes 8 | ## a new file to a temporary location and returns the 9 | ## path to that file. 10 | static func fix_paths(inputPath:String) -> String: 11 | var text = FileAccess.get_file_as_string(inputPath) 12 | if text == "": 13 | push_error("Failed to read file: " + inputPath) 14 | return "" 15 | 16 | var result = text 17 | for search in Mappings: 18 | var replace = Mappings[search] 19 | result = result.replace(search, replace) 20 | 21 | # make a folder in the user directory to store temporary files 22 | DirAccess.make_dir_recursive_absolute("user://_res_fixer") 23 | # generate a random file name, it's not UUID but it's good enough 24 | var name = (str(Time.get_ticks_msec()) + str(randi())).md5_text() + ".tres" 25 | 26 | # write patched resource back to temp file 27 | var final_name = "user://_res_fixer/" + name 28 | var file = FileAccess.open(final_name, FileAccess.WRITE) 29 | file.store_string(result) 30 | file.close() 31 | 32 | return final_name 33 | -------------------------------------------------------------------------------- /world_root/world_root.gd: -------------------------------------------------------------------------------- 1 | class_name WorldRoot 2 | extends Node2D 3 | 4 | signal level_exit_reached(next_level:String) 5 | 6 | 7 | func load_level_async(path:String): 8 | # wait a physics frame so we can modify the tree 9 | await get_tree().physics_frame 10 | get_tree().paused = true 11 | 12 | # load the next level 13 | var next_level:Level = load(path).instantiate() 14 | next_level.exit_reached.connect(_on_level_exit_reached) 15 | 16 | # kill everything below the world root 17 | for child in get_children(): 18 | if not child.is_in_group("world_root_no_touch"): 19 | remove_child(child) 20 | child.queue_free() 21 | 22 | # instantiate the new level 23 | # add to world root 24 | add_child(next_level) 25 | 26 | # move any other nodes to front so they render above the level 27 | for child in get_children(): 28 | if child.is_in_group("world_root_no_touch"): 29 | child.move_to_front() 30 | 31 | 32 | get_tree().paused = false 33 | # connect the signal to get notified when the exit is reached 34 | 35 | 36 | func _on_level_exit_reached(next_level:String): 37 | level_exit_reached.emit(next_level) 38 | 39 | ## returns the path to the currently loaded level 40 | func get_current_level_path(): 41 | # find the first node in the level group and return its scene path 42 | for node in get_tree().get_nodes_in_group("level"): 43 | return node.scene_file_path 44 | 45 | # if we have no level push an error (shouldn't really happen) 46 | push_error("currently no level is loaded") 47 | return "res://invalid.tscn" 48 | --------------------------------------------------------------------------------