Com apenas um mouse

Olá a todos, meu nome é Vyacheslav e sou um programador, mas especificamente agora estou engajado no desenvolvimento de jogos no GodotEngine, e ao mesmo tempo dirijo meu canal de telegrama, no qual escrevo notas sobre como criar meu próprio jogo neste motor e dar aos novatos material para estudar Godot.





Agora vamos ao que interessa, por que faríamos um inventário simples com Drag & Drop e um bônus meu?





Vamos começar. Não sou designer, então haverá uma versão funcional, faça você mesmo depois.





Primeiro, vamos criar um projeto e adicionar os nós necessários para trabalhar na versão mínima: 





Colocamos o PanelContainer no controle, estendemos-o através do botão Layout (Exibir) sobre todo o controle e imediatamente lançamos sinalizadores na expansão em altura e largura:





Jogamos um GridContainer (grade) nele com um filho, já vamos jogar nossos elementos nele, e para a conveniência da depuração, adicionaremos um botão para "pegar" o item, ele irá gerar um elemento aleatório com um elemento aleatório número.





Teremos 8 colunas no inventário e 4 linhas, para a variedade necessária preparei ícones de itens.





Baixe a fonte do Google e coloque-a no controle para que possamos alterar o tamanho da fonte:





A seguir, vamos estilizá-lo um pouco para que se pareça mais com um inventário, criar um slot e salvá-lo em um arquivo separado, uma vez que vamos criar slots dinamicamente:





A seguir, lançamos o seguinte script na cena principal:





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)

      
      



Uma opção intermediária é algo assim:





, , , 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 #  


      
      



Na verdade, ainda há algo a melhorar aqui, seria possível abandonar a matriz de slots e fazer tudo através da ferramenta embutida em Godot, mas mais sobre isso em um dos próximos artigos.





Lista completa no meu repositório github





UPD: corrigida a função get_empty_slot



na última listagem para remover a possibilidade de entrar em um loop infinito. o gita também é atualizado.





Também no meu canal de telegramas você pode ler os artigos anteriores, e ser o primeiro a ler o seguinte - https://t.me/holydevlog








All Articles