Unityでのインベントリとアイテム作成システムの作成
このチュートリアルでは、Unity で Minecraft スタイル インベントリおよびアイテム作成システムを作成する方法を示します。
ビデオ ゲームにおけるアイテム クラフト は、特定の (通常は単純な) アイテムを組み合わせて、新しく強化されたプロパティを備えたより複雑なアイテムを作成するプロセスです。たとえば、木と石を組み合わせてつるはしにしたり、金属板と木を組み合わせて剣を作ったりします。
以下の作成システムは モバイル対応 で完全に自動化されており、あらゆる UI レイアウト で動作し、カスタムの作成レシピを作成する機能を備えています。
ステップ 1: クラフト UI をセットアップする
まず、クラフト UI を設定します。
- 新しいキャンバスを作成します (Unity 上部タスクバー: ゲームオブジェクト -> UI -> キャンバス)
- Canvas オブジェクトを右クリックして、[UI] -> [画像] を選択して、新しい画像を作成します。
- 画像オブジェクトの名前を "CraftingPanel" に変更し、そのソース画像をデフォルトに変更します。 "UISprite"
- "CraftingPanel" RectTransform の値を (位置 X: 0 位置 Y: 0 幅: 410 高さ: 365) に変更します。
- "CraftingPanel" 内に 2 つのオブジェクトを作成します (CraftingPanel を右クリック -> [空の作成] を 2 回クリックします)
- 最初のオブジェクトの名前を "CraftingSlots" に変更し、その RectTransform 値を (「左上揃え」ピボット X: 0 ピボット Y: 1 位置 X: 50 位置 Y: -35 幅: 140 高さ: 140) に変更します。このオブジェクトにはクラフト スロットが含まれます。
- 2 番目のオブジェクトの名前を "PlayerSlots" に変更し、その RectTransform 値を (「上部を水平方向に伸ばす」ピボット X: 0.5 ピボット Y: 1 左: 0 位置 Y: -222 右: 0 高さ: 100) に変更します。このオブジェクトにはプレイヤー スロットが含まれます。
セクションのタイトル:
- ["PlayerSlots" オブジェクト] -> [UI] -> [テキスト] を右クリックして新しいテキストを作成し、名前を次のように変更します。 "SectionTitle"
- "SectionTitle" RectTransform の値を (「左上揃え」ピボット X: 0 ピボット Y: 0 位置 X: 5 位置 Y: 0 幅: 160 高さ: 30) に変更します。
- "SectionTitle" テキストを "Inventory" に変更し、フォント サイズを 18、配置を左中央、色を (0.2、0.2、0.2、1) に設定します。
- "SectionTitle" オブジェクトを複製し、そのテキストを "Crafting" に変更して "CraftingSlots" オブジェクトの下に移動し、前の "SectionTitle" と同じ RectTransform 値を設定します。
クラフトスロット:
クラフト スロットは、背景画像、アイテム画像、カウント テキストで構成されます。
- Canvas オブジェクトを右クリックして、[UI] -> [画像] を選択して、新しい画像を作成します。
- 新しい画像の名前を "slot_template" に変更し、RectTransform の値を (Post X: 0 Pos Y: 0 width: 40 Height: 40) に設定し、色を (0.32, 0.32, 0.32, 0.8) に変更します。
- "slot_template" を複製して "Item" に名前を変更し、"slot_template" オブジェクト内に移動し、RectTransform の寸法を (幅: 30 高さ: 30) に、色を (1, 1, 1, 1) に変更します。
- ["slot_template" オブジェクト] -> [UI] -> [テキスト] を右クリックして新しいテキストを作成し、名前を次のように変更します。 "Count"
- "Count" RectTransform の値を ("下揃え" ピボット X: 1 ピボット Y: 0 位置 X: 0 位置 Y: 0 幅: 30 高さ: 30) に変更します。
- "Count" テキストを乱数 (例: 12)、フォント スタイルを太字、フォント サイズを 14、配置を右下、色を (1, 1, 1, 1) に設定します。
- Shadow コンポーネントを "Count" Text に追加し、Effect Color を (0, 0, 0, 0.5) に設定します。
最終結果は次のようになります。
結果スロット (結果の作成に使用されます):
- "slot_template" オブジェクトを複製し、名前を次のように変更します。 "result_slot_template"
- "result_slot_template" の幅と高さを 50 に変更します。
ボタンと追加グラフィックの作成:
- ["CraftingSlots" オブジェクト] -> [UI] -> [ボタン] を右クリックして新しいボタンを作成し、名前を次のように変更します。 "CraftButton"
- "CraftButton" RectTransform の値を (「中央左揃え」、ピボット X: 1、ピボット Y: 0.5、位置 X: 0、位置 Y: 0、幅: 40、高さ: 40) に設定します。
- "CraftButton" のテキストを次のように変更します "Craft"
- ["CraftingSlots" オブジェクト] -> [UI] -> [画像] を右クリックして新しい画像を作成し、名前を次のように変更します。 "Arrow"
- "Arrow" RectTransform の値を (「中央右揃え」、ピボット X: 0、ピボット Y: 0.5、位置 X: 10、位置 Y: 0、幅: 30、高さ: 30) に設定します。
ソース画像には、以下の画像を使用できます (右クリック -> [名前を付けて保存] を選択してダウンロードします)。インポート後、テクスチャ タイプを "Sprite (2D and UI)" に、フィルター モードを に設定します。 "Point (no filter)"
- "CraftingSlots" を右クリック -> 空を作成し、名前を "ResultSlot" に変更します。このオブジェクトには結果スロットが含まれます。
- "ResultSlot" RectTransform の値を (「中央右揃え」ピボット X: 0 ピボット Y: 0.5 位置 X: 50 位置 Y: 0 幅: 50 高さ: 50) に設定します。
UI設定の準備が完了しました。
ステップ 2: プログラム作成システム
このクラフト システムは、SC_ItemCrafting.cs と SC_SlotTemplate.cs の 2 つのスクリプトで構成されます。
- という新しいスクリプトを作成し、"SC_ItemCrafting" という名前を付けて、その中に以下のコードを貼り付けます。
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);
}
}
}
}
- 新しいスクリプトを作成し、"SC_SlotTemplate" という名前を付けて、その中に以下のコードを貼り付けます。
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();
}
}
スロット テンプレートの準備:
- SC_SlotTemplate スクリプトを "slot_template" オブジェクトにアタッチし、その変数を割り当てます (同じオブジェクト上の画像コンポーネントは "Container" 変数に、子 "Item" 画像は "Item" 変数に、子 "Count" テキストは "Count" 変数に移動します)
- "result_slot_template" オブジェクトに対して同じプロセスを繰り返します (SC_SlotTemplate スクリプトをそれにアタッチし、同じ方法で変数を割り当てます)。
クラフトシステムの準備:
- SC_ItemCrafting スクリプトを Canvas オブジェクトにアタッチし、その変数を割り当てます (「PlayerSlots」オブジェクトは "Player Slots Container" 変数に移動し、"CraftingSlots" オブジェクトは "Crafting Slots Container" 変数に移動し、"ResultSlot" オブジェクトは *h51 に移動します) * 変数、"CraftButton" オブジェクトは "Craft Button" 変数に移動し、SC_SlotTemplate スクリプトがアタッチされた "slot_template" オブジェクトは "Slot Template" 変数に移動し、SC_SlotTemplate スクリプトがアタッチされた "result_slot_template" オブジェクトは "Result Slot Template" 変数に移動します):
すでにお気づきのとおり、"Player Slots" と "Items" という名前の 2 つの空の配列があります。"Player Slots" には使用可能なスロットの数 (アイテムありまたは空) が含まれ、"Items" には使用可能なすべてのアイテムとそのレシピ (オプション) が含まれます。
項目の設定:
以下のスプライトを確認してください (私の場合は 5 つのアイテムがあります)。
(ロック)
(ダイヤモンド)
(木材)
(剣)
(ダイヤモンド・ソード)
- 各スプライトをダウンロードし (右クリック -> 名前を付けて保存...)、プロジェクトにインポートします (インポート設定でテクスチャ タイプを "Sprite (2D and UI)" に、フィルター モードを に設定します) "Point (no filter)"
- SC_ItemCrafting で、項目サイズを 5 に変更し、各スプライトを項目スプライト変数に割り当てます。
"Stackable" 変数は、アイテムを 1 つのスロットにスタックできるかどうかを制御します (たとえば、岩、ダイヤモンド、木材などの単純なマテリアルのスタックのみを許可したい場合があります)。
"Craft Recipe" 変数はこのアイテムを作成できるかどうかを制御します (空の場合は作成できないことを意味します)
- "Player Slots" の場合、配列サイズを 27 に設定します (現在のクラフト パネルに最適ですが、任意の数値を設定できます)。
[Play] を押すと、スロットは正しく初期化されていますが、項目がないことがわかります。
各スロットにアイテムを追加するには、アイテム スプライトを "Item Sprite" 変数に割り当て、"Item Count" を任意の正の数に設定する必要があります (1 未満のすべて、および/またはスタック不可能なアイテムは 1 として解釈されます)。:
- "rock" スプライトを要素 0 / "Item Count" 14 に、"wood" スプライトを要素 1 / "Item Count" 8 に、"diamond" スプライトを要素 2 / "Item Count" 8 に割り当てます (スプライトが同じであることを確認してください) "Items" 配列内、そうでない場合は機能しません)。
アイテムがプレイヤー スロットに表示されるはずです。アイテムをクリックして、移動先のスロットをクリックすると、アイテムの位置を変更できます。
クラフトレシピ:
クラフトレシピを使用すると、他のアイテムを特定の順序で組み合わせてアイテムを作成できます。
クラフトレシピの形式は以下の通りです: [アイテムスプライト名]([アイテム数])※任意…カンマ(,)区切りで9回繰り返します。
レシピを見つける簡単な方法は、Play を押し、作成したい順序でアイテムを配置し、"Craft" を押し、その後 (Ctrl + Shift + C) を押して Unity コンソールを開き、新しく印刷された行 ("Craft" を複数回クリックして行を再印刷できます)、印刷された行がクラフトのレシピです。
たとえば、以下の組み合わせはこのレシピに対応します:rock,,rock,,rock,,rock,,wood (注: スプライトの名前が異なる場合は異なる場合があります)。
上記のレシピを使用して剣を作成していきます。
- 印刷された行をコピーし、"Items" 配列の "sword" 項目の下にある "Craft Recipe" 変数に貼り付けます。
同じ組み合わせを繰り返すと、剣を作成できるようになります。
ダイヤモンドの剣のレシピも同じですが、石の代わりにダイヤモンドが使用されます。
これでクラフトシステムの準備が整いました。