
お世話をしてコインが貯まってきたら、次はやっぱり使い道がほしいですよね!今回はペット育成ゲームのシリーズの7回目として、コインでごはんやおもちゃを買えるショップを作り、買ったアイテムをペットにあげられるようにしてみたいと思います。アイテムのデータはScriptableObjectで作るので、アイテムを増やしたいときもスクリプトを触らずに済みますよ!
- シリーズの目次:ペット育成ゲームの作り方
- 前回:#6 ペットの状態をJSONでまとめて保存しよう
今回作るもの
- アイテムのデータ(名前・アイコン・値段・回復量)をScriptableObjectで作る
- ショップの画面に、アイテムを一覧で並べる
- コインを使ってアイテムを買う(足りないときは「かう」を押せない)
- 買ったアイテムを、ペットにあげる(おなかやきげんが回復する)
- 持っているアイテムをセーブデータに保存する
| スクリプト | 今回やること |
|---|---|
| ItemData | アイテム1つ分のデータ(ScriptableObject、新しく作る) |
| ItemDatabase | 全部のアイテムを登録する図鑑(ScriptableObject、新しく作る) |
| InventorySlot | 「アイテムのIDと持っている数」の1組(新しく作る) |
| PetStatus | 持っているアイテムの一覧を足す |
| PetController | 買う・あげるを追加する |
| OfflineProgress | 読み込むときに、持っているアイテムも戻す |
| ShopItemView | ショップの1行分(新しく作る) |
| ShopView | ショップの画面(新しく作る) |
前回の続きから
- Unityのバージョンは6.3 LTS
- 記事作成時点では6000.3.22f1で確認しています
- テンプレートはUniversal 2D
- #6の状態:ペットの状態をJSONのファイルに保存・読み込みしている
今回は、スタートガイドの次の記事の内容を使います。
- 12-8 ScriptableObjectでデータを作ろう:アイテムのデータと、IDで保存する考え方
- 6-13 数が変わるデータを扱おう:List:持っているアイテムの一覧
- 9-6 Scroll ViewとLayout Groupでリストを作ろう:ショップの一覧
- 7-3 スクリプトからプレハブを生成しよう:1行分のプレハブを並べる
素材を取り込もう
- アイテムのアイコン5枚:item_onigiri・item_milk・item_cake・item_ball・item_plush
- 買ったときの効果音:se_buy(チャリン)
素材は下のリンクからダウンロードしてください。シリーズで使う画像と音が全部入っているので、1回ダウンロードすれば#8まで使えます。
- PetRaisingAssets.zip(約4MB)
取り込み方は2つあります。
- かんたん:zipの中の
PetRaisingAssets.unitypackageをダブルクリック(またはUnityでAssets>Import Package>Custom Package…)。画像の取り込み設定(Sprite・Single)ごと入ります - 自分で入れる:zipの中の
PetRaisingフォルダを、UnityのAssetsにドラッグ&ドロップ(画像はSprite Mode:Singleにする)
素材は自由に使ってOKです(クレジット不要。作ったゲームの公開・販売にも使えます)。素材そのものの再配布はご遠慮ください。
- アイコンは PetRaising/Sprites の中に「Items」フォルダを作って入れる
- 5枚をまとめて選び、Inspectorで次のようにしてApply
- Texture Type:Sprite (2D and UI)
- Sprite Mode:Single
- Multipleになっていると、アイコンが「item_onigiri_0」のような名前で切り分けられます。1枚の絵なのでSingleにしておきます
- se_buy は PetRaising/Sounds に入れる
アイテムのデータを作ろう(ItemData・ItemDatabase)
ItemData
12-8の記事のItemDataに、回復する量を足したものです。
- PetRaising/Scriptsに「ItemData」を作る
using UnityEngine;
// アイテム1つ分のデータ。Project ウインドの Create>PetRaising>Item Data でアセットを作れる
[CreateAssetMenu(fileName = "NewItem", menuName = "PetRaising/Item Data")]
public class ItemData : ScriptableObject
{
public string id; // セーブデータに保存する名前(英数字で、ほかのアイテムと重ならないように)
public string itemName; // 画面に出す名前
public Sprite icon; // アイコン
public int price; // 値段(コイン)
public float hungerAmount; // あげたときに回復するおなか
public float moodAmount; // あげたときに回復するきげん
}
- id
- セーブデータには、アイテムそのものではなくidを保存する(12-8の記事で紹介した理由)
- 一度決めたら変えない
- hungerAmount・moodAmount
- ごはん系はおなか、おもちゃ系はきげんが回復するようにする
ItemDatabase
全部のアイテムを登録しておく図鑑です。12-8の記事と同じものです。
- PetRaising/Scriptsに「ItemDatabase」を作る
using System.Collections.Generic;
using UnityEngine;
// ゲームに出てくるアイテムを全部登録しておく図鑑。ID からアイテムのデータを探せる
[CreateAssetMenu(fileName = "ItemDatabase", menuName = "PetRaising/Item Database")]
public class ItemDatabase : ScriptableObject
{
[SerializeField] private List<ItemData> items = new List<ItemData>();
public IReadOnlyList<ItemData> Items => items;
// ID が同じアイテムを探す(なければ null)
public ItemData FindById(string id)
{
foreach (ItemData item in items)
{
if (item.id == id)
{
return item;
}
}
return null;
}
}
アイテムのアセットを作る
- PetRaisingの中に「Data」フォルダ、その中に「Items」フォルダを作る
- Itemsフォルダで右クリック>Create>PetRaising>Item Data で、下の5つを作る
| ファイル名 | Id | Item Name | Icon | Price | Hunger Amount | Mood Amount |
|---|---|---|---|---|---|---|
| Onigiri | onigiri | おにぎり | item_onigiri | 15 | 40 | 0 |
| Milk | milk | ミルク | item_milk | 8 | 15 | 5 |
| Cake | cake | ケーキ | item_cake | 30 | 20 | 30 |
| Ball | ball | ボール | item_ball | 20 | 0 | 35 |
| Plush | plush | ぬいぐるみ | item_plush | 45 | 0 | 60 |


