前回は、羊オブジェクトの初期パラメータをScriptableObjectで作成して、羊の色替えなどを行いました。
前回の記事↓
今回は羊の購入UIと購入処理を実装していきます。
SheepGenerator修正
まずSheepGeneratorをちょっと修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SheepGenerator : MonoBehaviour { [SerializeField] private Sheep sheepPrefab; //生成する羊のPrefab //羊作成 public void CreateSheep() { var sheep = Instantiate(sheepPrefab); } void Update() { if (Input.GetKeyDown(KeyCode.S)) { CreateSheep(); } } } |
このままでは、前回折角作ったSheepDataによる複数の羊生成ができないので、生成メソッド(CreateSheep)ではSheepDataを受け取って、生成したプレファブにSheepDataをセットするよう修正します。(なお、テストで用意したUpdateメソッド処理は削ってしまいましょう)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
using System.Collections; using System.Collections.Generic; using UnityEngine; public class SheepGenerator : MonoBehaviour { [SerializeField] private Sheep sheepPrefab; //生成する羊のPrefab //羊作成 public void CreateSheep(SheepData sheepData) { var sheep = Instantiate(sheepPrefab); sheep.sheepData = sheepData; } } |
スクリプトは忘れずに保存しておきましょう。
羊購入ボタン(SheepButton)作成
では、先ほど修正したSheepGeneratorを使う羊購入ボタンを作っていきます。
HierarchyのCanvas/Panel(Shop)を右クリックして、UI→Buttonを選択し、ボタンを追加しましょう。
まだ、位置やサイズなどはそのままで構いません。
このButtonの名前をButton(SheepButton)に変更し、スクリプトを追加します。スクリプトの名前も「SheepButton」にしましょう。
このSheepButtonスクリプトを以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SheepButton : MonoBehaviour { [SerializeField] private Button button; public SheepData sheepData; public SheepGenerator sheepGenerator; public void CreateSheep() { sheepGenerator.CreateSheep(sheepData); } // Start is called before the first frame update void Start() { button.onClick.AddListener(CreateSheep); } // Update is called once per frame void Update() { } } |
まず、生成ボタンをInscpetorでセット出来るように、Button変数と生成したい羊データ(SheepData)用の変数、羊生成オブジェクト(SheepGenerator)を受け取るための変数を宣言しておきます。
なお、 Button を使うために using UnityEngine.UI;
が4行目に追加されているので注意してください。
1 2 3 4 5 6 |
[SerializeField] private Button button; public SheepData sheepData; public SheepGenerator sheepGenerator; |
次に、羊作成用の処理 CreateSheep
メソッドを用意しています。
1 2 3 4 5 |
//羊作成 private void CreateSheep() { sheepGenerator.CreateSheep(sheepData); } |
中では羊生成オブジェクトのCreateSheepメソッドを呼んでいるだけです。
そして、ボタンが押されたことによってCreateSheepが呼ばれる必要があるため、Startメソッド内でボタンクリックへの登録をしています。
1 2 3 4 5 |
// Start is called before the first frame update void Start() { button.onClick.AddListener(CreateSheep); } |
では、保存をして、Unity Editor上、InspectorでButtonコンポーネントとSheepGeneratorオブジェクトをそれぞれセットして、再生してみましょう。
ボタンを押す度に、羊が生成されるようになっていればばっちりです。
このSheepButtonはAssets/Prefabs/にドラッグアンドドロップしてPrefabにしておきます。
羊購入ボタンに羊情報表示
さて、SheepButtonの見た目も作っていきます。
SheepButtonプレファブを修正していきましょう。HierarchyのSheepButtonの横にある 「>」をクリックして、プレファブ編集モードにします。
羊購入ボタンの表示(UI)には
- 羊の色
- 羊の金額
- 羊の数/上限数
- 購入可否
が確認できるようにします。
Imageの追加
まず、羊の色情報用にImageをSheepButtonに追加します。
SheepButtonを右クリックして、UI→Imageを追加します。
ImageのSourceImageには Assets/Images の sheep を設定しておきましょう。
配置は、ボタンの幅全体に使用したいので、RectTransformのAnchorPresetsをShift+Altを押しながら右下をクリックしておきます。
もちろん、ボタンの幅全体に広がると羊画像はこのように広がってしまうので
Image TypeのPreserve Aspect にチェックを入れておきます。
このチェックを入れておく事で縦横幅は親オブジェクト(ボタン)のサイズをしようしますが、画像の比率は元の画像のままになるようになります。
親オブジェクト(SheepButton)をHierarchyで選択し、サイズを直接変更すると分かりやすいです。
(その際には Tキーを押して 操作モードをRectTool にしておくと良いです)
ここに、羊の金額や購入羊数などを表示するので、最終的にちょっと縦長にしておきましょう。
Textの設定
金額Text
羊の購入金額表示用のTextを作ります。
と言っても、Buttonオブジェクトには最初からTextが配置されているため、それを使用しましょう。
しかし、Textが先ほど追加したImageの後ろに隠れてしまっています。
これは賛否あるUnityのuiの仕様なのですが、Hierarchyでの上から順番に描画がされるためです。
ドラッグで順番を変えて、Imageの上にTextが表示されるように修正しておきます。
では、SheepButton内のTextオブジェクトをを以下のように修正します
- 名前: Text(Price)
- Font : Assets/Fonts/ShigotoMemogaki-Regular-1-01
- Text : “1,000,000,000”
- Font Style: BOLD
- Font Size : 40
- Best Fit : チェック(true)
この「Best Fit」にチェックを入れておく事で、文字が溢れそうになった時に勝手にフォントサイズを調整してくれるようになります。
今回のクリッカーゲームのような、金額がインフレするゲームでは付けておくと安心です。
頭数Text、購入可否Text
先ほどの金額Text(Text(Price))を選択して、CTRL+Dキーを2回押し、複製を二つ作ります。
羊購入処理
さて、今わかっているSheepButtonスクリプトの足りない処理を列挙すると
- お金が足りなくても羊が購入できてしまう
- 羊を購入しても金額が減らない
- 羊を購入しても羊の頭数が変わらない(羊の値段が上がっていかない)
となります。
では、修正していきましょう。
お金が無くても羊が購入出来てしまう
これは、購入ボタンの有効化・無効化を変更すれば良さそうです。
Buttonオブジェクトの有効、無効は interactableにtrueを入れれば有効、falseを入れれば無効(グレーアウトしてボタンが押せない状態)になります。
購入上限及び所持金不足の時にfalse、購入できる時だけtrueにしてあげるので、以下のように修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
if (currentCnt >= sheepData.maxCount) //購入上限かどうか { infoText.text = "完売"; button.interactable = false; //ボタン無効 } else if (wallet.money >= price) //所持金の方が上 { infoText.text = "購入"; button.interactable = true; //ボタン有効 } else //所持金が足りない { infoText.text = "お金が足りません"; button.interactable = false; //ボタン無効 } |
(慎重な方はこの時点でスクリプトを保存してUnityEditor上で挙動を確認してみましょう。こまめな確認は不具合を減らすのに非常に有効で良い事です。)
羊を購入しても金額が減らない・羊の頭数が変わらない(羊の値段が上がっていかない)
これは、羊生成処理:SheepCreateの中で所持金オブジェクト:Wallet.money を減らし、現在の頭数を表す sheepCntを増やすだけで良さそうです。
1 2 3 4 5 6 7 |
public void CreateSheep() { sheepGenerator.CreateSheep(sheepData); var price = sheepData.basePrice + sheepData.extendPrice * currentCnt; //現在の頭数から、次の購入金額を計算 wallet.money -= price; //購入した分所持金からマイナス currentCnt++; //現在の頭数+1 } |
この時点で、SheepButtonスクリプトはこのようになっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SheepButton : MonoBehaviour { [SerializeField] private Button button; public SheepData sheepData; public SheepGenerator sheepGenerator; //羊画像 [SerializeField] private Image sheepImage; //金額Text [SerializeField] private Text priceText; //頭数Text [SerializeField] private Text countText; //販売可否Text [SerializeField] private Text infoText; //所持金オブジェクト public Wallet wallet; //現在の頭数 public int currentCnt; public void CreateSheep() { var price = sheepData.basePrice + sheepData.extendPrice * currentCnt; //現在の頭数から、次の購入金額を計算 wallet.money -= price; //購入した分所持金からマイナス currentCnt++; //現在の頭数+1 sheepGenerator.CreateSheep(sheepData); } // Start is called before the first frame update void Start() { button.onClick.AddListener(CreateSheep); } // Update is called once per frame void Update() { var price = sheepData.basePrice + sheepData.extendPrice * currentCnt; //現在の頭数から、次の購入金額を計算 sheepImage.color = sheepData.color; //羊の色をセット priceText.text = price.ToString("C0"); //金額表示なので"C0"を指定 countText.text = $"{currentCnt}頭/{sheepData.maxCount}頭"; //現在の頭数と上限表示 if (currentCnt >= sheepData.maxCount) //購入上限かどうか { infoText.text = "完売"; button.interactable = false; //ボタン無効 } else if (wallet.money >= price) //所持金の方が上 { infoText.text = "購入"; button.interactable = true; //ボタン有効 } else //所持金が足りない { infoText.text = "お金が足りません"; button.interactable = false; //ボタン無効 } } } |
さて、保存をしてUnityEditorで確認してみましょう。
羊(Sheep)の購入・お金が足りなくて購入出来ないので羊毛(Wool)の売却・また羊の購入・・・と一連の流れが出来ました!
なお、最初からシーン上にいる1頭がカウントから外れてしまっていますが、この羊はあくまでもテスト用なので、最終的にはシーン配置からも外す想定です。
加えて羊毛の値段が高すぎてあっという間に購入上限に達してしまいますが、それはまた次回修正予定です。
リファクタリング
※ここは余裕が無い方・動きさえすればよい。という方は飛ばして頂いて構いません。
先ほどのSheepButtonスクリプト修正の中で
1 |
var price = sheepData.basePrice + sheepData.extendPrice * currentCnt; //現在の頭数から、次の購入金額を計算 |
という、購入金額(price)を求める計算が、2回出てきている事に気付きましたでしょうか?(分かりやすいように、コメントまで同じにしてあります)
CreateSheepメソッドで1回、Updateメソッドで1回です。(人によっては同じだと気付いてコピーアンドペーストをした方もいるかもしれません。)
もちろんこれでも想定通りに動きますが、このように「同じもの(今回で言うと購入金額)」を表すためのものが2か所で計算で求められている。 というのはあまりよくありません。
同じものを表す処理(特に計算処理等)が複数回出てきたら、出来るだけ1つにまとめるのをオススメします。
そのためには色々と方法はありますが、今回は「現在の羊の購入金額」を返却するメソッド「GetPrice()」を作ってしまいましょう。
1 2 3 4 5 |
//現在の羊の金額を返却 public int GetPrice() { return sheepData.basePrice + sheepData.extendPrice * currentCnt; //現在の頭数から、次の購入金額を計算 } |
そして元々購入金額を計算していた箇所2か所はこのGetPrice()を使用するように修正します。
修正後のSheepButtonスクリプトはこのようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SheepButton : MonoBehaviour { [SerializeField] private Button button; public SheepData sheepData; public SheepGenerator sheepGenerator; //羊画像 [SerializeField] private Image sheepImage; //金額Text [SerializeField] private Text priceText; //頭数Text [SerializeField] private Text countText; //販売可否Text [SerializeField] private Text infoText; //所持金オブジェクト public Wallet wallet; //現在の頭数 public int currentCnt; //現在の羊の金額を返却 public int GetPrice() { return sheepData.basePrice + sheepData.extendPrice * currentCnt; //現在の頭数から、次の購入金額を計算 } public void CreateSheep() { var price = GetPrice();//現在の購入金額を取得 wallet.money -= price; //購入した分所持金からマイナス currentCnt++; //現在の頭数+1 sheepGenerator.CreateSheep(sheepData); } // Start is called before the first frame update void Start() { button.onClick.AddListener(CreateSheep); } // Update is called once per frame void Update() { var price = GetPrice(); //現在の購入金額を取得 sheepImage.color = sheepData.color; //羊の色をセット priceText.text = price.ToString("C0"); //金額表示なので"C0"を指定 countText.text = $"{currentCnt}頭/{sheepData.maxCount}頭"; //現在の頭数と上限表示 if (currentCnt >= sheepData.maxCount) //購入上限かどうか { infoText.text = "完売"; button.interactable = false; //ボタン無効 } else if (wallet.money >= price) //所持金の方が上 { infoText.text = "購入"; button.interactable = true; //ボタン有効 } else //所持金が足りない { infoText.text = "お金が足りません"; button.interactable = false; //ボタン無効 } } } |
ただし、これは修正前と実行結果は何も変わりません(むしろ変わってしまっていたら何か修正を誤っている可能性が高いので、コードを見直してみてください)
なんでそんな無駄な事を? と思う方もおられるでしょう。
ただ、例えば「羊が安すぎて面白くないので、全体的に購入金額を倍に上げたい」と考えたとします。
そんな時に修正前のスクリプトだと誤ってどちらか一方の購入金額の計算だけを2倍にしてしまう。 というリスクが潜んでいます。
その点、今回のように GetPrice() という、購入金額を返却するメソッドがあれば、購入額を倍にするのであればそのメソッドの中を修正すればいいというのは一目瞭然ですし、「購入できるかどうかの判定の金額」と「実際の購入金額」が異なる事がなくなります。
すなわち仕様の変更に強くなっているわけです。
このように挙動は変えずに、スクリプトの内部構造を(良い方に)整理することをリファクタリングと言います。
もしかしたら既にこの一連の講座の中にも重複している処理や、無駄な処理なんかがあるかもしれません。 もし見つけたら「リファクタリング」してみてもいいかもしれませんね。(もちろん、「挙動が変わってないかどうか」の確認は念入りに!)
おさらいと次回予告
今回は羊購入ボタンを作っていきました。
次回は複数の羊の購入のための店(Shop)オブジェクトの作成などをやっていきます。
次回の記事↓
コメント
SheepButtonのスクリプトなんですけど、Buttonのコンポーネントを取得するのに[SerializeField]を使ってインスペクターから指定するのと、Awakeとかでthis.GetCompornentから指定するの、どちらも得られる結果は同じですが、インスペクターからの指定にしているのは何か理由があるのですか?
どちらでもOKです!
この講座はまだC#でのコーディングに慣れてない方も対象にしているのでインスペクターから設定する形を採用しました。
処理を使いまわしたい場合や保守性の観点を考えるとスクリプトから操作した方が良いかもしれません。
お好みで改造・変更していただいて問題ないかなと思います。
ご返信ありがとうございます!わかりました。
これまで我流でUnityを使ってきたので、ScriptableObjectの概念など知らなかったことが勉強できて大変助かっています。
分かりやすい講座を作ってくれてどうもありがとうございます。