今回はゲームコンテストで作ろうと思っていたオンラインゲームのショップ画面をプロトタイプで作成してみました
(たぶんコンテストにはこの要素はいれないです)
Demo
開発環境
- Unity 2019.4.28f1
- Backendless(BaaS)
使用したアセット
あまり関係ないですが,内部で以下も使用しています
(ほかのSaaSと同じなので,SaaSの説明を引用)
「SaaS」(Software as a Service:「サース」または「サーズ」)とは、ソフトウェアを利用者(クライアント)側に導入するのではなく、提供者(サーバー)側で稼働しているソフトウェアを、インターネット等のネットワーク経由で、利用者がサービスとして利用する状況を指します。
引用元
SaaSとは | クラウド・データセンター用語集/IDCフロンティア
参考にした参考書
booth.pm
公式ドキュメント
backendless.com
実装
実装した機能としては以下です
- Moqデータとしてコード側で一定するのデータをDBに追加する関数
- 画面上からアイテムの購入(図1)
- 購入したアイテムの削除
各種非同期部分の書きかたと改善作成
非同期部分の書き方はtwitter で KOGAさんにアドバイスいただきました
詳細は以下のtweetのスレをご確認ください
(今回はアドバイスをすべて反映できていません)
クラス図
DBHandler
using System;
using System.Collections.Generic;
using _OnlineShop.Scripts;
using UnityEngine;
using BackendlessAPI;
using BackendlessAPI.Persistence;
using Cysharp.Threading.Tasks;
using UniRx;
using UnityEditor;
using Random = UnityEngine.Random;
public class DBHandler : IDBHandler,IDisposable
{
private readonly CompositeDisposable _compositeDisposable = new CompositeDisposable();
public async void DeleteItemData(string tableName,Dictionary<string, object> itemInfo)
{
var savedContact = await Backendless.Persistence.Of(tableName).SaveAsync(itemInfo);
await Backendless.Persistence.Of(tableName).RemoveAsync(savedContact);
}
public async void AddItemData(string itemName,int itemCount,int itemPrice)
{
var dict = new Dictionary<string, object>
{
{"itemName",itemName},
{"itemCount",itemCount},
{"itemPrice",itemPrice}
};
var result = await Backendless.Data.Of("ShopList").SaveAsync(dict);
Debug.Log($"result:{result}");
}
public async void UpdateItemData(string itemGroup,string itemName,int itemCount,int itemPrice)
{
var result = await Backendless.Data.Of(itemGroup).FindFirstAsync();
result[itemName] = 30;
await Backendless.Data.Of("Person").SaveAsync(result);
}
public async UniTask<IList<Dictionary<string, object>>> GetTableData(string tableName)
{
var query = DataQueryBuilder.Create();
query.AddSortBy("created desc");
query.SetRelationsDepth( 0 );
return await Backendless.Data.Of(tableName).FindAsync(query).AsUniTask();
}
public void AddMoqData(string tableName)
{
for (var index = 1; index <= 6; index++)
{
AddItem(index);
}
async void AddItem(int itemNo)
{
var itemName = $"{tableName}" + itemNo;
var dict = new Dictionary<string, object>
{
{"itemName",itemName},
{"itemCount",Random.Range(0,30) * 5},
{"itemPrice",Random.Range(2,100) * 10}
};
await Backendless.Data.Of(tableName).SaveAsync(dict);
}
}
#if UNITY_EDITOR
void HandleOnPlayModeChanged( PlayModeStateChange stateChange )
{
if ( stateChange == PlayModeStateChange.ExitingEditMode || stateChange == PlayModeStateChange.ExitingPlayMode )
{
Backendless.RT.Disconnect();
}
}
#endif
public DBHandler()
{
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += HandleOnPlayModeChanged;
#endif
}
public void Dispose()
{
_compositeDisposable?.Dispose();
}
}
GameLifetimeScope
using _OnlineShop.Scripts;
using UnityEngine;
using VContainer;
using VContainer.Unity;
public class GameLifetimeScope : LifetimeScope
{
[SerializeField] private ShopShopView shopShopView;
protected override void Configure(IContainerBuilder builder)
{
builder.RegisterComponent(shopShopView).AsImplementedInterfaces();
builder.Register<DBHandler>(Lifetime.Singleton).AsImplementedInterfaces();
builder.RegisterEntryPoint<GameManager>();
}
}
GameManager
using System;
using System.Linq;
using _OnlineShop.Scripts;
using UniRx;
using UnityEngine;
using VContainer.Unity;
public class GameManager : IStartable,IDisposable
{
private readonly IDBHandler _dbHandler;
private readonly IShopView _shopView;
private readonly CompositeDisposable _compositeDisposable = new CompositeDisposable();
private GameManager(IDBHandler dbHandler,IShopView shopView)
{
_dbHandler = dbHandler;
_shopView = shopView;
}
public async void Start()
{
// _dbHandler.AddMoqData("Food");
var d = await _dbHandler.GetTableData("Food");
foreach (var (item,index) in d.Select((item,index) => (item,index)))
{
_shopView.SetText(item["itemName"].ToString(),item["itemCount"].ToString(),item["itemPrice"].ToString(),index);
}
_shopView.BuyItemObservable().Subscribe(value =>
{
_shopView.SetBuy(true,value);
Debug.Log($"food:{value}を購入");
_dbHandler.DeleteItemData("Food",d[value]);
}).AddTo(_compositeDisposable);
}
public void Dispose()
{
_compositeDisposable?.Dispose();
}
}
Item
using System;
using UniRx;
using UnityEngine;
using UnityEngine.UI;
namespace _OnlineShop.Scripts
{
public class Item : MonoBehaviour
{
[SerializeField] private Text itemNameText;
[SerializeField] private Text itemCountText;
[SerializeField] private Text itemPriceText;
[SerializeField] private Image itemIcon;
[SerializeField] private Button buyButton;
[SerializeField] private Image backgroundImage;
public IObservable<Unit> OnClickBuyButton()
{
return buyButton.OnClickAsObservable();
}
public void SetText(string itemName, int itemCount, int itemPrice,int iconNo)
{
itemNameText.text = itemName;
itemCountText.text = $"{itemCount}個";
itemPriceText.text = $"${itemPrice}";
itemIcon.sprite = GetIcon(iconNo);
}
private static Sprite GetIcon(int iconNo)
{
return Resources.Load<Sprite>($"Food/Icon{iconNo}");
}
public void SetBuy(bool isBuy)
{
backgroundImage.color = new Color(115, 115, 115);
buyButton.interactable = false;
}
}
}
ShopShopView
using System;
using System.Collections.Generic;
using System.Linq;
using UniRx;
using UnityEngine;
namespace _OnlineShop.Scripts
{
public class ShopShopView : MonoBehaviour,IShopView
{
[SerializeField] private List<Item> itemList = new List<Item>();
private readonly Subject<int> _buySubject = new Subject<int>();
private void Awake()
{
foreach (var (item,index) in itemList.Select((item,index) => (item,index)))
{
item.OnClickBuyButton().Subscribe(_ =>
{
_buySubject.OnNext(index);
}).AddTo(this);
}
}
public void SetView(bool isView)
{
gameObject.SetActive(isView);
}
public void SetText(string itemName, string itemCount, string itemPrice,int itemNo)
{
if (itemNo < itemList.Count)
{
itemList[itemNo].SetText(itemName,int.Parse(itemCount), int.Parse(itemPrice),itemNo+1);
}
}
public void SetBuy(bool isBuy,int itemNo)
{
itemList[itemNo].SetBuy(isBuy);
}
public IObservable<int> BuyItemObservable()
{
return _buySubject;
}
}
}