inventory hotbar wip

wip-inventory
John Montagu, the 4th Earl of Sandvich 2023-12-31 05:59:33 -08:00
parent 802dd7eba9
commit 54bde10278
Signed by: sandvich
GPG Key ID: 9A39BE37E602B22D
53 changed files with 1131 additions and 124 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 966 B

After

Width:  |  Height:  |  Size: 909 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 831 B

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 B

After

Width:  |  Height:  |  Size: 840 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 899 B

After

Width:  |  Height:  |  Size: 878 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 B

View File

@ -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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

View File

@ -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

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 868 B

View File

@ -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

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 B

After

Width:  |  Height:  |  Size: 387 B

View File

@ -522,16 +522,14 @@ point_count = 3
curve = SubResource("Curve_x3x4q") curve = SubResource("Curve_x3x4q")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_s1tqp"] [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_s1tqp"]
particle_flag_disable_z = true
emission_shape = 3 emission_shape = 3
emission_box_extents = Vector3(8, 1, 1) emission_box_extents = Vector3(8, 1, 1)
particle_flag_disable_z = true
direction = Vector3(0, -1, 0) direction = Vector3(0, -1, 0)
spread = 0.0 spread = 0.0
gravity = Vector3(0, 0, 0)
initial_velocity_min = 32.0 initial_velocity_min = 32.0
initial_velocity_max = 64.0 initial_velocity_max = 64.0
orbit_velocity_min = 0.0 gravity = Vector3(0, 0, 0)
orbit_velocity_max = 0.0
scale_max = 1.5 scale_max = 1.5
scale_curve = SubResource("CurveTexture_ssoms") scale_curve = SubResource("CurveTexture_ssoms")
color_ramp = SubResource("GradientTexture1D_pjeh8") color_ramp = SubResource("GradientTexture1D_pjeh8")
@ -542,8 +540,6 @@ color_initial_ramp = SubResource("GradientTexture1D_5606i")
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_j1srf"] [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_j1srf"]
particle_flag_disable_z = true particle_flag_disable_z = true
gravity = Vector3(0, 98, 0) gravity = Vector3(0, 98, 0)
orbit_velocity_min = 0.0
orbit_velocity_max = 0.0
[sub_resource type="RectangleShape2D" id="RectangleShape2D_uict5"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_uict5"]
size = Vector2(11, 5) size = Vector2(11, 5)
@ -774,9 +770,10 @@ Faction = 2
position = Vector2(0, -3.5) position = Vector2(0, -3.5)
shape = SubResource("RectangleShape2D_8lxmf") shape = SubResource("RectangleShape2D_8lxmf")
[node name="Inventory" type="Node2D" parent="."] [node name="Inventory" type="Node2D" parent="." node_paths=PackedStringArray("Items")]
y_sort_enabled = true y_sort_enabled = true
script = ExtResource("8_r8ejq") script = ExtResource("8_r8ejq")
Items = []
[node name="DocLance" parent="Inventory" instance=ExtResource("24_2es2r")] [node name="DocLance" parent="Inventory" instance=ExtResource("24_2es2r")]
unique_name_in_owner = true unique_name_in_owner = true

View File

@ -74,6 +74,8 @@ public sealed partial class Player : Character
var signal = Events.EventBus.SignalName.PlayerHealthChanged; var signal = Events.EventBus.SignalName.PlayerHealthChanged;
this.GetEventBus().EmitSignal(signal, args); this.GetEventBus().EmitSignal(signal, args);
}; };
Inventory.AddItemToHotbar(Inventory.Items[0]);
} }
public override void _Process(double delta) public override void _Process(double delta)
@ -224,6 +226,7 @@ public sealed partial class Player : Character
switch (inputMethod) switch (inputMethod)
{ {
case State.Global.InputMethod.Joystick: case State.Global.InputMethod.Joystick:
GD.Print(joystick);
if (joystick.IsZeroApprox()) if (joystick.IsZeroApprox())
{ {
return Direction; return Direction;

View File

@ -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="Script" path="res://Characters/Player.cs" id="1_flygr"]
[ext_resource type="Shader" path="res://Shaders/Flash.gdshader" id="2_ngsgt"] [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/DoubleValue.cs" id="5_txl0r"]
[ext_resource type="Script" path="res://Utils/Values/IntValue.cs" id="6_sunc5"] [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="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://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://Utils/AnimationManager.cs" id="7_sdgvb"]
[ext_resource type="Script" path="res://Items/Inventory.cs" id="7_xyenu"] [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://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/PlayerHealState.cs" id="13_t103m"]
[ext_resource type="Script" path="res://State/Character/PlayerMaxLevelState.cs" id="14_1sn10"] [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="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="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="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://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://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="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"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_h78y7"]
shader = ExtResource("2_ngsgt") shader = ExtResource("2_ngsgt")
@ -566,7 +562,7 @@ color_ramp = SubResource("GradientTexture1D_3jlnh")
light_mode = 1 light_mode = 1
[sub_resource type="RectangleShape2D" id="RectangleShape2D_bfqew"] [sub_resource type="RectangleShape2D" id="RectangleShape2D_bfqew"]
size = Vector2(12, 4) size = Vector2(12, 6)
[sub_resource type="LabelSettings" id="LabelSettings_q5h1n"] [sub_resource type="LabelSettings" id="LabelSettings_q5h1n"]
font_size = 24 font_size = 24
@ -601,11 +597,9 @@ script = ExtResource("4_06oya")
[node name="XP" type="Node" parent="Stats"] [node name="XP" type="Node" parent="Stats"]
script = ExtResource("5_txl0r") script = ExtResource("5_txl0r")
Value = null
[node name="Level" type="Node" parent="Stats"] [node name="Level" type="Node" parent="Stats"]
script = ExtResource("6_sunc5") script = ExtResource("6_sunc5")
Value = null
[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")] [node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")]
script = ExtResource("5_rgckv") script = ExtResource("5_rgckv")
@ -623,7 +617,7 @@ Character = NodePath("../..")
[node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("AbilityState", "IdleState", "MaxLevelState", "Character")] [node name="Move" type="Node" parent="StateMachine" node_paths=PackedStringArray("AbilityState", "IdleState", "MaxLevelState", "Character")]
script = ExtResource("7_dfqd8") script = ExtResource("7_dfqd8")
AbilityState = NodePath("../Roll") AbilityState = NodePath("../Dash")
IdleState = NodePath("../Idle") IdleState = NodePath("../Idle")
MaxLevelState = NodePath("../MaxLevel") MaxLevelState = NodePath("../MaxLevel")
Character = NodePath("../..") Character = NodePath("../..")
@ -802,6 +796,7 @@ offset = Vector2(-9.5, -14)
hframes = 6 hframes = 6
[node name="CollisionShape2D" type="CollisionShape2D" parent="."] [node name="CollisionShape2D" type="CollisionShape2D" parent="."]
position = Vector2(0, -1)
shape = SubResource("RectangleShape2D_bfqew") shape = SubResource("RectangleShape2D_bfqew")
[node name="Debug" type="Control" parent="."] [node name="Debug" type="Control" parent="."]
@ -819,32 +814,18 @@ text = "lol"
label_settings = SubResource("LabelSettings_q5h1n") label_settings = SubResource("LabelSettings_q5h1n")
horizontal_alignment = 1 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 y_sort_enabled = true
position = Vector2(0, -2) position = Vector2(0, -2)
script = ExtResource("7_xyenu") script = ExtResource("7_xyenu")
Items = [] Hotbar = []
Items = [ExtResource("31_vr68e")]
InventoryMap = { InventoryMap = {
"equip_1": 0, "equip_1": 0,
"equip_2": 1, "equip_2": 1,
"equip_3": 2 "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"] [node name="RemoteTransform2D2" type="RemoteTransform2D" parent="Inventory"]
position = Vector2(0, 4) position = Vector2(0, 4)

View File

@ -27,6 +27,9 @@ public partial class EventBus : Node
[Signal] [Signal]
public delegate void PlayerLevelChangedEventHandler(int level); public delegate void PlayerLevelChangedEventHandler(int level);
[Signal]
public delegate void PlayerInventoryUpdateEventHandler(Items.Inventory inventory);
[Signal] [Signal]
public delegate void PlayerHealthChangedEventHandler(HealthChangedArgs args); public delegate void PlayerHealthChangedEventHandler(HealthChangedArgs args);

View File

@ -9,7 +9,10 @@ public partial class Inventory : Node2D
public Character Character { get; private set; } public Character Character { get; private set; }
[Export] [Export]
public Array<Item> Items { get; private set; } public Array<Item> Hotbar { get; private set; }
[Export]
public Array<ItemMetadata> Items { get; private set; }
[Export] [Export]
public Dictionary<string, int> InventoryMap { get; set; } public Dictionary<string, int> InventoryMap { get; set; }
@ -17,7 +20,7 @@ public partial class Inventory : Node2D
[Signal] [Signal]
public delegate void UsedItemEventHandler(Item item); public delegate void UsedItemEventHandler(Item item);
public const int MaxCapacity = 32; public const int MaxCapacity = 3;
private Item _selectedItem; private Item _selectedItem;
@ -53,12 +56,18 @@ public partial class Inventory : Node2D
public override void _Ready() public override void _Ready()
{ {
if (Items is null) if (Hotbar is null)
{ {
// instantiating a new array will prevent characters from // instantiating a new array will prevent characters from
// sharing inventories // sharing inventories
Items = new Array<Item>(); Hotbar = new();
} }
if (Items is null)
{
Items = new();
}
Character = GetParent<Character>(); Character = GetParent<Character>();
foreach (Node child in GetChildren()) foreach (Node child in GetChildren())
{ {
@ -67,14 +76,17 @@ public partial class Inventory : Node2D
AddItem(item); AddItem(item);
} }
} }
Events.EventBus.Instance.EmitSignal(
Events.EventBus.SignalName.PlayerInventoryUpdate, this);
base._Ready(); base._Ready();
} }
public bool EquipIndex(int index) 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); return EquipItem(null, ref _selectedItem);
@ -84,7 +96,7 @@ public partial class Inventory : Node2D
{ {
if (item is not null) if (item is not null)
{ {
if (!Items.Contains(item)) if (!Hotbar.Contains(item))
{ {
GD.PrintErr("Tried to equip an item not in the inventory."); GD.PrintErr("Tried to equip an item not in the inventory.");
return false; return false;
@ -103,6 +115,9 @@ public partial class Inventory : Node2D
item.Equip(Character); item.Equip(Character);
} }
Events.EventBus.Instance.EmitSignal(
Events.EventBus.SignalName.PlayerInventoryUpdate, this);
return true; return true;
} }
@ -111,27 +126,36 @@ public partial class Inventory : Node2D
if (InventoryMap.ContainsKey(keymap)) if (InventoryMap.ContainsKey(keymap))
{ {
int idx = InventoryMap[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"); else GD.Print(keymap + " does not exist");
return null; 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) public Item AddItem(Item item)
{ {
if (Items.Count >= MaxCapacity) if (Hotbar.Count >= MaxCapacity)
{ {
return null; return null;
} }
item.CharacterOwner = Character; item.CharacterOwner = Character;
item.Visible = false; item.Visible = false;
if (!Items.Contains(item)) if (!Hotbar.Contains(item))
{ {
Items.Add(item); Hotbar.Add(item);
} }
return item; return item;
} }

View File

@ -8,6 +8,9 @@ public partial class ItemMetadata : Resource
[Export] [Export]
public Utils.ScenePath Instance { get; set; } public Utils.ScenePath Instance { get; set; }
[Export]
public Texture2D Icon { get; set; }
[Export] [Export]
public string Name { get; set; } public string Name { get; set; }

View File

@ -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://Utils/ScenePath.cs" id="1_mh5y8"]
[ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_asbkr"] [ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_asbkr"]
@ -10,5 +11,6 @@ Path = "res://Items/Weapons/Sword.tscn"
[resource] [resource]
script = ExtResource("2_asbkr") script = ExtResource("2_asbkr")
Instance = SubResource("Resource_qqiwa") Instance = SubResource("Resource_qqiwa")
Name = "Sword" Icon = ExtResource("1_j0i86")
Description = "A basic sword." Name = "The Fool's Sword"
Description = ""

View File

@ -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://Items/Weapons/Sword.cs" id="1_mlo73"]
[ext_resource type="Script" path="res://Utils/ScenePath.cs" id="2_g8hkw"] [ext_resource type="Resource" uid="uid://cl7jvdu2lnv2d" path="res://Items/Weapons/Sword.tres" id="2_atd4f"]
[ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_gy7e7"]
[ext_resource type="Script" path="res://State/Weapon/WeaponStateMachine.cs" id="2_vwirq"] [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="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"] [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="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"] [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"] [sub_resource type="Environment" id="Environment_72txp"]
background_mode = 3 background_mode = 3
glow_enabled = true glow_enabled = true
@ -332,7 +321,7 @@ Knockback = 64.0
ShouldHideIdle = true ShouldHideIdle = true
PlayerLevelGain = 1.0 PlayerLevelGain = 1.0
HandAnchor = NodePath("Anchor/Node2D/Sprite2D/Hand") HandAnchor = NodePath("Anchor/Node2D/Sprite2D/Hand")
Metadata = SubResource("Resource_advps") Metadata = ExtResource("2_atd4f")
[node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")] [node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")]
script = ExtResource("2_vwirq") script = ExtResource("2_vwirq")
@ -382,7 +371,7 @@ position = Vector2(-2.52724e-05, 7)
rotation = 1.5708 rotation = 1.5708
[node name="ParryParticles" type="GPUParticles2D" parent="Anchor/Node2D/Sprite2D"] [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) position = Vector2(0, -3)
rotation = 0.785398 rotation = 0.785398
emitting = false emitting = false

File diff suppressed because one or more lines are too long

View File

@ -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="PackedScene" uid="uid://73jm5qjy52vq" path="res://Dialogue/balloon.tscn" id="1_atjb1"]
[ext_resource type="Script" path="res://UI/UIController.cs" id="2_b4b6l"] [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://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://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://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="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="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"] [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 offset_bottom = 64.0
grow_horizontal = 2 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 layout_mode = 2
size_flags_horizontal = 3
theme_override_constants/margin_left = 16 theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 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 layout_mode = 2
theme_override_constants/separation = 12 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 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 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 visible = false
layout_mode = 2 layout_mode = 2
theme_override_constants/margin_left = 16 theme_override_constants/margin_left = 16
theme_override_constants/margin_top = 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"] [node name="Bottom" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"]
layout_mode = 1 layout_mode = 1
anchors_preset = 12 anchors_preset = 12

26
UI/Hotbar.cs 100644
View File

@ -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);
}
}
}

25
UI/Hotbar.tscn 100644
View File

@ -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

View File

@ -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()
{
}
}

View File

@ -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")

19
addons/GodotAnnotate/.gitignore vendored 100644
View File

@ -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

19
addons/GodotAnnotate/LICENSE.md vendored 100644
View File

@ -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.

90
addons/GodotAnnotate/README.md vendored 100644
View File

@ -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>
[![Godot Assets](https://img.shields.io/badge/Godot_Asset_Library-blue)](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.
![Annotate Example](examples/Annotate.gif)
### Polygon Mode
Use polygon mode to draw straight lines between clicks.
![Polygon Mode Example](examples/AnnotatePoly.gif)
### Erase
Erase any previously drawn annotate strokes.
![Erase Example](examples/Erase.gif)
### Control Annotation Visibility
Only show annotations in the 2D editor (optionally show in run mode).
![Visibility Example](examples/Visibility.gif)
### Save Canvas As Image
Save the canvas to disk as an image file.
![Save To Disk Example](examples/SaveToDisk.gif)
## 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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 KiB

View File

@ -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"

View File

@ -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")

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -0,0 +1,8 @@
class_name AnnotateLayer
extends Resource
##
## Resource containing a series of [AnnotateStroke]s.
##
@export
var strokes: Array[AnnotateStroke] = []

View File

@ -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

View File

@ -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

View File

@ -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