- Dataフォルダで右クリック>Create>PetRaising>Item Database で「ItemDatabase」を作る
- InspectorのItemsの「+」を押して、5つのアイテムを順番にドラッグ&ドロップする
- ショップには、ここに並べた順番で表示されます

持っているアイテムを持たせよう
InventorySlot
6-13の記事で作った「名前と数の1組」と同じ考え方です。名前の代わりに、アイテムのidを持たせます。
- PetRaising/Scriptsに「InventorySlot」を作る
using System;
// 持っているアイテム1種類分。アイテムの ID と、持っている数の1組
[Serializable]
public class InventorySlot
{
public string itemId; // ItemData の id
public int count; // 持っている数
}
PetStatus
#3で作ったPetStatusに、持っているアイテムの一覧を足します。PetStatusは#6のSaveDataに入っているので、これだけで持っているアイテムもJSONに保存されるようになります。
using System;
using System.Collections.Generic;
using UnityEngine;
// ペットの状態(おなか・きげん・コイン・持っているアイテム)をまとめたデータ
[Serializable]
public class PetStatus
{
public const float Max = 100f;
[Range(0f, Max)] public float hunger = 80f; // おなか(0でペコペコ)
[Range(0f, Max)] public float mood = 80f; // きげん(0で最悪)
public int coins = 0;
public List<InventorySlot> items = new List<InventorySlot>(); // 持っているアイテム(追加)
}
PetController
買う(Buy)とあげる(Give)を追加します。あげたときもペットが喜ぶので、#4のCareから「喜ぶ」部分をRejoiceに分けました。
using UnityEngine;
// ペットの状態を時間で減らし、少なくなったらしょんぼりさせる。お世話・買い物・アイテムを受け取る
public class PetController : MonoBehaviour
{
[SerializeField] private PetStatus status = new PetStatus();
[Header("バランス")]
[SerializeField] private float hoursToEmptyHunger = 6f; // おなかが100から0になるまでの時間(時間)
[SerializeField] private float hoursToEmptyMood = 8f; // きげんが100から0になるまでの時間(時間)
[SerializeField] private float sadLine = 30f; // これより少ないとしょんぼりする
[Header("お世話")]
[SerializeField] private float feedAmount = 30f; // ごはんで回復するおなか
[SerializeField] private float playAmount = 25f; // あそぶで回復するきげん
[SerializeField] private int coinsPerCare = 5; // お世話1回でもらえるコイン
[Header("確認用")]
[SerializeField] private float timeScale = 1f; // 時間の進む速さ(確認するときだけ大きくする)
[Header("参照")]
[SerializeField] private Animator animator;
[SerializeField] private StatusView statusView;
private static readonly int SadHash = Animator.StringToHash("Sad");
private static readonly int HappyHash = Animator.StringToHash("Happy");
public PetStatus Status => status;
private void Update()
{
PassTime(Time.deltaTime * timeScale);
}
// 〇秒ぶん時間を進める(閉じていた時間の計算からも呼ぶ)
public void PassTime(float seconds)
{
status.hunger = Mathf.Max(0f, status.hunger - PerSecond(hoursToEmptyHunger) * seconds);
status.mood = Mathf.Max(0f, status.mood - PerSecond(hoursToEmptyMood) * seconds);
bool isSad = status.hunger < sadLine || status.mood < sadLine;
animator.SetBool(SadHash, isSad);
statusView.Show(status);
}
// ごはんをあげる:おなかが回復する
public void Feed()
{
status.hunger = Mathf.Min(PetStatus.Max, status.hunger + feedAmount);
Care();
}
// あそぶ:きげんが回復する
public void Play()
{
status.mood = Mathf.Min(PetStatus.Max, status.mood + playAmount);
Care();
}
// ---- ここから変更 ----
// お世話したときに共通でやること
private void Care()
{
status.coins += coinsPerCare;
Rejoice();
}
// ペットが喜ぶ(お世話したときと、アイテムをあげたとき)
private void Rejoice()
{
animator.SetTrigger(HappyHash);
statusView.Show(status);
}
// アイテムを買う。コインが足りなければ false
public bool Buy(ItemData item)
{
if (status.coins < item.price)
{
return false;
}
status.coins -= item.price;
InventorySlot slot = FindSlot(item.id);
if (slot == null)
{
// まだ持っていないアイテムなら、新しく足す
slot = new InventorySlot { itemId = item.id, count = 0 };
status.items.Add(slot);
}
slot.count++;
statusView.Show(status);
return true;
}
// アイテムをあげる。持っていなければ false
public bool Give(ItemData item)
{
InventorySlot slot = FindSlot(item.id);
if (slot == null)
{
return false;
}
slot.count--;
if (slot.count <= 0)
{
status.items.Remove(slot); // 0個になったら一覧から消す
}
status.hunger = Mathf.Min(PetStatus.Max, status.hunger + item.hungerAmount);
status.mood = Mathf.Min(PetStatus.Max, status.mood + item.moodAmount);
Rejoice();
return true;
}
// そのアイテムを何個持っているか
public int CountOf(ItemData item)
{
InventorySlot slot = FindSlot(item.id);
return slot == null ? 0 : slot.count;
}
// ID が同じ InventorySlot を探す(なければ null)
private InventorySlot FindSlot(string itemId)
{
foreach (InventorySlot slot in status.items)
{
if (slot.itemId == itemId)
{
return slot;
}
}
return null;
}
// ---- ここまで変更 ----
// 「何時間で空になるか」から、1秒あたりに減る量を計算する
private static float PerSecond(float hoursToEmpty)
{
return PetStatus.Max / (hoursToEmpty * 60f * 60f);
}
}
- Buy
- コインが足りなければfalseを返して、何もしない
- 持っていないアイテムなら新しいInventorySlotを足し、持っていれば数を1増やす
- Give
- 持っていなければfalse。持っていれば数を1減らして、0個になったら一覧から消す
- アイテムの回復量の分だけ、おなかときげんを回復して喜ぶ
- CountOf
- ショップの「もっている:〇」の表示に使う
条件 ? A : Bは、条件がtrueならA、falseならBになる書き方。ここでは「見つからなければ0、見つかればその数」
- FindSlot
- 6-13の記事のFindSlotと同じ。foreachで1つずつ見て、idが同じものを返す
読み込むときに持っているアイテムも戻す(OfflineProgress)
#6のOfflineProgressは、おなか・きげん・コインを1つずつ戻していました。持っているアイテムも戻すように、Startの中に1行足します。
// 閉じたときの状態に戻す
PetStatus status = pet.Status;
status.hunger = data.petStatus.hunger;
status.mood = data.petStatus.mood;
status.coins = data.petStatus.coins;
status.items = data.petStatus.items; // 持っているアイテム(追加)
保存(Save)の方は、#6でPetStatusをまるごとSaveDataに入れているので、そのままでOKです。
ショップの1行を作ろう(ShopItemView)
ショップに並べる、アイテム1つ分の行です。9-6の記事のItemRowと同じように、ボタンの処理はこの行のスクリプトで受け取ります。
- PetRaising/Scriptsに「ShopItemView」を作る
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// ショップの1行分。アイテムのアイコン・名前・効果・値段・持っている数と、「かう」「あげる」ボタン
public class ShopItemView : MonoBehaviour
{
[SerializeField] private Image iconImage;
[SerializeField] private TMP_Text nameText;
[SerializeField] private TMP_Text effectText;
[SerializeField] private TMP_Text priceText;
[SerializeField] private TMP_Text ownedText;
[SerializeField] private Button buyButton;
[SerializeField] private Button giveButton;
private ItemData item;
private ShopView shop;
public ItemData Item => item;
private void Awake()
{
buyButton.onClick.AddListener(OnBuyClicked);
giveButton.onClick.AddListener(OnGiveClicked);
}
// 作ったあとに、表示するアイテムを設定する
public void Setup(ItemData newItem, ShopView owner)
{
item = newItem;
shop = owner;
iconImage.sprite = item.icon;
nameText.text = item.itemName;
effectText.text = MakeEffectText(item);
priceText.text = $"{item.price}コイン";
}
// コインと持っている数に合わせて、表示とボタンを更新する
public void Refresh(int coins, int ownedCount)
{
ownedText.text = $"もっている:{ownedCount}";
buyButton.interactable = coins >= item.price; // コインが足りないと押せない
giveButton.interactable = ownedCount > 0; // 持っていないと押せない
}
private void OnBuyClicked()
{
shop.Buy(item);
}
private void OnGiveClicked()
{
shop.Give(item);
}
// 「おなか+40」「きげん+35」のような効果の文字を作る
private static string MakeEffectText(ItemData data)
{
string effectText = "";
if (data.hungerAmount > 0)
{
effectText += $"おなか+{data.hungerAmount} ";
}
if (data.moodAmount > 0)
{
effectText += $"きげん+{data.moodAmount}";
}
return effectText.Trim();
}
}
- Setup
- 行を作った直後に1回だけ呼んで、アイコン・名前・効果・値段を入れる
- Refresh
- 買ったりあげたりするたびに呼んで、持っている数とボタンを更新する
- interactableをfalseにすると、ボタンが灰色になって押せなくなる(#4の押しすぎ防止と同じ)
- ボタンの処理はShopViewに任せる
- 行は「どのアイテムのボタンが押されたか」をShopViewに伝えるだけ。実際に買う・あげるのはShopView
1行分のプレハブを作る
まずはHierarchyに1行分を作り、プレハブにします。
- Hierarchyで右クリック>UI (Canvas)>Image で作り、名前を「ShopItemView」にする
- Width:900、Height:200、Color:少し暗めの白(例:#F4F4F4)
- Add Component>Layout Element、Min Height:200(Layout Groupに並べたときの高さ)
- ShopItemViewの子に、次のものを作る(すべてアンカーはtop-left、Pivot:X 0、Y 1)
| 名前 | 種類 | Pos X | Pos Y | Width × Height | 設定 |
|---|---|---|---|---|---|
| Icon | Image | 25 | -25 | 150 × 150 | Preserve Aspect にチェック |
| NameText | Text – TextMeshPro | 195 | -20 | 380 × 60 | Font Size:48 |
| EffectText | Text – TextMeshPro | 195 | -85 | 380 × 45 | Font Size:34、Color:グレー |
| OwnedText | Text – TextMeshPro | 195 | -135 | 380 × 45 | Font Size:34 |
- 右側のものは、アンカーを右にして置く
| 名前 | 種類 | アンカー・Pivot | Pos X | Pos Y | Width × Height | 設定 |
|---|---|---|---|---|---|---|
| PriceText | Text – TextMeshPro | top-right、Pivot:1, 1 | -20 | -20 | 300 × 60 | Font Size:44、右寄せ |
| BuyButton | Button – TextMeshPro | bottom-right、Pivot:1, 0 | -190 | 20 | 150 × 80 | 子のテキスト「かう」、Font Size:40 |
| GiveButton | Button – TextMeshPro | bottom-right、Pivot:1, 0 | -20 | 20 | 150 × 80 | 子のテキスト「あげる」、Font Size:40 |
- ShopItemViewにAdd Component>ShopItemView で、7つの欄にそれぞれの子をドラッグ&ドロップ
- HierarchyのShopItemViewを、ProjectウインドのPetRaisingの中の「Prefabs」フォルダ(なければ作る)にドラッグ&ドロップしてプレハブにする
- Hierarchyに残ったShopItemViewは削除する

プレハブの作り方は7-1 プレハブの基本概念と作成方法で紹介しています。
ショップの画面を作ろう(ShopView)
画面を作る
- HierarchyでCanvasを右クリック>UI (Canvas)>Panel で作り、名前を「ShopPanel」にする
- HierarchyでSafeAreaの下、WelcomePanelの上に置く(おかえりパネルがショップより手前に出るように)
- ImageのColorを黒、Aを150ぐらいにする
- ShopPanelの子に Image で「Window」を作る
- アンカー:stretch-stretch、Left・Right:40、Top:180、Bottom:180
- Source Image:UISprite、Image Type:Sliced
- Windowの子に、次のものを作る
| 名前 | 種類 | 置き方 | 設定 |
|---|---|---|---|
| TitleText | Text – TextMeshPro | アンカー:top-stretch、Pos Y:-30、Height:90 | 「ショップ」、Font Size:64、真ん中 |
| CoinText | Text – TextMeshPro | アンカー:top-right、Pivot:1, 1、Pos X:-40、Pos Y:-45、300 × 60 | 「コイン:0」、Font Size:44、右寄せ |
| CloseButton | Button – TextMeshPro | アンカー:bottom-center、Pos Y:90、360 × 110 | 子のテキスト「とじる」、Font Size:52 |
| Scroll View | Scroll View | アンカー:stretch-stretch、Left・Right:30、Top:140、Bottom:170 | 下を参照 |
- Scroll Viewの設定(9-6の記事と同じ)
- Scroll Rect の Horizontal のチェックを外す(縦にだけスクロール)
- Scrollbar Horizontalは削除
- Viewport>Content に Vertical Layout Group(Padding:すべて10、Spacing:16、Control Child Size:Width、Child Force Expand:Width)と Content Size Fitter(Vertical Fit:Preferred Size)を付ける

ShopView
ショップの画面を開いたり閉じたりして、行を並べるスクリプトです。
- PetRaising/Scriptsに「ShopView」を作る
using System.Collections.Generic;
using TMPro;
using UnityEngine;
// ショップの画面。アイテムを一覧に並べて、買う・あげるを受け付ける
public class ShopView : MonoBehaviour
{
[SerializeField] private PetController pet;
[SerializeField] private ItemDatabase database;
[SerializeField] private ShopItemView rowPrefab; // 1行分のプレハブ
[SerializeField] private Transform content; // Scroll View の Content
[SerializeField] private GameObject shopPanel;
[SerializeField] private TMP_Text coinText;
[Header("効果音")]
[SerializeField] private AudioSource seSource;
[SerializeField] private AudioClip buySe;
private readonly List<ShopItemView> rows = new List<ShopItemView>();
private void Start()
{
// 図鑑に登録したアイテムを、1行ずつ並べる
foreach (ItemData item in database.Items)
{
ShopItemView row = Instantiate(rowPrefab, content);
row.Setup(item, this);
rows.Add(row);
}
shopPanel.SetActive(false);
}
// ショップボタンの On Click に登録する
public void Open()
{
shopPanel.SetActive(true);
Refresh();
}
// とじるボタンの On Click に登録する
public void Close()
{
shopPanel.SetActive(false);
}
public void Buy(ItemData item)
{
if (pet.Buy(item))
{
seSource.PlayOneShot(buySe);
}
Refresh();
}
public void Give(ItemData item)
{
if (pet.Give(item))
{
Close(); // 閉じて、ペットが喜ぶところを見せる
}
}
private void Refresh()
{
int coins = pet.Status.coins;
coinText.text = $"コイン:{coins}";
foreach (ShopItemView row in rows)
{
row.Refresh(coins, pet.CountOf(row.Item));
}
}
}
- Start
- 図鑑(ItemDatabase)に登録した順番で、1行分のプレハブをContentの子として作る
- Vertical Layout Groupが、縦に並べてくれる
- 並べ終わったら、ショップの画面を閉じておく
- ShopViewはShopPanelの外に付ける
- ShopPanelが非表示のままだと、ShopPanelに付けたスクリプトのStartが呼ばれないため。いつも表示されているGameManagerに付ける
- Buy・Give
- PetControllerのBuy・Giveがtrueを返したら(買えた・あげられたら)、効果音を鳴らしたり画面を閉じたりする
- Refresh
- コインの表示と、全部の行の「持っている数」「ボタン」を更新する
ボタンにつなごう
- HierarchyでGameManagerを選び、Add Component>ShopView
- 次のようにドラッグ&ドロップする
- Pet:Pet
- Database:ItemDatabase
- Row Prefab:Prefabs/ShopItemView
- Content:ShopPanel>Window>Scroll View>Viewport>Content
- Shop Panel:ShopPanel
- Coin Text:CoinText
- Se Source:SE
- Buy Se:se_buy
- ShopButton(#1で作った「ショップ」ボタン)のOn Click ()に、GameManagerのShopView>Open ()を登録
- CloseButtonのOn Click ()に、GameManagerのShopView>Close ()を登録

動かして確認しよう
コインが少ないと何も買えないので、確認のときはInspectorでコインを増やしておきましょう。
- 再生(▶)する
- PetのPet ControllerのStatus>Coinsを30にする(確認用)
- 「ショップ」ボタンを押す
- 5つのアイテムが並んでいる
- ぬいぐるみ(45コイン)は、コインが足りないので「かう」が灰色になっている
- 持っていないので「あげる」は全部灰色
- おにぎりの「かう」を押す
- 「チャリン」と鳴って、コインが15減り、「もっている:1」になる
- 「あげる」が押せるようになる
- 残りのコインが15になったので、ボール・ケーキの「かう」も灰色になる
- おにぎりの「あげる」を押す
- ショップが閉じて、ペットが喜ぶ
- おなかが40回復している


保存も確認する
- アイテムをいくつか買ってから、再生を止める
- もう一度再生して、ショップを開く
- 買ったアイテムの「もっている」の数が、止める前と同じになっていればOK
- PetRaising>セーブデータの場所を開く で petdata.json を見ると、itemsにアイテムのidと数が保存されている
{
"petStatus": {
"hunger": 69.97750091552735,
"mood": 79.90492248535156,
"coins": 50,
"items": [
{
"itemId": "onigiri",
"count": 1
},
{
"itemId": "ball",
"count": 1
}
]
},
"lastTimeBinary": 5250942866309213391
}
スライムアイテムそのもの(ScriptableObject)ではなく、idの文字が保存されているのがポイントです。読み込んだあとは、CountOfでidを比べて数を数えています



アイテムを増やしたいときは、Item Dataのアセットを作って、ItemDatabaseに足すだけ!スクリプトは触らなくていいんだよ
値段と回復量のバランス
#4で決めた「お世話1回でコイン5枚、3秒に1回」をもとに、値段を決めています。
| アイテム | 値段 | お世話何回分 | 効果 | 考え方 |
|---|---|---|---|---|
| ミルク | 8 | 2回 | おなか+15・きげん+5 | 安くてちょっとだけ回復 |
| おにぎり | 15 | 3回 | おなか+40 | ごはんボタン(+30)より少しお得 |
| ボール | 20 | 4回 | きげん+35 | あそぶボタン(+25)より少しお得 |
| ケーキ | 30 | 6回 | おなか+20・きげん+30 | 両方回復する |
| ぬいぐるみ | 45 | 9回 | きげん+60 | 高いけど一気に回復 |
- ボタンでのお世話よりも「少しお得」にしておくと、コインを貯めて買う楽しみが出る
- 高いアイテムほど、1コインあたりの回復量が少しずつ良くなるようにしている
あとがき
今回はScriptableObjectでアイテムのデータを作り、コインで買ってペットにあげられるショップを作りました。コインの使い道が出来て、ぐっとゲームらしくなりました!次回はいよいよ最終回。お世話の回数でペットが進化して、最後にエンディングを迎えるところと、スマホで遊べるようにビルドするところまで作りたいと思います!
コメント
コメント一覧 (1件)
[…] 前回:#7 コインでアイテムを買えるショップを作ろう […]