【学習】Unity:アイテム所持とセーブ入門(第6回)― 1 マス:ItemButton とスロットの見た目
第5回 までで、OwnedItemsData.Instance に 追加・保存 までつなげました。
次はそれを 画面に並べて見せる ための、「インベントリの枠・1つ分」の部品です。
書籍準拠のサンプル(章 8-4-3 に相当する教材の流れ)では ItemButton.cs が 1 アイテム分の見た目(画像・個数・アクティブ切り替え)を担当します。
シリーズ
- 目次(00)
- 前回: 第5回
- 第6回(本記事):
ItemButton- 次回: 第7回:ItemsDialog で一覧と反映
今日のゴール
OwnedItemをセットしたらImageとTextが自動で変わる(プロパティのset)- 空きスロットのときは 画像も個数も隠す、
Button.interactableもfalse ItemTypeと Sprite を[Serializable]なクラス配列でインスペクタから結びつけられる
シーン側のひな形(Prefab 想定)
| 構成 | 役割 |
|---|---|
Button(ルート、ItemButton 付与) |
クリックの受け皿(※今回は中身 TODO) |
子:Image |
アイテムアイコン |
子:Text(Legacy の UnityEngine.UI.Text 想定) |
所持数 |
[RequireComponent(typeof(Button))] により、このスクリプトは ボタン必須です。
コードのブロックごとの説明
教材と 同じ ItemButton を、処理のかたまりごとに分けて読みます。すべてを一度にほしいときは、後半の 完成コード(コピー用) のコードブロックを使ってください。
ブロック①:名前空間と Button 必須
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Button))]
public class ItemButton : MonoBehaviourSystem/System.Linq… 後ほど[Serializable]クラス記述やFirstで使う。UnityEngine.UI… Legacy UI のImageとText。[RequireComponent(typeof(Button))]… このオブジェクトには必ずButtonがつく。付け忘れをコンパイル時〜追加時に防ぎます。
ブロック②:OwnedItem プロパティ(心臓部)
インベントリ 1マスぶんのデータ。代入すると同時にアイコン・個数・押せるかどうかまで更新します。
public OwnedItemsData.OwnedItem OwnedItem
{
get { return _ownedItem; }
set
{
_ownedItem = value;
// アイテムが割り当てられたかどうかでアイテム画像や所持個数の表示を切り替える
var isEmpty = null == _ownedItem;
image.gameObject.SetActive(!isEmpty);
number.gameObject.SetActive(!isEmpty);
_button.interactable = !isEmpty;
if (!isEmpty)
{
image.sprite = itemSprites.First(x => x.itemType == _ownedItem.Type).sprite;
number.text = "" + _ownedItem.Number;
}
}
}| 場所 | 何をしているか |
|---|---|
get |
裏側の _ownedItem を読むだけ |
_ownedItem = value |
渡された所持情報(または null)を保持 |
isEmpty |
null なら空きスロット。空きなら見た目を消したいので起点にする |
image / number の SetActive |
空き:アイコンと数字を消す。割当あり:両方見せる。親の Button だけ残し「枠だけのマス」にしやすい |
_button.interactable |
Button は SetActive とは別に クリック可否がある。空きでは false にして OnClick を防ぐ |
First(...).sprite |
所持には ItemType(種類の列挙)だけ。Sprite はインスペクタの itemSprites で対応を引く。該当が無いと First は例外。全種並べておく前提 |
number.text = "" + Number |
Text.text は文字列。数値は "" + で ToString() 相当。読み書き優先なら Number.ToString() でよい |
第7回 の一覧側は OwnedItem = ... と代入するだけでよく、個々の Image/Button をさわらなくて済む 役割分担になっています。
ブロック③:インスペクタ用フィールドと非公開フィールド
[SerializeField] private ItemTypeSpriteMap[] itemSprites; // 各アイテム用の画像を指定するフィールド
[SerializeField] private Image image;
[SerializeField] private Text number;
private Button _button;
private OwnedItemsData.OwnedItem _ownedItem;[SerializeField] private… コード外からは読めず、Inspector だけ割り当てる定番パターン。itemSprites… 種類ごとのSprite対応表。割当が揃っているか確認がポイント。_buttonと_ownedItemはAwake/プロパティからのみ触るためprivate。
ブロック④:Awake でボタン取得とクリック登録
private void Awake()
{
_button = GetComponent<Button>();
_button.onClick.AddListener(OnClick);
}Awake… オブジェクト初期化。子の参照は Inspector、コンポーネント取得はコード。onClick.AddListener(OnClick)… 実行時に メソッドを購読(UnityEvent)。今回OnClickは TODO で中身なしでも登録だけは済ませられる。
ブロック⑤:OnClick(この教材では未完)
private void OnClick()
{
// TODO ボタンを押した時の処理はここに書く
}- 後から「使用」「詳細ウィンドウ」などをここにつなぐ想定。空きでは
interactableが false のため発火しないようにしてある。
ブロック⑥:ItemTypeSpriteMap
/// <summary>
/// アイテムの種類とSpriteをインスペクタで紐付けられるようにするためのクラス
/// </summary>
[Serializable]
public class ItemTypeSpriteMap
{
public Item.ItemType itemType;
public Sprite sprite;
}[Serializable]+publicフィールド …ItemTypeSpriteMap[]を[SerializeField]すると、**インスペクタに「要素ごとの行」**が出やすくなる。書籍側のItem.ItemTypeと Sprite を 1対1 で並べるためのデータ形です。
完成コード(コピー用)
すべてを 1ファイルにしたいときは、このまま差し替えてよい一覧です。
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
[RequireComponent(typeof(Button))]
public class ItemButton : MonoBehaviour
{
public OwnedItemsData.OwnedItem OwnedItem
{
get { return _ownedItem; }
set
{
_ownedItem = value;
// アイテムが割り当てられたかどうかでアイテム画像や所持個数の表示を切り替える
var isEmpty = null == _ownedItem;
image.gameObject.SetActive(!isEmpty);
number.gameObject.SetActive(!isEmpty);
_button.interactable = !isEmpty;
if (!isEmpty)
{
image.sprite = itemSprites.First(x => x.itemType == _ownedItem.Type).sprite;
number.text = "" + _ownedItem.Number;
}
}
}
[SerializeField] private ItemTypeSpriteMap[] itemSprites; // 各アイテム用の画像を指定するフィールド
[SerializeField] private Image image;
[SerializeField] private Text number;
private Button _button;
private OwnedItemsData.OwnedItem _ownedItem;
private void Awake()
{
_button = GetComponent<Button>();
_button.onClick.AddListener(OnClick);
}
private void OnClick()
{
// TODO ボタンを押した時の処理はここに書く
}
/// <summary>
/// アイテムの種類とSpriteをインスペクタで紐付けられるようにするためのクラス
/// </summary>
[Serializable]
public class ItemTypeSpriteMap
{
public Item.ItemType itemType;
public Sprite sprite;
}
}プログラムの流れ
プログラムの流れ
外部が OwnedItem に値をセット
↓
null?
↓ Yes → 見た目を消す・押せなくする
↓ No
対応 Sprite を検索して Image に反映/個数テキスト更新・押せる重要ポイント
- UI の更新ロジックを「どこ?」に置くか … この教材では
OwnedItemのプロパティに集約し、呼び出し側は 代入するだけにしています First(x => …)… 種類ごとの画像がitemSpritesに並んでいないと ランタイムで例外になります。授業では 並び・抜けをチェックする習慣を付けるOnClickはあえて未完成 … 使う・アイテム説明などは 次の機能として課題化しやすいです
発展アイデア
FirstOrDefault+ null チェックで、Sprite 未定義でも落ちないようにする- UI Toolkit 版に読み替え(
VisualElement、Label) - 押したときに
OwnedItemsData.Instance.Use… とつなぐ(※例外設計との付き合い方は第4〜5回の前提と整合させる)
次に進む
1 個分ができたら、それを Instantiate で増やして **リスト全体を作ります。