Hello everyone, my name is Vyacheslav and I am a programmer, but specifically now I am engaged in game development on GodotEngine, and at the same time I run my telegram channel in which I write notes on creating my game on this engine and give newbies material for studying Godot.
Now let's get down to business, why would we make a simple inventory with Drag & Drop and a bonus from me?
Let's start. I am not a designer, so there will be a functional version, do it yourself later.
First, we will create a project and add the nodes necessary for work in the minimum version:
We throw the PanelContainer into the control, stretch it through the Layout button (View) over the entire control and immediately throw flags on the expansion in height and width:
We throw a GridContainer (grid) into it with a child, we will already throw our elements into it, and for the convenience of debugging we will add a button to βpick upβ the item, it will generate a random element with a random number.
We will have 8 columns in the inventory and 4 lines, for the necessary variety I have prepared item icons.
Download the font from Google and drop it into the control so that we can change the font size:
Next, let's style it a little so that it looks more like an inventory, create one slot, and save it to a separate file, since we will dynamically create slots:
Next, we throw the following script into the main scene:
extends Control
export (int, 1, 20) var columns = 8
export (int, 1, 20) var rows = 4
onready var inv = $InvContainer/InvContent
const slot_scene = preload("res://Slot.tscn")
func _ready():
inv.columns = columns
for i in range(columns*rows):
var slot = slot_scene.instance()
inv.add_child(slot)
An intermediate option is something like this:
, , , TextureRect Label - :
, , , , :
:
Slot , :
extends PanelContainer
onready var item = $Item
onready var icon = $Item/Icon
onready var count = $Item/Count
var item_type = null
var item_count = 0
func _ready():
update_data({"type": "item_type_1", "count": 0})
func update_data(data = null):
item.visible = data != null
if data:
icon.texture = load("res://graphics/%s.png" % data.type) #
count.text = str(data.count)
:
:
:
extends Control
export (int, 1, 20) var columns = 8
export (int, 1, 20) var rows = 4
onready var inv = $InvContainer/InvContent
const slot_scene = preload("res://Slot.tscn")
func _ready():
$InvContainer/HBoxContainer/Clear.connect("pressed", self, "clear_inventory")
inv.columns = columns
for i in range(columns*rows):
var slot = slot_scene.instance()
inv.add_child(slot)
func clear_inventory():
for child in inv.get_children(): #
child.update_data() #
, .
.
:
extends PanelContainer
onready var item = $Item
onready var icon = $Item/Icon
onready var count = $Item/Count
var item_data = null
func _ready():
update_data()
func empty():
return item_data == null
func update_data(data = null):
item.visible = data != null
item_data = data
if item:
icon.texture = load("res://graphics/%s.png" % item_data.type) #
count.text = str(item_data.count)
return true
:
func has_empty_slot(): #
for child in inv.get_children(): #
if child.empty():
return true
return false
func get_empty_slot(): #
var slot = null
if has_empty_slot():
# ,
#
while slot == null: # ,
var temp_slot = inv.get_child(rng.randi_range(0, columns*rows-1))
if temp_slot.empty():
slot = temp_slot
break
return slot
func add_item(): # ,
var slot = get_empty_slot()
if slot:
var data = {"type":"", "count": 0}
data.type = "item_type_" + str(rng.randi_range(1, 8))
data.count = rng.randi_range(1, 999)
slot.update_data(data)
βadd_itemβ, .
D&D(Drag&Drop).
, , .. .
:
, , .
extends PanelContainer
onready var icon = $Icon
onready var count = $Count
const path_to_items_icons = "res://graphics/%s.png"
func set_data(item_data):
icon.texture = load(path_to_items_icons % item_data.type) #
count.text = str(item_data.count)
:
, βNumβ, , , . , :
, ( ), )
, , , :
extends Control
export (int, 1, 20) var columns = 8 #-
export (int, 1, 20) var rows = 4 #-
const slot_scene = preload("res://Slot.tscn") #
onready var inv = $InvContainer/InvContent #
onready var titem = $TempItem # ,
onready var rng = RandomNumberGenerator.new() #
onready var item_dragging = null #
onready var prev_slot = null #
func ready():
titem.visible = false #
rng.randomize() #
$InvContainer/HBoxContainer/Clear.connect("pressed", self, "clear_inventory")
$InvContainer/HBoxContainer/Add.connect("pressed", self, "add_item")
inv.columns = columns # -
for i in range(columns*rows): #
var slot = slot_scene.instance() #
slot.name = "Slot%d" % i # , ,
slot.get_node("Num").text = str(i) # ,
,
inv.add_child(slot) #
func clear_inventory(): #
for child in inv.get_children(): #
child.update_data() #
func has_empty_slot(): #
for child in inv.get_children(): #
if child.empty():
return true
return false
func get_empty_slot(): #
var slot = null
if has_empty_slot():
# ,
#
while slot == null: # ,
var temp_slot = inv.get_child(rng.randi_range(0, columns*rows-1))
if temp_slot.empty():
slot = temp_slot
break
return slot
func add_item(): # ,
var slot = get_empty_slot()
if slot:
var data = {"type":"", "count": 0}
data.type = "item_type_" + str(rng.randi_range(1, 8))
data.count = rng.randi_range(1, 999)
slot.update_data(data)
func find_slot(pos:Vector2, need_data = false): #
# - ,
for c in inv.get_children(): #
if (need_data and not c.empty()) or (not need_data):
if Rect2(c.rect_position, c.rect_size).has_point(pos):
# ,
#
return c
return null
func _process(delta):
var mouse_pos = get_viewport().get_mouse_position() #
if Input.get_mouse_button_mask() == BUTTON_LEFT: #
if not item_dragging: #
var slot = find_slot(mouse_pos, true)#
if slot: #
item_dragging = slot.item_data #
titem.set_data(item_dragging) #
titem.visible = true #
titem.rect_position = slot.rect_position #
prev_slot = slot #
slot.update_data() #
else: # , , ( )
titem.rect_position = lerp(titem.rect_position, mouse_pos - titem.rect_size/2, 0.3)
else: #
if item_dragging: #
var slot = find_slot(mouse_pos, false) #
if slot: # ,
if not slot.update_data(item_dragging): # ,
prev_slot.update_data(item_dragging)
prev_slot = null #
item_dragging = null #
titem.visible = false #
, :
? , )
func check_data(data):
return "all" in available_types or data.type in available_types
func update_data(data = null):
item.visible = data != null
item_data = data
if item_data:
if check_data(data):
item.set_data(item_data)
return true
return false
return true
, _process:
func _process(delta):
var mouse_pos = get_viewport().get_mouse_position() #
if Input.get_mouse_button_mask() == BUTTON_LEFT: #
if not item_dragging: #
var slot = find_slot(mouse_pos, true)#
if slot: #
item_dragging = slot.item_data #
titem.set_data(item_dragging) #
titem.visible = true #
titem.rect_position = slot.rect_position #
prev_slot = slot #
slot.update_data() #
else: # , , ( )
titem.rect_position = lerp(titem.rect_position, mouse_pos - titem.rect_size/2, 0.3)
else: #
if item_dragging: #
var slot = find_slot(mouse_pos) #
# β1
#if slot: # ,
#if slot.empty(): #
#if slot.check_data(item_dragging): # ,
#slot.update_data(item_dragging)
#else: # ,
#prev_slot.update_data(item_dragging)
#else: # , ,
#if slot.check_data(item_dragging) and prev_slot.check_data(slot.item_data):
#prev_slot.update_data(slot.item_data)
#slot.update_data(item_dragging)
#else: # ,
#prev_slot.update_data(item_dragging)
# β2
if slot: #
if slot.check_data(item_dragging): # ,
if slot.empty(): #
slot.update_data(item_dragging)
else: # ,
if prev_slot.check_data(slot.item_data): # ,
prev_slot.update_data(slot.item_data)
slot.update_data(item_dragging)
else:
prev_slot.update_data(item_dragging)
prev_slot = null #
item_dragging = null #
titem.visible = false #
, , , , , , , , , , , , , 100 , , , , , )
, , -, , - , , .
:
extends PanelContainer
signal dropped(data)
export (Array) var available_types = ["all"]
#
enum Actions {NONE, TRASH} #
var cur_act = Actions.NONE #
onready var item = $Item
var item_data = null #
func _ready():
update_data()
func set_action(new_value):
cur_act = new_value
$Item.visible = false
$Trash.visible = false
match cur_act:
Actions.NONE:
$Item.visible = true
Actions.TRASH:
$Trash.visible = true
func empty():
return item_data == null
func check_data(data):
if cur_act:
return true
return "all" in available_types or data.type in available_types
func update_data(data = null):
if data and cur_act:
emit_signal("dropped", data)
return true
item.visible = data != null
item_data = data
if item_data:
if check_data(data):
item.set_data(item_data)
return true
return false
return true
:
func ready():
titem.visible = false #
rng.randomize() #
$InvContainer/HBoxContainer/Clear.connect("pressed", self, "clear_inventory")
$InvContainer/HBoxContainer/Add.connect("pressed", self, "add_item")
inv.columns = columns # -
for i in range(columns*rows): #
var slot = slot_scene.instance() #
slot.name = "Slot%d" % i # , ,
slot.get_node("Num").text = str(i) # , ,
slot.set_action(slot.Actions.NONE)
if i == columns*rows-1:
slot.set_action(slot.Actions.TRASH)
slot.connect("dropped", self, "trash_dropped")
inv.add_child(slot) #
func trash_dropped(data):
print("dropped ", data)
_ready, , .
, .
:
Helmet , , .
:
extends PanelContainer
signal dropped(path, data) #
signal accepted(path, data) #
export (Array) var available_types = ["all"]
#
enum Actions {NONE, TRASH} #
var cur_act = Actions.NONE #
onready var item = $Item
var item_data = null #
func _ready():
set_action()
update_data()
func set_action(new_value = Actions.NONE):
cur_act = new_value
$Item.visible = false
$Trash.visible = false
$Num.visible = false
match cur_act:
Actions.NONE:
$Item.visible = true
$Num.visible = true
Actions.TRASH:
$Trash.visible = true
func empty():
return item_data == null
func check_data(data):
if cur_act:
return true
return "all" in available_types or data.type in available_types
func update_data(data = null):
if data and cur_act:
emit_signal("dropped", get_path(), data)
return true
item.visible = data != null
item_data = data
if item_data:
if check_data(data):
item.set_data(item_data)
emit_signal("accepted", get_path(), data)
return true
return false
return true
, :
extends Control
export (int, 1, 20) var columns = 8 #-
export (int, 1, 20) var rows = 4 #-
export (Array, NodePath) var slots_containers #
onready var slots = [] #
const slot_scene = preload("res://scenes/Slot.tscn") #
onready var inv = $PlayerInv/Inv/InvContent #
onready var titem = $TempItem # ,
onready var clearButton = $PlayerInv/Inv/Button/Clear
onready var addButton = $PlayerInv/Inv/Button/Add
onready var rng = RandomNumberGenerator.new() #
onready var item_dragging = null #
onready var prev_slot = null #
func ready():
titem.visible = false #
rng.randomize() #
clearButton.connect("pressed", self, "clear_inventory")
addButton.connect("pressed", self, "add_item")
inv.columns = columns # -
for i in range(columns*rows): #
var slot = slot_scene.instance() #
slot.name = "Slot%d" % i # , ,
slot.get_node("Num").text = str(i) # , ,
inv.add_child(slot) #
if i == columns*rows-1:
slot.set_action(slot.Actions.TRASH)
slots.push_back(slot)
for slots_node in slots_containers: #
for slot in get_node(slots_node).get_children():
slots.push_back(slot)
for slot in slots:
slot.connect("accepted", self, "slot_accepted")
slot.connect("dropped", self, "trash_dropped")
func slot_accepted(path, data):
print("accepted ", path, " ", data)
func trash_dropped(path, data):
print("dropped ", path, " ", data)
func clear_inventory(): #
for child in slots: #
child.update_data() #
func has_empty_slot(): #
for child in slots: #
if child.empty() and child.cur_act != child.Actions.TRASH:
return true
return false
func get_empty_slot(): #
var rand_slot = null
if has_empty_slot():
var empty_slots = [] #
for slot in slots: #
if slot.empty() and slot.cur_act != slot.Actions.TRASH:
empty_slots.push_back(slot)
rand_slot = empty_slots[(rng.randi_range(0, empty_slots.size()-1))] #
return rand_slot
func add_item(): # ,
var slot = get_empty_slot()
if slot:
var data = {"type":"", "count": 0}
data.type = "item_type_" + str(rng.randi_range(1, 8))
data.count = rng.randi_range(1, 999)
slot.update_data(data)
func find_slot(pos:Vector2, need_data = false): #
# - ,
for c in slots: #
if (need_data and not c.empty()) or (not need_data):
if c.get_global_rect().has_point(pos):
# ,
#
return c
return null
func _process(delta):
var mouse_pos = get_viewport().get_mouse_position() #
if Input.get_mouse_button_mask() == BUTTON_LEFT: #
if not item_dragging: #
var slot = find_slot(mouse_pos, true)#
if slot: #
item_dragging = slot.item_data #
titem.set_data(item_dragging) #
titem.visible = true #
titem.rect_position = slot.get_global_rect().position #
prev_slot = slot #
slot.update_data() #
else: # , , ( )
titem.rect_position = lerp(titem.rect_position, mouse_pos - titem.rect_size/2, 0.3)
else: #
if item_dragging: #
var slot = find_slot(mouse_pos) #
if slot: #
if slot.check_data(item_dragging): # ,
if slot.empty(): #
slot.update_data(item_dragging)
else: # ,
if prev_slot.check_data(slot.item_data): # ,
prev_slot.update_data(slot.item_data)
slot.update_data(item_dragging)
else:
prev_slot.update_data(item_dragging)
prev_slot = null #
item_dragging = null #
In fact, there is still something to improve here, it would be possible to abandon the array of slots and do everything through the built-in tool in Godot, but more on that in one of the next articles.
Full listing in my github repository
UPD: Corrected the function get_empty_slot
in the last listing to remove the possibility of getting into an infinite loop. the gita is also updated.
Also in my telegram channel you can read the previous articles, and be the first to read the following - https://t.me/holydevlog