Realizarea inventarului și a unui sistem de fabricare a articolelor în Unity
În acest tutorial, voi arăta cum să faci un inventar în stil Minecraft și un sistem de fabricare a articolelor în Unity.
Crearea articolelor în jocurile video este un proces de combinare a unor articole specifice (de obicei mai simple) în articole mai complexe, cu proprietăți noi și îmbunătățite. De exemplu, combinând lemnul și piatra într-un târnăcop sau combinarea foii de metal și lemnul într-o sabie.
Sistemul de creație de mai jos este compatibil cu dispozitivele mobile și complet automatizat, ceea ce înseamnă că va funcționa cu orice aspect UI și cu capacitatea de a crea rețete personalizate de crafting.
Pasul 1: Configurați interfața de utilizare Crafting
Începem prin a configura interfața de utilizare de crafting:
- Creați o pânză nouă (Unity Top Taskbar: GameObject -> UI -> Canvas)
- Creați o imagine nouă făcând clic dreapta pe Obiect Canvas -> UI -> Imagine
- Redenumiți obiectul imagine în "CraftingPanel" și schimbați imaginea sursă la implicit "UISprite"
- Schimbați valorile "CraftingPanel" RectTransform în (Poz X: 0 Poz Y: 0 Lățime: 410 Înălțime: 365)
- Creați două obiecte în interiorul "CraftingPanel" (dați clic dreapta pe CraftingPanel -> Create Empty, de 2 ori)
- Redenumiți primul obiect în "CraftingSlots" și modificați valorile lui RectTransform în ("Aliniere sus stânga" Pivot X: 0 Pivot Y: 1 Poz X: 50 Poz Y: -35 Lățime: 140 Înălțime: 140). Acest obiect va conține sloturi de crafting.
- Redenumiți cel de-al doilea obiect în "PlayerSlots" și modificați valorile lui RectTransform în („Întindere superioară orizontală” Pivot X: 0,5 Pivot Y: 1 Stânga: 0 Poz Y: -222 Dreapta: 0 Înălțime: 100). Acest obiect va conține sloturi pentru jucători.
Titlul secțiunii:
- Creați text nou făcând clic dreapta pe "PlayerSlots" Obiect -> UI -> Text și redenumiți-l în "SectionTitle"
- Schimbați valorile "SectionTitle" RectTransform la ("Aliniere sus stânga" Pivot X: 0 Pivot Y: 0 Pos X: 5 Pos Y: 0 Lățime: 160 Înălțime: 30)
- Schimbați textul "SectionTitle" la "Inventory" și setați-i Dimensiunea fontului la 18, Alinierea la mijlocul stânga și Culoare la (0.2, 0.2, 0.2, 1)
- Duplicați obiectul "SectionTitle", schimbați-i Textul în "Crafting" și mutați-l sub obiectul "CraftingSlots", apoi setați aceleași valori RectTransform ca și precedentul "SectionTitle".
Slot de creație:
Slotul de crafting va consta dintr-o imagine de fundal, o imagine de articol și un text de numărare:
- Creați o imagine nouă făcând clic dreapta pe Obiect Canvas -> UI -> Imagine
- Redenumiți noua imagine la "slot_template", setați-i valorile RectTransform la (Post X: 0 Pos Y: 0 Width: 40 Height: 40) și schimbați-i culoarea la (0.32, 0.32, 0.32, 0.8)
- Duplicați "slot_template" și redenumiți-l în "Item", mutați-l în interiorul obiectului "slot_template", modificați dimensiunile RectTransform la (Lățime: 30 Înălțime: 30) și Culoare la (1, 1, 1, 1)
- Creați text nou făcând clic dreapta pe "slot_template" Obiect -> UI -> Text și redenumiți-l în "Count"
- Schimbați valorile "Count" RectTransform la ("Aliniere în dreapta jos" Pivot X: 1 Pivot Y: 0 Pos X: 0 Pos Y: 0 Lățime: 30 Înălțime: 30)
- Setați "Count" Textul la un număr aleatoriu (de ex. 12), Stilul fontului la Bold, Dimensiunea fontului la 14, Alinierea la dreapta jos și Culoare la (1, 1, 1, 1)
- Adăugați componenta Shadow la "Count" Text și setați culoarea efectului la (0, 0, 0, 0,5)
Rezultatul final ar trebui să arate astfel:
Slot pentru rezultate (care va fi folosit pentru crearea rezultatelor):
- Duplicați obiectul "slot_template" și redenumiți-l în "result_slot_template"
- Schimbați lățimea și înălțimea "result_slot_template" la 50
Buton de creare și grafică suplimentară:
- Creați un buton nou făcând clic dreapta pe "CraftingSlots" Object -> UI -> Button și redenumiți-l în "CraftButton"
- Setați valorile "CraftButton" RectTransform la ("Aliniere la mijloc stânga" Pivot X: 1 Pivot Y: 0,5 Pos X: 0 Pos Y: 0 Lățime: 40 Înălțime: 40)
- Modificați textul "CraftButton" la "Craft"
- Creați o nouă imagine făcând clic dreapta pe "CraftingSlots" Obiect -> UI -> Imagine și redenumiți-o în "Arrow"
- Setați valorile "Arrow" RectTransform la ("Aliniere la mijloc dreapta" Pivot X: 0 Pivot Y: 0,5 Poz X: 10 Poz Y: 0 Lățime: 30 Înălțime: 30)
Pentru imaginea sursă, puteți utiliza imaginea de mai jos (clic dreapta -> Salvare ca... pentru a o descărca). După importare, setați tipul de textură la "Sprite (2D and UI)" și modul de filtrare la "Point (no filter)"
- Faceți clic dreapta pe "CraftingSlots" -> Create Empty și redenumiți-l în "ResultSlot", acest obiect va conține slotul rezultat
- Setați valorile "ResultSlot" RectTransform la ("Aliniere la mijloc dreapta" Pivot X: 0 Pivot Y: 0,5 Poz X: 50 Poz Y: 0 Lățime: 50 Înălțime: 50)
Configurarea UI este gata.
Pasul 2: Program Crafting System
Acest sistem de creație va consta din 2 scripturi, SC_ItemCrafting.cs și SC_SlotTemplate.cs
- Creați un script nou, denumiți-l "SC_ItemCrafting" apoi inserați codul de mai jos în el:
SC_ItemCrafting.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SC_ItemCrafting : MonoBehaviour
{
public RectTransform playerSlotsContainer;
public RectTransform craftingSlotsContainer;
public RectTransform resultSlotContainer;
public Button craftButton;
public SC_SlotTemplate slotTemplate;
public SC_SlotTemplate resultSlotTemplate;
[System.Serializable]
public class SlotContainer
{
public Sprite itemSprite; //Sprite of the assigned item (Must be the same sprite as in items array), or leave null for no item
public int itemCount; //How many items in this slot, everything equal or under 1 will be interpreted as 1 item
[HideInInspector]
public int tableID;
[HideInInspector]
public SC_SlotTemplate slot;
}
[System.Serializable]
public class Item
{
public Sprite itemSprite;
public bool stackable = false; //Can this item be combined (stacked) together?
public string craftRecipe; //Item Keys that are required to craft this item, separated by comma (Tip: Use Craft Button in Play mode and see console for printed recipe)
}
public SlotContainer[] playerSlots;
SlotContainer[] craftSlots = new SlotContainer[9];
SlotContainer resultSlot = new SlotContainer();
//List of all available items
public Item[] items;
SlotContainer selectedItemSlot = null;
int craftTableID = -1; //ID of table where items will be placed one at a time (ex. Craft table)
int resultTableID = -1; //ID of table from where we can take items, but cannot place to
ColorBlock defaultButtonColors;
// Start is called before the first frame update
void Start()
{
//Setup slot element template
slotTemplate.container.rectTransform.pivot = new Vector2(0, 1);
slotTemplate.container.rectTransform.anchorMax = slotTemplate.container.rectTransform.anchorMin = new Vector2(0, 1);
slotTemplate.craftingController = this;
slotTemplate.gameObject.SetActive(false);
//Setup result slot element template
resultSlotTemplate.container.rectTransform.pivot = new Vector2(0, 1);
resultSlotTemplate.container.rectTransform.anchorMax = resultSlotTemplate.container.rectTransform.anchorMin = new Vector2(0, 1);
resultSlotTemplate.craftingController = this;
resultSlotTemplate.gameObject.SetActive(false);
//Attach click event to craft button
craftButton.onClick.AddListener(PerformCrafting);
//Save craft button default colors
defaultButtonColors = craftButton.colors;
//InitializeItem Crafting Slots
InitializeSlotTable(craftingSlotsContainer, slotTemplate, craftSlots, 5, 0);
UpdateItems(craftSlots);
craftTableID = 0;
//InitializeItem Player Slots
InitializeSlotTable(playerSlotsContainer, slotTemplate, playerSlots, 5, 1);
UpdateItems(playerSlots);
//InitializeItemResult Slot
InitializeSlotTable(resultSlotContainer, resultSlotTemplate, new SlotContainer[] { resultSlot }, 0, 2);
UpdateItems(new SlotContainer[] { resultSlot });
resultTableID = 2;
//Reset Slot element template (To be used later for hovering element)
slotTemplate.container.rectTransform.pivot = new Vector2(0.5f, 0.5f);
slotTemplate.container.raycastTarget = slotTemplate.item.raycastTarget = slotTemplate.count.raycastTarget = false;
}
void InitializeSlotTable(RectTransform container, SC_SlotTemplate slotTemplateTmp, SlotContainer[] slots, int margin, int tableIDTmp)
{
int resetIndex = 0;
int rowTmp = 0;
for (int i = 0; i < slots.Length; i++)
{
if (slots[i] == null)
{
slots[i] = new SlotContainer();
}
GameObject newSlot = Instantiate(slotTemplateTmp.gameObject, container.transform);
slots[i].slot = newSlot.GetComponent<SC_SlotTemplate>();
slots[i].slot.gameObject.SetActive(true);
slots[i].tableID = tableIDTmp;
float xTmp = (int)((margin + slots[i].slot.container.rectTransform.sizeDelta.x) * (i - resetIndex));
if (xTmp + slots[i].slot.container.rectTransform.sizeDelta.x + margin > container.rect.width)
{
resetIndex = i;
rowTmp++;
xTmp = 0;
}
slots[i].slot.container.rectTransform.anchoredPosition = new Vector2(margin + xTmp, -margin - ((margin + slots[i].slot.container.rectTransform.sizeDelta.y) * rowTmp));
}
}
//Update Table UI
void UpdateItems(SlotContainer[] slots)
{
for (int i = 0; i < slots.Length; i++)
{
Item slotItem = FindItem(slots[i].itemSprite);
if (slotItem != null)
{
if (!slotItem.stackable)
{
slots[i].itemCount = 1;
}
//Apply total item count
if (slots[i].itemCount > 1)
{
slots[i].slot.count.enabled = true;
slots[i].slot.count.text = slots[i].itemCount.ToString();
}
else
{
slots[i].slot.count.enabled = false;
}
//Apply item icon
slots[i].slot.item.enabled = true;
slots[i].slot.item.sprite = slotItem.itemSprite;
}
else
{
slots[i].slot.count.enabled = false;
slots[i].slot.item.enabled = false;
}
}
}
//Find Item from the items list using sprite as reference
Item FindItem(Sprite sprite)
{
if (!sprite)
return null;
for (int i = 0; i < items.Length; i++)
{
if (items[i].itemSprite == sprite)
{
return items[i];
}
}
return null;
}
//Find Item from the items list using recipe as reference
Item FindItem(string recipe)
{
if (recipe == "")
return null;
for (int i = 0; i < items.Length; i++)
{
if (items[i].craftRecipe == recipe)
{
return items[i];
}
}
return null;
}
//Called from SC_SlotTemplate.cs
public void ClickEventRecheck()
{
if (selectedItemSlot == null)
{
//Get clicked slot
selectedItemSlot = GetClickedSlot();
if (selectedItemSlot != null)
{
if (selectedItemSlot.itemSprite != null)
{
selectedItemSlot.slot.count.color = selectedItemSlot.slot.item.color = new Color(1, 1, 1, 0.5f);
}
else
{
selectedItemSlot = null;
}
}
}
else
{
SlotContainer newClickedSlot = GetClickedSlot();
if (newClickedSlot != null)
{
bool swapPositions = false;
bool releaseClick = true;
if (newClickedSlot != selectedItemSlot)
{
//We clicked on the same table but different slots
if (newClickedSlot.tableID == selectedItemSlot.tableID)
{
//Check if new clicked item is the same, then stack, if not, swap (Unless it's a crafting table, then do nothing)
if (newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
{
Item slotItem = FindItem(selectedItemSlot.itemSprite);
if (slotItem.stackable)
{
//Item is the same and is stackable, remove item from previous position and add its count to a new position
selectedItemSlot.itemSprite = null;
newClickedSlot.itemCount += selectedItemSlot.itemCount;
selectedItemSlot.itemCount = 0;
}
else
{
swapPositions = true;
}
}
else
{
swapPositions = true;
}
}
else
{
//Moving to different table
if (resultTableID != newClickedSlot.tableID)
{
if (craftTableID != newClickedSlot.tableID)
{
if (newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
{
Item slotItem = FindItem(selectedItemSlot.itemSprite);
if (slotItem.stackable)
{
//Item is the same and is stackable, remove item from previous position and add its count to a new position
selectedItemSlot.itemSprite = null;
newClickedSlot.itemCount += selectedItemSlot.itemCount;
selectedItemSlot.itemCount = 0;
}
else
{
swapPositions = true;
}
}
else
{
swapPositions = true;
}
}
else
{
if (newClickedSlot.itemSprite == null || newClickedSlot.itemSprite == selectedItemSlot.itemSprite)
{
//Add 1 item from selectedItemSlot
newClickedSlot.itemSprite = selectedItemSlot.itemSprite;
newClickedSlot.itemCount++;
selectedItemSlot.itemCount--;
if (selectedItemSlot.itemCount <= 0)
{
//We placed the last item
selectedItemSlot.itemSprite = null;
}
else
{
releaseClick = false;
}
}
else
{
swapPositions = true;
}
}
}
}
}
if (swapPositions)
{
//Swap items
Sprite previousItemSprite = selectedItemSlot.itemSprite;
int previousItemConunt = selectedItemSlot.itemCount;
selectedItemSlot.itemSprite = newClickedSlot.itemSprite;
selectedItemSlot.itemCount = newClickedSlot.itemCount;
newClickedSlot.itemSprite = previousItemSprite;
newClickedSlot.itemCount = previousItemConunt;
}
if (releaseClick)
{
//Release click
selectedItemSlot.slot.count.color = selectedItemSlot.slot.item.color = Color.white;
selectedItemSlot = null;
}
//Update UI
UpdateItems(playerSlots);
UpdateItems(craftSlots);
UpdateItems(new SlotContainer[] { resultSlot });
}
}
}
SlotContainer GetClickedSlot()
{
for (int i = 0; i < playerSlots.Length; i++)
{
if (playerSlots[i].slot.hasClicked)
{
playerSlots[i].slot.hasClicked = false;
return playerSlots[i];
}
}
for (int i = 0; i < craftSlots.Length; i++)
{
if (craftSlots[i].slot.hasClicked)
{
craftSlots[i].slot.hasClicked = false;
return craftSlots[i];
}
}
if (resultSlot.slot.hasClicked)
{
resultSlot.slot.hasClicked = false;
return resultSlot;
}
return null;
}
void PerformCrafting()
{
string[] combinedItemRecipe = new string[craftSlots.Length];
craftButton.colors = defaultButtonColors;
for (int i = 0; i < craftSlots.Length; i++)
{
Item slotItem = FindItem(craftSlots[i].itemSprite);
if (slotItem != null)
{
combinedItemRecipe[i] = slotItem.itemSprite.name + (craftSlots[i].itemCount > 1 ? "(" + craftSlots[i].itemCount + ")" : "");
}
else
{
combinedItemRecipe[i] = "";
}
}
string combinedRecipe = string.Join(",", combinedItemRecipe);
print(combinedRecipe);
//Search if recipe match any of the item recipe
Item craftedItem = FindItem(combinedRecipe);
if (craftedItem != null)
{
//Clear Craft slots
for (int i = 0; i < craftSlots.Length; i++)
{
craftSlots[i].itemSprite = null;
craftSlots[i].itemCount = 0;
}
resultSlot.itemSprite = craftedItem.itemSprite;
resultSlot.itemCount = 1;
UpdateItems(craftSlots);
UpdateItems(new SlotContainer[] { resultSlot });
}
else
{
ColorBlock colors = craftButton.colors;
colors.selectedColor = colors.pressedColor = new Color(0.8f, 0.55f, 0.55f, 1);
craftButton.colors = colors;
}
}
// Update is called once per frame
void Update()
{
//Slot UI follow mouse position
if (selectedItemSlot != null)
{
if (!slotTemplate.gameObject.activeSelf)
{
slotTemplate.gameObject.SetActive(true);
slotTemplate.container.enabled = false;
//Copy selected item values to slot template
slotTemplate.count.color = selectedItemSlot.slot.count.color;
slotTemplate.item.sprite = selectedItemSlot.slot.item.sprite;
slotTemplate.item.color = selectedItemSlot.slot.item.color;
}
//Make template slot follow mouse position
slotTemplate.container.rectTransform.position = Input.mousePosition;
//Update item count
slotTemplate.count.text = selectedItemSlot.slot.count.text;
slotTemplate.count.enabled = selectedItemSlot.slot.count.enabled;
}
else
{
if (slotTemplate.gameObject.activeSelf)
{
slotTemplate.gameObject.SetActive(false);
}
}
}
}
- Creați un nou script, numiți-l "SC_SlotTemplate" apoi inserați codul de mai jos în interiorul acestuia:
SC_SlotTemplate.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class SC_SlotTemplate : MonoBehaviour, IPointerClickHandler
{
public Image container;
public Image item;
public Text count;
[HideInInspector]
public bool hasClicked = false;
[HideInInspector]
public SC_ItemCrafting craftingController;
//Do this when the mouse is clicked over the selectable object this script is attached to.
public void OnPointerClick(PointerEventData eventData)
{
hasClicked = true;
craftingController.ClickEventRecheck();
}
}
Pregătirea șabloanelor de slot:
- Atașați scriptul SC_SlotTemplate la obiectul "slot_template" și atribuiți variabilele acestuia (componenta imagine de pe același obiect merge la variabila "Container", copil "Item" Imaginea merge la variabila "Item" și un copil "Count" Textul trece la variabila "Count")
- Repetați același proces pentru obiectul "result_slot_template" (atașați-i scriptul SC_SlotTemplate și atribuiți variabile în același mod).
Pregătirea sistemului Craft:
- Atașați scriptul SC_ItemCrafting obiectului Canvas și atribuiți variabilele acestuia (Obiectul „PlayerSlots” merge la variabila "Player Slots Container", "CraftingSlots" Obiectul merge la variabila "Crafting Slots Container", "ResultSlot" Obiectul merge la "Result Slot Container" variabilă, "CraftButton" Obiectul merge la variabila "Craft Button", "slot_template" Obiectul cu scriptul SC_SlotTemplate atașat merge la variabila "Slot Template" și "result_slot_template" Obiectul cu scriptul SC_SlotTemplate atașat merge la variabila "Result Slot Template"):
După cum ați observat deja, există două matrice goale numite "Player Slots" și "Items". "Player Slots" va conține numărul de sloturi disponibile (cu Articol sau goale) și "Items" va conține toate articolele disponibile împreună cu rețetele lor (opțional).
Configurarea elementelor:
Verificați sprite-urile de mai jos (în cazul meu voi avea 5 articole):
(stâncă)
(diamant)
(lemn)
(sabie)
(sabie de diamant)
- Descărcați fiecare sprite (clic dreapta -> Salvați ca...) și importați-le în proiectul dvs. (În setările de import setați tipul texturii la "Sprite (2D and UI)" și modul de filtrare la "Point (no filter)"
- În SC_ItemCrafting modificați Dimensiunea elementelor la 5 și atribuiți fiecare sprite variabilei Item Sprite.
"Stackable" variabila controlează dacă articolele pot fi stivuite împreună într-un singur slot (de exemplu, poate doriți să permiteți stivuirea numai pentru materiale simple, cum ar fi piatra, diamantul și lemnul).
"Craft Recipe" variabile controlează dacă acest articol poate fi fabricat (gol înseamnă că nu poate fi fabricat)
- Pentru "Player Slots" setați dimensiunea matricei la 27 (cel mai potrivit pentru panoul de creație actual, dar puteți seta orice număr).
Când apăsați pe Play, veți observa că sloturile sunt inițializate corect, dar nu există elemente:
Pentru a adăuga un articol la fiecare spațiu, va trebui să atribuim un articol Sprite variabilei "Item Sprite" și să setăm "Item Count" la orice număr pozitiv (totul sub 1 și/sau articolele care nu se pot stivui vor fi interpretate ca 1):
- Atribuiți sprite-ul "rock" Elementului 0 / "Item Count" 14, sprite-ul "wood" Elementului 1 / "Item Count" 8, "diamond" Sprite-ului Elementului 2 / "Item Count" 8 (Asigurați-vă că sprite-urile sunt aceleași cu în "Items" Array, altfel nu va funcționa).
Elementele ar trebui să apară acum în sloturile pentru jucători, le puteți schimba poziția făcând clic pe element, apoi făcând clic pe slotul în care doriți să-l mutați.
Rețete de fabricare:
Crearea rețetelor vă permite să creați un articol combinând alte articole într-o anumită ordine:
Formatul pentru elaborarea rețetei este următorul: [item_sprite_name]([item count])*opțional... repetat de 9 ori, separat prin virgulă (,)
O modalitate ușoară de a descoperi rețeta este apăsând pe Play, apoi plasând articolele în ordinea pe care doriți să le creați, apoi apăsând "Craft", după aceea, apăsați (Ctrl + Shift + C) pentru a deschide Unity Consola și a vedea linie nou tipărită (Puteți face clic pe "Craft" de mai multe ori pentru a reimprima linia), linia tipărită este rețeta de crafting.
De exemplu, combinația de mai jos corespunde acestei rețete: rock,,rock,,rock,,rock,,wood (NOTĂ: poate fi diferit pentru tine dacă sprite-urile tale au nume diferite).
Vom folosi rețeta de mai sus pentru a crea o sabie.
- Copiați linia tipărită și inserați-o în matricea "Items" în variabila "Craft Recipe" sub elementul "sword":
Acum, când repeți aceeași combinație, ar trebui să poți crea o sabie.
O rețetă pentru o sabie de diamant este aceeași, dar în loc de piatră este un diamant:
Sistemul Crafting este acum gata.