├── .gitignore ├── Background.svg ├── LICENSE ├── NOTES.md ├── README.md ├── godot ├── Global.gd ├── TacticalSpaceCombat.gd ├── TacticalSpaceCombat.tscn ├── TacticalSpaceCombat │ ├── Assets │ │ ├── Assets.svg │ │ ├── Assets.svg.import │ │ ├── Background.png │ │ ├── Background.png.import │ │ ├── Font │ │ │ ├── MontserratExtraBold.otf │ │ │ ├── MontserratExtraBold16.tres │ │ │ └── MontserratExtraBold24.tres │ │ ├── Shield.svg │ │ ├── Shield.svg.import │ │ ├── ShipTileSet.tres │ │ ├── Shockwave.tres │ │ ├── Theme.tres │ │ ├── UnitHitpointsBg.tres │ │ └── UnitHitpointsFg.tres │ ├── End.gd │ ├── End.tscn │ ├── Ship │ │ ├── Hazards.gd │ │ ├── Hazards │ │ │ ├── Breach.gd │ │ │ ├── Breach.tscn │ │ │ ├── Fire.gd │ │ │ ├── Fire.tscn │ │ │ └── Hazard.gd │ │ ├── Projectiles.gd │ │ ├── Rooms.gd │ │ ├── Rooms │ │ │ ├── Door.gd │ │ │ ├── Door.tscn │ │ │ ├── Room.gd │ │ │ ├── Room.tscn │ │ │ └── Wall.tscn │ │ ├── Shield.gd │ │ ├── Shield.tscn │ │ ├── ShipAI.tscn │ │ ├── ShipPlayer.tscn │ │ ├── ShipTemplate.gd │ │ ├── ShipTemplate.tscn │ │ ├── TileMap.gd │ │ ├── Units.gd │ │ ├── Units │ │ │ ├── Unit.gd │ │ │ ├── Unit.tscn │ │ │ ├── UnitPlayer.gd │ │ │ └── UnitPlayer.tscn │ │ └── Weapons │ │ │ ├── AttackLabel.gd │ │ │ ├── AttackLabel.tscn │ │ │ ├── Controller.gd │ │ │ ├── ControllerAILaser.gd │ │ │ ├── ControllerAIProjectile.gd │ │ │ ├── ControllerPlayer.gd │ │ │ ├── ControllerPlayerLaser.gd │ │ │ ├── ControllerPlayerProjectile.gd │ │ │ ├── LaserArea.gd │ │ │ ├── LaserTracker.gd │ │ │ ├── LaserTracker.tscn │ │ │ ├── Projectile.gd │ │ │ ├── Projectile.tscn │ │ │ ├── Weapon.gd │ │ │ ├── WeaponLaser.gd │ │ │ ├── WeaponLaser.tscn │ │ │ ├── WeaponProjectile.gd │ │ │ └── WeaponProjectile.tscn │ ├── UI │ │ ├── UIFeedback.tscn │ │ ├── UISystem.tscn │ │ ├── UIUnit.tscn │ │ ├── UIWeapon.tscn │ │ └── UIWeapons.tscn │ └── Utils.gd ├── default_env.tres ├── icon.png ├── icon.png.import └── project.godot ├── images └── godot-tactical-space-combat.png └── start-project ├── TacticalSpaceCombat ├── Assets │ ├── Assets.svg │ ├── Assets.svg.import │ ├── Background.png │ ├── Background.png.import │ ├── Font │ │ ├── MontserratExtraBold.otf │ │ ├── MontserratExtraBold16.tres │ │ └── MontserratExtraBold24.tres │ ├── Shield.svg │ ├── Shield.svg.import │ ├── ShipTileSet.tres │ ├── Shockwave.tres │ ├── Theme.tres │ ├── UnitHitpointsBg.tres │ └── UnitHitpointsFg.tres ├── Ship │ ├── Hazards │ │ └── .gitkeep │ ├── Rooms │ │ └── .gitkeep │ ├── Units │ │ └── .gitkeep │ └── Weapons │ │ └── .gitkeep └── UI │ └── .gitkeep ├── addons └── ColorPickerPresets │ ├── .gitignore │ ├── ColorPickerPresets.gd │ ├── LICENSE │ ├── README.md │ ├── plugin.cfg │ ├── presets.hex │ └── readme │ ├── colorpicker-presets.png │ ├── install-project-settings.png │ └── lospec-download.png ├── default_env.tres ├── icon.png ├── icon.png.import └── project.godot /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | 11 | # ColorPickPresets 12 | start-project/addons/ColorPickerPresets/**/*.import 13 | -------------------------------------------------------------------------------- /Background.svg: -------------------------------------------------------------------------------- 1 | 2 | 18 | 45 | 47 | 55 | 60 | 61 | 69 | 74 | 75 | 78 | 82 | 86 | 87 | 90 | 96 | 97 | 106 | 109 | 115 | 116 | 126 | 129 | 135 | 136 | 137 | 141 | 146 | 151 | 157 | 162 | 168 | 173 | 179 | 184 | 190 | 195 | 201 | 207 | 213 | 219 | 225 | 231 | 237 | 243 | 249 | 255 | 261 | 267 | 273 | 279 | 285 | 291 | 297 | 303 | 309 | 315 | 321 | 327 | 333 | 339 | 345 | 351 | 357 | 363 | 369 | 375 | 381 | 387 | 393 | 399 | 405 | 411 | 417 | 423 | 429 | 435 | 441 | 447 | 453 | 459 | 465 | 471 | 477 | 483 | 489 | 495 | 501 | 507 | 513 | 519 | 525 | 531 | 537 | 543 | 549 | 555 | 561 | 567 | 573 | 579 | 585 | 591 | 597 | 603 | 609 | 615 | 621 | 627 | 633 | 639 | 645 | 651 | 657 | 663 | 669 | 675 | 681 | 687 | 693 | 699 | 705 | 711 | 717 | 723 | 729 | 735 | 741 | 747 | 753 | 759 | 765 | 771 | 777 | 783 | 789 | 795 | 801 | 807 | 813 | 819 | 825 | 831 | 837 | 843 | 849 | 855 | 861 | 867 | 873 | 879 | 885 | 891 | 897 | 903 | 909 | 915 | 921 | 927 | 933 | 939 | 945 | 951 | 957 | 963 | 969 | 975 | 981 | 987 | 993 | 999 | 1005 | 1011 | 1017 | 1023 | 1029 | 1035 | 1041 | 1047 | 1053 | 1059 | 1065 | 1071 | 1077 | 1083 | 1089 | 1095 | 1101 | 1107 | 1113 | 1119 | 1125 | 1131 | 1137 | 1143 | 1149 | 1155 | 1161 | 1167 | 1173 | 1179 | 1185 | 1191 | 1197 | 1203 | 1209 | 1215 | 1221 | 1227 | 1233 | 1239 | 1245 | 1251 | 1257 | 1263 | 1269 | 1275 | 1281 | 1287 | 1293 | 1299 | 1305 | 1311 | 1317 | 1323 | 1337 | 1343 | 1346 | 1350 | 1354 | 1358 | 1363 | 1364 | 1369 | 1372 | 1378 | 1385 | 1389 | 1390 | 1404 | 1405 | 1406 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 GDQuest 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 | -------------------------------------------------------------------------------- /NOTES.md: -------------------------------------------------------------------------------- 1 | # General 2 | 3 | - `NavigationPolygonInstance` there seems to be [a bug](https://github.com/godotengine/godot/issues/38204#issuecomment-678620211) with `NavigationPolygonInstance` when making polygons from outlines 4 | 5 | # FTL 6 | 7 | - Grid movement with large grids 8 | - Grid spreading fire **not fun** 9 | 10 | # Tips 11 | 12 | - To duplicate root node in a scene use _Right Click > Merge From Scene_ 13 | - Use white sprite to tint it for fast coloring 14 | - Positive rotation turns right 15 | - Connect signals with default binds (true/false eg.) 16 | - Implementation details are dictated by the scope of the project. For example, in our case, path finding is constructed so that units move to closest available positions. In FTL this is implemented in a simpler way: units just move in predefined tile positions if available. In our case path finding might be influenced by positioning of computers in rooms that have specific placement and units have to go to them in order to be operated. We won't go this far, but this is something that has to be discussed in order to determine the implementation details. 17 | 18 | ## Doors 19 | 20 | Encountered issues: 21 | 22 | - Timing when multiple units traversing the same door. 23 | - Simple mechanic with lots of corner cases due to timing. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Godot 2D Tactical Space Combat 2 | 3 | ![FTL-like demo screenshot](images/godot-tactical-space-combat.png) 4 | 5 | This demo is a Real-Time Space Combat Simulator based on Faster Than Light gameplay for Godot designed for the course Godot 2D Secrets. 6 | 7 | It's currently a work-in-progress. 8 | 9 | ➡ Follow us on [Twitter](https://twitter.com/NathanGDQuest) and [YouTube](https://www.youtube.com/c/gdquest/) for free game creation tutorials, tips, and news! 10 | 11 | **You need Godot 3.2.4+ to run this project.** 12 | -------------------------------------------------------------------------------- /godot/Global.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | 3 | enum Layers { NONE } 4 | 5 | var winner_is_player := false 6 | 7 | 8 | func _ready() -> void: 9 | var label := 'layer_names/2d_physics/layer_%d' 10 | for i in range(20): 11 | var layer_name: String = ProjectSettings.get_setting(label % (i + 1)) 12 | if layer_name != '': 13 | Layers[layer_name.to_upper()] = 1 << i 14 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const UIUnit = preload("TacticalSpaceCombat/UI/UIUnit.tscn") 4 | const UIWeapons = preload("TacticalSpaceCombat/UI/UIWeapons.tscn") 5 | const UIWeapon = preload("TacticalSpaceCombat/UI/UIWeapon.tscn") 6 | const UISystem = preload("TacticalSpaceCombat/UI/UISystem.tscn") 7 | 8 | const END_SCENE = "TacticalSpaceCombat/End.tscn" 9 | 10 | onready var ship_player: Node2D = $ShipPlayer 11 | onready var ship_ai: Node2D = $ViewportContainer/Viewport/ShipAI 12 | onready var ui_units: VBoxContainer = $UI/Units 13 | onready var ui_doors: Button = $UI/Systems/Doors 14 | onready var ui_systems: HBoxContainer = $UI/Systems 15 | onready var ui_hitpoints_player: Label = $UI/HitPoints 16 | onready var ui_hitpoints_ai: Label = $ViewportContainer/Viewport/UI/HitPoints 17 | 18 | 19 | func _ready() -> void: 20 | ship_player.connect("hitpoints_changed", self, "_on_Ship_hitpoints_changed") 21 | ship_ai.connect("hitpoints_changed", self, "_on_Ship_hitpoints_changed") 22 | ui_doors.connect("pressed", ship_player, "_on_UIDoorsButton_pressed") 23 | 24 | _ready_sensors() 25 | _ready_shield() 26 | _ready_units() 27 | _ready_weapons_ai() 28 | _ready_weapons_player() 29 | 30 | _on_Ship_hitpoints_changed(ship_player.hitpoints, true) 31 | _on_Ship_hitpoints_changed(ship_ai.hitpoints, false) 32 | 33 | 34 | func _ready_units() -> void: 35 | for unit in ship_player.units.get_children(): 36 | var ui_unit: ColorRect = UIUnit.instance() 37 | ui_units.add_child(ui_unit) 38 | unit.setup(ui_unit) 39 | 40 | 41 | func _ready_weapons_ai() -> void: 42 | for controller in ship_ai.weapons.get_children(): 43 | if controller is ControllerAIProjectile: 44 | controller.connect("targeting", ship_player.rooms, "_on_Controller_targeting") 45 | controller.weapon.connect( 46 | "projectile_exited", ship_player.projectiles, "_on_Weapon_projectile_exited" 47 | ) 48 | ship_player.rooms.connect("targeted", controller, "_on_Ship_targeted") 49 | elif controller is ControllerAILaser: 50 | var laser_tracker: Node = ship_player.add_laser_tracker(controller.weapon.color) 51 | controller.connect( 52 | "targeting", laser_tracker, "_on_Controller_targeting", [], CONNECT_DEFERRED 53 | ) 54 | controller.weapon.connect("fire_started", laser_tracker, "_on_Weapon_fire_started") 55 | controller.weapon.connect("fire_stopped", laser_tracker, "_on_Weapon_fire_stopped") 56 | laser_tracker.connect("targeted", controller, "_on_Ship_targeted") 57 | 58 | 59 | func _ready_weapons_player() -> void: 60 | for controller in ship_player.weapons.get_children(): 61 | if not ui_systems.has_node("Weapons"): 62 | ui_systems.add_child(UIWeapons.instance()) 63 | var ui_weapon: VBoxContainer = UIWeapon.instance() 64 | ui_systems.get_node("Weapons").add_child(ui_weapon) 65 | 66 | if controller is ControllerPlayerProjectile: 67 | controller.weapon.connect( 68 | "projectile_exited", ship_ai.projectiles, "_on_Weapon_projectile_exited" 69 | ) 70 | for room in ship_ai.rooms.get_children(): 71 | controller.connect("targeting", room, "_on_Controller_targeting") 72 | room.connect("targeted", controller, "_on_Ship_targeted") 73 | 74 | elif controller is ControllerPlayerLaser: 75 | var laser_tracker: Node = ship_ai.add_laser_tracker(controller.weapon.color) 76 | controller.connect( 77 | "targeting", laser_tracker, "_on_Controller_targeting", [], CONNECT_DEFERRED 78 | ) 79 | controller.weapon.connect("fire_started", laser_tracker, "_on_Weapon_fire_started") 80 | controller.weapon.connect("fire_stopped", laser_tracker, "_on_Weapon_fire_stopped") 81 | laser_tracker.connect("targeted", controller, "_on_Ship_targeted") 82 | controller.setup(ui_weapon) 83 | 84 | 85 | func _ready_shield() -> void: 86 | var ui_shield := UISystem.instance() 87 | ui_shield.connect("toggled", ship_player.shield, "set_powered") 88 | ui_shield.toggle_mode = true 89 | ui_shield.pressed = ship_player.shield.powered 90 | ui_shield.disabled = not ship_player.shield.powered 91 | ui_shield.text = "S" 92 | ui_systems.add_child(ui_shield) 93 | 94 | 95 | func _ready_sensors() -> void: 96 | ship_ai.has_sensors = ship_player.has_sensors 97 | for unit in ship_ai.units.get_children(): 98 | unit.visible = ship_player.has_sensors 99 | 100 | if ship_player.has_sensors: 101 | var ui_sensors := UISystem.instance() 102 | ui_sensors.text = "s" 103 | ui_sensors.disabled = true 104 | ui_systems.add_child(ui_sensors) 105 | ui_systems.add_child(VSeparator.new()) 106 | 107 | 108 | func _on_Ship_hitpoints_changed(hitpoints: int, is_player: bool) -> void: 109 | var label := ui_hitpoints_player if is_player else ui_hitpoints_ai 110 | label.text = "HP: %d" % hitpoints 111 | if hitpoints == 0: 112 | Global.winner_is_player = not is_player 113 | get_tree().change_scene(END_SCENE) 114 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=9 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/ShipAI.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/ShipPlayer.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://TacticalSpaceCombat/Assets/Theme.tres" type="Theme" id=3] 6 | [ext_resource path="res://TacticalSpaceCombat/UI/UISystem.tscn" type="PackedScene" id=4] 7 | [ext_resource path="res://TacticalSpaceCombat.gd" type="Script" id=5] 8 | [ext_resource path="res://TacticalSpaceCombat/Assets/Font/MontserratExtraBold24.tres" type="DynamicFont" id=6] 9 | [ext_resource path="res://TacticalSpaceCombat/Assets/Background.png" type="Texture" id=7] 10 | 11 | [sub_resource type="Environment" id=1] 12 | background_mode = 4 13 | glow_enabled = true 14 | glow_levels/1 = true 15 | glow_levels/2 = true 16 | glow_levels/5 = false 17 | 18 | [node name="TacticalSpaceCombat" type="Node2D"] 19 | script = ExtResource( 5 ) 20 | 21 | [node name="Background" type="Sprite" parent="."] 22 | modulate = Color( 1, 1, 1, 0.588235 ) 23 | texture = ExtResource( 7 ) 24 | centered = false 25 | 26 | [node name="ShipPlayer" parent="." instance=ExtResource( 2 )] 27 | position = Vector2( 256, 384 ) 28 | 29 | [node name="ViewportContainer" type="ViewportContainer" parent="."] 30 | margin_left = 1024.0 31 | margin_top = 128.0 32 | margin_right = 1792.0 33 | margin_bottom = 960.0 34 | mouse_filter = 2 35 | __meta__ = { 36 | "_edit_use_anchors_": false 37 | } 38 | 39 | [node name="Viewport" type="Viewport" parent="ViewportContainer"] 40 | size = Vector2( 768, 832 ) 41 | handle_input_locally = false 42 | render_target_update_mode = 3 43 | physics_object_picking = true 44 | 45 | [node name="Background" type="Sprite" parent="ViewportContainer/Viewport"] 46 | modulate = Color( 1, 0.709804, 0.709804, 1 ) 47 | position = Vector2( -1000, 0 ) 48 | texture = ExtResource( 7 ) 49 | centered = false 50 | 51 | [node name="ShipAI" parent="ViewportContainer/Viewport" instance=ExtResource( 1 )] 52 | position = Vector2( 192, 288 ) 53 | 54 | [node name="UI" type="Control" parent="ViewportContainer/Viewport"] 55 | margin_right = 40.0 56 | margin_bottom = 40.0 57 | 58 | [node name="HitPoints" type="Label" parent="ViewportContainer/Viewport/UI"] 59 | margin_right = 40.0 60 | margin_bottom = 40.0 61 | custom_fonts/font = ExtResource( 6 ) 62 | text = "HP: 100" 63 | 64 | [node name="UI" type="Control" parent="."] 65 | margin_right = 40.0 66 | margin_bottom = 40.0 67 | mouse_filter = 2 68 | theme = ExtResource( 3 ) 69 | __meta__ = { 70 | "_edit_use_anchors_": false 71 | } 72 | 73 | [node name="Units" type="VBoxContainer" parent="UI"] 74 | custom_constants/separation = 0 75 | __meta__ = { 76 | "_edit_use_anchors_": false 77 | } 78 | 79 | [node name="Systems" type="HBoxContainer" parent="UI"] 80 | margin_top = 1008.0 81 | margin_right = 32.0 82 | margin_bottom = 1040.0 83 | rect_min_size = Vector2( 0, 72 ) 84 | __meta__ = { 85 | "_edit_use_anchors_": false 86 | } 87 | 88 | [node name="Doors" parent="UI/Systems" instance=ExtResource( 4 )] 89 | text = "D" 90 | 91 | [node name="HitPoints" type="Label" parent="UI"] 92 | margin_left = 64.0 93 | margin_right = 194.0 94 | margin_bottom = 48.0 95 | custom_fonts/font = ExtResource( 6 ) 96 | text = "HP: 100" 97 | __meta__ = { 98 | "_edit_use_anchors_": false 99 | } 100 | 101 | [node name="WorldEnvironment" type="WorldEnvironment" parent="."] 102 | environment = SubResource( 1 ) 103 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Assets.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Assets.svg-d190b656a20dba5761c3cdba806b46c8.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://TacticalSpaceCombat/Assets/Assets.svg" 13 | dest_files=[ "res://.import/Assets.svg-d190b656a20dba5761c3cdba806b46c8.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/godot/TacticalSpaceCombat/Assets/Background.png -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Background.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Background.png-b24c8e0ead373f89e95cd7345d823255.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://TacticalSpaceCombat/Assets/Background.png" 13 | dest_files=[ "res://.import/Background.png-b24c8e0ead373f89e95cd7345d823255.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/godot/TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Font/MontserratExtraBold16.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | use_mipmaps = true 7 | use_filter = true 8 | font_data = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Font/MontserratExtraBold24.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 32 7 | use_mipmaps = true 8 | use_filter = true 9 | font_data = ExtResource( 1 ) 10 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Shield.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Shield.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Shield.svg-f7f94db2102f53848a907508d9426cbe.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://TacticalSpaceCombat/Assets/Shield.svg" 13 | dest_files=[ "res://.import/Shield.svg-f7f94db2102f53848a907508d9426cbe.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/ShipTileSet.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="TileSet" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | 5 | [resource] 6 | 0/name = "Tile" 7 | 0/texture = ExtResource( 1 ) 8 | 0/tex_offset = Vector2( 0, 0 ) 9 | 0/modulate = Color( 1, 1, 1, 1 ) 10 | 0/region = Rect2( 0, 0, 448, 448 ) 11 | 0/tile_mode = 1 12 | 0/autotile/bitmask_mode = 1 13 | 0/autotile/bitmask_flags = [ Vector2( 0, 0 ), 432, Vector2( 0, 1 ), 438, Vector2( 0, 2 ), 54, Vector2( 0, 3 ), 440, Vector2( 0, 4 ), 62, Vector2( 0, 5 ), 48, Vector2( 1, 0 ), 504, Vector2( 1, 1 ), 511, Vector2( 1, 2 ), 63, Vector2( 1, 3 ), 248, Vector2( 1, 4 ), 59, Vector2( 1, 5 ), 56, Vector2( 2, 0 ), 216, Vector2( 2, 1 ), 219, Vector2( 2, 2 ), 27, Vector2( 2, 3 ), 434, Vector2( 2, 4 ), 182, Vector2( 2, 5 ), 24, Vector2( 3, 0 ), 176, Vector2( 3, 1 ), 178, Vector2( 3, 2 ), 50, Vector2( 3, 3 ), 218, Vector2( 3, 4 ), 155, Vector2( 3, 5 ), 447, Vector2( 3, 6 ), 510, Vector2( 4, 0 ), 184, Vector2( 4, 1 ), 186, Vector2( 4, 2 ), 58, Vector2( 4, 3 ), 506, Vector2( 4, 4 ), 191, Vector2( 4, 5 ), 255, Vector2( 4, 6 ), 507, Vector2( 5, 0 ), 152, Vector2( 5, 1 ), 154, Vector2( 5, 2 ), 26, Vector2( 5, 3 ), 446, Vector2( 5, 4 ), 443, Vector2( 5, 5 ), 254, Vector2( 6, 0 ), 144, Vector2( 6, 1 ), 146, Vector2( 6, 2 ), 18, Vector2( 6, 3 ), 251, Vector2( 6, 4 ), 254, Vector2( 6, 5 ), 443 ] 14 | 0/autotile/icon_coordinate = Vector2( 1, 1 ) 15 | 0/autotile/tile_size = Vector2( 64, 64 ) 16 | 0/autotile/spacing = 0 17 | 0/autotile/occluder_map = [ ] 18 | 0/autotile/navpoly_map = [ ] 19 | 0/autotile/priority_map = [ ] 20 | 0/autotile/z_index_map = [ ] 21 | 0/occluder_offset = Vector2( 0, 0 ) 22 | 0/navigation_offset = Vector2( 0, 0 ) 23 | 0/shape_offset = Vector2( 0, 0 ) 24 | 0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 25 | 0/shape_one_way = false 26 | 0/shape_one_way_margin = 0.0 27 | 0/shapes = [ ] 28 | 0/z_index = 0 29 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/Shockwave.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="ShaderMaterial" load_steps=2 format=2] 2 | 3 | [sub_resource type="Shader" id=1] 4 | code = "shader_type canvas_item; 5 | 6 | 7 | uniform float force; 8 | uniform float size; 9 | 10 | 11 | float donought(float l) { 12 | return (1.0 - smoothstep(size - 0.1, size, l)) * smoothstep(size - 0.2, size - 0.1, l); 13 | } 14 | 15 | 16 | void fragment() { 17 | vec2 uv = UV - vec2(0.5); 18 | float lr = length(uv) + 5e-2; 19 | float lg = length(uv); 20 | float lb = length(uv) - 5e-2; 21 | vec2 dispr = normalize(uv) * force * donought(lr); 22 | vec2 dispg = normalize(uv) * force * donought(lg); 23 | vec2 dispb = normalize(uv) * force * donought(lb); 24 | float r = texture(SCREEN_TEXTURE, SCREEN_UV - dispr).r; 25 | float g = texture(SCREEN_TEXTURE, SCREEN_UV - dispg).g; 26 | float b = texture(SCREEN_TEXTURE, SCREEN_UV - dispb).b; 27 | COLOR.rgb = vec3(r, g, b); 28 | }" 29 | 30 | [resource] 31 | resource_local_to_scene = true 32 | shader = SubResource( 1 ) 33 | shader_param/force = 0.0 34 | shader_param/size = 0.0 35 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/UnitHitpointsBg.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0.152941, 0.152941, 0.211765, 1 ) 5 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Assets/UnitHitpointsFg.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0.239216, 0.431373, 0.439216, 1 ) 5 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/End.gd: -------------------------------------------------------------------------------- 1 | extends CenterContainer 2 | 3 | const MAIN_SCENE := "res://TacticalSpaceCombat.tscn" 4 | 5 | onready var label: Label = $VBoxContainer/Label 6 | onready var button_retry: Button = $VBoxContainer/ButtonRetry 7 | onready var button_quit: Button = $VBoxContainer/ButtonQuit 8 | 9 | 10 | func _ready() -> void: 11 | button_retry.connect("pressed", get_tree(), "change_scene", [MAIN_SCENE]) 12 | button_quit.connect("pressed", get_tree(), "quit") 13 | label.text = "You %s!" % ("Won" if Global.winner_is_player else "Lost") 14 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/End.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Theme.tres" type="Theme" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/End.gd" type="Script" id=2] 5 | 6 | [node name="End" type="CenterContainer"] 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | theme = ExtResource( 1 ) 10 | script = ExtResource( 2 ) 11 | __meta__ = { 12 | "_edit_use_anchors_": false 13 | } 14 | 15 | [node name="VBoxContainer" type="VBoxContainer" parent="."] 16 | margin_left = 916.0 17 | margin_top = 492.0 18 | margin_right = 1003.0 19 | margin_bottom = 587.0 20 | 21 | [node name="Label" type="Label" parent="VBoxContainer"] 22 | margin_right = 87.0 23 | margin_bottom = 29.0 24 | text = "You Won!" 25 | 26 | [node name="ButtonRetry" type="Button" parent="VBoxContainer"] 27 | margin_top = 33.0 28 | margin_right = 87.0 29 | margin_bottom = 62.0 30 | text = "Retry" 31 | 32 | [node name="ButtonQuit" type="Button" parent="VBoxContainer"] 33 | margin_top = 66.0 34 | margin_right = 87.0 35 | margin_bottom = 95.0 36 | text = "Quit" 37 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Hazards.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | var _rng := RandomNumberGenerator.new() 4 | var _slots := {} 5 | 6 | 7 | func _on_Hazard_tree_exited(hazard_position: Vector2) -> void: 8 | _slots.erase(hazard_position) 9 | 10 | 11 | func add(hazard_scene: PackedScene, offset: Vector2) -> Node: 12 | var out: Node = null 13 | if not offset in _slots: 14 | var hazard := hazard_scene.instance() 15 | hazard.position = offset 16 | hazard.connect("tree_exited", self, "_on_Hazard_tree_exited", [hazard.position]) 17 | add_child(hazard) 18 | if hazard is Fire: 19 | var index := 0 20 | for child in get_children(): 21 | if child is Fire: 22 | index += 1 23 | else: 24 | break 25 | move_child(hazard, index) 26 | _slots[hazard.position] = null 27 | out = hazard 28 | return out 29 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Hazards/Breach.gd: -------------------------------------------------------------------------------- 1 | class_name Breach 2 | extends Hazard 3 | 4 | 5 | func _set_hitpoints(value: int) -> void: 6 | ._set_hitpoints(value) 7 | 8 | if THRESHOLD.low <= _hitpoints and _hitpoints < THRESHOLD.medium: 9 | scale = 0.75 * Vector2.ONE 10 | elif _hitpoints < THRESHOLD.low: 11 | scale = 0.5 * Vector2.ONE 12 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Hazards/Breach.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Hazards/Breach.gd" type="Script" id=2] 5 | 6 | [node name="Breach" type="Sprite"] 7 | texture = ExtResource( 1 ) 8 | region_enabled = true 9 | region_rect = Rect2( 384, 384, 64, 64 ) 10 | script = ExtResource( 2 ) 11 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Hazards/Fire.gd: -------------------------------------------------------------------------------- 1 | class_name Fire 2 | extends Hazard 3 | 4 | signal attacked(attack, position) 5 | signal spread(position) 6 | 7 | export (float, 0.0, 1.0) var chance_attack := 0.1 8 | export (float, 0.0, 1.0) var chance_spread := 0.05 9 | export (int, 0, 100) var o2_damage := 30 10 | 11 | var _rng := RandomNumberGenerator.new() 12 | 13 | onready var timer: Timer = $Timer 14 | onready var animation_player: AnimationPlayer = $AnimationPlayer 15 | 16 | 17 | func _ready() -> void: 18 | timer.connect("timeout", self, "_on_Timer_timeout") 19 | _rng.randomize() 20 | 21 | 22 | func _on_Timer_timeout() -> void: 23 | if _rng.randf() < chance_attack: 24 | emit_signal("attacked", attack, position) 25 | 26 | if _rng.randf() < chance_spread: 27 | emit_signal("spread", position) 28 | 29 | 30 | func _set_hitpoints(value: int) -> void: 31 | ._set_hitpoints(value) 32 | 33 | var animation := "high" 34 | if _hitpoints < THRESHOLD.low: 35 | animation = "low" 36 | elif _hitpoints < THRESHOLD.medium: 37 | animation = "medium" 38 | 39 | if animation_player.current_animation != animation: 40 | animation_player.play(animation) 41 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Hazards/Fire.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/Hazards/Breach.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Hazards/Fire.gd" type="Script" id=2] 5 | 6 | [sub_resource type="Animation" id=1] 7 | resource_name = "RESET" 8 | length = 0.001 9 | tracks/0/type = "value" 10 | tracks/0/path = NodePath(".:scale") 11 | tracks/0/interp = 1 12 | tracks/0/loop_wrap = true 13 | tracks/0/imported = false 14 | tracks/0/enabled = true 15 | tracks/0/keys = { 16 | "times": PoolRealArray( 0 ), 17 | "transitions": PoolRealArray( 1 ), 18 | "update": 0, 19 | "values": [ Vector2( 1, 1 ) ] 20 | } 21 | 22 | [sub_resource type="Animation" id=2] 23 | resource_name = "high" 24 | length = 0.5 25 | loop = true 26 | tracks/0/type = "value" 27 | tracks/0/path = NodePath(".:scale") 28 | tracks/0/interp = 1 29 | tracks/0/loop_wrap = true 30 | tracks/0/imported = false 31 | tracks/0/enabled = true 32 | tracks/0/keys = { 33 | "times": PoolRealArray( 0, 0.4 ), 34 | "transitions": PoolRealArray( -2, 1 ), 35 | "update": 0, 36 | "values": [ Vector2( 1, 1 ), Vector2( 0.8, 0.8 ) ] 37 | } 38 | 39 | [sub_resource type="Animation" id=3] 40 | resource_name = "low" 41 | length = 0.5 42 | loop = true 43 | tracks/0/type = "value" 44 | tracks/0/path = NodePath(".:scale") 45 | tracks/0/interp = 1 46 | tracks/0/loop_wrap = true 47 | tracks/0/imported = false 48 | tracks/0/enabled = true 49 | tracks/0/keys = { 50 | "times": PoolRealArray( 0, 0.4 ), 51 | "transitions": PoolRealArray( -2, -2 ), 52 | "update": 0, 53 | "values": [ Vector2( 0.4, 0.4 ), Vector2( 0.2, 0.2 ) ] 54 | } 55 | 56 | [sub_resource type="Animation" id=4] 57 | resource_name = "medium" 58 | length = 0.5 59 | loop = true 60 | tracks/0/type = "value" 61 | tracks/0/path = NodePath(".:scale") 62 | tracks/0/interp = 1 63 | tracks/0/loop_wrap = true 64 | tracks/0/imported = false 65 | tracks/0/enabled = true 66 | tracks/0/keys = { 67 | "times": PoolRealArray( 0, 0.4 ), 68 | "transitions": PoolRealArray( -2, 1 ), 69 | "update": 0, 70 | "values": [ Vector2( 0.8, 0.8 ), Vector2( 0.4, 0.4 ) ] 71 | } 72 | 73 | [node name="Fire" instance=ExtResource( 1 )] 74 | region_rect = Rect2( 320, 384, 64, 64 ) 75 | script = ExtResource( 2 ) 76 | attack = 1 77 | 78 | [node name="Timer" type="Timer" parent="." index="0"] 79 | wait_time = 0.5 80 | autostart = true 81 | 82 | [node name="AnimationPlayer" type="AnimationPlayer" parent="." index="1"] 83 | autoplay = "high" 84 | anims/RESET = SubResource( 1 ) 85 | anims/high = SubResource( 2 ) 86 | anims/low = SubResource( 3 ) 87 | anims/medium = SubResource( 4 ) 88 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Hazards/Hazard.gd: -------------------------------------------------------------------------------- 1 | class_name Hazard 2 | extends Sprite 3 | 4 | const THRESHOLD := {"medium": 70, "low": 30} 5 | 6 | export (int) var attack := 10 7 | 8 | var _hitpoints := 100 setget _set_hitpoints 9 | 10 | 11 | func take_damage(value: int) -> void: 12 | _set_hitpoints(_hitpoints - value) 13 | 14 | 15 | func _set_hitpoints(value: int) -> void: 16 | _hitpoints = value 17 | if _hitpoints <= 0: 18 | queue_free() 19 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Projectiles.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const Projectile = preload("Weapons/Projectile.tscn") 4 | 5 | var _rng := RandomNumberGenerator.new() 6 | var _mean_position := Vector2.INF 7 | 8 | 9 | func setup(mean_position: Vector2) -> void: 10 | _mean_position = mean_position 11 | 12 | 13 | func _ready() -> void: 14 | _rng.randomize() 15 | 16 | 17 | func _on_Weapon_projectile_exited(params: Dictionary) -> void: 18 | var projectile: RigidBody2D = Projectile.instance() 19 | var spawn_position := _mean_position + Utils.randvf_circle(_rng, projectile.MAX_DISTANCE) 20 | var direction: Vector2 = (params.target_position - spawn_position).normalized() 21 | projectile.collision_layer = params.physics_layer 22 | projectile.position = spawn_position 23 | projectile.linear_velocity = direction * projectile.linear_velocity.length() 24 | projectile.rotation = direction.angle() 25 | projectile.max_distance *= 2 26 | projectile.params = params 27 | add_child(projectile) 28 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Rooms.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal targeted(msg) 4 | 5 | var mean_position := Vector2.INF 6 | 7 | var _rng := RandomNumberGenerator.new() 8 | var _rooms_count := 0 9 | 10 | 11 | func _ready() -> void: 12 | _rng.randomize() 13 | _rooms_count = get_child_count() 14 | mean_position = _get_mean_position() 15 | 16 | 17 | func _on_Controller_targeting(msg: Dictionary) -> void: 18 | var r := _rng.randi_range(0, _rooms_count - 1) 19 | var room: BaseRoom = get_child(r) 20 | msg.type = Controller.Type.PROJECTILE 21 | msg.target_position = room.position 22 | emit_signal("targeted", msg) 23 | 24 | 25 | func get_laser_points(targeting_length: float) -> Array: 26 | var room_index_first := _rng.randi_range(0, _rooms_count - 1) 27 | var remaining := [] 28 | for room_index in range(_rooms_count): 29 | if room_index != room_index_first: 30 | remaining.push_back(room_index) 31 | var index = _rng.randi_range(0, remaining.size() - 1) 32 | var room_index_second = remaining[index] 33 | 34 | var point1: Vector2 = get_child(room_index_first).randv() 35 | var point2: Vector2 = get_child(room_index_second).randv() 36 | point2 = point1.move_toward(point2, targeting_length) 37 | return [point1, point2] 38 | 39 | 40 | func _get_mean_position() -> Vector2: 41 | var out := Vector2.ZERO 42 | for room in get_children(): 43 | out += room.position 44 | 45 | if _rooms_count > 0: 46 | out /= _rooms_count 47 | return out 48 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Rooms/Door.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | signal opened 4 | 5 | var is_open := false setget set_is_open 6 | var rooms := [] 7 | 8 | var _units := 0 9 | 10 | onready var sprite: Sprite = $Sprite 11 | onready var timer: Timer = $Timer 12 | 13 | 14 | func _ready() -> void: 15 | connect("area_entered", self, "_on_area_entered_exited", [true]) 16 | connect("area_exited", self, "_on_area_entered_exited", [false]) 17 | timer.connect("timeout", self, "set_is_open", [true]) 18 | 19 | 20 | func _on_area_entered_exited(area: Area2D, has_entered: bool) -> void: 21 | if area.is_in_group("unit"): 22 | _units += 1 if has_entered else -1 23 | 24 | match [_units, has_entered]: 25 | [0, false]: 26 | self.is_open = false 27 | 28 | [1, true]: 29 | timer.start() 30 | 31 | elif area.is_in_group("room") and has_entered: 32 | rooms.push_back(area) 33 | 34 | 35 | func set_is_open(value: bool) -> void: 36 | is_open = value 37 | if is_open: 38 | sprite.frame = 1 39 | emit_signal("opened") 40 | else: 41 | sprite.frame = 0 42 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Rooms/Door.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms/Door.gd" type="Script" id=2] 5 | 6 | [sub_resource type="RectangleShape2D" id=1] 7 | extents = Vector2( 8, 16 ) 8 | 9 | [node name="Door" type="Area2D" groups=[ 10 | "door", 11 | ]] 12 | script = ExtResource( 2 ) 13 | 14 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 15 | shape = SubResource( 1 ) 16 | 17 | [node name="Sprite" type="Sprite" parent="."] 18 | texture = ExtResource( 1 ) 19 | vframes = 2 20 | region_enabled = true 21 | region_rect = Rect2( 0, 384, 64, 12 ) 22 | 23 | [node name="Timer" type="Timer" parent="."] 24 | wait_time = 0.3 25 | one_shot = true 26 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Rooms/Room.gd: -------------------------------------------------------------------------------- 1 | tool 2 | class_name BaseRoom 3 | extends Area2D 4 | 5 | signal targeted(msg) 6 | signal modifier_changed(type, value) 7 | signal fog_changed(room, has_fog) 8 | 9 | enum Type { EMPTY, SENSORS, HELM, WEAPONS, MEDBAY } 10 | 11 | const FOG_COLOR := Color("ffe478") 12 | const SPRITE := { 13 | Type.EMPTY: Vector2.INF, 14 | Type.SENSORS: Vector2(128, 384), 15 | Type.HELM: Vector2(160, 384), 16 | Type.WEAPONS: Vector2(128, 416), 17 | Type.MEDBAY: Vector2(160, 416) 18 | } 19 | 20 | export (Type) var type := Type.EMPTY 21 | export var size := Vector2.ONE setget set_size 22 | 23 | var units := {} 24 | var o2 := 100 setget set_o2 25 | 26 | var _target_index := -1 27 | var _modifiers := { 28 | Type.EMPTY: [0.0, 0.0], 29 | Type.SENSORS: [0.0, 0.0], 30 | Type.HELM: [0.0, 0.5], 31 | Type.WEAPONS: [1.0, 2.0], 32 | Type.MEDBAY: [0.0, 0.0] 33 | } 34 | 35 | var _fog := {false: Rect2()} 36 | var _rng := RandomNumberGenerator.new() 37 | var _entrances := {} 38 | var _area := 0 39 | var _top_left := Vector2.ZERO 40 | var _bottom_right := Vector2.ZERO 41 | 42 | var _iter_index := 0 43 | 44 | var _tilemap: TileMap = null 45 | 46 | onready var collision_shape: CollisionShape2D = $CollisionShape2D 47 | onready var feedback: NinePatchRect = $Feedback 48 | onready var sprite_target: Sprite = $SpriteTarget 49 | onready var hit_area: Area2D = $HitArea2D 50 | onready var color_rect_o2: ColorRect = $ColorRectO2 51 | onready var sprite_type: Sprite = $SpriteType 52 | 53 | 54 | func setup(tilemap: TileMap) -> void: 55 | _tilemap = tilemap 56 | _setup_extents() 57 | 58 | if not Engine.editor_hint: 59 | hit_area.collision_mask = ( 60 | Global.Layers.SHIPPLAYER 61 | if owner.is_in_group("player") 62 | else Global.Layers.SHIPAI 63 | ) 64 | 65 | _area = size.x * size.y 66 | _top_left = _tilemap.world_to_map(position - collision_shape.shape.extents) 67 | _bottom_right = _top_left + size 68 | 69 | feedback.rect_position -= collision_shape.shape.extents 70 | feedback.rect_size = 2 * collision_shape.shape.extents 71 | 72 | color_rect_o2.rect_position = feedback.rect_position 73 | color_rect_o2.rect_size = feedback.rect_size 74 | 75 | sprite_type.visible = type != Type.EMPTY 76 | sprite_type.region_rect = Rect2(SPRITE[type], _tilemap.cell_size / 2) 77 | 78 | _fog[true] = Rect2(feedback.rect_position, feedback.rect_size) 79 | 80 | 81 | func _setup_extents() -> void: 82 | if _tilemap != null: 83 | collision_shape.shape.extents = 0.5 * _tilemap.map_to_world(size) 84 | 85 | 86 | func _ready() -> void: 87 | if Engine.editor_hint: 88 | return 89 | 90 | connect("mouse_entered", self, "_on_mouse_entered_exited", [true]) 91 | connect("mouse_exited", self, "_on_mouse_entered_exited", [false]) 92 | connect("area_entered", self, "_on_area_entered_exited", [true]) 93 | connect("area_exited", self, "_on_area_entered_exited", [false]) 94 | connect("input_event", self, "_on_input_event") 95 | _rng.randomize() 96 | 97 | if type == Type.MEDBAY: 98 | var medbay_timer := Timer.new() 99 | medbay_timer.connect("timeout", self, "_on_MedbayTimer_timeout") 100 | medbay_timer.autostart = true 101 | add_child(medbay_timer) 102 | 103 | 104 | func _on_input_event(_v: Viewport, event: InputEvent, _s: int) -> void: 105 | if ( 106 | event.is_action_pressed("left_click") 107 | and Input.get_current_cursor_shape() == Input.CURSOR_CROSS 108 | and _target_index != -1 109 | ): 110 | sprite_target.visible = true 111 | sprite_target.get_child(_target_index).visible = true 112 | emit_signal( 113 | "targeted", 114 | { 115 | "type": Controller.Type.PROJECTILE, 116 | "index": _target_index, 117 | "target_position": position 118 | } 119 | ) 120 | _target_index = -1 121 | 122 | 123 | func _on_mouse_entered_exited(has_entered: bool) -> void: 124 | feedback.visible = has_entered 125 | var group := "selected-room" 126 | if has_entered: 127 | add_to_group(group) 128 | elif is_in_group(group): 129 | remove_from_group(group) 130 | 131 | 132 | func _on_area_entered_exited(area: Area2D, has_entered: bool) -> void: 133 | if area.is_in_group("door"): 134 | var entrance := position - area.position 135 | entrance = entrance.project(Vector2.DOWN.rotated(area.rotation)).normalized() 136 | entrance *= 0.5 * _tilemap.cell_size.x 137 | entrance += area.position 138 | entrance = _tilemap.world_to_map(entrance) 139 | _entrances[entrance] = null 140 | 141 | elif area.is_in_group("unit"): 142 | if has_entered: 143 | units[area.owner] = null 144 | emit_signal("modifier_changed", type, _modifiers[type][1]) 145 | else: 146 | units.erase(area.owner) 147 | if units.empty(): 148 | emit_signal("modifier_changed", type, _modifiers[type][0]) 149 | update() 150 | 151 | 152 | func _on_Controller_targeting(msg: Dictionary) -> void: 153 | _target_index = msg.index 154 | if _target_index != -1: 155 | sprite_target.visible = false 156 | sprite_target.get_child(_target_index).visible = false 157 | for node in sprite_target.get_children(): 158 | if node.visible: 159 | sprite_target.visible = true 160 | break 161 | 162 | 163 | func _on_MedbayTimer_timeout() -> void: 164 | for unit in units: 165 | unit.heal() 166 | 167 | 168 | func _draw() -> void: 169 | if Engine.editor_hint: 170 | return 171 | 172 | var has_fog: bool = not owner.has_sensors 173 | if owner.is_in_group("player"): 174 | has_fog = has_fog and units.empty() 175 | emit_signal("fog_changed", self, has_fog) 176 | draw_rect(_fog[has_fog], FOG_COLOR) 177 | 178 | 179 | func randv() -> Vector2: 180 | var top_left_world := _tilemap.map_to_world(_top_left) 181 | var bottom_right_world := _tilemap.map_to_world(_bottom_right) 182 | return Utils.randvf_range(_rng, top_left_world, bottom_right_world) 183 | 184 | 185 | func randvi() -> Vector2: 186 | var offset := Utils.randvi_range(_rng, _top_left, _bottom_right - Vector2.ONE) 187 | offset = _tilemap.map_to_world(offset) + _tilemap.cell_size / 2 188 | return offset 189 | 190 | 191 | func has_point(point: Vector2) -> bool: 192 | return Rect2(_top_left, size).has_point(point) 193 | 194 | 195 | func set_size(value: Vector2) -> void: 196 | for axis in [Vector2.AXIS_X, Vector2.AXIS_Y]: 197 | size[axis] = max(1, value[axis]) 198 | _setup_extents() 199 | 200 | 201 | func set_o2(value: int) -> void: 202 | o2 = clamp(value, 0, 100) 203 | color_rect_o2.color.a = lerp(0.5, 0, o2 / 100.0) 204 | 205 | 206 | func get_slot(slots: Dictionary, unit: Unit) -> Vector2: 207 | var out := Vector2.INF 208 | var entrance := _get_entrance(_tilemap.world_to_map(unit.path_follow.position)) 209 | 210 | var valid_positions := [] 211 | for offset in self: 212 | valid_positions.push_back([offset, offset.distance_to(entrance)]) 213 | valid_positions.sort_custom(self, "sort_by_second_index") 214 | 215 | for data in valid_positions: 216 | var offset: Vector2 = data[0] 217 | if not (offset in slots and slots[offset] != unit): 218 | out = offset 219 | break 220 | return out 221 | 222 | 223 | static func sort_by_second_index(a: Array, b: Array) -> bool: 224 | return a[1] < b[1] 225 | 226 | 227 | func _get_entrance(from: Vector2) -> Vector2: 228 | var out := Vector2.INF 229 | var distance := INF 230 | for entrance in _entrances: 231 | var curve: Curve2D = _tilemap.find_path(from, entrance) 232 | var length := curve.get_baked_length() 233 | if distance > length: 234 | distance = length 235 | out = entrance 236 | return out 237 | 238 | 239 | func _iter_init(_arg) -> bool: 240 | _iter_index = 0 241 | return _iter_is_running() 242 | 243 | 244 | func _iter_next(_arg) -> bool: 245 | _iter_index += 1 246 | return _iter_is_running() 247 | 248 | 249 | func _iter_get(_arg) -> Vector2: 250 | var offset := Utils.index_to_xy(size.x, _iter_index) 251 | return _top_left + offset 252 | 253 | 254 | func _iter_is_running() -> bool: 255 | return _iter_index < _area 256 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Rooms/Room.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=6 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms/Room.gd" type="Script" id=2] 5 | [ext_resource path="res://TacticalSpaceCombat/UI/UIFeedback.tscn" type="PackedScene" id=3] 6 | 7 | [sub_resource type="CircleShape2D" id=1] 8 | 9 | [sub_resource type="RectangleShape2D" id=2] 10 | resource_local_to_scene = true 11 | extents = Vector2( 32, 32 ) 12 | 13 | [node name="Room" type="Area2D" groups=["room"]] 14 | script = ExtResource( 2 ) 15 | 16 | [node name="HitArea2D" type="Area2D" parent="."] 17 | collision_layer = 0 18 | collision_mask = 0 19 | 20 | [node name="CollisionShape2D" type="CollisionShape2D" parent="HitArea2D"] 21 | shape = SubResource( 1 ) 22 | 23 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 24 | shape = SubResource( 2 ) 25 | 26 | [node name="Feedback" parent="." instance=ExtResource( 3 )] 27 | modulate = Color( 1, 0.709804, 0.439216, 1 ) 28 | 29 | [node name="SpriteType" type="Sprite" parent="."] 30 | visible = false 31 | modulate = Color( 0.890196, 0.411765, 0.337255, 1 ) 32 | texture = ExtResource( 1 ) 33 | region_enabled = true 34 | 35 | [node name="SpriteTarget" type="Sprite" parent="."] 36 | visible = false 37 | modulate = Color( 0.45098, 0.152941, 0.360784, 1 ) 38 | texture = ExtResource( 1 ) 39 | region_enabled = true 40 | region_rect = Rect2( 96, 384, 32, 32 ) 41 | 42 | [node name="1" type="Sprite" parent="SpriteTarget"] 43 | visible = false 44 | position = Vector2( 0, -16 ) 45 | texture = ExtResource( 1 ) 46 | region_enabled = true 47 | region_rect = Rect2( 88, 384, 8, 8 ) 48 | 49 | [node name="2" type="Sprite" parent="SpriteTarget"] 50 | visible = false 51 | position = Vector2( 16, 0 ) 52 | texture = ExtResource( 1 ) 53 | region_enabled = true 54 | region_rect = Rect2( 88, 392, 8, 8 ) 55 | 56 | [node name="3" type="Sprite" parent="SpriteTarget"] 57 | visible = false 58 | position = Vector2( 0, 16 ) 59 | texture = ExtResource( 1 ) 60 | region_enabled = true 61 | region_rect = Rect2( 88, 400, 8, 8 ) 62 | 63 | [node name="4" type="Sprite" parent="SpriteTarget"] 64 | visible = false 65 | position = Vector2( -16, 0 ) 66 | texture = ExtResource( 1 ) 67 | region_enabled = true 68 | region_rect = Rect2( 88, 408, 8, 8 ) 69 | 70 | [node name="ColorRectO2" type="ColorRect" parent="."] 71 | margin_right = 40.0 72 | margin_bottom = 40.0 73 | mouse_filter = 2 74 | color = Color( 0.690196, 0.188235, 0.360784, 0 ) 75 | __meta__ = { 76 | "_edit_use_anchors_": false 77 | } 78 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Rooms/Wall.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="Wall" type="Line2D"] 4 | points = PoolVector2Array( -36, 0, 28, 0 ) 5 | width = 4.0 6 | default_color = Color( 0.301961, 0.65098, 1, 1 ) 7 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Shield.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Area2D 3 | 4 | export var powered := true setget set_powered 5 | export var hitpoints_max := 4 6 | export var charge_time := 5 7 | export var radius := 300 setget set_radius 8 | export var height := 100 setget set_height 9 | 10 | var hitpoints := 0 setget set_hitpoints 11 | var is_on := false 12 | 13 | onready var collision_shape: CollisionShape2D = $CollisionShape2D 14 | onready var polygon: Polygon2D = $Polygon2D 15 | onready var timer: Timer = $Timer 16 | 17 | 18 | func setup(mean_position: Vector2, mask: int) -> void: 19 | position = mean_position 20 | collision_mask = mask 21 | 22 | 23 | func _ready() -> void: 24 | self.radius = radius 25 | self.height = height 26 | if Engine.editor_hint: 27 | return 28 | 29 | connect("body_entered", self, "_on_body_entered") 30 | timer.connect("timeout", self, "_on_Timer_timeout") 31 | polygon.self_modulate.a = 0 32 | polygon.polygon = _get_shape_points() 33 | timer.wait_time = charge_time 34 | if powered: 35 | timer.start() 36 | 37 | 38 | func _on_Timer_timeout() -> void: 39 | self.hitpoints += 1 40 | 41 | 42 | func _on_body_entered(body: Node) -> void: 43 | if is_on: 44 | self.hitpoints -= 1 45 | timer.start() 46 | body.animation_player.play("feedback") 47 | 48 | 49 | func _get_shape_points() -> PoolVector2Array: 50 | var out := PoolVector2Array() 51 | var weight := TAU / 24.0 52 | var shape: CapsuleShape2D = collision_shape.shape 53 | for i in range(24): 54 | var offset := Vector2(0, 0.5 * shape.height * (-1 if i > 6 and i <= 18 else 1)) 55 | out.push_back(shape.radius * Vector2(sin(weight * i), cos(weight * i)) + offset) 56 | if i == 6 or i == 18: 57 | out.push_back(shape.radius * Vector2(sin(weight * i), cos(weight * i)) - offset) 58 | return collision_shape.transform.xform(out) 59 | 60 | 61 | func set_powered(value: bool) -> void: 62 | powered = value 63 | self.hitpoints = 0 64 | if timer != null: 65 | timer.call("start" if powered else "stop") 66 | 67 | 68 | func set_radius(value: int) -> void: 69 | radius = value 70 | if collision_shape != null: 71 | collision_shape.shape.radius = radius 72 | 73 | 74 | func set_height(value: int) -> void: 75 | height = value 76 | if collision_shape != null: 77 | collision_shape.shape.height = height 78 | 79 | 80 | func set_hitpoints(value: int) -> void: 81 | hitpoints = clamp(value, 0, hitpoints_max) 82 | is_on = hitpoints > 0 83 | if polygon != null and timer != null: 84 | polygon.self_modulate.a = hitpoints / float(hitpoints_max) 85 | timer.call("stop" if hitpoints == hitpoints_max else "start") 86 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Shield.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Shield.svg" type="Texture" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Shield.gd" type="Script" id=2] 5 | 6 | [sub_resource type="CapsuleShape2D" id=1] 7 | resource_local_to_scene = true 8 | 9 | [node name="Shield" type="Area2D"] 10 | script = ExtResource( 2 ) 11 | 12 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 13 | rotation = 1.5708 14 | shape = SubResource( 1 ) 15 | 16 | [node name="Polygon2D" type="Polygon2D" parent="."] 17 | modulate = Color( 0.890196, 0.411765, 0.341176, 0.392157 ) 18 | texture = ExtResource( 1 ) 19 | 20 | [node name="Timer" type="Timer" parent="."] 21 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/ShipAI.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/ShipTemplate.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms/Door.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms/Room.tscn" type="PackedScene" id=3] 6 | 7 | [node name="ShipAI" instance=ExtResource( 1 )] 8 | 9 | [node name="Room01" parent="Rooms" index="0" instance=ExtResource( 3 )] 10 | position = Vector2( 64, 64 ) 11 | type = 2 12 | size = Vector2( 2, 2 ) 13 | 14 | [node name="Room02" parent="Rooms" index="1" instance=ExtResource( 3 )] 15 | position = Vector2( 224, 96 ) 16 | size = Vector2( 3, 1 ) 17 | 18 | [node name="Room03" parent="Rooms" index="2" instance=ExtResource( 3 )] 19 | position = Vector2( 160, 224 ) 20 | size = Vector2( 1, 3 ) 21 | 22 | [node name="Room04" parent="Rooms" index="3" instance=ExtResource( 3 )] 23 | position = Vector2( 288, 256 ) 24 | size = Vector2( 3, 2 ) 25 | 26 | [node name="Door0102" parent="Doors" index="0" instance=ExtResource( 2 )] 27 | position = Vector2( 128, 96 ) 28 | rotation = 1.57079 29 | 30 | [node name="Door0203" parent="Doors" index="1" instance=ExtResource( 2 )] 31 | position = Vector2( 160, 128 ) 32 | 33 | [node name="Door0304" parent="Doors" index="2" instance=ExtResource( 2 )] 34 | position = Vector2( 192, 224 ) 35 | rotation = 1.57079 36 | 37 | [node name="Shield" parent="." index="9"] 38 | powered = false 39 | charge_time = 4 40 | radius = 250 41 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/ShipPlayer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=11 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/ShipTemplate.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms/Door.tscn" type="PackedScene" id=2] 5 | [ext_resource path="res://TacticalSpaceCombat/Ship/Units.gd" type="Script" id=3] 6 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms/Room.tscn" type="PackedScene" id=5] 7 | [ext_resource path="res://TacticalSpaceCombat/Ship/Units/UnitPlayer.tscn" type="PackedScene" id=8] 8 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/WeaponProjectile.tscn" type="PackedScene" id=9] 9 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/ControllerPlayerLaser.gd" type="Script" id=10] 10 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/ControllerPlayerProjectile.gd" type="Script" id=11] 11 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms/Wall.tscn" type="PackedScene" id=12] 12 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/WeaponLaser.tscn" type="PackedScene" id=13] 13 | 14 | [node name="ShipPlayer" groups=["player"] instance=ExtResource( 1 )] 15 | 16 | [node name="TileMap" parent="." index="0"] 17 | show_behind_parent = true 18 | 19 | [node name="Room01" parent="Rooms" index="0" instance=ExtResource( 5 )] 20 | position = Vector2( 96, 64 ) 21 | type = 1 22 | size = Vector2( 3, 2 ) 23 | 24 | [node name="Room02" parent="Rooms" index="1" instance=ExtResource( 5 )] 25 | position = Vector2( 128, 256 ) 26 | size = Vector2( 2, 2 ) 27 | 28 | [node name="Room03" parent="Rooms" index="2" instance=ExtResource( 5 )] 29 | position = Vector2( 224, 160 ) 30 | type = 4 31 | size = Vector2( 1, 3 ) 32 | 33 | [node name="Room04" parent="Rooms" index="3" instance=ExtResource( 5 )] 34 | position = Vector2( 352, 256 ) 35 | type = 3 36 | size = Vector2( 3, 2 ) 37 | 38 | [node name="Room05" parent="Rooms" index="4" instance=ExtResource( 5 )] 39 | position = Vector2( 384, 128 ) 40 | type = 2 41 | size = Vector2( 2, 2 ) 42 | 43 | [node name="Door0103" parent="Doors" index="0" instance=ExtResource( 2 )] 44 | position = Vector2( 192, 96 ) 45 | rotation = 1.5708 46 | 47 | [node name="Door0203" parent="Doors" index="1" instance=ExtResource( 2 )] 48 | position = Vector2( 192, 224 ) 49 | rotation = 1.5708 50 | 51 | [node name="Door0304" parent="Doors" index="2" instance=ExtResource( 2 )] 52 | position = Vector2( 256, 224 ) 53 | rotation = 1.5708 54 | 55 | [node name="Door0506" parent="Doors" index="3" instance=ExtResource( 2 )] 56 | position = Vector2( 352, 192 ) 57 | 58 | [node name="Wall" parent="Walls" index="0" instance=ExtResource( 12 )] 59 | position = Vector2( 416, 192 ) 60 | 61 | [node name="Units" parent="." index="5"] 62 | self_modulate = Color( 0.235294, 0.639216, 0.439216, 0.607843 ) 63 | script = ExtResource( 3 ) 64 | 65 | [node name="Unit01" parent="Units" index="0" instance=ExtResource( 8 )] 66 | 67 | [node name="PathFollow2D" parent="Units/Unit01" index="0"] 68 | position = Vector2( 96, 224 ) 69 | 70 | [node name="CollisionShape2D" parent="Units/Unit01/PathFollow2D/AreaUnit" index="1"] 71 | modulate = Color( 0.709804, 0, 1, 1 ) 72 | 73 | [node name="Hitpoints" parent="Units/Unit01" index="1"] 74 | position = Vector2( 96, 224 ) 75 | 76 | [node name="Unit02" parent="Units" index="1" instance=ExtResource( 8 )] 77 | 78 | [node name="PathFollow2D" parent="Units/Unit02" index="0"] 79 | position = Vector2( 160, 288 ) 80 | 81 | [node name="CollisionShape2D" parent="Units/Unit02/PathFollow2D/AreaUnit" index="1"] 82 | modulate = Color( 0.709804, 0, 1, 1 ) 83 | 84 | [node name="Hitpoints" parent="Units/Unit02" index="1"] 85 | position = Vector2( 160, 288 ) 86 | 87 | [node name="ControllerProjectile" type="Node2D" parent="Weapons" index="0"] 88 | position = Vector2( 384, 32 ) 89 | script = ExtResource( 11 ) 90 | 91 | [node name="Weapon" parent="Weapons/ControllerProjectile" index="0" instance=ExtResource( 9 )] 92 | attack = 1 93 | chance_fire = 0.3 94 | chance_breach = 1.0 95 | 96 | [node name="ControllerLaser1" type="Node2D" parent="Weapons" index="1"] 97 | position = Vector2( 480, 256 ) 98 | script = ExtResource( 10 ) 99 | 100 | [node name="Weapon" parent="Weapons/ControllerLaser1" index="0" instance=ExtResource( 13 )] 101 | weapon_name = "Laser 1" 102 | charge_time = 10.0 103 | attack = 1 104 | chance_fire = 0.2 105 | chance_breach = 0.2 106 | targeting_length = 160 107 | 108 | [node name="ControllerLaser2" type="Node2D" parent="Weapons" index="2"] 109 | position = Vector2( 352, 352 ) 110 | script = ExtResource( 10 ) 111 | 112 | [node name="Weapon" parent="Weapons/ControllerLaser2" index="0" instance=ExtResource( 13 )] 113 | weapon_name = "Laser 2" 114 | charge_time = 14.0 115 | chance_breach = 0.1 116 | targeting_length = 80 117 | 118 | [node name="Shield" parent="." index="9"] 119 | charge_time = 3 120 | 121 | [editable path="Doors/Door0103"] 122 | [editable path="Doors/Door0203"] 123 | [editable path="Doors/Door0304"] 124 | [editable path="Doors/Door0506"] 125 | [editable path="Units/Unit01"] 126 | [editable path="Units/Unit02"] 127 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/ShipTemplate.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Node2D 3 | 4 | signal hitpoints_changed(hitpoints, is_player) 5 | 6 | const LaserTracker := preload("Weapons/LaserTracker.tscn") 7 | const AttackLabel := preload("Weapons/AttackLabel.tscn") 8 | const BreachScene := preload("Hazards/Breach.tscn") 9 | const FireScene := preload("Hazards/Fire.tscn") 10 | 11 | export (int, 0, 30) var hitpoints := 30 12 | export (int, 0, 100) var o2_asphyxiation := 10 13 | export (int, 0, 10) var o2_replenish := 2 14 | 15 | var has_sensors := false 16 | 17 | var _evasion := 0.0 18 | var _slots := {} 19 | var _rng := RandomNumberGenerator.new() 20 | 21 | onready var tilemap: TileMap = $TileMap 22 | onready var rooms: Node2D = $Rooms 23 | onready var doors: Node2D = $Doors 24 | onready var units: Node2D = $Units 25 | onready var weapons: Node2D = $Weapons 26 | onready var projectiles: Node2D = $Projectiles 27 | onready var lasers: Node2D = $Lasers 28 | onready var shield: Area2D = $Shield 29 | onready var hazards: Node2D = $Hazards 30 | onready var timer_hazards: Timer = $TimerHazards 31 | 32 | 33 | func _ready() -> void: 34 | call("_ready_editor_hint" if Engine.editor_hint else "_ready_not_editor_hint") 35 | 36 | 37 | func _ready_editor_hint() -> void: 38 | for room in rooms.get_children(): 39 | room.setup(tilemap) 40 | 41 | 42 | func _ready_not_editor_hint() -> void: 43 | timer_hazards.connect("timeout", self, "_on_TimerHazards_timeout") 44 | _rng.randomize() 45 | 46 | for unit in units.get_children(): 47 | unit.connect("died", self, "_on_Unit_died") 48 | for door in doors.get_children(): 49 | door.connect("opened", unit, "set_is_walking", [true]) 50 | 51 | var position_map := tilemap.world_to_map(unit.path_follow.position) 52 | _slots[position_map] = unit 53 | 54 | for room in rooms.get_children(): 55 | room.connect("modifier_changed", self, "_on_Room_modifier_changed") 56 | room.connect("fog_changed", self, "_on_Room_fog_changed") 57 | room.connect("area_entered", self, "_on_RoomArea2D_area_entered", [room]) 58 | room.hit_area.connect("body_entered", self, "_on_RoomHitArea2D_body_entered", [room]) 59 | room.setup(tilemap) 60 | 61 | for point in room: 62 | tilemap.set_cellv(point, 0) 63 | 64 | if is_in_group("player") and room.type == BaseRoom.Type.SENSORS: 65 | has_sensors = true 66 | 67 | tilemap.setup(rooms, doors) 68 | projectiles.setup(rooms.mean_position) 69 | shield.setup( 70 | rooms.mean_position, 71 | Global.Layers.SHIPPLAYER if is_in_group("player") else Global.Layers.SHIPAI 72 | ) 73 | 74 | 75 | func _get_configuration_warning() -> String: 76 | var is_verified := ( 77 | is_in_group("player") 78 | and weapons.get_child_count() <= 4 79 | or not is_in_group("player") 80 | ) 81 | return "" if is_verified else "%s can't have more than 4 weapons!" % name 82 | 83 | 84 | func _unhandled_input(event: InputEvent) -> void: 85 | if not event.is_action_pressed("right_click"): 86 | return 87 | 88 | for unit in get_tree().get_nodes_in_group("selected-unit"): 89 | var point1: Vector2 = tilemap.world_to_map(unit.path_follow.position) 90 | for room in get_tree().get_nodes_in_group("selected-room"): 91 | if not room.owner.is_in_group("player"): 92 | break 93 | 94 | var point2: Vector2 = room.get_slot(_slots, unit) 95 | if is_inf(point2.x): 96 | break 97 | 98 | var path: Curve2D = tilemap.find_path(point1, point2) 99 | Utils.erase_value(_slots, unit) 100 | _slots[point2] = unit 101 | unit.walk(path) 102 | 103 | 104 | func _on_UIDoorsButton_pressed() -> void: 105 | var has_opened_doors := false 106 | for door in doors.get_children(): 107 | if door.is_open: 108 | has_opened_doors = true 109 | break 110 | 111 | for door in doors.get_children(): 112 | door.is_open = not has_opened_doors 113 | 114 | 115 | func _on_RoomHitArea2D_body_entered(body: RigidBody2D, room: BaseRoom) -> void: 116 | if not room.position.is_equal_approx(body.params.target_position) or _rng.randf() < _evasion: 117 | return 118 | 119 | body.animation_player.play("feedback") 120 | _handle_attack(body.params, room) 121 | 122 | 123 | func _on_RoomArea2D_area_entered(area: Area2D, room: BaseRoom) -> void: 124 | if area.is_in_group("laser"): 125 | _handle_attack(area.params, room) 126 | 127 | 128 | func _on_Room_modifier_changed(type: int, value: float) -> void: 129 | match type: 130 | BaseRoom.Type.HELM: 131 | _evasion = value 132 | BaseRoom.Type.WEAPONS: 133 | for controller in weapons.get_children(): 134 | controller.weapon.modifier = value 135 | 136 | 137 | func _on_Room_fog_changed(room: BaseRoom, has_fog: bool) -> void: 138 | for hazard in hazards.get_children(): 139 | if room.has_point(tilemap.world_to_map(hazard.position)): 140 | hazard.visible = not has_fog 141 | 142 | 143 | func _on_Unit_died(unit: Unit) -> void: 144 | Utils.erase_value(_slots, unit) 145 | for room in rooms.get_children(): 146 | room.units.erase(unit) 147 | 148 | 149 | func _on_Fire_spread(fire_position: Vector2) -> void: 150 | var neighbor_position := _get_neighbor_position(fire_position) 151 | var fire: Fire = hazards.add(FireScene, neighbor_position) 152 | if fire != null: 153 | fire.connect("attacked", self, "_take_damage") 154 | fire.connect("spread", self, "_on_Fire_spread") 155 | for room in rooms.get_children(): 156 | if room.has_point(tilemap.world_to_map(fire.position)): 157 | fire.visible = not _has_fog(room) 158 | break 159 | 160 | 161 | func _on_TimerHazards_timeout() -> void: 162 | _hazards_breach() 163 | _hazards_units() 164 | _hazards_o2() 165 | 166 | 167 | func _hazards_breach() -> void: 168 | for hazard in hazards.get_children(): 169 | if hazard is Fire: 170 | continue 171 | 172 | for room in rooms.get_children(): 173 | if room.has_point(tilemap.world_to_map(hazard.position)): 174 | room.o2 -= hazard.attack 175 | 176 | 177 | func _hazards_units() -> void: 178 | for room in rooms.get_children(): 179 | var hazard_was_attacked := false 180 | for hazard in hazards.get_children(): 181 | if room.has_point(tilemap.world_to_map(hazard.position)): 182 | for unit in room.units: 183 | if hazard is Fire: 184 | unit.take_damage(hazard.attack) 185 | 186 | if not hazard_was_attacked: 187 | hazard.take_damage(unit.attack) 188 | hazard_was_attacked = true 189 | 190 | if hazard is Fire and room.o2 < o2_asphyxiation: 191 | hazard.take_damage(hazard.o2_damage) 192 | 193 | for unit in room.units: 194 | if room.o2 < o2_asphyxiation: 195 | unit.take_damage(unit.o2_damage) 196 | 197 | 198 | func _hazards_o2() -> void: 199 | var o2_associations := {} 200 | var room_associations := {} 201 | for door in doors.get_children(): 202 | if not door.is_open: 203 | continue 204 | 205 | var o2_mean := 0.0 206 | for room in door.rooms: 207 | o2_mean += room.o2 208 | room_associations[room] = room_associations.get(room, 0) + 1 209 | o2_mean /= door.rooms.size() 210 | 211 | for room in door.rooms: 212 | o2_associations[room] = o2_associations.get(room, 0) + o2_mean 213 | 214 | for room in rooms.get_children(): 215 | if room in o2_associations: 216 | o2_associations[room] /= room_associations[room] 217 | room.o2 = lerp(room.o2, o2_associations[room], 0.5) 218 | room.o2 += o2_replenish 219 | 220 | 221 | func add_laser_tracker(color: Color) -> Node: 222 | var laser_tracker := LaserTracker.instance() 223 | lasers.add_child(laser_tracker) 224 | laser_tracker.setup(color, rooms, shield) 225 | return laser_tracker 226 | 227 | 228 | func _has_fog(room: BaseRoom) -> bool: 229 | var room_has_fog := not has_sensors 230 | if is_in_group("player"): 231 | room_has_fog = room_has_fog and room.units.empty() 232 | return room_has_fog 233 | 234 | 235 | func _handle_attack(params: Dictionary, room: BaseRoom) -> void: 236 | var room_has_fog := _has_fog(room) 237 | 238 | if _rng.randf() < params.chance_fire: 239 | var fire: Fire = hazards.add(FireScene, room.randvi()) 240 | if fire != null: 241 | fire.connect("attacked", self, "_take_damage") 242 | fire.connect("spread", self, "_on_Fire_spread") 243 | fire.visible = not room_has_fog 244 | 245 | if _rng.randf() < params.chance_breach: 246 | var breach: Breach = hazards.add(BreachScene, room.randvi()) 247 | if breach != null: 248 | breach.visible = not room_has_fog 249 | 250 | _take_damage(params.attack, room.position) 251 | 252 | 253 | func _take_damage(attack: int, object_position: Vector2) -> void: 254 | var attack_label := AttackLabel.instance() 255 | attack_label.setup(attack, object_position) 256 | add_child(attack_label) 257 | 258 | hitpoints -= attack 259 | hitpoints = max(0, hitpoints) 260 | emit_signal("hitpoints_changed", hitpoints, is_in_group("player")) 261 | 262 | 263 | func _get_neighbor_position(at: Vector2) -> Vector2: 264 | var neighbors := [] 265 | var point1 := tilemap.world_to_map(at) 266 | for offset in Utils.DIRECTIONS: 267 | var point2: Vector2 = point1 + offset 268 | if tilemap.get_cellv(point2) != tilemap.INVALID_CELL: 269 | neighbors.push_back(tilemap.map_to_world(point2) + 0.5 * tilemap.cell_size) 270 | var index := _rng.randi_range(0, neighbors.size() - 1) 271 | return neighbors[index] 272 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/ShipTemplate.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=8 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/Shield.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Assets/ShipTileSet.tres" type="TileSet" id=2] 5 | [ext_resource path="res://TacticalSpaceCombat/Ship/Hazards.gd" type="Script" id=3] 6 | [ext_resource path="res://TacticalSpaceCombat/Ship/ShipTemplate.gd" type="Script" id=4] 7 | [ext_resource path="res://TacticalSpaceCombat/Ship/TileMap.gd" type="Script" id=6] 8 | [ext_resource path="res://TacticalSpaceCombat/Ship/Projectiles.gd" type="Script" id=7] 9 | [ext_resource path="res://TacticalSpaceCombat/Ship/Rooms.gd" type="Script" id=8] 10 | 11 | [node name="ShipTemplate" type="Node2D"] 12 | script = ExtResource( 4 ) 13 | 14 | [node name="TileMap" type="TileMap" parent="."] 15 | tile_set = ExtResource( 2 ) 16 | format = 1 17 | script = ExtResource( 6 ) 18 | 19 | [node name="Rooms" type="Node2D" parent="."] 20 | script = ExtResource( 8 ) 21 | 22 | [node name="Hazards" type="Node2D" parent="."] 23 | position = Vector2( 1, 0 ) 24 | script = ExtResource( 3 ) 25 | 26 | [node name="Doors" type="Node2D" parent="."] 27 | 28 | [node name="Walls" type="Node2D" parent="."] 29 | 30 | [node name="Units" type="Node2D" parent="."] 31 | self_modulate = Color( 0.239216, 0.431373, 0.439216, 0.607843 ) 32 | 33 | [node name="Weapons" type="Node2D" parent="."] 34 | 35 | [node name="Projectiles" type="Node2D" parent="."] 36 | script = ExtResource( 7 ) 37 | 38 | [node name="Lasers" type="Node2D" parent="."] 39 | 40 | [node name="Shield" parent="." instance=ExtResource( 1 )] 41 | 42 | [node name="TimerHazards" type="Timer" parent="."] 43 | wait_time = 0.5 44 | autostart = true 45 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/TileMap.gd: -------------------------------------------------------------------------------- 1 | extends TileMap 2 | 3 | var _astar: AStar2D = AStar2D.new() 4 | var _size: Vector2 = Vector2.ZERO 5 | 6 | 7 | func setup(rooms: Node2D, doors: Node2D) -> void: 8 | update_bitmask_region() 9 | _size = get_used_rect().size 10 | for room in rooms.get_children(): 11 | for point in room: 12 | var id := Utils.xy_to_index(_size.x, point) 13 | _astar.add_point(id, point) 14 | for neighbor in _get_neighbors(room, point): 15 | var neighbor_id := Utils.xy_to_index(_size.x, neighbor) 16 | _astar.add_point(neighbor_id, neighbor) 17 | _astar.connect_points(id, neighbor_id) 18 | 19 | var offset := cell_size / 2 * Vector2.UP 20 | for door in doors.get_children(): 21 | var id1 := Utils.xy_to_index(_size.x, world_to_map(door.transform.xform(offset))) 22 | var id2 := Utils.xy_to_index(_size.x, world_to_map(door.transform.xform(-offset))) 23 | _astar.connect_points(id1, id2) 24 | 25 | 26 | func find_path(point1: Vector2, point2: Vector2) -> Curve2D: 27 | var out := Curve2D.new() 28 | var id1 := Utils.xy_to_index(_size.x, point1) 29 | var id2 := Utils.xy_to_index(_size.x, point2) 30 | if _astar.has_point(id1) and _astar.has_point(id2): 31 | var path := _astar.get_point_path(id1, id2) 32 | for i in range(1, path.size()): 33 | out.add_point(map_to_world(path[i]) + cell_size / 2) 34 | return out 35 | 36 | 37 | func _get_neighbors(room: BaseRoom, point: Vector2) -> Array: 38 | var out := [] 39 | for offset in Utils.DIRECTIONS: 40 | offset += point 41 | if room.has_point(offset): 42 | out.push_back(offset) 43 | return out 44 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Units.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | const DEFAULT_POLYGON := PoolVector2Array([Vector2.ZERO, Vector2.ZERO, Vector2.ZERO, Vector2.ZERO]) 4 | 5 | var _is_selecting := false 6 | var _polygon := DEFAULT_POLYGON 7 | 8 | 9 | func _input(event: InputEvent) -> void: 10 | if not (event is InputEventMouse and Input.get_current_cursor_shape() == Input.CURSOR_ARROW): 11 | return 12 | 13 | var mouse_position := get_local_mouse_position() 14 | if event.is_action_pressed("left_click"): 15 | _is_selecting = true 16 | 17 | for index in range(_polygon.size()): 18 | _polygon[index] = mouse_position 19 | 20 | for unit in get_children(): 21 | unit.is_selected = false 22 | 23 | elif _is_selecting and event is InputEventMouseMotion: 24 | _polygon[1] = Vector2(mouse_position.x, _polygon[0].y) 25 | _polygon[2] = mouse_position 26 | _polygon[3] = Vector2(_polygon[0].x, mouse_position.y) 27 | 28 | elif event.is_action_released("left_click"): 29 | select_units() 30 | 31 | _is_selecting = false 32 | _polygon = DEFAULT_POLYGON 33 | 34 | update() 35 | 36 | 37 | func _draw() -> void: 38 | draw_polygon(_polygon, [self_modulate]) 39 | 40 | 41 | func select_units() -> void: 42 | var query := Physics2DShapeQueryParameters.new() 43 | var shape := ConvexPolygonShape2D.new() 44 | shape.points = _polygon 45 | 46 | query.set_shape(shape) 47 | query.transform = global_transform 48 | query.collide_with_bodies = false 49 | query.collide_with_areas = true 50 | query.collision_layer = Global.Layers.UI 51 | 52 | for dict in get_world_2d().direct_space_state.intersect_shape(query): 53 | dict.collider.owner.is_selected = true 54 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Units/Unit.gd: -------------------------------------------------------------------------------- 1 | class_name Unit 2 | extends Path2D 3 | 4 | signal died(unit) 5 | 6 | const COLORS := {"default": Color("323e4f"), "selected": Color("3ca370")} 7 | 8 | export var speed := 150 9 | export var attack := 15 10 | export var o2_damage := 10 11 | export var heal_recovery := 15 12 | 13 | var is_walking: bool setget set_is_walking 14 | 15 | onready var path_follow: PathFollow2D = $PathFollow2D 16 | onready var area_unit: Area2D = $PathFollow2D/AreaUnit 17 | onready var hitpoints: ProgressBar = $Hitpoints/Hitpoints 18 | 19 | 20 | func _ready() -> void: 21 | area_unit.connect("area_entered", self, "_on_AreaUnit_area_entered") 22 | area_unit.modulate = COLORS.default 23 | self.is_walking = false 24 | 25 | 26 | func _on_AreaUnit_area_entered(area: Area2D) -> void: 27 | if area.is_in_group("door") and not area.is_open: 28 | self.is_walking = false 29 | 30 | 31 | func _process(delta: float) -> void: 32 | path_follow.offset += speed * delta 33 | if path_follow.offset >= curve.get_baked_length(): 34 | self.is_walking = false 35 | 36 | 37 | func walk(path: Curve2D) -> void: 38 | if path.get_point_count() == 0: 39 | return 40 | 41 | curve = path 42 | curve.add_point(path_follow.position, Vector2.ZERO, Vector2.ZERO, 0) 43 | path_follow.offset = 0 44 | self.is_walking = true 45 | 46 | 47 | func heal() -> void: 48 | hitpoints.value += heal_recovery 49 | 50 | 51 | func take_damage(other_attack: int) -> void: 52 | hitpoints.value -= other_attack 53 | if hitpoints.value <= 0: 54 | queue_free() 55 | emit_signal("died", self) 56 | 57 | 58 | func set_is_walking(value: bool) -> void: 59 | is_walking = value 60 | set_process(is_walking) 61 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Units/Unit.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Units/Unit.gd" type="Script" id=2] 5 | [ext_resource path="res://TacticalSpaceCombat/Assets/UnitHitpointsBg.tres" type="StyleBox" id=3] 6 | [ext_resource path="res://TacticalSpaceCombat/Assets/UnitHitpointsFg.tres" type="StyleBox" id=4] 7 | 8 | [sub_resource type="Curve2D" id=1] 9 | _data = { 10 | "points": PoolVector2Array( ) 11 | } 12 | 13 | [sub_resource type="RectangleShape2D" id=2] 14 | extents = Vector2( 8, 8 ) 15 | 16 | [node name="Unit" type="Path2D"] 17 | self_modulate = Color( 0.501961, 0.6, 1, 0.701961 ) 18 | curve = SubResource( 1 ) 19 | script = ExtResource( 2 ) 20 | 21 | [node name="PathFollow2D" type="PathFollow2D" parent="."] 22 | cubic_interp = false 23 | loop = false 24 | 25 | [node name="AreaUnit" type="Area2D" parent="PathFollow2D" groups=[ 26 | "unit", 27 | ]] 28 | 29 | [node name="Sprite" type="Sprite" parent="PathFollow2D/AreaUnit"] 30 | texture = ExtResource( 1 ) 31 | region_enabled = true 32 | region_rect = Rect2( 64, 384, 24, 32 ) 33 | 34 | [node name="CollisionShape2D" type="CollisionShape2D" parent="PathFollow2D/AreaUnit"] 35 | shape = SubResource( 2 ) 36 | 37 | [node name="RemoteTransform2D" type="RemoteTransform2D" parent="PathFollow2D"] 38 | remote_path = NodePath("../../Hitpoints") 39 | update_rotation = false 40 | 41 | [node name="Hitpoints" type="Node2D" parent="."] 42 | 43 | [node name="Hitpoints" type="ProgressBar" parent="Hitpoints"] 44 | margin_left = -16.0 45 | margin_top = -20.0 46 | margin_right = 16.0 47 | margin_bottom = -16.0 48 | rect_min_size = Vector2( 32, 4 ) 49 | mouse_filter = 2 50 | custom_styles/fg = ExtResource( 4 ) 51 | custom_styles/bg = ExtResource( 3 ) 52 | value = 100.0 53 | rounded = true 54 | percent_visible = false 55 | __meta__ = { 56 | "_edit_use_anchors_": false 57 | } 58 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Units/UnitPlayer.gd: -------------------------------------------------------------------------------- 1 | extends Unit 2 | 3 | var is_selected: bool setget set_is_selected 4 | 5 | var _ui_unit_feedback: NinePatchRect 6 | 7 | onready var area_select: Area2D = $PathFollow2D/AreaSelect 8 | 9 | 10 | func setup(ui_unit: ColorRect) -> void: 11 | _ui_unit_feedback = ui_unit.get_node("Feedback") 12 | ui_unit.get_node("Icon").modulate = COLORS.default 13 | ui_unit.connect("gui_input", self, "_on_UIUnit_gui_input") 14 | connect("tree_exited", ui_unit, "queue_free") 15 | 16 | 17 | func _ready() -> void: 18 | self.is_selected = false 19 | 20 | 21 | func _on_UIUnit_gui_input(event: InputEvent) -> void: 22 | if event.is_action_pressed("left_click"): 23 | self.is_selected = true 24 | 25 | 26 | func set_is_selected(value: bool) -> void: 27 | var group := "selected-unit" 28 | 29 | is_selected = value 30 | if is_selected: 31 | area_unit.modulate = COLORS.selected 32 | add_to_group(group) 33 | else: 34 | area_unit.modulate = COLORS.default 35 | if is_in_group(group): 36 | remove_from_group(group) 37 | 38 | if _ui_unit_feedback != null: 39 | _ui_unit_feedback.visible = is_selected 40 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Units/UnitPlayer.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/Units/Unit.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Units/UnitPlayer.gd" type="Script" id=2] 5 | 6 | [sub_resource type="CircleShape2D" id=1] 7 | radius = 24.0208 8 | 9 | [node name="UnitPlayer" instance=ExtResource( 1 )] 10 | script = ExtResource( 2 ) 11 | 12 | [node name="AreaSelect" type="Area2D" parent="PathFollow2D" index="1"] 13 | modulate = Color( 0.572549, 1, 0.545098, 1 ) 14 | collision_layer = 524288 15 | collision_mask = 0 16 | 17 | [node name="CollisionShape2D" type="CollisionShape2D" parent="PathFollow2D/AreaSelect" index="0"] 18 | shape = SubResource( 1 ) 19 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/AttackLabel.gd: -------------------------------------------------------------------------------- 1 | extends Control 2 | 3 | onready var label: Label = $Label 4 | onready var animation_player: AnimationPlayer = $AnimationPlayer 5 | 6 | var _position := Vector2.ZERO 7 | var _text := "" 8 | 9 | 10 | func setup(attack: int, position: Vector2) -> void: 11 | _position = position 12 | _text = "%d" % attack 13 | 14 | 15 | func _ready() -> void: 16 | rect_position = _position 17 | label.text = _text 18 | animation_player.play("feedback") 19 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/AttackLabel.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=5 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Theme.tres" type="Theme" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/AttackLabel.gd" type="Script" id=2] 5 | 6 | [sub_resource type="Animation" id=1] 7 | resource_name = "RESET" 8 | length = 0.001 9 | tracks/0/type = "value" 10 | tracks/0/path = NodePath("Label:rect_position") 11 | tracks/0/interp = 1 12 | tracks/0/loop_wrap = true 13 | tracks/0/imported = false 14 | tracks/0/enabled = true 15 | tracks/0/keys = { 16 | "times": PoolRealArray( 0 ), 17 | "transitions": PoolRealArray( 1 ), 18 | "update": 0, 19 | "values": [ Vector2( -10, -15 ) ] 20 | } 21 | 22 | [sub_resource type="Animation" id=2] 23 | resource_name = "feedback" 24 | length = 0.6 25 | tracks/0/type = "value" 26 | tracks/0/path = NodePath("Label:rect_position") 27 | tracks/0/interp = 1 28 | tracks/0/loop_wrap = true 29 | tracks/0/imported = false 30 | tracks/0/enabled = true 31 | tracks/0/keys = { 32 | "times": PoolRealArray( 0, 0.6 ), 33 | "transitions": PoolRealArray( 2, 1 ), 34 | "update": 0, 35 | "values": [ Vector2( -10, -15 ), Vector2( -10, -60 ) ] 36 | } 37 | tracks/1/type = "method" 38 | tracks/1/path = NodePath(".") 39 | tracks/1/interp = 1 40 | tracks/1/loop_wrap = true 41 | tracks/1/imported = false 42 | tracks/1/enabled = true 43 | tracks/1/keys = { 44 | "times": PoolRealArray( 0.6 ), 45 | "transitions": PoolRealArray( 1 ), 46 | "values": [ { 47 | "args": [ ], 48 | "method": "queue_free" 49 | } ] 50 | } 51 | 52 | [node name="AttackLabel" type="Control"] 53 | theme = ExtResource( 1 ) 54 | script = ExtResource( 2 ) 55 | __meta__ = { 56 | "_edit_use_anchors_": false 57 | } 58 | 59 | [node name="Label" type="Label" parent="."] 60 | margin_left = -10.0 61 | margin_top = -15.0 62 | margin_right = 10.0 63 | margin_bottom = 15.0 64 | grow_horizontal = 2 65 | grow_vertical = 2 66 | rect_min_size = Vector2( 20, 30 ) 67 | custom_colors/font_color = Color( 0.294118, 0.356863, 0.670588, 1 ) 68 | custom_colors/font_color_shadow = Color( 1, 1, 0.921569, 1 ) 69 | custom_constants/shadow_as_outline = 1 70 | text = "0" 71 | align = 1 72 | valign = 1 73 | __meta__ = { 74 | "_edit_use_anchors_": false 75 | } 76 | 77 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 78 | anims/RESET = SubResource( 1 ) 79 | anims/feedback = SubResource( 2 ) 80 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/Controller.gd: -------------------------------------------------------------------------------- 1 | class_name Controller 2 | extends Node2D 3 | 4 | signal targeting(msg) 5 | 6 | enum Type {PROJECTILE, LASER} 7 | 8 | onready var weapon: Weapon = null if Engine.editor_hint else $Weapon 9 | 10 | 11 | func _on_Ship_targeted(msg: Dictionary) -> void: 12 | match msg: 13 | {"type": Type.PROJECTILE, ..}: 14 | if msg.index == get_index(): 15 | weapon.target_position = msg.target_position 16 | {"type": Type.LASER, "success": true}: 17 | weapon.has_targeted = true 18 | 19 | 20 | func _get_configuration_warning() -> String: 21 | return "" if has_node("Weapon") else "%s needs a Weapon child" % name 22 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/ControllerAILaser.gd: -------------------------------------------------------------------------------- 1 | tool 2 | class_name ControllerAILaser 3 | extends Controller 4 | 5 | 6 | func _ready() -> void: 7 | if Engine.editor_hint: 8 | return 9 | 10 | var msg := {"targeting_length": weapon.targeting_length} 11 | weapon.connect("fire_stopped", self, "emit_signal", ["targeting", msg]) 12 | 13 | yield(get_tree(), "idle_frame") 14 | emit_signal("targeting", msg) 15 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/ControllerAIProjectile.gd: -------------------------------------------------------------------------------- 1 | tool 2 | class_name ControllerAIProjectile 3 | extends Controller 4 | 5 | 6 | func _ready() -> void: 7 | if Engine.editor_hint: 8 | return 9 | 10 | weapon.setup(Global.Layers.SHIPPLAYER) 11 | 12 | var msg := {"index": get_index()} 13 | weapon.connect("fired", self, "emit_signal", ["targeting", msg]) 14 | 15 | yield(get_tree(), "idle_frame") 16 | emit_signal("targeting", msg) 17 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/ControllerPlayer.gd: -------------------------------------------------------------------------------- 1 | class_name ControllerPlayer 2 | extends Controller 3 | 4 | var _ui_weapon_button: Button 5 | var _ui_weapon_progress_bar: ProgressBar 6 | 7 | 8 | func setup(ui_weapon: VBoxContainer) -> void: 9 | _ui_weapon_button = ui_weapon.get_node("Button") 10 | _ui_weapon_progress_bar = ui_weapon.get_node("ProgressBar") 11 | _ui_weapon_progress_bar.min_value = Weapon.MIN_CHARGE 12 | _ui_weapon_progress_bar.max_value = Weapon.MAX_CHARGE 13 | 14 | _ui_weapon_button.connect("gui_input", self, "_on_UIWeaponButton_gui_input") 15 | _ui_weapon_button.connect("toggled", self, "_on_UIWeaponButton_toggled") 16 | _ui_weapon_button.text = weapon.weapon_name 17 | 18 | 19 | func _ready() -> void: 20 | weapon.tween.connect("tween_step", self, "_on_WeaponTween_tween_step") 21 | 22 | 23 | func _input(event: InputEvent) -> void: 24 | if ( 25 | event.is_action("right_click") 26 | and _ui_weapon_button.pressed 27 | and Input.get_current_cursor_shape() == Input.CURSOR_CROSS 28 | ): 29 | _ui_weapon_button.pressed = false 30 | 31 | 32 | func _on_WeaponTween_tween_step(_o: Object, _k: NodePath, _e: float, value: float) -> void: 33 | _ui_weapon_progress_bar.value = value 34 | 35 | 36 | func _on_UIWeaponButton_gui_input(event: InputEvent) -> void: 37 | if event.is_action_pressed("right_click"): 38 | _ui_weapon_button.pressed = false 39 | 40 | 41 | func _on_UIWeaponButton_toggled(is_pressed: bool) -> void: 42 | var cursor_shape := Input.CURSOR_CROSS if is_pressed else Input.CURSOR_ARROW 43 | Input.set_default_cursor_shape(cursor_shape) 44 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/ControllerPlayerLaser.gd: -------------------------------------------------------------------------------- 1 | class_name ControllerPlayerLaser 2 | extends ControllerPlayer 3 | 4 | 5 | func _on_Ship_targeted(msg: Dictionary) -> void: 6 | ._on_Ship_targeted(msg) 7 | _ui_weapon_button.pressed = false 8 | 9 | match msg: 10 | {"type": Type.LASER, "success": true}: 11 | weapon.fire() 12 | 13 | 14 | func _on_UIWeaponButton_gui_input(event: InputEvent) -> void: 15 | ._on_UIWeaponButton_gui_input(event) 16 | if event.is_action_pressed("right_click"): 17 | weapon.has_targeted = false 18 | 19 | 20 | func _on_UIWeaponButton_toggled(is_pressed: bool) -> void: 21 | ._on_UIWeaponButton_toggled(is_pressed) 22 | var msg := {"is_targeting": is_pressed, "targeting_length": weapon.targeting_length} 23 | emit_signal("targeting", msg) 24 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/ControllerPlayerProjectile.gd: -------------------------------------------------------------------------------- 1 | class_name ControllerPlayerProjectile 2 | extends ControllerPlayer 3 | 4 | 5 | func _ready() -> void: 6 | weapon.setup(Global.Layers.SHIPAI) 7 | 8 | 9 | func _on_Ship_targeted(msg: Dictionary) -> void: 10 | ._on_Ship_targeted(msg) 11 | if msg.index == get_index(): 12 | _ui_weapon_button.pressed = false 13 | 14 | match msg: 15 | {"type": Type.PROJECTILE, ..}: 16 | weapon.fire() 17 | 18 | 19 | func _on_UIWeaponButton_gui_input(event: InputEvent) -> void: 20 | ._on_UIWeaponButton_gui_input(event) 21 | if event.is_action_pressed("right_click"): 22 | weapon.target_position = Vector2.INF 23 | emit_signal("targeting", {"index": get_index()}) 24 | emit_signal("targeting", {"index": -1}) 25 | 26 | 27 | func _on_UIWeaponButton_toggled(is_pressed: bool) -> void: 28 | ._on_UIWeaponButton_toggled(is_pressed) 29 | var index := -1 30 | if is_pressed: 31 | index = get_index() 32 | weapon.target_position = Vector2.INF 33 | emit_signal("targeting", {"index": index}) 34 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/LaserArea.gd: -------------------------------------------------------------------------------- 1 | extends Area2D 2 | 3 | var params := {} 4 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/LaserTracker.gd: -------------------------------------------------------------------------------- 1 | extends Node2D 2 | 3 | signal targeted(msg) 4 | 5 | const LINE_DEFAULT := PoolVector2Array([Vector2.INF, Vector2.INF]) 6 | 7 | var _is_targeting := false 8 | var _targeting_length := 0 9 | var _rng := RandomNumberGenerator.new() 10 | var _rooms: Node2D = null 11 | var _shield: Area2D = null 12 | var _shield_polygon := PoolVector2Array() 13 | 14 | onready var tween: Tween = $Tween 15 | onready var area: Area2D = $Area2D 16 | onready var line: Line2D = $Line2D 17 | onready var target_line: Line2D = $TargetLine2D 18 | 19 | 20 | func setup(color: Color, rooms: Node2D, shield: Area2D) -> void: 21 | _rooms = rooms 22 | _shield = shield 23 | _shield_polygon = _shield.polygon.polygon 24 | _shield_polygon = _shield.transform.xform(_shield_polygon) 25 | line.default_color = color 26 | target_line.default_color = color 27 | 28 | 29 | func _ready() -> void: 30 | _rng.randomize() 31 | target_line.points = LINE_DEFAULT 32 | line.points = LINE_DEFAULT 33 | 34 | 35 | func _unhandled_input(event: InputEvent) -> void: 36 | if not (event is InputEventMouse and _is_targeting): 37 | return 38 | 39 | if event.is_action_pressed("left_click"): 40 | target_line.points[0] = get_local_mouse_position() 41 | elif target_line.points[0] != Vector2.INF and event is InputEventMouseMotion: 42 | var offset: Vector2 = get_local_mouse_position() - target_line.points[0] 43 | offset = offset.clamped(_targeting_length) 44 | target_line.points[1] = target_line.points[0] + offset 45 | elif event.is_action_released("left_click"): 46 | _is_targeting = false 47 | var msg := {"type": Controller.Type.LASER, "success": target_line.points[1] != Vector2.INF} 48 | emit_signal("targeted", msg) 49 | 50 | 51 | func _on_Controller_targeting(msg: Dictionary) -> void: 52 | match msg: 53 | {"targeting_length": var targeting_length, "is_targeting": var is_targeting}: 54 | _is_targeting = is_targeting 55 | _targeting_length = targeting_length 56 | if _is_targeting: 57 | target_line.points = LINE_DEFAULT 58 | {"targeting_length": var targeting_length}: 59 | target_line.points = _rooms.get_laser_points(targeting_length) 60 | emit_signal("targeted", {"type": Controller.Type.LASER, "success": true}) 61 | 62 | 63 | func _on_Weapon_fire_started(params: Dictionary) -> void: 64 | if Vector2.INF in target_line.points: 65 | return 66 | 67 | area.set_deferred("monitorable", true) 68 | area.params = params 69 | 70 | line.points[0] = _rooms.mean_position + Utils.randvf_circle(_rng, Projectile.MAX_DISTANCE) 71 | tween.interpolate_method( 72 | self, "_swipe_laser", target_line.points[0], target_line.points[1], params.duration 73 | ) 74 | tween.start() 75 | 76 | 77 | func _on_Weapon_fire_stopped() -> void: 78 | tween.remove_all() 79 | target_line.points = LINE_DEFAULT 80 | line.points = LINE_DEFAULT 81 | area.position = Vector2.ZERO 82 | area.set_deferred("monitorable", false) 83 | 84 | 85 | func _swipe_laser(offset: Vector2) -> void: 86 | line.points[1] = offset 87 | if _shield.is_on: 88 | for points in Geometry.clip_polyline_with_polygon_2d(line.points, _shield_polygon): 89 | line.points = points 90 | area.position = line.points[1] 91 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/LaserTracker.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=4 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/LaserArea.gd" type="Script" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/LaserTracker.gd" type="Script" id=2] 5 | 6 | [sub_resource type="CircleShape2D" id=1] 7 | 8 | [node name="LaserTracker" type="Node2D"] 9 | script = ExtResource( 2 ) 10 | 11 | [node name="Tween" type="Tween" parent="."] 12 | 13 | [node name="Area2D" type="Area2D" parent="." groups=[ 14 | "laser", 15 | ]] 16 | monitorable = false 17 | script = ExtResource( 1 ) 18 | 19 | [node name="CollisionShape2D" type="CollisionShape2D" parent="Area2D"] 20 | shape = SubResource( 1 ) 21 | 22 | [node name="Line2D" type="Line2D" parent="."] 23 | modulate = Color( 2.5, 1.5, 1.5, 1 ) 24 | width = 2.0 25 | antialiased = true 26 | 27 | [node name="TargetLine2D" type="Line2D" parent="."] 28 | width = 2.0 29 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/Projectile.gd: -------------------------------------------------------------------------------- 1 | class_name Projectile 2 | extends RigidBody2D 3 | 4 | const MAX_DISTANCE := 2000 5 | 6 | var max_distance := MAX_DISTANCE 7 | var params := {} 8 | 9 | var _origin := Vector2.ZERO 10 | 11 | onready var animation_player: AnimationPlayer = $AnimationPlayer 12 | 13 | 14 | func _ready() -> void: 15 | _origin = position 16 | 17 | 18 | func _process(delta: float) -> void: 19 | if position.distance_to(_origin) > max_distance: 20 | queue_free() 21 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/Projectile.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=7 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/Projectile.gd" type="Script" id=2] 5 | [ext_resource path="res://TacticalSpaceCombat/Assets/Shockwave.tres" type="Material" id=3] 6 | 7 | [sub_resource type="CapsuleShape2D" id=1] 8 | radius = 4.0 9 | height = 18.0 10 | 11 | [sub_resource type="Animation" id=2] 12 | resource_name = "RESET" 13 | length = 0.001 14 | tracks/0/type = "value" 15 | tracks/0/path = NodePath("Shockwave:material:shader_param/force") 16 | tracks/0/interp = 1 17 | tracks/0/loop_wrap = true 18 | tracks/0/imported = false 19 | tracks/0/enabled = true 20 | tracks/0/keys = { 21 | "times": PoolRealArray( 0 ), 22 | "transitions": PoolRealArray( 1 ), 23 | "update": 0, 24 | "values": [ 0.0 ] 25 | } 26 | tracks/1/type = "value" 27 | tracks/1/path = NodePath("Shockwave:material:shader_param/size") 28 | tracks/1/interp = 1 29 | tracks/1/loop_wrap = true 30 | tracks/1/imported = false 31 | tracks/1/enabled = true 32 | tracks/1/keys = { 33 | "times": PoolRealArray( 0 ), 34 | "transitions": PoolRealArray( 1 ), 35 | "update": 0, 36 | "values": [ 0.0 ] 37 | } 38 | tracks/2/type = "value" 39 | tracks/2/path = NodePath("Sprite:visible") 40 | tracks/2/interp = 1 41 | tracks/2/loop_wrap = true 42 | tracks/2/imported = false 43 | tracks/2/enabled = true 44 | tracks/2/keys = { 45 | "times": PoolRealArray( 0 ), 46 | "transitions": PoolRealArray( 1 ), 47 | "update": 1, 48 | "values": [ true ] 49 | } 50 | tracks/3/type = "value" 51 | tracks/3/path = NodePath("Shockwave:visible") 52 | tracks/3/interp = 1 53 | tracks/3/loop_wrap = true 54 | tracks/3/imported = false 55 | tracks/3/enabled = true 56 | tracks/3/keys = { 57 | "times": PoolRealArray( 0 ), 58 | "transitions": PoolRealArray( 1 ), 59 | "update": 1, 60 | "values": [ false ] 61 | } 62 | 63 | [sub_resource type="Animation" id=3] 64 | resource_name = "feedback" 65 | length = 0.5 66 | step = 0.05 67 | tracks/0/type = "value" 68 | tracks/0/path = NodePath("Shockwave:material:shader_param/force") 69 | tracks/0/interp = 1 70 | tracks/0/loop_wrap = true 71 | tracks/0/imported = false 72 | tracks/0/enabled = true 73 | tracks/0/keys = { 74 | "times": PoolRealArray( 0, 0.45 ), 75 | "transitions": PoolRealArray( 2, 1 ), 76 | "update": 0, 77 | "values": [ 0.2, 0.0 ] 78 | } 79 | tracks/1/type = "value" 80 | tracks/1/path = NodePath("Shockwave:material:shader_param/size") 81 | tracks/1/interp = 1 82 | tracks/1/loop_wrap = true 83 | tracks/1/imported = false 84 | tracks/1/enabled = true 85 | tracks/1/keys = { 86 | "times": PoolRealArray( 0, 0.5 ), 87 | "transitions": PoolRealArray( -2, 1 ), 88 | "update": 0, 89 | "values": [ 0.0, 0.5 ] 90 | } 91 | tracks/2/type = "method" 92 | tracks/2/path = NodePath(".") 93 | tracks/2/interp = 1 94 | tracks/2/loop_wrap = true 95 | tracks/2/imported = false 96 | tracks/2/enabled = true 97 | tracks/2/keys = { 98 | "times": PoolRealArray( 0, 0.5 ), 99 | "transitions": PoolRealArray( 1, 1 ), 100 | "values": [ { 101 | "args": [ Vector2( 0, 0 ) ], 102 | "method": "set_linear_velocity" 103 | }, { 104 | "args": [ ], 105 | "method": "queue_free" 106 | } ] 107 | } 108 | tracks/3/type = "value" 109 | tracks/3/path = NodePath("Sprite:visible") 110 | tracks/3/interp = 1 111 | tracks/3/loop_wrap = true 112 | tracks/3/imported = false 113 | tracks/3/enabled = true 114 | tracks/3/keys = { 115 | "times": PoolRealArray( 0 ), 116 | "transitions": PoolRealArray( 1 ), 117 | "update": 1, 118 | "values": [ false ] 119 | } 120 | tracks/4/type = "value" 121 | tracks/4/path = NodePath("Shockwave:visible") 122 | tracks/4/interp = 1 123 | tracks/4/loop_wrap = true 124 | tracks/4/imported = false 125 | tracks/4/enabled = true 126 | tracks/4/keys = { 127 | "times": PoolRealArray( 0 ), 128 | "transitions": PoolRealArray( 1 ), 129 | "update": 1, 130 | "values": [ true ] 131 | } 132 | 133 | [node name="Projectile" type="RigidBody2D"] 134 | collision_layer = 0 135 | collision_mask = 0 136 | linear_velocity = Vector2( 750, 0 ) 137 | script = ExtResource( 2 ) 138 | 139 | [node name="Sprite" type="Sprite" parent="."] 140 | modulate = Color( 0.921569, 0.337255, 0.294118, 1 ) 141 | texture = ExtResource( 1 ) 142 | region_enabled = true 143 | region_rect = Rect2( 130, 419, 28, 12 ) 144 | 145 | [node name="CollisionShape2D" type="CollisionShape2D" parent="."] 146 | rotation = 1.5708 147 | shape = SubResource( 1 ) 148 | 149 | [node name="Shockwave" type="ColorRect" parent="."] 150 | visible = false 151 | material = ExtResource( 3 ) 152 | margin_left = -64.0 153 | margin_top = -64.0 154 | margin_right = 64.0 155 | margin_bottom = 64.0 156 | mouse_filter = 2 157 | __meta__ = { 158 | "_edit_use_anchors_": false 159 | } 160 | 161 | [node name="AnimationPlayer" type="AnimationPlayer" parent="."] 162 | autoplay = "RESET" 163 | anims/RESET = SubResource( 2 ) 164 | anims/feedback = SubResource( 3 ) 165 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/Weapon.gd: -------------------------------------------------------------------------------- 1 | class_name Weapon 2 | extends Sprite 3 | 4 | const MIN_CHARGE := 0 5 | const MAX_CHARGE := 100 6 | 7 | export var weapon_name := "" 8 | export var charge_time := 2.0 9 | export (int, 0, 5) var attack := 2 10 | export (float, 0, 1) var chance_fire := 0.0 11 | export (float, 0, 1) var chance_breach := 0.0 12 | 13 | var is_charging := false setget set_is_charging 14 | var modifier := 1.0 setget set_modifier 15 | 16 | var _charge := MIN_CHARGE 17 | 18 | onready var tween: Tween = $Tween 19 | 20 | 21 | func _ready() -> void: 22 | tween.connect("tween_all_completed", self, "set_is_charging", [false]) 23 | self.is_charging = true 24 | 25 | 26 | func fire() -> void: 27 | pass 28 | 29 | 30 | func set_is_charging(value: bool) -> void: 31 | is_charging = value 32 | if is_charging: 33 | tween.interpolate_property(self, "_charge", MIN_CHARGE, MAX_CHARGE, charge_time) 34 | tween.start() 35 | fire() 36 | 37 | 38 | func set_modifier(value: float) -> void: 39 | modifier = value 40 | tween.speed = modifier 41 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/WeaponLaser.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Weapon 3 | 4 | signal fire_started(params) 5 | signal fire_stopped 6 | 7 | export (int, 0, 250) var targeting_length := 140 8 | export var color := Color("b0305c") 9 | 10 | var has_targeted := false 11 | 12 | onready var timer: Timer = $Timer 13 | onready var line: Line2D = $Line2D 14 | 15 | 16 | func _ready() -> void: 17 | if Engine.editor_hint: 18 | return 19 | 20 | timer.connect("timeout", self, "emit_signal", ["fire_stopped"]) 21 | timer.connect("timeout", self, "set_is_charging", [true]) 22 | timer.connect("timeout", line, "set_visible", [false]) 23 | line.default_color = color 24 | 25 | 26 | func _get_configuration_warning() -> String: 27 | var parent := get_parent() 28 | var is_verified := parent != null and parent is ControllerAILaser or parent is ControllerPlayerLaser 29 | return "" if is_verified else "WeaponLaser needs to be a parent of Controller*Laser" 30 | 31 | 32 | func fire() -> void: 33 | if not can_fire(): 34 | return 35 | 36 | timer.start() 37 | has_targeted = false 38 | line.visible = true 39 | var params := { 40 | "duration": timer.wait_time, 41 | "attack": attack, 42 | "chance_fire": chance_fire, 43 | "chance_breach": chance_breach 44 | } 45 | emit_signal("fire_started", params) 46 | 47 | 48 | func can_fire() -> bool: 49 | return not is_charging and has_targeted 50 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/WeaponLaser.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/WeaponProjectile.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/WeaponLaser.gd" type="Script" id=2] 5 | 6 | [node name="Weapon" instance=ExtResource( 1 )] 7 | region_rect = Rect2( 96, 416, 32, 32 ) 8 | script = ExtResource( 2 ) 9 | weapon_name = "Laser" 10 | 11 | [node name="Timer" type="Timer" parent="." index="1"] 12 | wait_time = 0.6 13 | one_shot = true 14 | 15 | [node name="Line2D" type="Line2D" parent="." index="2"] 16 | visible = false 17 | modulate = Color( 2, 2, 2, 1 ) 18 | points = PoolVector2Array( 0, 0, 1920, 0 ) 19 | width = 2.0 20 | antialiased = true 21 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/WeaponProjectile.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends Weapon 3 | 4 | signal fired 5 | signal projectile_exited(params) 6 | 7 | const Projectile := preload("Projectile.tscn") 8 | 9 | var target_position := Vector2.INF 10 | 11 | var _physics_layer := -1 12 | 13 | 14 | func setup(physics_layer: int) -> void: 15 | _physics_layer = physics_layer 16 | 17 | 18 | func _get_configuration_warning() -> String: 19 | var parent := get_parent() 20 | var is_verified := parent != null and parent is ControllerAIProjectile or parent is ControllerPlayerProjectile 21 | return "" if is_verified else "WeaponProjectile needs to be a parent of Controller*Projectile" 22 | 23 | 24 | func fire() -> void: 25 | if not can_fire(): 26 | return 27 | 28 | self.is_charging = true 29 | var projectile: RigidBody2D = Projectile.instance() 30 | projectile.linear_velocity = projectile.linear_velocity.rotated(global_rotation) 31 | var params := { 32 | "physics_layer": _physics_layer, 33 | "target_position": target_position, 34 | "attack": attack, 35 | "chance_fire": chance_fire, 36 | "chance_breach": chance_breach 37 | } 38 | projectile.connect("tree_exited", self, "emit_signal", ["projectile_exited", params]) 39 | add_child(projectile) 40 | emit_signal("fired") 41 | 42 | 43 | func can_fire() -> bool: 44 | return not is_charging and target_position != Vector2.INF 45 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Ship/Weapons/WeaponProjectile.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Ship/Weapons/WeaponProjectile.gd" type="Script" id=2] 5 | 6 | [node name="Weapon" type="Sprite"] 7 | self_modulate = Color( 1, 1, 0.921569, 1 ) 8 | texture = ExtResource( 1 ) 9 | region_enabled = true 10 | region_rect = Rect2( 64, 416, 32, 32 ) 11 | script = ExtResource( 2 ) 12 | weapon_name = "Projectile" 13 | 14 | [node name="Tween" type="Tween" parent="."] 15 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/UI/UIFeedback.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | 5 | [node name="Feedback" type="NinePatchRect"] 6 | visible = false 7 | anchor_right = 1.0 8 | anchor_bottom = 1.0 9 | rect_min_size = Vector2( 64, 64 ) 10 | texture = ExtResource( 1 ) 11 | region_rect = Rect2( 0, 416, 32, 32 ) 12 | patch_margin_left = 8 13 | patch_margin_top = 8 14 | patch_margin_right = 8 15 | patch_margin_bottom = 8 16 | __meta__ = { 17 | "_edit_use_anchors_": false 18 | } 19 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/UI/UISystem.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene format=2] 2 | 3 | [node name="System" type="Button"] 4 | margin_top = 40.0 5 | margin_right = 32.0 6 | margin_bottom = 72.0 7 | rect_min_size = Vector2( 32, 32 ) 8 | size_flags_vertical = 10 9 | text = "S" 10 | __meta__ = { 11 | "_edit_use_anchors_": false 12 | } 13 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/UI/UIUnit.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=3 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/UI/UIFeedback.tscn" type="PackedScene" id=1] 4 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=2] 5 | 6 | [node name="Unit" type="ColorRect"] 7 | margin_right = 64.0 8 | margin_bottom = 64.0 9 | rect_min_size = Vector2( 64, 64 ) 10 | color = Color( 0.494118, 0.494118, 0.560784, 1 ) 11 | __meta__ = { 12 | "_edit_use_anchors_": false 13 | } 14 | 15 | [node name="Icon" type="NinePatchRect" parent="."] 16 | anchor_left = 0.5 17 | anchor_top = 0.5 18 | anchor_right = 0.5 19 | anchor_bottom = 0.5 20 | margin_left = -12.0 21 | margin_top = -16.0 22 | margin_right = 12.0 23 | margin_bottom = 16.0 24 | texture = ExtResource( 2 ) 25 | region_rect = Rect2( 64, 384, 24, 32 ) 26 | __meta__ = { 27 | "_edit_use_anchors_": false 28 | } 29 | 30 | [node name="Feedback" parent="." instance=ExtResource( 1 )] 31 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/UI/UIWeapon.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [sub_resource type="ButtonGroup" id=1] 4 | resource_local_to_scene = false 5 | 6 | [node name="Weapon" type="VBoxContainer"] 7 | margin_right = 128.0 8 | margin_bottom = 72.0 9 | rect_min_size = Vector2( 128, 72 ) 10 | custom_constants/separation = 0 11 | __meta__ = { 12 | "_edit_use_anchors_": false 13 | } 14 | 15 | [node name="Button" type="Button" parent="."] 16 | margin_right = 128.0 17 | margin_bottom = 64.0 18 | rect_min_size = Vector2( 128, 64 ) 19 | size_flags_horizontal = 3 20 | size_flags_vertical = 3 21 | toggle_mode = true 22 | group = SubResource( 1 ) 23 | text = "Weapon" 24 | __meta__ = { 25 | "_edit_use_anchors_": false 26 | } 27 | 28 | [node name="ProgressBar" type="ProgressBar" parent="."] 29 | margin_top = 64.0 30 | margin_right = 128.0 31 | margin_bottom = 72.0 32 | rect_min_size = Vector2( 0, 8 ) 33 | percent_visible = false 34 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/UI/UIWeapons.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/UI/UISystem.tscn" type="PackedScene" id=1] 4 | 5 | [node name="Weapons" type="HBoxContainer"] 6 | show_behind_parent = true 7 | rect_min_size = Vector2( 0, 72 ) 8 | custom_constants/separation = 4 9 | __meta__ = { 10 | "_edit_use_anchors_": false 11 | } 12 | 13 | [node name="Button" parent="." instance=ExtResource( 1 )] 14 | disabled = true 15 | text = "W" 16 | -------------------------------------------------------------------------------- /godot/TacticalSpaceCombat/Utils.gd: -------------------------------------------------------------------------------- 1 | class_name Utils 2 | 3 | const DIRECTIONS := [ 4 | Vector2.UP, 5 | Vector2.RIGHT + Vector2.UP, 6 | Vector2.RIGHT, 7 | Vector2.RIGHT + Vector2.DOWN, 8 | Vector2.DOWN, 9 | Vector2.LEFT + Vector2.DOWN, 10 | Vector2.LEFT, 11 | Vector2.LEFT + Vector2.UP 12 | ] 13 | 14 | static func xy_to_index(width: int, offset: Vector2) -> int: 15 | return int(offset.x + width * offset.y) 16 | 17 | static func index_to_xy(width: int, index: int) -> Vector2: 18 | return Vector2(index % width, index / width) 19 | 20 | static func erase_value(dict: Dictionary, value) -> bool: 21 | var out := false 22 | for key in dict: 23 | if dict[key] == value: 24 | out = dict.erase(key) 25 | return out 26 | 27 | static func randvf_circle(_rng: RandomNumberGenerator, radius: float) -> Vector2: 28 | return (radius * Vector2.RIGHT).rotated(_rng.randf_range(0, TAU)) 29 | 30 | static func randvf_range(_rng: RandomNumberGenerator, top_left: Vector2, bottom_right: Vector2) -> Vector2: 31 | var x := _rng.randf_range(top_left.x, bottom_right.x) 32 | var y := _rng.randf_range(top_left.y, bottom_right.y) 33 | return Vector2(x, y) 34 | 35 | static func randvi_range(_rng: RandomNumberGenerator, top_left: Vector2, bottom_right: Vector2) -> Vector2: 36 | var x := _rng.randi_range(top_left.x, bottom_right.x) 37 | var y := _rng.randi_range(top_left.y, bottom_right.y) 38 | return Vector2(x, y) 39 | -------------------------------------------------------------------------------- /godot/default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /godot/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/godot/icon.png -------------------------------------------------------------------------------- /godot/icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | process/normal_map_invert_y=false 32 | stream=false 33 | size_limit=0 34 | detect_3d=true 35 | svg/scale=1.0 36 | -------------------------------------------------------------------------------- /godot/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=4 10 | 11 | _global_script_classes=[ { 12 | "base": "Area2D", 13 | "class": "BaseRoom", 14 | "language": "GDScript", 15 | "path": "res://TacticalSpaceCombat/Ship/Rooms/Room.gd" 16 | }, { 17 | "base": "Hazard", 18 | "class": "Breach", 19 | "language": "GDScript", 20 | "path": "res://TacticalSpaceCombat/Ship/Hazards/Breach.gd" 21 | }, { 22 | "base": "Node2D", 23 | "class": "Controller", 24 | "language": "GDScript", 25 | "path": "res://TacticalSpaceCombat/Ship/Weapons/Controller.gd" 26 | }, { 27 | "base": "Controller", 28 | "class": "ControllerAILaser", 29 | "language": "GDScript", 30 | "path": "res://TacticalSpaceCombat/Ship/Weapons/ControllerAILaser.gd" 31 | }, { 32 | "base": "Controller", 33 | "class": "ControllerAIProjectile", 34 | "language": "GDScript", 35 | "path": "res://TacticalSpaceCombat/Ship/Weapons/ControllerAIProjectile.gd" 36 | }, { 37 | "base": "Controller", 38 | "class": "ControllerPlayer", 39 | "language": "GDScript", 40 | "path": "res://TacticalSpaceCombat/Ship/Weapons/ControllerPlayer.gd" 41 | }, { 42 | "base": "ControllerPlayer", 43 | "class": "ControllerPlayerLaser", 44 | "language": "GDScript", 45 | "path": "res://TacticalSpaceCombat/Ship/Weapons/ControllerPlayerLaser.gd" 46 | }, { 47 | "base": "ControllerPlayer", 48 | "class": "ControllerPlayerProjectile", 49 | "language": "GDScript", 50 | "path": "res://TacticalSpaceCombat/Ship/Weapons/ControllerPlayerProjectile.gd" 51 | }, { 52 | "base": "Hazard", 53 | "class": "Fire", 54 | "language": "GDScript", 55 | "path": "res://TacticalSpaceCombat/Ship/Hazards/Fire.gd" 56 | }, { 57 | "base": "Sprite", 58 | "class": "Hazard", 59 | "language": "GDScript", 60 | "path": "res://TacticalSpaceCombat/Ship/Hazards/Hazard.gd" 61 | }, { 62 | "base": "RigidBody2D", 63 | "class": "Projectile", 64 | "language": "GDScript", 65 | "path": "res://TacticalSpaceCombat/Ship/Weapons/Projectile.gd" 66 | }, { 67 | "base": "Path2D", 68 | "class": "Unit", 69 | "language": "GDScript", 70 | "path": "res://TacticalSpaceCombat/Ship/Units/Unit.gd" 71 | }, { 72 | "base": "Reference", 73 | "class": "Utils", 74 | "language": "GDScript", 75 | "path": "res://TacticalSpaceCombat/Utils.gd" 76 | }, { 77 | "base": "Sprite", 78 | "class": "Weapon", 79 | "language": "GDScript", 80 | "path": "res://TacticalSpaceCombat/Ship/Weapons/Weapon.gd" 81 | } ] 82 | _global_script_class_icons={ 83 | "BaseRoom": "", 84 | "Breach": "", 85 | "Controller": "", 86 | "ControllerAILaser": "", 87 | "ControllerAIProjectile": "", 88 | "ControllerPlayer": "", 89 | "ControllerPlayerLaser": "", 90 | "ControllerPlayerProjectile": "", 91 | "Fire": "", 92 | "Hazard": "", 93 | "Projectile": "", 94 | "Unit": "", 95 | "Utils": "", 96 | "Weapon": "" 97 | } 98 | 99 | [application] 100 | 101 | config/name="2D Tactical Space Combat" 102 | run/main_scene="res://TacticalSpaceCombat.tscn" 103 | config/icon="res://icon.png" 104 | 105 | [autoload] 106 | 107 | Global="*res://Global.gd" 108 | 109 | [display] 110 | 111 | window/size/width=1920 112 | window/size/height=1080 113 | window/size/fullscreen=true 114 | window/stretch/mode="viewport" 115 | window/stretch/aspect="keep" 116 | 117 | [editor_plugins] 118 | 119 | enabled=PoolStringArray( ) 120 | 121 | [input] 122 | 123 | left_click={ 124 | "deadzone": 0.5, 125 | "events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":1,"pressed":false,"doubleclick":false,"script":null) 126 | ] 127 | } 128 | right_click={ 129 | "deadzone": 0.5, 130 | "events": [ Object(InputEventMouseButton,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"button_mask":0,"position":Vector2( 0, 0 ),"global_position":Vector2( 0, 0 ),"factor":1.0,"button_index":2,"pressed":false,"doubleclick":false,"script":null) 131 | ] 132 | } 133 | 134 | [layer_names] 135 | 136 | 2d_physics/layer_1="Ships" 137 | 2d_physics/layer_2="ShipPlayer" 138 | 2d_physics/layer_3="ShipAI" 139 | 2d_physics/layer_20="UI" 140 | 141 | [physics] 142 | 143 | 2d/default_gravity=0 144 | 2d/default_linear_damp=0.0 145 | 2d/default_angular_damp=0.0 146 | 147 | [rendering] 148 | 149 | environment/default_clear_color=Color( 0.152941, 0.152941, 0.211765, 1 ) 150 | environment/default_environment="res://default_env.tres" 151 | -------------------------------------------------------------------------------- /images/godot-tactical-space-combat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/images/godot-tactical-space-combat.png -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Assets.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Assets.svg-d190b656a20dba5761c3cdba806b46c8.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://TacticalSpaceCombat/Assets/Assets.svg" 13 | dest_files=[ "res://.import/Assets.svg-d190b656a20dba5761c3cdba806b46c8.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=false 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/TacticalSpaceCombat/Assets/Background.png -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Background.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Background.png-b24c8e0ead373f89e95cd7345d823255.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://TacticalSpaceCombat/Assets/Background.png" 13 | dest_files=[ "res://.import/Background.png-b24c8e0ead373f89e95cd7345d823255.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Font/MontserratExtraBold16.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | use_mipmaps = true 7 | use_filter = true 8 | font_data = ExtResource( 1 ) 9 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Font/MontserratExtraBold24.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="DynamicFont" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Font/MontserratExtraBold.otf" type="DynamicFontData" id=1] 4 | 5 | [resource] 6 | size = 32 7 | use_mipmaps = true 8 | use_filter = true 9 | font_data = ExtResource( 1 ) 10 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Shield.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 40 | 42 | 43 | 45 | image/svg+xml 46 | 48 | 49 | 50 | 51 | 52 | 57 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Shield.svg.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/Shield.svg-f7f94db2102f53848a907508d9426cbe.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://TacticalSpaceCombat/Assets/Shield.svg" 13 | dest_files=[ "res://.import/Shield.svg-f7f94db2102f53848a907508d9426cbe.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=1 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/ShipTileSet.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="TileSet" load_steps=2 format=2] 2 | 3 | [ext_resource path="res://TacticalSpaceCombat/Assets/Assets.svg" type="Texture" id=1] 4 | 5 | [resource] 6 | 0/name = "Tile" 7 | 0/texture = ExtResource( 1 ) 8 | 0/tex_offset = Vector2( 0, 0 ) 9 | 0/modulate = Color( 1, 1, 1, 1 ) 10 | 0/region = Rect2( 0, 0, 448, 448 ) 11 | 0/tile_mode = 1 12 | 0/autotile/bitmask_mode = 1 13 | 0/autotile/bitmask_flags = [ Vector2( 0, 0 ), 432, Vector2( 0, 1 ), 438, Vector2( 0, 2 ), 54, Vector2( 0, 3 ), 440, Vector2( 0, 4 ), 62, Vector2( 0, 5 ), 48, Vector2( 1, 0 ), 504, Vector2( 1, 1 ), 511, Vector2( 1, 2 ), 63, Vector2( 1, 3 ), 248, Vector2( 1, 4 ), 59, Vector2( 1, 5 ), 56, Vector2( 2, 0 ), 216, Vector2( 2, 1 ), 219, Vector2( 2, 2 ), 27, Vector2( 2, 3 ), 434, Vector2( 2, 4 ), 182, Vector2( 2, 5 ), 24, Vector2( 3, 0 ), 176, Vector2( 3, 1 ), 178, Vector2( 3, 2 ), 50, Vector2( 3, 3 ), 218, Vector2( 3, 4 ), 155, Vector2( 3, 5 ), 447, Vector2( 3, 6 ), 510, Vector2( 4, 0 ), 184, Vector2( 4, 1 ), 186, Vector2( 4, 2 ), 58, Vector2( 4, 3 ), 506, Vector2( 4, 4 ), 191, Vector2( 4, 5 ), 255, Vector2( 4, 6 ), 507, Vector2( 5, 0 ), 152, Vector2( 5, 1 ), 154, Vector2( 5, 2 ), 26, Vector2( 5, 3 ), 446, Vector2( 5, 4 ), 443, Vector2( 5, 5 ), 254, Vector2( 6, 0 ), 144, Vector2( 6, 1 ), 146, Vector2( 6, 2 ), 18, Vector2( 6, 3 ), 251, Vector2( 6, 4 ), 254, Vector2( 6, 5 ), 443 ] 14 | 0/autotile/icon_coordinate = Vector2( 1, 1 ) 15 | 0/autotile/tile_size = Vector2( 64, 64 ) 16 | 0/autotile/spacing = 0 17 | 0/autotile/occluder_map = [ ] 18 | 0/autotile/navpoly_map = [ ] 19 | 0/autotile/priority_map = [ ] 20 | 0/autotile/z_index_map = [ ] 21 | 0/occluder_offset = Vector2( 0, 0 ) 22 | 0/navigation_offset = Vector2( 0, 0 ) 23 | 0/shape_offset = Vector2( 0, 0 ) 24 | 0/shape_transform = Transform2D( 1, 0, 0, 1, 0, 0 ) 25 | 0/shape_one_way = false 26 | 0/shape_one_way_margin = 0.0 27 | 0/shapes = [ ] 28 | 0/z_index = 0 29 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/Shockwave.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="ShaderMaterial" load_steps=2 format=2] 2 | 3 | [sub_resource type="Shader" id=1] 4 | code = "shader_type canvas_item; 5 | 6 | 7 | uniform float force; 8 | uniform float size; 9 | 10 | 11 | float donought(float l) { 12 | return (1.0 - smoothstep(size - 0.1, size, l)) * smoothstep(size - 0.2, size - 0.1, l); 13 | } 14 | 15 | 16 | void fragment() { 17 | vec2 uv = UV - vec2(0.5); 18 | float lr = length(uv) + 5e-2; 19 | float lg = length(uv); 20 | float lb = length(uv) - 5e-2; 21 | vec2 dispr = normalize(uv) * force * donought(lr); 22 | vec2 dispg = normalize(uv) * force * donought(lg); 23 | vec2 dispb = normalize(uv) * force * donought(lb); 24 | float r = texture(SCREEN_TEXTURE, SCREEN_UV - dispr).r; 25 | float g = texture(SCREEN_TEXTURE, SCREEN_UV - dispg).g; 26 | float b = texture(SCREEN_TEXTURE, SCREEN_UV - dispb).b; 27 | COLOR.rgb = vec3(r, g, b); 28 | }" 29 | 30 | [resource] 31 | resource_local_to_scene = true 32 | shader = SubResource( 1 ) 33 | shader_param/force = 0.0 34 | shader_param/size = 0.0 35 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/UnitHitpointsBg.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0.152941, 0.152941, 0.211765, 1 ) 5 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Assets/UnitHitpointsFg.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="StyleBoxFlat" format=2] 2 | 3 | [resource] 4 | bg_color = Color( 0.239216, 0.431373, 0.439216, 1 ) 5 | -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Ship/Hazards/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/TacticalSpaceCombat/Ship/Hazards/.gitkeep -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Ship/Rooms/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/TacticalSpaceCombat/Ship/Rooms/.gitkeep -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Ship/Units/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/TacticalSpaceCombat/Ship/Units/.gitkeep -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/Ship/Weapons/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/TacticalSpaceCombat/Ship/Weapons/.gitkeep -------------------------------------------------------------------------------- /start-project/TacticalSpaceCombat/UI/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/TacticalSpaceCombat/UI/.gitkeep -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Godot-specific ignores 3 | .import/ 4 | export.cfg 5 | export_presets.cfg 6 | 7 | # Mono-specific ignores 8 | .mono/ 9 | data_*/ 10 | -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/ColorPickerPresets.gd: -------------------------------------------------------------------------------- 1 | tool 2 | extends EditorPlugin 3 | 4 | const PRESETS_FILENAME := 'presets.hex' 5 | 6 | 7 | func _enter_tree() -> void: 8 | var presets := PoolColorArray() 9 | var presets_raw := PoolStringArray() 10 | var presets_file := File.new() 11 | var presets_path: String = get_script().resource_path.get_base_dir().plus_file(PRESETS_FILENAME) 12 | 13 | if presets_file.open(presets_path, File.READ) == OK: 14 | presets_raw = presets_file.get_as_text().split("\n") 15 | presets_file.close() 16 | 17 | for hex in presets_raw: 18 | if hex.is_valid_html_color(): 19 | presets.push_back(Color(hex)) 20 | 21 | get_editor_interface().get_editor_settings().set_project_metadata( 22 | "color_picker", "presets", presets 23 | ) 24 | -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Răzvan C. Rădulescu 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 | -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/README.md: -------------------------------------------------------------------------------- 1 | # Godot ColorPicker Presets 2 | 3 | Reads a color presets hex file in the addon local directory, called `presets.hex`. It adds the colors to the editor ColorPicker for quick access. 4 | 5 | This repository includes a `presets.hex` file as an example. It's the [Pear36 color palette](https://lospec.com/palette-list/pear36) and you can directly download it from lospec. 6 | 7 | ![lospec download](./readme/lospec-download.png) 8 | 9 | Follow the format of placing one hex color value per line: 10 | 11 | ``` 12 | 5e315b 13 | 8c3f5d 14 | ba6156 15 | etc. 16 | ``` 17 | 18 | ## ✗ WARNING 19 | 20 | The addon: 21 | 22 | 1. Doesn't check the length of the color palette/file. 23 | 1. Skips malformed text lines without warning. It uses the ones it can convert to `Color`. 24 | 1. Overwrites the _ColorPicker_ presets whenever you reopen the project or re-enable the addon. 25 | 26 | ## ✓ Install 27 | 28 | 1. Make a new folder at `res://addons/ColorPickerPresets/`. 29 | 1. Copy the contents of this repository into `res://addons/ColorPickerPresets/`. 30 | 1. Replace `res://addons/ColorPickerPresets/presets.hex` with your prefered version. 31 | 1. Enable the addon from `Project > Project Settings... > Plugins`. 32 | 1. Profit. 33 | 34 | ![install project settings](./readme/install-project-settings.png) 35 | 36 | ## Where do I find the presets? 37 | 38 | They'll be available in the editor _ColorPicker_. 39 | 40 | ![ColorPicker presets](./readme/colorpicker-presets.png) 41 | -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/plugin.cfg: -------------------------------------------------------------------------------- 1 | [plugin] 2 | 3 | name="ColorPickerPresets" 4 | description="Sets the editor color picker presets from 5 | `presets.hex`, if it exists. The file is local to the 6 | addon folder." 7 | author="Răzvan Cosmin Rădulescu - razcore" 8 | version="0.1" 9 | script="ColorPickerPresets.gd" 10 | -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/presets.hex: -------------------------------------------------------------------------------- 1 | 5e315b 2 | 8c3f5d 3 | ba6156 4 | f2a65e 5 | ffe478 6 | cfff70 7 | 8fde5d 8 | 3ca370 9 | 3d6e70 10 | 323e4f 11 | 322947 12 | 473b78 13 | 4b5bab 14 | 4da6ff 15 | 66ffe3 16 | ffffeb 17 | c2c2d1 18 | 7e7e8f 19 | 606070 20 | 43434f 21 | 272736 22 | 3e2347 23 | 57294b 24 | 964253 25 | e36956 26 | ffb570 27 | ff9166 28 | eb564b 29 | b0305c 30 | 73275c 31 | 422445 32 | 5a265e 33 | 80366b 34 | bd4882 35 | ff6b97 36 | ffb5b5 37 | -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/readme/colorpicker-presets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/addons/ColorPickerPresets/readme/colorpicker-presets.png -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/readme/install-project-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/addons/ColorPickerPresets/readme/install-project-settings.png -------------------------------------------------------------------------------- /start-project/addons/ColorPickerPresets/readme/lospec-download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/addons/ColorPickerPresets/readme/lospec-download.png -------------------------------------------------------------------------------- /start-project/default_env.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="Environment" load_steps=2 format=2] 2 | 3 | [sub_resource type="ProceduralSky" id=1] 4 | 5 | [resource] 6 | background_mode = 2 7 | background_sky = SubResource( 1 ) 8 | -------------------------------------------------------------------------------- /start-project/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gdquest-demos/godot-2d-tactical-space-combat/1bb28381626a7c7ea8a16fc929a20a2aa1510d74/start-project/icon.png -------------------------------------------------------------------------------- /start-project/icon.png.import: -------------------------------------------------------------------------------- 1 | [remap] 2 | 3 | importer="texture" 4 | type="StreamTexture" 5 | path="res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" 6 | metadata={ 7 | "vram_texture": false 8 | } 9 | 10 | [deps] 11 | 12 | source_file="res://icon.png" 13 | dest_files=[ "res://.import/icon.png-487276ed1e3a0c39cad0279d744ee560.stex" ] 14 | 15 | [params] 16 | 17 | compress/mode=0 18 | compress/lossy_quality=0.7 19 | compress/hdr_mode=0 20 | compress/bptc_ldr=0 21 | compress/normal_map=0 22 | flags/repeat=0 23 | flags/filter=true 24 | flags/mipmaps=false 25 | flags/anisotropic=false 26 | flags/srgb=2 27 | process/fix_alpha_border=true 28 | process/premult_alpha=false 29 | process/HDR_as_SRGB=false 30 | process/invert_color=false 31 | stream=false 32 | size_limit=0 33 | detect_3d=true 34 | svg/scale=1.0 35 | -------------------------------------------------------------------------------- /start-project/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=4 10 | 11 | [application] 12 | 13 | config/name="2D Tactical Space Combat - Start Project" 14 | config/icon="res://icon.png" 15 | 16 | [editor_plugins] 17 | 18 | enabled=PoolStringArray( "res://addons/ColorPickerPresets/plugin.cfg" ) 19 | 20 | [rendering] 21 | 22 | environment/default_environment="res://default_env.tres" 23 | --------------------------------------------------------------------------------