├── .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 |
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 | 
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 |
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 |
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 | 
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 | 
35 |
36 | ## Where do I find the presets?
37 |
38 | They'll be available in the editor _ColorPicker_.
39 |
40 | 
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 |
--------------------------------------------------------------------------------