Avec une seule souris

Bonjour à tous, je m'appelle Vyacheslav et je suis programmeur, mais plus précisément maintenant, je suis engagé dans le développement de jeux sur GodotEngine, et en même temps je dirige ma chaîne de télégramme, dans laquelle j'écris des notes sur la création de mon propre jeu sur ce moteur et donner aux débutants du matériel pour étudier Godot.





Passons maintenant aux choses sérieuses, pourquoi ferions-nous un simple inventaire avec Drag & Drop et un bonus de ma part?





Commençons. Je ne suis pas designer, il y aura donc une version fonctionnelle, faites-le vous-même plus tard.





Tout d'abord, nous allons créer un projet et ajouter les nœuds nécessaires au travail dans la version minimale: 





Nous lançons le PanelContainer dans le contrôle, l'étirons via le bouton Disposition (Affichage) sur l'ensemble du contrôle et lançons immédiatement des indicateurs sur l'expansion en hauteur et en largeur:





Nous y jetons un GridContainer (grille) avec un enfant, nous y jetterons déjà nos éléments, et pour la commodité du débogage, nous ajouterons un bouton pour «ramasser» l'élément, cela générera un élément aléatoire avec un aléatoire numéro.





Nous aurons 8 colonnes dans l'inventaire et 4 lignes, pour la variété nécessaire, j'ai préparé des icônes d'objets.





Téléchargez la police de Google et déposez-la dans le contrôle afin que nous puissions modifier la taille de la police:





Ensuite, stylisons-le un peu pour qu'il ressemble plus à un inventaire, créez un emplacement et enregistrez-le dans un fichier séparé, car nous allons créer dynamiquement des slots:





Ensuite, nous lançons le script suivant dans la scène principale:





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)

      
      



Une option intermédiaire est quelque chose comme ceci:





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


      
      



En fait, il y a encore quelque chose à améliorer ici, il serait possible d'abandonner le tableau des slots et de tout faire via l'outil intégré à Godot, mais plus à ce sujet dans l'un des prochains articles.





Liste complète dans mon référentiel github





UPD: Correction de la fonction get_empty_slot



dans la dernière liste pour supprimer la possibilité d'entrer dans une boucle infinie. la gita est également mise à jour.





Également dans ma chaîne de télégramme, vous pouvez lire les articles précédents et être le premier à lire ce qui suit - https://t.me/holydevlog








All Articles