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")
|
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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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
|
||||||
|
|
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="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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
[![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)
|
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
|