inventory hotbar wip
| Before Width: | Height: | Size: 966 B After Width: | Height: | Size: 909 B | 
| Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 804 B | 
| Before Width: | Height: | Size: 865 B After Width: | Height: | Size: 840 B | 
| Before Width: | Height: | Size: 899 B After Width: | Height: | Size: 878 B | 
| After Width: | Height: | Size: 115 B | 
|  | @ -0,0 +1,34 @@ | |||
| [remap] | ||||
| 
 | ||||
| importer="texture" | ||||
| type="CompressedTexture2D" | ||||
| uid="uid://b16461tjso0j7" | ||||
| path="res://.godot/imported/hotbar-inactive.png-6fcc3850a902bef479b03a3199eff195.ctex" | ||||
| metadata={ | ||||
| "vram_texture": false | ||||
| } | ||||
| 
 | ||||
| [deps] | ||||
| 
 | ||||
| source_file="res://Assets/Sprites/UI/hotbar-inactive.png" | ||||
| dest_files=["res://.godot/imported/hotbar-inactive.png-6fcc3850a902bef479b03a3199eff195.ctex"] | ||||
| 
 | ||||
| [params] | ||||
| 
 | ||||
| compress/mode=0 | ||||
| compress/high_quality=false | ||||
| compress/lossy_quality=0.7 | ||||
| compress/hdr_compression=1 | ||||
| compress/normal_map=0 | ||||
| compress/channel_pack=0 | ||||
| mipmaps/generate=false | ||||
| mipmaps/limit=-1 | ||||
| roughness/mode=0 | ||||
| roughness/src_normal="" | ||||
| process/fix_alpha_border=true | ||||
| process/premult_alpha=false | ||||
| process/normal_map_invert_y=false | ||||
| process/hdr_as_srgb=false | ||||
| process/hdr_clamp_exposure=false | ||||
| process/size_limit=0 | ||||
| detect_3d/compress_to=1 | ||||
| After Width: | Height: | Size: 248 B | 
|  | @ -0,0 +1,34 @@ | |||
| [remap] | ||||
| 
 | ||||
| importer="texture" | ||||
| type="CompressedTexture2D" | ||||
| uid="uid://bd81g8aivb2ql" | ||||
| path="res://.godot/imported/menu-rect-no-bg-32.png-abe037c99735f5bb541fe56551407940.ctex" | ||||
| metadata={ | ||||
| "vram_texture": false | ||||
| } | ||||
| 
 | ||||
| [deps] | ||||
| 
 | ||||
| source_file="res://Assets/Sprites/UI/menu-rect-no-bg-32.png" | ||||
| dest_files=["res://.godot/imported/menu-rect-no-bg-32.png-abe037c99735f5bb541fe56551407940.ctex"] | ||||
| 
 | ||||
| [params] | ||||
| 
 | ||||
| compress/mode=0 | ||||
| compress/high_quality=false | ||||
| compress/lossy_quality=0.7 | ||||
| compress/hdr_compression=1 | ||||
| compress/normal_map=0 | ||||
| compress/channel_pack=0 | ||||
| mipmaps/generate=false | ||||
| mipmaps/limit=-1 | ||||
| roughness/mode=0 | ||||
| roughness/src_normal="" | ||||
| process/fix_alpha_border=true | ||||
| process/premult_alpha=false | ||||
| process/normal_map_invert_y=false | ||||
| process/hdr_as_srgb=false | ||||
| process/hdr_clamp_exposure=false | ||||
| process/size_limit=0 | ||||
| detect_3d/compress_to=1 | ||||
| After Width: | Height: | Size: 868 B | 
|  | @ -0,0 +1,34 @@ | |||
| [remap] | ||||
| 
 | ||||
| importer="texture" | ||||
| type="CompressedTexture2D" | ||||
| uid="uid://dvccvnhd8p6oe" | ||||
| path="res://.godot/imported/menu-rect-no-bg.png-97eadcb3fdb4cea6e754caa7d4b779f4.ctex" | ||||
| metadata={ | ||||
| "vram_texture": false | ||||
| } | ||||
| 
 | ||||
| [deps] | ||||
| 
 | ||||
| source_file="res://Assets/Sprites/UI/menu-rect-no-bg.png" | ||||
| dest_files=["res://.godot/imported/menu-rect-no-bg.png-97eadcb3fdb4cea6e754caa7d4b779f4.ctex"] | ||||
| 
 | ||||
| [params] | ||||
| 
 | ||||
| compress/mode=0 | ||||
| compress/high_quality=false | ||||
| compress/lossy_quality=0.7 | ||||
| compress/hdr_compression=1 | ||||
| compress/normal_map=0 | ||||
| compress/channel_pack=0 | ||||
| mipmaps/generate=false | ||||
| mipmaps/limit=-1 | ||||
| roughness/mode=0 | ||||
| roughness/src_normal="" | ||||
| process/fix_alpha_border=true | ||||
| process/premult_alpha=false | ||||
| process/normal_map_invert_y=false | ||||
| process/hdr_as_srgb=false | ||||
| process/hdr_clamp_exposure=false | ||||
| process/size_limit=0 | ||||
| detect_3d/compress_to=1 | ||||
| Before Width: | Height: | Size: 387 B After Width: | Height: | Size: 387 B | 
|  | @ -522,16 +522,14 @@ point_count = 3 | |||
| curve = SubResource("Curve_x3x4q") | ||||
| 
 | ||||
| [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_s1tqp"] | ||||
| particle_flag_disable_z = true | ||||
| emission_shape = 3 | ||||
| emission_box_extents = Vector3(8, 1, 1) | ||||
| particle_flag_disable_z = true | ||||
| direction = Vector3(0, -1, 0) | ||||
| spread = 0.0 | ||||
| gravity = Vector3(0, 0, 0) | ||||
| initial_velocity_min = 32.0 | ||||
| initial_velocity_max = 64.0 | ||||
| orbit_velocity_min = 0.0 | ||||
| orbit_velocity_max = 0.0 | ||||
| gravity = Vector3(0, 0, 0) | ||||
| scale_max = 1.5 | ||||
| scale_curve = SubResource("CurveTexture_ssoms") | ||||
| color_ramp = SubResource("GradientTexture1D_pjeh8") | ||||
|  | @ -542,8 +540,6 @@ color_initial_ramp = SubResource("GradientTexture1D_5606i") | |||
| [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_j1srf"] | ||||
| particle_flag_disable_z = true | ||||
| gravity = Vector3(0, 98, 0) | ||||
| orbit_velocity_min = 0.0 | ||||
| orbit_velocity_max = 0.0 | ||||
| 
 | ||||
| [sub_resource type="RectangleShape2D" id="RectangleShape2D_uict5"] | ||||
| size = Vector2(11, 5) | ||||
|  | @ -774,9 +770,10 @@ Faction = 2 | |||
| position = Vector2(0, -3.5) | ||||
| shape = SubResource("RectangleShape2D_8lxmf") | ||||
| 
 | ||||
| [node name="Inventory" type="Node2D" parent="."] | ||||
| [node name="Inventory" type="Node2D" parent="." node_paths=PackedStringArray("Items")] | ||||
| y_sort_enabled = true | ||||
| script = ExtResource("8_r8ejq") | ||||
| Items = [] | ||||
| 
 | ||||
| [node name="DocLance" parent="Inventory" instance=ExtResource("24_2es2r")] | ||||
| unique_name_in_owner = true | ||||
|  |  | |||
|  | @ -74,6 +74,8 @@ public sealed partial class Player : Character | |||
|             var signal = Events.EventBus.SignalName.PlayerHealthChanged; | ||||
|             this.GetEventBus().EmitSignal(signal, args); | ||||
|         }; | ||||
| 
 | ||||
|         Inventory.AddItemToHotbar(Inventory.Items[0]); | ||||
|     } | ||||
| 
 | ||||
