前回の記事でブラックジャックの大まかな流れを作成することができました。
前回の記事:
今回はHit、Stand処理を作り、カードを引くか引かないかの分岐や、ジャック、クイーン、キングを10扱いにしたり、エースを11or1で選択できるようにしていきます。
ここまで作り終えたら、ブラックジャックは完成です!
ゲームの結果をポイントに反映させてみよう!
前回の記事ではベットを決定し、カードを配り、プレイヤーの行動の分岐&実行処理まで作りました。
あとはプレイヤーの行動に合わせてカードの数字を足し21を超えたかどうかを判定すればゲームの流れは完成します。
それでは実際にプレイヤーが引いたカードの数値を足してみて21を超えたかどうか判定してみましょう!
ヒットを作る
プレイヤーの行動にはヒットとスタンドの2種類ありますが、まずヒットの方を作ってみましょう!
前回の記事でプレイヤーの行動はSceneManagerのAction列挙型で定義しています。
また、GameLoopメソッドの中でプレイヤーの行動に合わせた処理を行なっています。
ヒットの処理を実装する場所はその中のAction.Hitの部分になります。
新しくSceneManagerコンポーネントに追加するCheckPlayerCardsメソッドはプレイヤーが引いたカードの数字を足し合わせて21を超えたかどうかを判定するメソッドになります。
そのメソッドがfalseを返した場合はヒットを選択した場合でもループから抜けるようにします。
また、現在のゲームに勝ったか負けたかを表すdoWin変数も追加しています。
プレイヤーの行動用のループを抜けたあとにポイント計算を行なっているので、doWin変数はそちらで使用します。
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 |
//…
public class SceneManager : MonoBehaviour { //… IEnumerator GameLoop() { while(true) { //ベットを決めるまで待つ //… //カードを配る //… // プレイヤーが行動を決めるまでまつ bool waitAction = true; bool doWin = false; do { //… // 行う行動に合わせて処理を分岐する switch (CurrentAction) { case Action.Hit: PlayerDealCard(); waitAction = true; if(!CheckPlayerCard()) { waitAction = false; doWin = false; } break; case Action.Stand: //… } } while (waitAction); // ゲームの結果を判定する if(doWin) { currentPoint += currentBets; } else { currentPoint -= currentBets; } PointText.text = currentPoint.ToString(); } } //…
bool CheckPlayerCard() { var sumNumber = 0; foreach(var card in Player.transform.GetComponentsInChildren<Card>()) { sumNumber += card.Number; } return (sumNumber < 21); } //… } |
上のサンプルコードでのTransform.GetComponentsInChildren
メソッドは子GameObjectにアタッチされているコンポーネント全てを取得するメソッドなので覚えておくと便利でしょう。
ヒットを選択した際にプレイヤーカードの数の合計が21を超えた場合、ポイントがベットの分減るようになっているので確認してみましょう!
実行したところ、少しわかりづらいのでゲームの結果を判定した後にその結果を画面に表示させてみましょう!
シーンの「Canvas」にUI > Textをクリックしテキストを追加してください。名前は「ResultText」にしてください。
「ResultText」はゲームの結果がわかるように大きなものにしてください。
追加したら、次のコードをSceneManagerコンポーネントに追加してください。
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 |
//…
public class SceneManager : MonoBehaviour { //… public Text ResultText; public float WaitResultSeconds = 2; //… IEnumerator GameLoop() { //… ResultText.gameObject.SetActive(false); while(true) { //ベットを決めるまで待つ //… //カードを配る //… // プレイヤーが行動を決めるまでまつ //… // ゲームの結果を判定する ResultText.gameObject.SetActive(true); if (doWin) { currentPoint += currentBets; ResultText.text = "Win!! +" + currentBets; } else { currentPoint -= currentBets; ResultText.text = "Lose... -" + currentBets; } PointText.text = currentPoint.ToString(); yield return new WaitForSeconds(WaitResultSeconds); ResultText.gameObject.SetActive(false); } } //… } |
問題なければ結果に応じてテキストが画面に表示されるようになっています。
スタンドを作る
次にプレイヤーの行動のスタンドを作りましょう!
こちらもヒットと同じくGameLoopメソッドの中でプレイヤーの行動の処理を行なっている部分のAction.Standの部分に処理を追加します。
スタンドの処理はStandActionメソッドの中で行なっています。
StandActionメソッドはプレイヤーが勝つとtrueを返し、負けるとfalseを返すようになっています。
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 |
//…
public class SceneManager : MonoBehaviour { //… IEnumerator GameLoop() { while(true) { //ベットを決めるまで待つ //… //カードを配る //… // プレイヤーが行動を決めるまでまつ bool waitAction = true; bool doWin = false; do { //… // 行う行動に合わせて処理を分岐する switch (CurrentAction) { case Action.Hit: //… case Action.Stand: waitAction = false; doWin = StandAction(); break; //… } } while (waitAction); // ゲームの結果を判定する //… } } //… bool StandAction() { var sumPlayerNumber = 0; foreach (var card in Player.transform.GetComponentsInChildren<Card>()) { sumPlayerNumber += card.Number; } var sumDealerNumber = 0; foreach (var card in Dealer.transform.GetComponentsInChildren<Card>()) { sumDealerNumber += card.Number; if (card.IsReverse) {//裏面のカードを表向きにする card.SetCard(card.Number, card.CurrentMark, false); } } return sumPlayerNumber > sumDealerNumber; } //… } |
実装できたら再生して動作を確認しましょう!
J、Q、Kを10として扱うようにしよう!
実際のブラックジャックではJack、Queen、Kingは数字の10として扱いますが、まだその処理を作っていないので引いたカードによっては最初から21を超えてしまう場合もあります。
そのようなケースにはサンプルコードでは対応していないので、次はその部分の対応をしてみましょう!
CardコンポーネントにUseNumberプロパティを追加してください。
UseNumberプロパティはNumberフィールドの値を見てブラックジャックで使う数字に変換するものになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//…
public class Card : MonoBehaviour { //… public int UseNumber { get { if (Number > 10) return 10; return Number; } } //… } |
追加できたら、SceneManagerコンポーネントの中からカードの合計を計算している部分のCardコンポーネントのNumberフィールドの部分をUseNumberプロパティに変更します。
変更する部分はCheckPlayerメソッドとStandActionメソッドの中になります。
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 |
//…
public class SceneManager : MonoBehaviour { //… bool CheckPlayerCard() { var sumNumber = 0; foreach(var card in Player.transform.GetComponentsInChildren<Card>()) { sumNumber += card.UseNumber; // <- 変更 } return (sumNumber <= 21); } //…
bool StandAction() { var sumPlayerNumber = 0; foreach (var card in Player.transform.GetComponentsInChildren<Card>()) { sumPlayerNumber += card.UseNumber; // <- 変更 } var sumDealerNumber = 0; foreach (var card in Dealer.transform.GetComponentsInChildren<Card>()) { sumDealerNumber += card.UseNumber; // <- 変更 if (card.IsReverse) {//裏面のカードを表向きにする card.SetCard(card.Number, card.CurrentMark, false); } } return sumPlayerNumber > sumDealerNumber; } //…
} |
変更できたら再生して動作を確認しましょう!
エースを1か11か選択できるようにしよう!
次にブラックジャックでは数字の1の場合は1か11のどちらか選択できるようになっているのでそちらの対応もしていきましょう!
エースの数値選択の機能実装のためにはCardコンポーネントを拡張する必要があります。
- Bool型のIsLargeフィールドを追加する
- UseNumberプロパティを拡張し数字の1の場合はIsLargeフィールドに合わせて返す値を1または11で切り替えるようにする
また、エースの場合のみ数字の1か11どちらを選択したかわかるように「Card」プレハブの右下あたりにテキストを追加します。
「Card」プレハブに追加するGameObject
- テキストを追加し、名前を「OptionalNumberText」にする。
- 右下に配置する。
- 見た目を整える。
追加した「OptionalNumberText」は、CardコンポーネントのSetCardメソッドの中で制御します。
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 |
//…
public class Card : MonoBehaviour { //… public bool IsLarge = false; public int UseNumber { get { if (Number > 10) return 10; if(Number == 1) { return IsLarge ? 11 : 1; } return Number; } } //… public void SetCard(int number, Mark mark, bool isReverse) { //… var optionalNumberObj = transform.Find("OptionalNumberText"); optionalNumberObj.gameObject.SetActive(!IsReverse && Number == 1); if (Number == 1) { var optionalNumberText = optionalNumberObj.GetComponent<Text>(); optionalNumberText.text = UseNumber.ToString(); } } } |
上のコードを書いてCardコンポーネントへのエースカードの数値変換処理はできました。
IPointerClickHandlerインターフェースについて
あとはどちらの数字にするか選択できるようにするだけです。
ここは簡単にエースのカードをクリックすると1か11かで切り替わるようにしましょう!
注意点としてUnity UIのコンポーネントではOnMouseAsButtonメソッドでのクリック操作は基本的にできません。
代わりにUnity UIではUnityEngine.EventSystems.IPointerClickHandler
インターフェースをコンポーネントに継承することでどのGameObjectでもクリック操作に対応することができます。
UnityEngine.EventSystems.IPointerClickHandler
インターフェースを継承した場合はOnPointerClick(PointerEventData eventData)メソッドを実装する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//… using UnityEngine.EventSystems; //…
public class Card : MonoBehaviour, IPointerClickHandler { //… public void OnPointerClick(PointerEventData eventData) { if (Number == 1) { IsLarge = !IsLarge; SetCard(Number, CurrentMark, IsReverse); } } } |
このサンプルコードを追加すれば、カードがAの時にクリックすると先ほど追加したテキストが1か11かで切り替わるようになります。
ちなみにUnityEngine.EventSystems.IPointerClickHandler
には他にもいくつかの種類があります。これらのインターフェースを継承することでクリック以外の操作にも対応することができます。
下にあげたインターフェイスは全てUnityEngine.EventSystems
の中にあります。
- IPointerEnterHandler:マウスカーソルなどのポインターが上に来た時のイベント
- IPointerExitHandler:マウスカーソルなどのポインターが上から外れた時のイベント
- IPointerDownHandler:マウスカーソルなどのポインターを押した時のイベント
- IPointerUpHandler:マウスカーソルなどのポインターを話した時のイベント
- IPointerClickHandler:マウスカーソルなどのポインターでクリックした時のイベント
- IBeginDragHandler:マウスカーソルなどのポインターでドラックし始めた時のイベント
- IDragHandler:マウスカーソルなどのポインターでドラックしている時のイベント
- IEndDragHandler:マウスカーソルなどのポインターでドラックし終えた時のイベント
- IDropHandler:マウスカーソルなどのポインターでドラック中にドロップ操作をした時のイベント
ちなみにこれらのインターフェースはシーン上にEventSystemコンポーネント及びInputModuleコンポーネントがないと作動しません。
これらのコンポーネントはメニューのGameObject > UI > Event Systemで作成できるGameObjectにアタッチされているので上記インターフェースを利用したい場合は忘れずシーンに配置しておくようにしましょう。
ディーラーのエースは11にする
トランプのカードのエースを1か11かで選択できるようになりました。
ディーラーにもエースが配られる場合もあるのでその場合は必ず11になるようにSceneManagerコンポーネントを修正していきます。
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 |
//…
public class SceneManager : MonoBehaviour { //… void DealCards() { foreach (Transform card in Dealer.transform) { Object.Destroy(card.gameObject); } foreach (Transform card in Player.transform) { Object.Destroy(card.gameObject); } //ディーラーに2枚カードを配る var holeCardObj = Object.Instantiate(CardPrefab, Dealer.transform); holeCardObj.IsLarge = holeCardObj.Number == 1; //ディーラーのエースカードは必ず11にする var holeCard = DealCard(); holeCardObj.SetCard(holeCard.Number, holeCard.Mark, true); var upCardObj = Object.Instantiate(CardPrefab, Dealer.transform); upCardObj.IsLarge = upCardObj.Number == 1; //ディーラーのエースカードは必ず11にする var upCard = DealCard(); upCardObj.SetCard(upCard.Number, upCard.Mark, false); //プレイヤーにカードを2枚配る for (var i = 0; i < 2; ++i) { var cardObj = Object.Instantiate(CardPrefab, Player.transform); var card = DealCard(); cardObj.SetCard(card.Number, card.Mark, false); } } //…
} |
追加できたら、ディーラーのエースは必ず11になるはずです。
ちなみにディーラーのエースをクリックすると数値も切り替わってしまいますが、裏技的な感じがするのであえてここではそのままにしておきます。
そのような処理を防ぎたい場合はCardコンポーネントのOnPointerClickメソッドでディーラーのカードの時だけクリックできないようにする必要がありますので腕に自信がある人は作ってみるのもいいでしょう。
ゲームオーバーとクリア処理を追加する。
最後にゲームポイントを見て0以下になったらゲームオーバー、目標ポイントを上回ったらゲームクリアになるようにしましょう!
変更する部分はSceneManagerコンポーネントのGameLoopメソッドのwhilte文の最後になります。
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 |
//…
public class SceneManager : MonoBehaviour { //… public Text GoalPointText; public int goalPoint = 40; //… private void Awake() { //… GoalPointText.text = goalPoint.ToString(); } //… IEnumerator GameLoop() { //… while (true) { //…while文の最後に追加する if(currentPoint <= 0) { ResultText.gameObject.SetActive(true); ResultText.text = "Game Over..."; break; } if(currentPoint >= goalPoint) { ResultText.gameObject.SetActive(true); ResultText.text = "Game Clear!!"; break; } } } //…
} |
上のスクリプトを追加したら、シーンに目標ポイントを表示するためのGameObjectを追加しましょう!
追加する場所は「UI」の子GameObjectがいいでしょう。
「UI」の子GameObjectである「Point」を複製して「GoalPoint」と名付けましょう。
「GoalPoint」の子GameObjectには次の操作を行ってください。
- 「Label」のテキストを「目標ポイント」に変更する
- 「PointText」の名前を「GoalPointText」に変更する
ここまでできたらSceneManagerコンポーネントのGoalPointTextに「GoalPoints」を必ずアタッチしてください。
この状態で再生するとポイントによってゲームオーバーになったり、クリアになったりするはずです。目標ポイントは適宜調整してください。
ここまででついにブラックジャック完成です!
まとめ
今回の記事でブラックジャックを完成させることができました!
実際にゲームを作る際はブロック崩しやシューティングゲームなどより、ブラックジャックのようなカードゲームの方がUIやゲームの流れがきっちりしているので手間がかかりやすくなります。
今回のブラックジャックの作り方講座ではゲームに必要な要素を最小限作っているだけですが、それでも結構な分量になります。
腕に自信がある方は本講座で作ったものを見栄えよくしたり、より遊びやすくなるように改造していくといいでしょう!
その際は毎回一から作成するのではなく、既に作ったものを使い回して作業量を少なくするのがおすすめです。
将来の拡張性のことを考えて、プレハブの内容やコンポーネントの内容をシンプルに保てるといいですね。
今回の記事ではコンポーネントのメソッド単位が長いものもいくつか出てきましたが、そのようなメソッドもよく見ると分割してもいい部分やちょっと工夫すると他の部分でも使いまわせそうな処理があります。
同じ処理が出てきてる部分を上手にまとめることができると利用しやすいコンポーネントやクラスなどになっていくので、そのあたりも今後意識しながら作っていきましょう!
例として挙げると、SceneManagerコンポーネントのDealCardsメソッドの次の部分を見てみましょう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//…
public class SceneManager : MonoBehaviour { //…
void DealCards() { foreach (Transform card in Dealer.transform) { Object.Destroy(card.gameObject); } foreach (Transform card in Player.transform) { Object.Destroy(card.gameObject); } //… } //…
} |
シーンに配られたカードを削除している処理ですが、次のように子GameObjectを削除するメソッドを新しく追加し、それを使うようにするとわかりやすくなります。
今回例に挙げたものでは拡張メソッドとして定義するとSceneManager以外でも使えるようになるので、より便利になると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//どこか別のファイルに定義する public static partial class TransformExtensions { public static void DestroyAllChildGameObject(this Transform parent) { foreach (Transform child in parent) { Object.Destroy(child.gameObject); } } } //使用例 //簡単に子GameObjectが削除できるようになる Dealer.transform.DestroyAllChildObject(); Player.transform.DestroyAllChildObject(); |
今回の記事をまとめると次のようになります。
- コンポーネントに
UnityEngine.EventSystem.IPointerClickHandler
を継承するとクリックした時の処理を追加できる。 - 似たような処理を上手にまとめるとコンポーネントを作りやすくなるのでどんどんまとめるのがおすすめ!
次からはまた別のゲームを作っていきましょう!
追記:ブラックジャックゲームのバグを修正しよう!
ここまでで完成!したと思っていましたが、バグを発見しました。
あなたはどこにバグがあるかわかったかな?
答えは・・・
「”A”のカードを11にしてから22を超えた時の処理が組まれていない」
です。わざわざ負ける選択肢を選ぶ人はいないはずですが、念のため修正しておきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
~~ bool StandAction() { var sumPlayerNumber = 0; foreach (var card in Player.transform.GetComponentsInChildren<Card>()) { sumPlayerNumber += card.UseNumber; } var sumDealerNumber = 0; foreach (var card in Dealer.transform.GetComponentsInChildren<Card>()) { sumDealerNumber += card.UseNumber; if (card.IsReverse) {//裏面のカードを表向きにする card.SetCard(card.Number, card.CurrentMark, false); } } if (!CheckPlayerCard()) return false; return sumPlayerNumber > sumDealerNumber; } |
if (!CheckPlayerCard()) return false;を追加し、プレイヤーのカードがStandを選んだ場合でも22以上になってないか判定しています。
参考用:スクリプトの完成図
ちなみに今回の記事で作成したスクリプトの完成図は次のようになります。
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; public class Card : MonoBehaviour, IPointerClickHandler { public class Data { public Mark Mark; public int Number; } // Start is called before the first frame update public enum Mark { Heart, Diamond, Spade, Crub, } public bool IsReverse = false; [Range(1, 13)] public int Number = 1; public Mark CurrentMark = Mark.Heart; public bool IsLarge = false; public int UseNumber { get { if (Number > 10) return 10; if (Number == 1) { return IsLarge ? 11 : 1; } return Number; } } public void SetCard(int number, Mark mark, bool isReverse) { Number = Mathf.Clamp(number, 1, 13); CurrentMark = mark; IsReverse = isReverse; //CardプレハブのGameObjectを更新する。 //カードの裏表に合わせて色などを設定する var image = GetComponent<Image>(); if (IsReverse) { image.color = Color.black; } else { image.color = Color.white; } foreach (Transform child in transform) { child.gameObject.SetActive(!IsReverse); } //マークに合わせてGameObjectを設定する var markObj = transform.Find("Mark"); var markText = markObj.GetComponent<Text>(); switch (CurrentMark) { case Mark.Heart: markText.text = "❤️"; markText.color = Color.red; break; case Mark.Diamond: markText.text = "♦️"; markText.color = Color.red; break; case Mark.Spade: markText.text = "♠️"; markText.color = Color.black; break; case Mark.Crub: markText.text = "♣️"; markText.color = Color.black; break; } //数字に合わせてGameObjectを設定する var numberObj = transform.Find("NumberText"); var numberText = numberObj.GetComponent<Text>(); if (Number == 1) { numberText.text = "A"; } else if (Number == 11) { numberText.text = "J"; } else if (Number == 12) { numberText.text = "Q"; } else if (Number == 13) { numberText.text = "K"; } else { numberText.text = Number.ToString(); } //エースカードを1か11に切り替える var optionalNumberObj = transform.Find("OptionalNumberText"); optionalNumberObj.gameObject.SetActive(!IsReverse && Number == 1); if (Number == 1) { var optionalNumberText = optionalNumberObj.GetComponent<Text>(); optionalNumberText.text = UseNumber.ToString(); } } private void OnValidate() { SetCard(Number, CurrentMark, IsReverse); } public void OnPointerClick(PointerEventData eventData) { if (Number == 1) { IsLarge = !IsLarge; SetCard(Number, CurrentMark, IsReverse); } } } |
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class SceneManager : MonoBehaviour { public Card CardPrefab; public GameObject Dealer; public GameObject Player; public GameObject BetsInputDialog; public InputField BetsInput; public Button BetsInputOKButton; public Text BetsText; public Text PointText; public Text ResultText; public float WaitResultSeconds = 2; //パラメータ public int StartPoint = 20; int currentPoint; int currentBets; [Min(100)] public int ShuffleCount = 100; public Text GoalPointText; public int goalPoint = 40; List<Card.Data> cards; public enum Action { WaitAction = 0, Hit = 1, Stand = 2, } Action CurrentAction = Action.WaitAction; public void SetAction(int action) { CurrentAction = (Action)action; } private void Awake() { BetsInput.onValidateInput = BetsInputOnValidateInput; BetsInput.onValueChanged.AddListener(BetsInputOnValueChanged); GoalPointText.text = goalPoint.ToString(); } char BetsInputOnValidateInput(string text, int startIndex, char addedChar) { if (!char.IsDigit(addedChar)) return '\0'; return addedChar; } void BetsInputOnValueChanged(string text) { BetsInputOKButton.interactable = false; if (int.TryParse(BetsInput.text, out var bets)) { if (0 < bets && bets <= currentPoint) { BetsInputOKButton.interactable = true; } } } IEnumerator GameLoop() { currentPoint = StartPoint; BetsText.text = "0"; PointText.text = currentPoint.ToString(); ResultText.gameObject.SetActive(false); while (true) { InitCards(); //カードを初期化する yield return null;//何か実装するまで残しておく //ベットを決めるまで待つ do { BetsInputDialog.SetActive(true); yield return new WaitWhile(() => BetsInputDialog.activeSelf); //入力したテキストを使用できるものかチェックする if (int.TryParse(BetsInput.text, out var bets)) { if (0 < bets && bets <= currentPoint) { currentBets = bets; break; } } } while (true); //画面の更新 BetsInputDialog.SetActive(false); BetsText.text = currentBets.ToString(); //カードを配る DealCards(); // プレイヤーが行動を決めるまで待つ bool waitAction = true; bool doWin = false; do { CurrentAction = Action.WaitAction; yield return new WaitWhile(() => CurrentAction == Action.WaitAction); // 行う行動に合わせて処理を分岐する switch (CurrentAction) { case Action.Hit: PlayerDealCard(); waitAction = true; if (!CheckPlayerCard()) { waitAction = false; doWin = false; } break; case Action.Stand: waitAction = false; doWin = StandAction(); break; default: waitAction = true; throw new System.Exception("知らない行動をしようとしています。"); } } while (waitAction); //ゲームの結果を判定する ResultText.gameObject.SetActive(true); if (doWin) { currentPoint += currentBets; ResultText.text = "Win!! +" + currentBets; } else { currentPoint -= currentBets; ResultText.text = "Lose... -" + currentBets; } PointText.text = currentPoint.ToString(); yield return new WaitForSeconds(WaitResultSeconds); ResultText.gameObject.SetActive(false); //ゲームオーバー・ゲームクリア処理 if (currentPoint <= 0) { ResultText.gameObject.SetActive(true); ResultText.text = "Game Over..."; break; } if (currentPoint >= goalPoint) { ResultText.gameObject.SetActive(true); ResultText.text = "Game Clear!!"; break; } } } Coroutine _gameLoopCoroutine; private void Start() { _gameLoopCoroutine = StartCoroutine(GameLoop()); } void InitCards() { cards = new List<Card.Data>(13 * 4); var marks = new List<Card.Mark>() { Card.Mark.Heart, Card.Mark.Diamond, Card.Mark.Spade, Card.Mark.Crub, }; foreach(var mark in marks) { for(var num=1; num<=13; ++num) { var card = new Card.Data() { Mark = mark, Number = num, }; cards.Add(card); } } ShuffleCards(); } void ShuffleCards() { //シャッフルする var random = new System.Random(); for(var i=0; i<ShuffleCount; ++i) { var index = random.Next(cards.Count); var index2 = random.Next(cards.Count); //カードの位置を入れ替える。 var tmp = cards[index]; cards[index] = cards[index2]; cards[index2] = tmp; } } Card.Data DealCard() { if (cards.Count <= 0) return null; var card = cards[0]; cards.Remove(card); return card; } void DealCards() { foreach (Transform card in Dealer.transform) { Object.Destroy(card.gameObject); } foreach (Transform card in Player.transform) { Object.Destroy(card.gameObject); } //ディーラーに2枚カードを配る var holeCardObj = Object.Instantiate(CardPrefab, Dealer.transform); holeCardObj.IsLarge = holeCardObj.Number == 1; //ディーラーのエースカードは必ず11にする var holeCard = DealCard(); holeCardObj.SetCard(holeCard.Number, holeCard.Mark, true); var upCardObj = Object.Instantiate(CardPrefab, Dealer.transform); upCardObj.IsLarge = upCardObj.Number == 1; //ディーラーのエースカードは必ず11にする var upCard = DealCard(); upCardObj.SetCard(upCard.Number, upCard.Mark, false); //プレイヤーにカードを2枚配る for (var i = 0; i < 2; ++i) { var cardObj = Object.Instantiate(CardPrefab, Player.transform); var card = DealCard(); cardObj.SetCard(card.Number, card.Mark, false); } } void PlayerDealCard() { var cardObj = Object.Instantiate(CardPrefab, Player.transform); var card = DealCard(); cardObj.SetCard(card.Number, card.Mark, false); } bool CheckPlayerCard() { var sumNumber = 0; foreach (var card in Player.transform.GetComponentsInChildren<Card>()) { sumNumber += card.UseNumber; } return (sumNumber < 21); } bool StandAction() { var sumPlayerNumber = 0; foreach (var card in Player.transform.GetComponentsInChildren<Card>()) { sumPlayerNumber += card.UseNumber; } var sumDealerNumber = 0; foreach (var card in Dealer.transform.GetComponentsInChildren<Card>()) { sumDealerNumber += card.UseNumber; if (card.IsReverse) {//裏面のカードを表向きにする card.SetCard(card.Number, card.CurrentMark, false); } } if (!CheckPlayerCard()) return false; return sumPlayerNumber > sumDealerNumber; } } |
コメント
完成しました!!いつも丁寧で応用の効く解説ほんとうに助かります!!
完成おめでとうございます!
そしてコメントありがとうございます^^
これからも役立つ講座作り続けます!
全5回、拝読させていただきました。
ここまでやったのですが何故かベッドを入力→カードを引くという処理が実行されません。スクリプトを穴があくほど見直したのですがこちらでは皆目検討もつきませんでした。ありがちなミスなどがあればご教授お願いします。
講座読んでいただきありがとうございます!
最後までできた人が何人もおられるので恐らくどこかでミスがあるのかなと思います。
アタッチのところで何か忘れてるものがあるとかかがUnityだとありがちですね。
古典的ではありますが、ボタンを引くという動作の前後にDebug.Log(“ここまで動いてるよ”);などを入れて動作がどこまで走ってるかを確認するなどしてみると良いかなと思います。
コメントが遅れました。無事に動くようになりました!どうやら原因はOKButtonのOnClick()でFalseにしていなかったのとEditor And Runtimeに設定してないことだったようです。
ともかく分かりやすい講座をありがとうございました!
おー!無事に完成できたようで何よりです!
わざわざ報告コメントしていただきありがとうございました!
最後まで読んでいただけてうれしいです^^
完成しました!最後まで分かりやすく丁寧な解説で楽しく終えることができました!!ありがとうございます!!!
恐縮ながら質問させていただきたいです。完成形のSceneManagerの256行目で、IsLargeをtrueにしてホールカードがAであれば11にする処理を行っていますが、249行目のSetCard()より前にこの処理をしていないと、SetCard()内でIsLargeの判定を行っているので、Aを11にできないのではないでしょうか?(ホールカードがIsLarge=trueなのにOptionalNumberText=”1″になってしまう感じです!)
完成おめでとうございます!
そして質問ありがとうございます。
isorokuさんの仰る通りでした!順序ミスってますね。
早速記事の方修正しておきました。
また、エース関係の処理を見直していく過程でもう一つバグを発見したので追記しておきました。そちらももしよかったらご覧ください。
わざわざコメントしていただきありがとうございました!
すみません、HitButtonとStandButtonのOnClick()に入れるものがよく分かりません。どうか教えて頂けないでしょうか?
ブラックジャック講座4章の「プレイヤーの行動を選択するためのボタンを実装しよう」の内容にてテキストで解説してある通り、二つのボタンに入れるメソッドはSetActionメソッドになります。
視覚的にわかりやすいように画像を二枚追加しておいたのでご確認ください。
“設定するメソッドはSetActionメソッドになります。”の下に画像を追加しています。
上手くサイトにコメントが表示されず、何度も同じ質問をしていまい申し訳ありませんでした。ご回答ありがとうございます。
すみません、HItButtonとStandButtonのOnClick()の中身がよく分かりません。どうか教えて頂けないでしょうか?よろしくお願いします。