|     public override void _Process(double delta) | ||||
|  | @ -224,6 +226,7 @@ public sealed partial class Player : Character | |||
|         switch (inputMethod) | ||||
|         { | ||||
|             case State.Global.InputMethod.Joystick: | ||||
|                 GD.Print(joystick); | ||||
|                 if (joystick.IsZeroApprox()) | ||||
|                 { | ||||
|                     return Direction; | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| [gd_scene load_steps=76 format=3 uid="uid://b2254pup8k161"] | ||||
| [gd_scene load_steps=72 format=3 uid="uid://b2254pup8k161"] | ||||
| 
 | ||||
| [ext_resource type="Script" path="res://Characters/Player.cs" id="1_flygr"] | ||||
| [ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="2_ngsgt"] | ||||
|  | @ -10,7 +10,6 @@ | |||
| [ext_resource type="Script" path="res://Utils/Values/DoubleValue.cs" id="5_txl0r"] | ||||
| [ext_resource type="Script" path="res://Utils/Values/IntValue.cs" id="6_sunc5"] | ||||
| [ext_resource type="Script" path="res://State/Character/PlayerIdleState.cs" id="6_wkfdm"] | ||||
| [ext_resource type="PackedScene" uid="uid://dvqap2uhcah63" path="res://Items/Weapons/Sword.tscn" id="7_4rxuv"] | ||||
| [ext_resource type="Script" path="res://State/Character/PlayerMoveState.cs" id="7_dfqd8"] | ||||
| [ext_resource type="Script" path="res://Utils/AnimationManager.cs" id="7_sdgvb"] | ||||
| [ext_resource type="Script" path="res://Items/Inventory.cs" id="7_xyenu"] | ||||
|  | @ -29,16 +28,13 @@ | |||
| [ext_resource type="Script" path="res://BoundingBoxes/InteractionRay.cs" id="13_hs3u1"] | ||||
| [ext_resource type="Script" path="res://State/Character/PlayerHealState.cs" id="13_t103m"] | ||||
| [ext_resource type="Script" path="res://State/Character/PlayerMaxLevelState.cs" id="14_1sn10"] | ||||
| [ext_resource type="PackedScene" uid="uid://p7oijq6dbvvk" path="res://Items/Weapons/DocLance.tscn" id="14_bj0lo"] | ||||
| [ext_resource type="Texture2D" uid="uid://d1ukste16yq6v" path="res://Assets/Sprites/Particles/player-light.png" id="15_3hahh"] | ||||
| [ext_resource type="Script" path="res://Utils/DamageTime.cs" id="15_4xl06"] | ||||
| [ext_resource type="AudioStream" uid="uid://st8qgqiygy5a" path="res://Assets/Sounds/electricity.wav" id="15_61bua"] | ||||
| [ext_resource type="PackedScene" uid="uid://ce0ph4wk0ylra" path="res://UI/TargetTracer.tscn" id="22_hxi53"] | ||||
| [ext_resource type="PackedScene" uid="uid://5y1acxl4j4n7" path="res://Items/Weapons/Pugio.tscn" id="22_mqpn7"] | ||||
| [ext_resource type="PackedScene" uid="uid://d1d4vg7we5rjr" path="res://Items/Weapons/Shotgun.tscn" id="22_rmciq"] | ||||
| [ext_resource type="Texture2D" uid="uid://bd8l8kafb42dt" path="res://Assets/Sprites/Particles/circle.png" id="22_uefct"] | ||||
| [ext_resource type="Texture2D" uid="uid://bcgm3r168qjn3" path="res://Assets/Sprites/Particles/cast-effect.png" id="24_njn4h"] | ||||
| [ext_resource type="PackedScene" uid="uid://cgg0sfm2qeiwn" path="res://Items/Weapons/Bow.tscn" id="29_7j1fs"] | ||||
| [ext_resource type="Resource" uid="uid://cl7jvdu2lnv2d" path="res://Items/Weapons/Sword.tres" id="31_vr68e"] | ||||
| 
 | ||||
| [sub_resource type="ShaderMaterial" id="ShaderMaterial_h78y7"] | ||||
| shader = ExtResource("2_ngsgt") | ||||
|  | @ -566,7 +562,7 @@ color_ramp = SubResource("GradientTexture1D_3jlnh") | |||
| light_mode = 1 | ||||
| 
 | ||||
| [sub_resource type="RectangleShape2D" id="RectangleShape2D_bfqew"] | ||||
| size = Vector2(12, 4) | ||||
| size = Vector2(12, 6) | ||||
| 
 | ||||
| [sub_resource type="LabelSettings" id="LabelSettings_q5h1n"] | ||||
| font_size = 24 | ||||
|  | @ -601,11 +597,9 @@ script = ExtResource("4_06oya") | |||
| 
 | ||||
| [node name="XP" type="Node" parent="Stats"] | ||||
| script = ExtResource("5_txl0r") | ||||
| Value = null | ||||
| 
 | ||||
| [node name="Level" type="Node" parent="Stats"] | ||||
| script = ExtResource("6_sunc5") | ||||
| Value = null | ||||
| 
 | ||||
| [node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")] | ||||
| script = ExtResource("5_rgckv") | ||||
|  | @ -623,7 +617,7 @@ Character = NodePath("../..") | |||
| 
 | ||||
| [node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("AbilityState", "IdleState", "MaxLevelState", "Character")] | ||||
| script = ExtResource("7_dfqd8") | ||||
| AbilityState = NodePath("../Roll") | ||||
| AbilityState = NodePath("../Dash") | ||||
| IdleState = NodePath("../Idle") | ||||
| MaxLevelState = NodePath("../MaxLevel") | ||||
| Character = NodePath("../..") | ||||
|  | @ -802,6 +796,7 @@ offset = Vector2(-9.5, -14) | |||
| hframes = 6 | ||||
| 
 | ||||
| [node name="CollisionShape2D" type="CollisionShape2D" parent="."] | ||||
| position = Vector2(0, -1) | ||||
| shape = SubResource("RectangleShape2D_bfqew") | ||||
| 
 | ||||
| [node name="Debug" type="Control" parent="."] | ||||
|  | @ -819,32 +814,18 @@ text = "lol" | |||
| label_settings = SubResource("LabelSettings_q5h1n") | ||||
| horizontal_alignment = 1 | ||||
| 
 | ||||
| [node name="Inventory" type="Node2D" parent="." node_paths=PackedStringArray("Items")] | ||||
| [node name="Inventory" type="Node2D" parent="." node_paths=PackedStringArray("Hotbar")] | ||||
| y_sort_enabled = true | ||||
| position = Vector2(0, -2) | ||||
| script = ExtResource("7_xyenu") | ||||
| Items = [] | ||||
| Hotbar = [] | ||||
| Items = [ExtResource("31_vr68e")] | ||||
| InventoryMap = { | ||||
| "equip_1": 0, | ||||
| "equip_2": 1, | ||||
| "equip_3": 2 | ||||
| } | ||||
| 
 | ||||
| [node name="DocLance" parent="Inventory" instance=ExtResource("14_bj0lo")] | ||||
| visible = false | ||||
| 
 | ||||
| [node name="Node2D" parent="Inventory" instance=ExtResource("7_4rxuv")] | ||||
| visible = false | ||||
| 
 | ||||
| [node name="Bow" parent="Inventory" instance=ExtResource("29_7j1fs")] | ||||
| visible = false | ||||
| 
 | ||||
| [node name="Sword" parent="Inventory" instance=ExtResource("22_mqpn7")] | ||||
| visible = false | ||||
| 
 | ||||
| [node name="Shotgun" parent="Inventory" instance=ExtResource("22_rmciq")] | ||||
| visible = false | ||||
| 
 | ||||
| [node name="RemoteTransform2D2" type="RemoteTransform2D" parent="Inventory"] | ||||
| position = Vector2(0, 4) | ||||
| 
 | ||||
|  |  | |||
|  | @ -27,6 +27,9 @@ public partial class EventBus : Node | |||
|     [Signal] | ||||
|     public delegate void PlayerLevelChangedEventHandler(int level); | ||||
| 
 | ||||
|     [Signal] | ||||
|     public delegate void PlayerInventoryUpdateEventHandler(Items.Inventory inventory); | ||||
| 
 | ||||
|     [Signal] | ||||
|     public delegate void PlayerHealthChangedEventHandler(HealthChangedArgs args); | ||||
| 
 | ||||
|  |  | |||
|  | @ -9,7 +9,10 @@ public partial class Inventory : Node2D | |||
|     public Character Character { get; private set; } | ||||
| 
 | ||||
|     [Export] | ||||
|     public Array<Item> Items { get; private set; } | ||||
|     public Array<Item> Hotbar { get; private set; } | ||||
| 
 | ||||
|     [Export] | ||||
|     public Array<ItemMetadata> Items { get; private set; } | ||||
| 
 | ||||
|     [Export] | ||||
|     public Dictionary<string, int> InventoryMap { get; set; } | ||||
|  | @ -17,7 +20,7 @@ public partial class Inventory : Node2D | |||
|     [Signal] | ||||
|     public delegate void UsedItemEventHandler(Item item); | ||||
| 
 | ||||
|     public const int MaxCapacity = 32; | ||||
|     public const int MaxCapacity = 3; | ||||
| 
 | ||||
|     private Item _selectedItem; | ||||
| 
 | ||||
|  | @ -53,12 +56,18 @@ public partial class Inventory : Node2D | |||
| 
 | ||||
|     public override void _Ready() | ||||
|     { | ||||
|         if (Items is null) | ||||
|         if (Hotbar is null) | ||||
|         { | ||||
|             // instantiating a new array will prevent characters from | ||||
|             // sharing inventories | ||||
|             Items = new Array<Item>(); | ||||
|             Hotbar = new(); | ||||
|         } | ||||
| 
 | ||||
|         if (Items is null) | ||||
|         { | ||||
|             Items = new(); | ||||
|         } | ||||
| 
 | ||||
|         Character = GetParent<Character>(); | ||||
|         foreach (Node child in GetChildren()) | ||||
|         { | ||||
|  | @ -67,14 +76,17 @@ public partial class Inventory : Node2D | |||
|                 AddItem(item); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         Events.EventBus.Instance.EmitSignal( | ||||
|             Events.EventBus.SignalName.PlayerInventoryUpdate, this); | ||||
|         base._Ready(); | ||||
|     } | ||||
| 
 | ||||
|     public bool EquipIndex(int index) | ||||
|     { | ||||
|         if (index < Items.Count) | ||||
|         if (index < Hotbar.Count) | ||||
|         { | ||||
|             return EquipItem(Items[index], ref _selectedItem); | ||||
|             return EquipItem(Hotbar[index], ref _selectedItem); | ||||
|         } | ||||
| 
 | ||||
|         return EquipItem(null, ref _selectedItem); | ||||
|  | @ -84,7 +96,7 @@ public partial class Inventory : Node2D | |||
|     { | ||||
|         if (item is not null) | ||||
|         { | ||||
|             if (!Items.Contains(item)) | ||||
|             if (!Hotbar.Contains(item)) | ||||
|             { | ||||
|                 GD.PrintErr("Tried to equip an item not in the inventory."); | ||||
|                 return false; | ||||
|  | @ -103,6 +115,9 @@ public partial class Inventory : Node2D | |||
|             item.Equip(Character); | ||||
|         } | ||||
| 
 | ||||
|         Events.EventBus.Instance.EmitSignal( | ||||
|             Events.EventBus.SignalName.PlayerInventoryUpdate, this); | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|  | @ -111,27 +126,36 @@ public partial class Inventory : Node2D | |||
|         if (InventoryMap.ContainsKey(keymap)) | ||||
|         { | ||||
|             int idx = InventoryMap[keymap]; | ||||
|             if (idx < Items.Count) | ||||
|             if (idx < Hotbar.Count) | ||||
|             { | ||||
|                 return Items[InventoryMap[keymap]]; | ||||
|                 return Hotbar[InventoryMap[keymap]]; | ||||
|             } | ||||
|         } | ||||
|         else GD.Print(keymap + " does not exist"); | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public Item AddItemToHotbar(ItemMetadata metadata) | ||||
|     { | ||||
|         var item = metadata.Instance.Instantiate<Item>(); | ||||
|         AddItem(item); | ||||
|         AddChild(item); | ||||
|         GD.Print("Added " + item.Metadata.Name); | ||||
|         return item; | ||||
|     } | ||||
| 
 | ||||
|     public Item AddItem(Item item) | ||||
|     { | ||||
|         if (Items.Count >= MaxCapacity) | ||||
|         if (Hotbar.Count >= MaxCapacity) | ||||
|         { | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         item.CharacterOwner = Character; | ||||
|         item.Visible = false; | ||||
|         if (!Items.Contains(item)) | ||||
|         if (!Hotbar.Contains(item)) | ||||
|         { | ||||
|             Items.Add(item); | ||||
|             Hotbar.Add(item); | ||||
|         } | ||||
|         return item; | ||||
|     } | ||||
|  |  | |||
|  | @ -8,6 +8,9 @@ public partial class ItemMetadata : Resource | |||
|     [Export] | ||||
|     public Utils.ScenePath Instance { get; set; } | ||||
| 
 | ||||
|     [Export] | ||||
|     public Texture2D Icon { get; set; } | ||||
| 
 | ||||
|     [Export] | ||||
|     public string Name { get; set; } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| [gd_resource type="Resource" script_class="ItemMetadata" load_steps=4 format=3 uid="uid://cl7jvdu2lnv2d"] | ||||
| [gd_resource type="Resource" script_class="ItemMetadata" load_steps=5 format=3 uid="uid://cl7jvdu2lnv2d"] | ||||
| 
 | ||||
| [ext_resource type="Texture2D" uid="uid://dfpe74vxvuwal" path="res://Assets/Sprites/Items/pugio.png" id="1_j0i86"] | ||||
| [ext_resource type="Script" path="res://Utils/ScenePath.cs" id="1_mh5y8"] | ||||
| [ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_asbkr"] | ||||
| 
 | ||||
|  | @ -10,5 +11,6 @@ Path = "res://Items/Weapons/Sword.tscn" | |||
| [resource] | ||||
| script = ExtResource("2_asbkr") | ||||
| Instance = SubResource("Resource_qqiwa") | ||||
| Name = "Sword" | ||||
| Description = "A basic sword." | ||||
| Icon = ExtResource("1_j0i86") | ||||
| Name = "The Fool's Sword" | ||||
| Description = "" | ||||
|  |  | |||
|  | @ -1,8 +1,7 @@ | |||
| [gd_scene load_steps=29 format=3 uid="uid://dvqap2uhcah63"] | ||||
| [gd_scene load_steps=26 format=3 uid="uid://dvqap2uhcah63"] | ||||
| 
 | ||||
| [ext_resource type="Script" path="res://Items/Weapons/Sword.cs" id="1_mlo73"] | ||||
| [ext_resource type="Script" path="res://Utils/ScenePath.cs" id="2_g8hkw"] | ||||
| [ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_gy7e7"] | ||||
| [ext_resource type="Resource" uid="uid://cl7jvdu2lnv2d" path="res://Items/Weapons/Sword.tres" id="2_atd4f"] | ||||
| [ext_resource type="Script" path="res://State/Weapon/WeaponStateMachine.cs" id="2_vwirq"] | ||||
| [ext_resource type="Script" path="res://State/Weapon/SwordIdleState.cs" id="3_nw6r0"] | ||||
| [ext_resource type="Texture2D" uid="uid://dp7osg05ip5oo" path="res://Assets/Sprites/sword.png" id="3_r75ni"] | ||||
|  | @ -17,16 +16,6 @@ | |||
| [ext_resource type="Texture2D" uid="uid://cmvh6pc71ir1m" path="res://Assets/Sprites/sword-swing-large.png" id="10_672jv"] | ||||
| [ext_resource type="AudioStream" uid="uid://qvthq6tppp63" path="res://Assets/Sounds/whoosh.wav" id="10_mfnl7"] | ||||
| 
 | ||||
| [sub_resource type="Resource" id="Resource_qqiwa"] | ||||
| script = ExtResource("2_g8hkw") | ||||
| Path = "res://Items/Weapons/Sword.tscn" | ||||
| 
 | ||||
| [sub_resource type="Resource" id="Resource_advps"] | ||||
| script = ExtResource("2_gy7e7") | ||||
| Instance = SubResource("Resource_qqiwa") | ||||
| Name = "The Fool's Sword" | ||||
| Description = "The Top Right's Sword" | ||||
| 
 | ||||
| [sub_resource type="Environment" id="Environment_72txp"] | ||||
| background_mode = 3 | ||||
| glow_enabled = true | ||||
|  | @ -332,7 +321,7 @@ Knockback = 64.0 | |||
| ShouldHideIdle = true | ||||
| PlayerLevelGain = 1.0 | ||||
| HandAnchor = NodePath("Anchor/Node2D/Sprite2D/Hand") | ||||
| Metadata = SubResource("Resource_advps") | ||||
| Metadata = ExtResource("2_atd4f") | ||||
| 
 | ||||
| [node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")] | ||||
| script = ExtResource("2_vwirq") | ||||
|  | @ -382,7 +371,7 @@ position = Vector2(-2.52724e-05, 7) | |||
| rotation = 1.5708 | ||||
| 
 | ||||
| [node name="ParryParticles" type="GPUParticles2D" parent="Anchor/Node2D/Sprite2D"] | ||||
| modulate = Color(1.2, 1.2, 1.2, 1) | ||||
| modulate = Color(4, 4, 4, 1) | ||||
| position = Vector2(0, -3) | ||||
| rotation = 0.785398 | ||||
| emitting = false | ||||
|  |  | |||
								
									
									
										
											22
										
									
									UI/Base.tscn
									
									
									
									
								
								
							
							|  | @ -1,4 +1,4 @@ | |||
| [gd_scene load_steps=11 format=3 uid="uid://c271rdjhd1gfo"] | ||||
| [gd_scene load_steps=12 format=3 uid="uid://c271rdjhd1gfo"] | ||||
| 
 | ||||
| [ext_resource type="PackedScene" uid="uid://73jm5qjy52vq" path="res://Dialogue/balloon.tscn" id="1_atjb1"] | ||||
| [ext_resource type="Script" path="res://UI/UIController.cs" id="2_b4b6l"] | ||||
|  | @ -6,6 +6,7 @@ | |||
| [ext_resource type="PackedScene" uid="uid://01d24ij5av1y" path="res://UI/BossBar.tscn" id="4_igi28"] | ||||
| [ext_resource type="PackedScene" uid="uid://cr7tkxctmyags" path="res://UI/LevelBar.tscn" id="4_rcekd"] | ||||
| [ext_resource type="PackedScene" uid="uid://c77754nvmckn" path="res://UI/LocationDisplay.tscn" id="5_cr6vo"] | ||||
| [ext_resource type="PackedScene" uid="uid://sfs8dpfitpdu" path="res://UI/Hotbar.tscn" id="5_mmp18"] | ||||
| [ext_resource type="PackedScene" uid="uid://d3q1yu3n7cqfj" path="res://UI/SceneTransition.tscn" id="6_j0nhv"] | ||||
| [ext_resource type="Shader" path="res://Shaders/Vignette.gdshader" id="8_k080s"] | ||||
| [ext_resource type="Texture2D" uid="uid://dxtpp41y85tv5" path="res://Assets/Sprites/Misc/white.png" id="9_2hlfh"] | ||||
|  | @ -61,27 +62,36 @@ anchor_right = 1.0 | |||
| offset_bottom = 64.0 | ||||
| grow_horizontal = 2 | ||||
| 
 | ||||
| [node name="Margin" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top"] | ||||
| [node name="Left" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top"] | ||||
| layout_mode = 2 | ||||
| size_flags_horizontal = 3 | ||||
| theme_override_constants/margin_left = 16 | ||||
| theme_override_constants/margin_top = 16 | ||||
| 
 | ||||
| [node name="VBoxContainer" type="VBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin"] | ||||
| [node name="VBoxContainer" type="VBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Left"] | ||||
| layout_mode = 2 | ||||
| theme_override_constants/separation = 12 | ||||
| 
 | ||||
| [node name="HealthBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer" instance=ExtResource("3_j1j6h")] | ||||
| [node name="HealthBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Left/VBoxContainer" instance=ExtResource("3_j1j6h")] | ||||
| layout_mode = 2 | ||||
| 
 | ||||
| [node name="LevelBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer" instance=ExtResource("4_rcekd")] | ||||
| [node name="LevelBar" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Left/VBoxContainer" instance=ExtResource("4_rcekd")] | ||||
| layout_mode = 2 | ||||
| 
 | ||||
| [node name="Margin2" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Margin/VBoxContainer"] | ||||
| [node name="Margin2" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Left/VBoxContainer"] | ||||
| visible = false | ||||
| layout_mode = 2 | ||||
| theme_override_constants/margin_left = 16 | ||||
| theme_override_constants/margin_top = 16 | ||||
| 
 | ||||
| [node name="Right" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top"] | ||||
| layout_mode = 2 | ||||
| theme_override_constants/margin_top = 16 | ||||
| theme_override_constants/margin_right = 16 | ||||
| 
 | ||||
| [node name="GridContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Right" instance=ExtResource("5_mmp18")] | ||||
| layout_mode = 2 | ||||
| 
 | ||||
| [node name="Bottom" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] | ||||
| layout_mode = 1 | ||||
| anchors_preset = 12 | ||||
|  |  | |||
|  | @ -0,0 +1,26 @@ | |||
| using Godot; | ||||
| using SupaLidlGame.Items; | ||||
| 
 | ||||
| namespace SupaLidlGame.UI; | ||||
| 
 | ||||
| public partial class Hotbar : GridContainer | ||||
| { | ||||
|     [Export] | ||||
|     private Godot.Collections.Array<InventorySlot> _slots; | ||||
| 
 | ||||
|     public override void _Ready() | ||||
|     { | ||||
|         Events.EventBus.Instance.PlayerInventoryUpdate += OnInventoryUpdate; | ||||
|     } | ||||
| 
 | ||||
|     public void OnInventoryUpdate(Inventory inventory) | ||||
|     { | ||||
|         for (int i = 0; i < 3; i++) | ||||
|         { | ||||
|             var slot = _slots[i]; | ||||
|             slot.Item = inventory.Hotbar[i].Metadata; | ||||
|             slot.IsSelected = inventory.SelectedItem == inventory.Hotbar[i]; | ||||
|             GD.Print(inventory.Hotbar[i].Metadata.Name); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,25 @@ | |||
| [gd_scene load_steps=3 format=3 uid="uid://sfs8dpfitpdu"] | ||||
| 
 | ||||
| [ext_resource type="Script" path="res://UI/Hotbar.cs" id="1_2sak2"] | ||||
| [ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/InventorySlot.tscn" id="1_ct3cn"] | ||||
| 
 | ||||
| [node name="GridContainer" type="GridContainer" node_paths=PackedStringArray("_slots")] | ||||
| anchors_preset = 1 | ||||
| anchor_left = 1.0 | ||||
| anchor_right = 1.0 | ||||
| offset_left = -112.0 | ||||
| offset_bottom = 32.0 | ||||
| grow_horizontal = 0 | ||||
| theme_override_constants/h_separation = 8 | ||||
| columns = 3 | ||||
| script = ExtResource("1_2sak2") | ||||
| _slots = [NodePath("InventorySlot"), NodePath("InventorySlot2"), NodePath("InventorySlot3")] | ||||
| 
 | ||||
| [node name="InventorySlot" parent="." instance=ExtResource("1_ct3cn")] | ||||
| layout_mode = 2 | ||||
| 
 | ||||
| [node name="InventorySlot2" parent="." instance=ExtResource("1_ct3cn")] | ||||
| layout_mode = 2 | ||||
| 
 | ||||
| [node name="InventorySlot3" parent="." instance=ExtResource("1_ct3cn")] | ||||
| layout_mode = 2 | ||||
|  | @ -0,0 +1,73 @@ | |||
| using Godot; | ||||
| using GodotUtilities; | ||||
| using GodotUtilities.SourceGenerators; | ||||
| 
 | ||||
| namespace SupaLidlGame.UI; | ||||
| 
 | ||||
| [Scene] | ||||
| public partial class InventorySlot : ColorRect | ||||
| { | ||||
|     [Node("TextureRect")] | ||||
|     private TextureRect _textureRect; | ||||
| 
 | ||||
|     [Node("Selected")] | ||||
|     private NinePatchRect _selected; | ||||
| 
 | ||||
|     [Node("Unselected")] | ||||
|     private NinePatchRect _unselected; | ||||
| 
 | ||||
|     private static Texture2D _placeholderTexture; | ||||
| 
 | ||||
|     private Items.ItemMetadata _item; | ||||
| 
 | ||||
|     public Items.ItemMetadata Item | ||||
|     { | ||||
|         get => _item; | ||||
|         set | ||||
|         { | ||||
|             _item = value; | ||||
| 
 | ||||
|             if (_item is null) | ||||
|             { | ||||
|                 _textureRect.Texture = _placeholderTexture; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 _textureRect.Texture = _item.Icon; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private bool _isSelected = false; | ||||
| 
 | ||||
|     public bool IsSelected | ||||
|     { | ||||
|         get => _isSelected; | ||||
|         set | ||||
|         { | ||||
|             _isSelected = value; | ||||
|             _selected.Visible = _isSelected; | ||||
|             _unselected.Visible = !_isSelected; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static InventorySlot() | ||||
|     { | ||||
|         _placeholderTexture = ResourceLoader.Load<Texture2D>( | ||||
|             "res://Assets/Sprites/UI/hotbar-inactive.png"); | ||||
|     } | ||||
| 
 | ||||
|     public override void _Notification(int what) | ||||
|     { | ||||
|         if (what == NotificationSceneInstantiated) | ||||
|         { | ||||
|             WireNodes(); | ||||
|         } | ||||
|         base._Notification(what); | ||||
|     } | ||||
| 
 | ||||
|     public override void _Ready() | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,39 @@ | |||
| [gd_scene load_steps=4 format=3 uid="uid://ctad0dkoyw8ad"] | ||||
| 
 | ||||
| [ext_resource type="Script" path="res://UI/InventorySlot.cs" id="1_llonk"] | ||||
| [ext_resource type="Texture2D" uid="uid://bd81g8aivb2ql" path="res://Assets/Sprites/UI/menu-rect-no-bg-32.png" id="2_vvog5"] | ||||
| [ext_resource type="Texture2D" uid="uid://b16461tjso0j7" path="res://Assets/Sprites/UI/hotbar-inactive.png" id="3_jr23q"] | ||||
| 
 | ||||
| [node name="InventorySlot" type="ColorRect"] | ||||
| custom_minimum_size = Vector2(32, 32) | ||||
| color = Color(1, 1, 1, 0) | ||||
| script = ExtResource("1_llonk") | ||||
| 
 | ||||
| [node name="TextureRect" type="TextureRect" parent="."] | ||||
| layout_mode = 1 | ||||
| anchors_preset = 15 | ||||
| anchor_right = 1.0 | ||||
| anchor_bottom = 1.0 | ||||
| grow_horizontal = 2 | ||||
| grow_vertical = 2 | ||||
| stretch_mode = 3 | ||||
| 
 | ||||
| [node name="Selected" type="NinePatchRect" parent="."] | ||||
| visible = false | ||||
| layout_mode = 1 | ||||
| anchors_preset = 15 | ||||
| anchor_right = 1.0 | ||||
| anchor_bottom = 1.0 | ||||
| grow_horizontal = 2 | ||||
| grow_vertical = 2 | ||||
| texture = ExtResource("2_vvog5") | ||||
| 
 | ||||
| [node name="Unselected" type="NinePatchRect" parent="."] | ||||
| self_modulate = Color(1, 1, 1, 0.5) | ||||
| layout_mode = 1 | ||||
| anchors_preset = 15 | ||||
| anchor_right = 1.0 | ||||
| anchor_bottom = 1.0 | ||||
| grow_horizontal = 2 | ||||
| grow_vertical = 2 | ||||
| texture = ExtResource("3_jr23q") | ||||
|  | @ -0,0 +1,19 @@ | |||
| # Godot 4+ specific ignores | ||||
| .godot/ | ||||
| 
 | ||||
| # Godot-specific ignores | ||||
| .import/ | ||||
| export.cfg | ||||
| export_presets.cfg | ||||
| 
 | ||||
| 
 | ||||
| # Imported translations (automatically generated from CSV files) | ||||
| *.translation | ||||
| 
 | ||||
| # Mono-specific ignores | ||||
| .mono/ | ||||
| data_*/ | ||||
| mono_crash.*.json | ||||
| 
 | ||||
| # Only used for asset library, so no .import file is needed. | ||||
| annotate_layer.png.import | ||||
|  | @ -0,0 +1,19 @@ | |||
| Copyright (c) 2023 Zarstensen | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
|  | @ -0,0 +1,90 @@ | |||
| <div align="center"> | ||||
|   <h1 align="center">Godot Annotate</h1> | ||||
|   <img src=annotate_layer.svg alt="Icon" width="200" height="200"/> | ||||
| </div> | ||||
| 
 | ||||
| [](https://godotengine.org/asset-library/asset/2432) | ||||
| 
 | ||||
| This is a [Godot](https://godotengine.org/) plugin which allows one to make planning  annotations and sketches directly in the 2D editor, without affecting runtime visuals, using a custom 'AnnotateCanvas' node. | ||||
| 
 | ||||
| - [Features](#features) | ||||
|   - [Annotate](#annotate) | ||||
|   - [Polygon Mode](#polygon-mode) | ||||
|   - [Erase](#erase) | ||||
|   - [Control Annotation Visibility](#control-annotation-visibility) | ||||
|   - [Save Canvas As Image](#save-canvas-as-image) | ||||
| - [Usage](#usage) | ||||
|   - [Controls](#controls) | ||||
|   - [Locking](#locking) | ||||
| - [Installing](#installing) | ||||
| - [Links](#links) | ||||
| - [License](#license) | ||||
| 
 | ||||
| ## Features | ||||
| 
 | ||||
| ### Annotate | ||||
| 
 | ||||
| Annotate with variable brush size and color directly in the 2D editor using the 'AnnotateCanvas' node. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Polygon Mode | ||||
| 
 | ||||
| Use polygon mode to draw straight lines between clicks. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Erase | ||||
| 
 | ||||
| Erase any previously drawn annotate strokes. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Control Annotation Visibility | ||||
| 
 | ||||
| Only show annotations in the 2D editor (optionally show in run mode). | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ### Save Canvas As Image | ||||
| 
 | ||||
| Save the canvas to disk as an image file. | ||||
| 
 | ||||
|  | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| To start annotating, add the 'AnnotateCanvas' node to a godot scene. | ||||
| 
 | ||||
| ### Controls | ||||
| 
 | ||||
| **Left Mouse Button** | ||||
| : Annotate on the currenty selected 'AnnotateCanvas' node. | ||||
| 
 | ||||
| **Alt + Left Mouse Button** | ||||
| : Annotate on the currently selected 'AnnotateCanvas' node using the polygon mode. | ||||
| 
 | ||||
| **Right Mouse Button** | ||||
| : Erase annotate strokes on the currently selected 'AnnotateCanvas' node. | ||||
| 
 | ||||
| **Shift + Mouse Scroll** | ||||
| : Change brush size. | ||||
| 
 | ||||
| **Shift + Alt + S** | ||||
| : Save the selected 'AnnotateCanvas' to disk as an image. | ||||
| 
 | ||||
| ### Locking | ||||
| 
 | ||||
| Locking an 'AnnotateCanvas' node does not prevent it from being drawn on, instead toggle the 'Advanced > Lock Canvas' property to prevent this. | ||||
| 
 | ||||
| ## Installing | ||||
| 
 | ||||
| See [Installing Plugins](https://docs.godotengine.org/en/stable/tutorials/plugins/editor/installing_plugins.html), for how to add this plugin to your Godot project. | ||||
| 
 | ||||
| ## Links | ||||
| 
 | ||||
| [Godot Assets](https://godotengine.org/asset-library/asset/2432) | ||||
| 
 | ||||
| ## License | ||||
| 
 | ||||
| See [LICENSE](LICENSE) | ||||
| After Width: | Height: | Size: 3.2 KiB | 
|  | @ -0,0 +1,49 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||||
| <svg | ||||
|    height="16" | ||||
|    viewBox="0 0 16 16" | ||||
|    width="16" | ||||
|    version="1.1" | ||||
|    id="svg587" | ||||
|    sodipodi:docname="AnnotateLayer.svg" | ||||
|    inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)" | ||||
|    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | ||||
|    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | ||||
|    xmlns="http://www.w3.org/2000/svg" | ||||
|    xmlns:svg="http://www.w3.org/2000/svg"> | ||||
|   <defs | ||||
|      id="defs591" /> | ||||
|   <sodipodi:namedview | ||||
|      id="namedview589" | ||||
|      pagecolor="#505050" | ||||
|      bordercolor="#ffffff" | ||||
|      borderopacity="1" | ||||
|      inkscape:showpageshadow="0" | ||||
|      inkscape:pageopacity="0" | ||||
|      inkscape:pagecheckerboard="1" | ||||
|      inkscape:deskcolor="#505050" | ||||
|      showgrid="false" | ||||
|      inkscape:zoom="32.093751" | ||||
|      inkscape:cx="1.7604674" | ||||
|      inkscape:cy="5.8422589" | ||||
|      inkscape:window-width="2400" | ||||
|      inkscape:window-height="1271" | ||||
|      inkscape:window-x="3191" | ||||
|      inkscape:window-y="194" | ||||
|      inkscape:window-maximized="1" | ||||
|      inkscape:current-layer="svg587" /> | ||||
|   <path | ||||
|      d="m 12.584856,1 c -0.554,0 -1,0.446 -1,1 v 2 h 4 V 2 c 0,-0.554 -0.446,-1 -1,-1 z m -1,4 v 7 l 2,3 2,-3 V 5 Z m 1,1 h 1 v 5 h -1 z" | ||||
|      fill="#e0e0e0" | ||||
|      id="path585" | ||||
|      style="fill:#8da5f3" /> | ||||
|   <path | ||||
|      d="m 8.4863563,12.390043 c 0,0 -3.8050791,0.460698 -3.4912538,-0.995337 C 5.3089277,9.9386714 8.6407192,7.5625717 6.8053389,7.5086833 4.969959,7.4547952 0.52452178,8.4713646 1.5739053,5.9582204 2.0534377,4.8097982 4.3793367,3.5400372 4.3793367,3.5400372" | ||||
|      fill="none" | ||||
|      stroke="#8da5f3" | ||||
|      stroke-linecap="round" | ||||
|      stroke-linejoin="round" | ||||
|      stroke-width="2" | ||||
|      id="path710" | ||||
|      sodipodi:nodetypes="czzsc" /> | ||||
| </svg> | ||||
| After Width: | Height: | Size: 1.7 KiB | 
|  | @ -0,0 +1,37 @@ | |||
| [remap] | ||||
| 
 | ||||
| importer="texture" | ||||
| type="CompressedTexture2D" | ||||
| uid="uid://b0c04klyd6mgf" | ||||
| path="res://.godot/imported/annotate_layer.svg-6785b3ede543715b4d920be19a30a9b4.ctex" | ||||
| metadata={ | ||||
| "vram_texture": false | ||||
| } | ||||
| 
 | ||||
| [deps] | ||||
| 
 | ||||
| source_file="res://addons/GodotAnnotate/annotate_layer.svg" | ||||
| dest_files=["res://.godot/imported/annotate_layer.svg-6785b3ede543715b4d920be19a30a9b4.ctex"] | ||||
| 
 | ||||
| [params] | ||||
| 
 | ||||
| compress/mode=0 | ||||
| compress/high_quality=false | ||||
| compress/lossy_quality=0.7 | ||||
| compress/hdr_compression=1 | ||||
| compress/normal_map=0 | ||||
| compress/channel_pack=0 | ||||
| mipmaps/generate=false | ||||
| mipmaps/limit=-1 | ||||
| roughness/mode=0 | ||||
| roughness/src_normal="" | ||||
| process/fix_alpha_border=true | ||||
| process/premult_alpha=false | ||||
| process/normal_map_invert_y=false | ||||
| process/hdr_as_srgb=false | ||||
| process/hdr_clamp_exposure=false | ||||
| process/size_limit=0 | ||||
| detect_3d/compress_to=1 | ||||
| svg/scale=1.0 | ||||
| editor/scale_with_editor_scale=false | ||||
| editor/convert_colors_with_editor_theme=false | ||||
| After Width: | Height: | Size: 1.2 MiB | 
| After Width: | Height: | Size: 310 KiB | 
| After Width: | Height: | Size: 251 KiB | 
| After Width: | Height: | Size: 518 KiB | 
| After Width: | Height: | Size: 780 KiB | 
|  | @ -0,0 +1,7 @@ | |||
| [plugin] | ||||
| 
 | ||||
| name="Godot Annotate" | ||||
| description="Adds annotation to the 2D editor, allowing one to sketch directly inside the editor, without affecting runtime visuals." | ||||
| author="Zarstensen" | ||||
| version="0.3.0" | ||||
| script="src/godot_annotate.gd" | ||||
|  | @ -0,0 +1,5 @@ | |||
| [gd_scene format=3 uid="uid://bueej38g03yw2"] | ||||
| 
 | ||||
| [node name="CanvasImageDialog" type="FileDialog"] | ||||
| title = "Select Where To Save Canvas Image" | ||||
| filters = PackedStringArray("*.png, *.jpg, *.jpeg ; Images") | ||||
|  | @ -0,0 +1,20 @@ | |||
| [gd_scene format=3 uid="uid://bkubc0dybo25h"] | ||||
| 
 | ||||
| [node name="UpscaleFactorDialog" type="ConfirmationDialog"] | ||||
| title = "Canvas Upscale Factor" | ||||
| size = Vector2i(200, 88) | ||||
| visible = true | ||||
| min_size = Vector2i(100, 70) | ||||
| 
 | ||||
| [node name="UpscaleFactorInput" type="SpinBox" parent="."] | ||||
| anchors_preset = 4 | ||||
| anchor_top = 0.5 | ||||
| anchor_bottom = 0.5 | ||||
| offset_left = 8.0 | ||||
| offset_top = 8.0 | ||||
| offset_right = 192.0 | ||||
| offset_bottom = 39.0 | ||||
| grow_vertical = 2 | ||||
| step = 0.1 | ||||
| value = 1.0 | ||||
| alignment = 1 | ||||
|  | @ -0,0 +1,147 @@ | |||
| @tool | ||||
| class_name AnnotateCanvas | ||||
| extends Node2D | ||||
| ## | ||||
| ## Node allowing user to paint and view [AnnotateStroke]s on a [AnnotateLayer] in the 2D editor. | ||||
| ## | ||||
| 
 | ||||
| ## Percentage size increase to stroke size caused by shift + scroll. | ||||
| const SIZE_SCROLL_PERC: float = 0.1 | ||||
| 
 | ||||
| @export_group("Brush") | ||||
| 
 | ||||
| ## How large the brush size will be when [member brush_size] = 100. | ||||
| @export_range(0, 9999, 1.0, "or_greater") | ||||
| var max_brush_size: float = 50 | ||||
| 
 | ||||
| ## Current size of the brush used to paint strokes. | ||||
| ## Represents a percentage of [member max_brush_size], which is used for constructing [AnnotateStroke]s. | ||||
| ## [br] | ||||
| ## [br] | ||||
| ## Shortcut: shift + scroll  | ||||
| @export_range(1, 100, 0.1) | ||||
| var brush_size: float = 50 | ||||
| 
 | ||||
| 
 | ||||
| @export | ||||
| var brush_color: Color = Color(141 / 255.0, 165 / 255.0, 243 / 255.0) | ||||
| 
 | ||||
| @export_group("Advanced") | ||||
| 
 | ||||
| ## Do not remove [AnnotateCanvas] node from scene when running outside editor. | ||||
| ## User will not be able to paint on the canvas, even if this is set to [code] true [/code] | ||||
| @export | ||||
| var show_when_running := false | ||||
| 
 | ||||
| ## Lock [AnnotateCanvas] node from being drawn on. | ||||
| @export | ||||
| var lock_canvas := false | ||||
| 
 | ||||
| ## Percentage of brush radius must be between a new point inserted with [method insert_point], | ||||
| ## for it to be added to the [member points] array. | ||||
| @export_range(0, 2, 0.05) | ||||
| var min_point_distance = 0.25 | ||||
| 
 | ||||
| ## Current [AnnotateLayer] resource which is painted on when user annotates. | ||||
| @export | ||||
| var layer_resource: AnnotateLayer = AnnotateLayer.new() | ||||
| 
 | ||||
| ## Stroke currently being painted by the user. | ||||
| var _active_stroke: AnnotateStrokeLine | ||||
| ## [code] true [/code] if user is currently trying to erase strokes. | ||||
| var _erasing := false | ||||
| 
 | ||||
| ## Array of [AnnotateStrokeLine]s, which all visually represents all of the [AnnotateStroke] resources | ||||
| ## in the layer_resource array. | ||||
| var _stroke_lines: Array[AnnotateStrokeLine] = [ ] | ||||
| 
 | ||||
| func get_canvas_area() -> Rect2: | ||||
| 	 | ||||
| 	if layer_resource.strokes.size() <= 0: | ||||
| 		return Rect2() | ||||
| 	 | ||||
| 	var canvas_area := layer_resource.strokes[0].boundary | ||||
| 	 | ||||
| 	for stroke in layer_resource.strokes.slice(1): | ||||
| 		canvas_area = canvas_area.merge(stroke.boundary) | ||||
| 		 | ||||
| 	return canvas_area | ||||
| 
 | ||||
| func _ready(): | ||||
| 	if not Engine.is_editor_hint() and not show_when_running: | ||||
| 		queue_free() | ||||
| 	 | ||||
| 	# restore lines from previously saved state. | ||||
| 	 | ||||
| 	for stroke in layer_resource.strokes: | ||||
| 		var line := AnnotateStrokeLine.from_stroke(stroke) | ||||
| 		add_child(line) | ||||
| 		_stroke_lines.append(line) | ||||
| 
 | ||||
| func _on_begin_stroke(): | ||||
| 	_active_stroke = AnnotateStrokeLine.new(brush_size / 100 * max_brush_size, brush_color) | ||||
| 	add_child(_active_stroke) | ||||
| 	_stroke_lines.append(_active_stroke) | ||||
| 	# instantly insert a point, to avoid the user having to drag the cursor, | ||||
| 	# in order to insert a point. | ||||
| 	_active_stroke.try_annotate_point(get_local_mouse_position(), min_point_distance, true) | ||||
| 
 | ||||
| func _on_end_stroke(): | ||||
| 	if !GodotAnnotate.poly_in_progress: | ||||
| 		# force insert final point, as the stroke should end where the user stopped the stroke, | ||||
| 		# even if the final point is within AnnotateStroke.MIN_POINT_DISTANCE. | ||||
| 		_active_stroke.try_annotate_point(get_local_mouse_position(), min_point_distance, true) | ||||
| 		 | ||||
| 	layer_resource.strokes.append(_active_stroke.to_stroke()) | ||||
| 	_active_stroke = null | ||||
| 	 | ||||
| func _on_begin_erase(): | ||||
| 	_erasing = true | ||||
| 
 | ||||
| func _on_end_erase(): | ||||
| 	_erasing = false | ||||
| 
 | ||||
| func _on_draw_poly_stroke(): | ||||
| 	_active_stroke.try_annotate_point(get_local_mouse_position(), min_point_distance, false) | ||||
| 
 | ||||
| func _on_stroke_resize(direction: float): | ||||
| 	brush_size *= 1 + direction * SIZE_SCROLL_PERC | ||||
| 	brush_size = min(100, max(brush_size, 1)) | ||||
| 
 | ||||
| func _on_capture_canvas(file: String, scale: float): | ||||
| 	add_child(AnnotateCanvasCaptureViewport.new(self, file, scale)) | ||||
| 
 | ||||
| func _process(delta): | ||||
| 	if _active_stroke && !GodotAnnotate.poly_in_progress: | ||||
| 		_active_stroke.try_annotate_point(get_local_mouse_position(), min_point_distance, false) | ||||
| 		 | ||||
| 	if _erasing: | ||||
| 		var erase_stroke_indexes: Array[int] = [] | ||||
| 		 | ||||
| 		for i in range(_stroke_lines.size()): | ||||
| 			if _stroke_lines[i].collides_with(get_local_mouse_position(), brush_size / 100 * max_brush_size): | ||||
| 				erase_stroke_indexes.append(i) | ||||
| 		 | ||||
| 		for erase_count in range(erase_stroke_indexes.size()): | ||||
| 			# subtract the target index by the amount of strokes deleted, | ||||
| 			# since these strokes no longer exist in the array. | ||||
| 			 | ||||
| 			var remove_index := erase_stroke_indexes[erase_count] - erase_count | ||||
| 			 | ||||
| 			layer_resource.strokes.remove_at(remove_index) | ||||
| 			_stroke_lines[remove_index].queue_free() | ||||
| 			_stroke_lines.remove_at(remove_index) | ||||
| 		 | ||||
| 	queue_redraw() | ||||
| 
 | ||||
| func _draw(): | ||||
| 	if lock_canvas: | ||||
| 		return | ||||
| 	 | ||||
| 	if GodotAnnotate.poly_in_progress: | ||||
| 		draw_dashed_line(_active_stroke.points[-1], get_local_mouse_position(), brush_color, brush_size * 0.125, brush_size * 0.25) | ||||
| 	 | ||||
| 	if _erasing: | ||||
| 		draw_arc(get_local_mouse_position(), brush_size / 100 * max_brush_size / 2, 0, TAU, 32, Color.INDIAN_RED, 3, true) | ||||
| 	elif GodotAnnotate.selected_canvas == self: | ||||
| 		draw_circle(get_local_mouse_position(), brush_size / 100 * max_brush_size / 2, brush_color) | ||||
|  | @ -0,0 +1,43 @@ | |||
| class_name AnnotateCanvasCaptureViewport | ||||
| extends SubViewport | ||||
| ## | ||||
| ## Class responsible for saving an image version of a AnnotateCanvas node to disk. | ||||
| ## | ||||
| 
 | ||||
| var _first_process := true | ||||
| var _file_location: String | ||||
| 
 | ||||
| ## Construct a AnnotateCanvasCaptureViewport node. [br] | ||||
| ## [param AnnotateCanvas]: AnnotateCanvas which should be saved as an image to disk. | ||||
| ## [param file]: Filename the image will be stored under. | ||||
| ## [param scale]: What the resolution of the image should be scaled by. | ||||
| func _init(canvas: AnnotateCanvas, file: String, scale: float = 1.0): | ||||
| 	canvas = canvas.duplicate() | ||||
| 	_file_location = file | ||||
| 	 | ||||
| 	var canvas_area := canvas.get_canvas_area() | ||||
| 	 | ||||
| 	# viewports render anything from (0, 0) to (width, height), | ||||
| 	# so we want to offset the canvas contents to make sure they fit inside this area. | ||||
| 	canvas.position = -canvas_area.position * scale | ||||
| 	canvas.scale *= scale | ||||
| 	canvas_area.position = Vector2.ZERO | ||||
| 	 | ||||
| 	add_child(canvas) | ||||
| 	size = canvas_area.size * scale | ||||
| 	 | ||||
| 	render_target_update_mode = SubViewport.UPDATE_ALWAYS | ||||
| 	transparent_bg = true | ||||
| 
 | ||||
| func _process(_delta): | ||||
| 	if _first_process: | ||||
| 		# allow viewport to update itself before capturing | ||||
| 		_first_process = false | ||||
| 		return | ||||
| 		 | ||||
| 	get_texture().get_image().save_png(_file_location) | ||||
| 	 | ||||
| 	# make sure new image file is visible in the editor filesystem | ||||
| 	GodotAnnotate.editor_interface.get_resource_filesystem().scan() | ||||
| 	 | ||||
| 	queue_free() | ||||
|  | @ -0,0 +1,8 @@ | |||
| class_name AnnotateLayer | ||||
| extends Resource | ||||
| ## | ||||
| ## Resource containing a series of [AnnotateStroke]s. | ||||
| ## | ||||
| 
 | ||||
| @export | ||||
| var strokes: Array[AnnotateStroke] = [] | ||||
|  | @ -0,0 +1,30 @@ | |||
| class_name AnnotateStroke | ||||
| extends Resource | ||||
| ## | ||||
| ## Resource representing a single stroke in a AnnotateCanvas node. | ||||
| ## | ||||
| 
 | ||||
| ## Diameter of the brush used to paint the stroke. | ||||
| @export | ||||
| var size: float | ||||
| 
 | ||||
| @export | ||||
| var color: Color | ||||
| 
 | ||||
| ## Represents the smallest possible rectangle with no rotation which contains the entire stroke. | ||||
| @export | ||||
| var boundary: Rect2 | ||||
| 
 | ||||
| ## List of points representing the shape of the brush, | ||||
| ## with the stroke starting at the first element and ending at the last. | ||||
| # points is not typed for compatility for v0.1.x, should be typed in future versions. | ||||
| @export | ||||
| var points = PackedVector2Array() | ||||
| 
 | ||||
| ## Construct a stroke with the given brush information. | ||||
| ## use [method insert_point] to modify stroke. | ||||
| func _init(_size: float = 1, _color: Color = Color.DODGER_BLUE, _points: PackedVector2Array = PackedVector2Array(), _boundary: Rect2 = Rect2()): | ||||
| 	size = _size | ||||
| 	color = _color | ||||
| 	points = _points | ||||
| 	boundary = _boundary | ||||
|  | @ -0,0 +1,96 @@ | |||
| class_name AnnotateStrokeLine | ||||
| extends Line2D | ||||
| ## | ||||
| ## Node responsible for a visual representation of a AnnotateStroke resource. | ||||
| ## | ||||
| 
 | ||||
| ## Percentage of point position to increment point position by, if overlapping with another point. | ||||
| const OVERLAP_INCR_PERC = 0.0001 | ||||
| ## Minimum increment of point, if point overlaps with another point. | ||||
| const MIN_FLOAT_TRES_VAL = 0.001 | ||||
| 
 | ||||
| ## same as [AnnotateStroke.boundary] | ||||
| var boundary: Rect2 = Rect2() | ||||
| 
 | ||||
| ## Construct a stroke line with the given stroke size and color. | ||||
| ## Its capping and joint mode are all set to round. | ||||
| func _init(size: float, color: Color): | ||||
| 	width = size | ||||
| 	default_color = color | ||||
| 	# TODO: should probably make this customisable in some way. | ||||
| 	# not sure if this should be custom for each stroke, or just the canvas in general. | ||||
| 	round_precision = 32 | ||||
| 
 | ||||
| 	begin_cap_mode = Line2D.LINE_CAP_ROUND | ||||
| 	end_cap_mode = Line2D.LINE_CAP_ROUND | ||||
| 	joint_mode = Line2D.LINE_JOINT_ROUND | ||||
| 
 | ||||
| ## Construct a stroke line which visually represents the given [AnnotateStroke] resource. | ||||
| static func from_stroke(stroke: AnnotateStroke) -> AnnotateStrokeLine: | ||||
| 	var stroke_line = AnnotateStrokeLine.new(stroke.size, stroke.color) | ||||
| 	stroke_line.boundary = stroke.boundary | ||||
| 	 | ||||
| 	# v0.1.x uses Array[Vector2] instead of PackedVector2Array in the AnnotateStroke resource. | ||||
| 	if stroke.points is Array[Vector2]: | ||||
| 		stroke.points = PackedVector2Array(stroke.points) | ||||
| 	 | ||||
| 	stroke_line.points = stroke.points | ||||
| 	 | ||||
| 	return stroke_line | ||||
| 
 | ||||
| ## Convert the stroke line back to a [AnnotateStroke] resource, | ||||
| ## which will construct this exact stroke line when passed to [method from_stroke] | ||||
| func to_stroke() -> AnnotateStroke: | ||||
| 	return AnnotateStroke.new(width, default_color, points, boundary) | ||||
| 
 | ||||
| ## Attempts to insert the given point at the end of the stroke line. | ||||
| ## If the point is less than [param perc_min_point_dist], it will not be added, | ||||
| ## unless [param force] is set to true. | ||||
| func try_annotate_point(point: Vector2, perc_min_point_dist: float, force: bool): | ||||
| 	var size_vec = Vector2(width, width) | ||||
| 	 | ||||
| 	if points.size() <= 0: | ||||
| 		boundary = Rect2(point - size_vec, size_vec) | ||||
| 	elif points[points.size() - 1].distance_to(point) < perc_min_point_dist * width: | ||||
| 		 | ||||
| 		if force: | ||||
| 			# if two points overlap exactly, then their end caps are not drawn. | ||||
| 			# therefore we offset the point by a very small value to make sure this does not happen. | ||||
| 			# also, for some reason, tres files does not allow tools to store floats with more than | ||||
| 			# 3 decimals precission, so we cannot increment by less than 0.001, since this will not | ||||
| 			# be stored in the AnnotateStroke resource saved to disk. | ||||
| 			 | ||||
| 			var increment := point * OVERLAP_INCR_PERC | ||||
| 			 | ||||
| 			increment.x = min(increment.x, MIN_FLOAT_TRES_VAL) | ||||
| 			increment.y = min(increment.y, MIN_FLOAT_TRES_VAL) | ||||
| 			 | ||||
| 			point += increment | ||||
| 		else: | ||||
| 			# ignore points which are too close to each other, to reduce memory usage. | ||||
| 			return | ||||
| 
 | ||||
| 	for dir in [Vector2.UP, Vector2.DOWN, Vector2.LEFT, Vector2.RIGHT]: | ||||
| 		boundary = boundary.expand(point + size_vec * dir / 2) | ||||
| 	 | ||||
| 	add_point(point) | ||||
| 	return point | ||||
| 
 | ||||
| ## Checks if the given stroke line collides with a circle centered at [param brush center] | ||||
| ## which has a diamater of [param brush_width] | ||||
| func collides_with(brush_center: Vector2, brush_width: float) -> bool: | ||||
| 	var nearest_x := max(boundary.position.x, min(brush_center.x, boundary.end.x)) | ||||
| 	var nearest_y := max(boundary.position.y, min(brush_center.y, boundary.end.y)) | ||||
| 	# check if erase circle overlaps with stroke boundary | ||||
| 	var nearest_boundary_point := Vector2(nearest_x, nearest_y) | ||||
| 	 | ||||
| 	if nearest_boundary_point.distance_squared_to(brush_center) > (brush_width / 2) ** 2: | ||||
| 		return false | ||||
| 
 | ||||
| 	# check if erase circle overlaps with any points in stroke line, | ||||
| 	# only if above is true to reduce number of distance checks. | ||||
| 	for stroke_points in points: | ||||
| 		if stroke_points.distance_squared_to(brush_center) < (width / 2 + brush_width / 2) ** 2: | ||||
| 			return true | ||||
| 
 | ||||
| 	return false | ||||
|  | @ -0,0 +1,106 @@ | |||
| @tool | ||||
| class_name GodotAnnotate | ||||
| extends EditorPlugin | ||||
| ## | ||||
| ## Handles initialization, deinitialization and event forwarding to [AnnotateCanvas] nodes. | ||||
| ## | ||||
| 
 | ||||
| static var selected_canvas: AnnotateCanvas | ||||
| 
 | ||||
| static var poly_in_progress := false | ||||
| 
 | ||||
| static var canvas_image_dialog_scene := preload("res://addons/GodotAnnotate/res/CanvasImageDialog.tscn") | ||||
| static var upscale_factor_dialog_scene := preload("res://addons/GodotAnnotate/res/UpscaleFactorDialog.tscn") | ||||
| 
 | ||||
| static var editor_interface: EditorInterface | ||||
| 
 | ||||
| func _enter_tree(): | ||||
| 	add_custom_type("AnnotateCanvas", "Node2D", preload("res://addons/GodotAnnotate/src/annotate_canvas.gd"), preload("res://addons/GodotAnnotate/annotate_layer.svg")) | ||||
| 	editor_interface = get_editor_interface() | ||||
| 
 | ||||
| func _exit_tree(): | ||||
| 	remove_custom_type("AnnotateCanvas") | ||||
| 
 | ||||
| ## Forwards relevant 2d editor user inputs to an [AnnotateCanvas] node. | ||||
| ## TODO: clean this up a bit. | ||||
| func _forward_canvas_gui_input(event): | ||||
| 	if not selected_canvas or selected_canvas.lock_canvas: | ||||
| 		return false | ||||
| 	 | ||||
| 	if event is InputEventKey: | ||||
| 		 | ||||
| 		# canvas capture (shortcut: shift + alt + s) | ||||
| 		if event.keycode == KEY_S && event.pressed && event.alt_pressed && event.shift_pressed: | ||||
| 			var upscale_factor_dialog := upscale_factor_dialog_scene.instantiate() | ||||
| 			upscale_factor_dialog.confirmed.connect(func(): | ||||
| 				 | ||||
| 				var canvas_image_dialog := canvas_image_dialog_scene.instantiate() | ||||
| 				canvas_image_dialog.file_selected.connect(func(file): | ||||
| 					 | ||||
| 					# upscale factor is present in the spinbox child of the upscale factor dialog. | ||||
| 					selected_canvas._on_capture_canvas(file, upscale_factor_dialog.get_child(0).value) | ||||
| 				 | ||||
| 				) | ||||
| 				 | ||||
| 				get_editor_interface().popup_dialog_centered(canvas_image_dialog) | ||||
| 			 | ||||
| 			) | ||||
| 			 | ||||
| 			get_editor_interface().popup_dialog_centered(upscale_factor_dialog) | ||||
| 		 | ||||
| 		# polygon drawing | ||||
| 		if poly_in_progress: | ||||
| 			if event.keycode == KEY_ALT && !event.pressed: | ||||
| 				selected_canvas._on_end_stroke() | ||||
| 				poly_in_progress = false | ||||
| 				return true | ||||
| 	 | ||||
| 	if event is InputEventMouseButton: | ||||
| 		# drawing | ||||
| 		if event.button_index == MOUSE_BUTTON_LEFT && event.pressed: | ||||
| 			if event.alt_pressed && not poly_in_progress: | ||||
| 				selected_canvas._on_begin_stroke() | ||||
| 				poly_in_progress = true | ||||
| 			if poly_in_progress: | ||||
| 				selected_canvas._on_draw_poly_stroke() | ||||
| 			else: | ||||
| 				selected_canvas._on_begin_stroke() | ||||
| 			return true | ||||
| 		elif event.button_index == MOUSE_BUTTON_LEFT && not event.pressed && not poly_in_progress: | ||||
| 			if !poly_in_progress: | ||||
| 				selected_canvas._on_end_stroke() | ||||
| 			return true | ||||
| 		 | ||||
| 		# erasing | ||||
| 		elif event.button_index == MOUSE_BUTTON_RIGHT && event.pressed: | ||||
| 			selected_canvas._on_begin_erase() | ||||
| 			return true | ||||
| 		elif event.button_index == MOUSE_BUTTON_RIGHT && not event.pressed: | ||||
| 			selected_canvas._on_end_erase() | ||||
| 			return true | ||||
| 		 | ||||
| 		# stroke size (shift + scroll) | ||||
| 		# cannot use ctrl or alt, since they control view position and zoom, | ||||
| 		# and cannot be prevented from being forwarded by returning true. | ||||
| 		elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN && Input.is_key_pressed(KEY_SHIFT): | ||||
| 			if event.pressed: | ||||
| 				selected_canvas._on_stroke_resize(-1) | ||||
| 			 | ||||
| 			return true | ||||
| 		elif event.button_index == MOUSE_BUTTON_WHEEL_UP && Input.is_key_pressed(KEY_SHIFT): | ||||
| 			if event.pressed: | ||||
| 				selected_canvas._on_stroke_resize(1) | ||||
| 			 | ||||
| 			return true | ||||
| 
 | ||||
| 	return false | ||||
| 
 | ||||
| ## Keeps track of currently selected node, as special action is required when an [AnnotateCanvas] node is selected. | ||||
| func _handles(object): | ||||
| 	if object is AnnotateCanvas: | ||||
| 		selected_canvas = object | ||||
| 		return true | ||||
| 	 | ||||
| 	selected_canvas = null | ||||
| 	 | ||||
| 	return false | ||||