前回までの記事で、マナポイントの実装まで出来たね!
やってない人は前回の記事参照↓
今回の記事では、敵カードの攻撃を実装していくよ!!
完成形はこんな感じ↓
今回の敵の攻撃を実装出来れば、完成はもうすぐ!!!
ゲームシステムの大枠はこれで完成するのでかなりカードゲームらしくなるよ!
では張り切っていこー!!
Contents
【全体概要】全体の流れの説明
ではまず、今回やっていく作業全体の説明をしていきます!
今回の実装の流れはこんな感じ↓
- 敵の攻撃可能なカードに緑の枠を付ける
- リーダーへの攻撃処理を実装する
- プレイヤーの場にカードがある場合は、カードに攻撃するようにする
- 処理に待ち時間を作る
今回はコードの追記を説明しながら、
「ここのコードは~という意味だよ!」という解説を進めていくよ!!
【お願い & 注意点】
・今回はコードの説明をしながらゲームを実装する構成にしました。
・今までと説明の順番を変えてみたので、
「コードを書いてから後でまとめて説明」or
「コードを書きながら説明」
のどっちが分かりやすいかをコメントで教えて下さい!
・今後、より分かりやすい記事を提供する為にコメントお願いします!!
【敵の攻撃の実装 & コード解説】
①敵のカードを攻撃可能にする
まずは自分のカードと同じく、相手の場の攻撃可能なカードにも緑の枠を付ける処理を追記していこう!
という訳で、まず相手の場の「攻撃可能なカード」とはどんなカードかを考えよう。
今作ってるゲームでは召喚酔い(カードを出したターンは攻撃出来ない。次のターンになると攻撃できるようになる。)を採用してるので、
相手の場の攻撃可能なカード
というのは、
ターンの最初からフィールドに存在するカード
ってことになるよね?
なので「相手のターンの始まり」から「カードの召喚」の間に、相手のフィールドに出てるカードに緑の枠を付ける処理を追記すればOK!!
緑の枠を付ける処理は、以前作成したものがあるのでそれを利用しよう!
これね↓
1 2 3 4 5 6 7 8 9 |
// 指定のカードリストのカードを攻撃可能にして、緑の枠を付けるメソッド void SetAttackableFieldCard(CardController[] cardList, bool canAttack) { foreach (CardController card in cardList) { card.model.canAttack = canAttack; card.view.SetCanAttackPanel(canAttack); } } |
という訳でここでは、
GameManagerのEnemyTurnメソッドにオレンジ部分を追記しよう↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
void EnemyTurn() { Debug.Log("Enemyのターン"); // ←← 敵のターンの始まり CardController[] enemyFieldCardList = enemyField.GetComponentsInChildren<CardController>(); /// 敵のフィールドのカードを攻撃可能にして、緑の枠を付ける /// SetAttackableFieldCard(enemyFieldCardList,true); if (enemyFieldCardList.Length < 5) // ←←敵のカードの召喚 { if (enemyFieldCardList.Length < 2) { CreateCard(3, enemyField); } else { CreateCard(2, enemyField); } } ChangeTurn(); // ターンエンドする } |
エラーが出てないことを確認してゲームを実行してみよう!
ターンエンドボタンを押すとフィールドに出ていた敵のカードに、
攻撃可能になったことを表す緑の枠が付いたかな??
無事付いたならOK!!次に進もう!
②リーダーへの攻撃を実装する
敵のカードは攻撃可能にはなったけど、何もしないで佇んでるだけ。。
なので次は、自分のリーダーに攻撃してくるようにリーダーへの攻撃を実装しよう!
ここでは以下の処理を追記して、リーダーへの攻撃を実装していくよ!
- 敵のフィールドのカードのリストを作る
- 敵のフィールドカードのリストの中から、攻撃可能なカードがある限り下記の処理を繰り返し実行する
【繰り返し処理内容】
- 攻撃可能なカードの1番目のカードをattackCardとする
- attackCardがリーダーに攻撃する
- attackCardは攻撃済みになる
今回の大事なコード
今回出てくるコードでめっちゃ大事になるのが、この2つ!!
- While
- Array.Exists
それぞれの説明をすると、、
■Whileって??
Whileは
「条件」を満たす限り、
while内の「処理」が繰り返し実行するよ。
ってもの!!
(「条件」を満たさなくなった時点で終了。)
使い方としてはこうだね↓
While (“条件”)
{
ループさせる処理
}
つまり
「お腹がいっぱいになるまでりんごを食べ続けるコードを書きたいよ!!」
って場合は、
While (お腹減ってる)
{
りんごを食べる
}
となるわけさ!!
■Array.Existsって??
もう一つのArray.Existsは、
条件を指定して検索するメソッドで、
条件に合った要素が1つでもあればTrueを返すよ。
ってもの。
使い方としてはこうだね↓
Array.Exists(“配列”, “要素” => ”条件”)
つまり、
「カードパックを買った時に、レアカードがあったらTrueを返すコードが書きたい!!」って時は、
Array.Exists(カードパックのカード達, card => めっちゃレアカード)
というわけだ!!
※Arrayについて詳しく知りたい場合はこちらの記事を読んでみて!
コードの追記
では実際にコードを追記していこう!!
まずはArray.Existsを使うために、using Systemを追記しよう!
GameManagerのNameSpace(一番上の所)にオレンジ部分を追記↓
1 2 3 4 5 |
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using System; |
次にGameManagerのEnemyTurnメソッドにオレンジ部分を追記↓
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 |
void EnemyTurn() { Debug.Log("Enemyのターン"); CardController[] enemyFieldCardList = enemyField.GetComponentsInChildren<CardController>(); foreach (CardController card in enemyFieldCardList) { card.model.canAttack = true; card.view.SetCanAttackPanel(card.model.canAttack); } if (enemyFieldCardList.Length < 5) { if (enemyFieldCardList.Length < 2) { CreateCard(3, enemyField); } else { CreateCard(2, enemyField); } } CardController[] enemyFieldCardListSecond = enemyField.GetComponentsInChildren<CardController>(); while (Array.Exists(enemyFieldCardListSecond, card => card.model.canAttack)) { // 攻撃可能カードを取得 CardController[] enemyCanAttackCardList = Array.FindAll(enemyFieldCardListSecond, card => card.model.canAttack); CardController attackCard = enemyCanAttackCardList[0]; AttackToLeader(attackCard, false); enemyFieldCardList = enemyField.GetComponentsInChildren<CardController>(); } ChangeTurn(); // ターンエンドする } |
GameManagerのAttackToLeaderメソッドのオレンジ部分を追記、修正↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public void AttackToLeader(CardController attackCard, bool isPlayerCard) { if (attackCard.model.canAttack == false) { return; } if (attackCard.model.PlayerCard == true) // attackCardがプレイヤーのカードなら { enemyLeaderHP -= attackCard.model.power; // 敵のリーダーのHPを減らす } else // attackCardが敵のカードなら { playerLeaderHP -= attackCard.model.power; // プレイヤーのリーダーのHPを減らす } //enemyLeaderHP -= attackCard.model.power; // コメントアウトする attackCard.model.canAttack = false; attackCard.view.SetCanAttackPanel(false); Debug.Log("敵のHPは、" + enemyLeaderHP); ShowLeaderHP(); } |
最後にリーダーのHPを上げておいた方が分かりやすいので、雑にHPを50,000に変更しておこう。
GameManagerのStartGameメソッドのオレンジ部分を修正↓
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
void StartGame() // 初期値の設定 { enemyLeaderHP = 5000; playerLeaderHP = 50000; ShowLeaderHP(); /// マナの初期値設定 /// playerManaPoint = 1; playerDefaultManaPoint = 1; ShowManaPoint(); // 初期手札を配る SetStartHand(); // ターンの決定 TurnCalc(); } |
ではエラーメッセージが出てないことを確認して、ゲームを実行してみよう!
攻撃可能な敵のカードのパワーの合計分、毎ターン自分のリーダーのHPが減ってることを確認できたかな??
確認出来たらOK! 次に進もう!!
③フィールドに合わせて攻撃対象を変更する
敵のカードが攻撃してくるようにはなったけど、まだまだリーダーにしか攻撃してこないヤンキープレイしかしてこない!!
という訳で、もう少し頭脳的なプレイをして貰う為に自分のフィールドにカードが出てる場合にはカードに攻撃するという処理を実装していこう!
実装方法としては、こんな感じの流れかな↓
- 「playerFieldCardList」(プレイヤー側のフィールドにいるカードのリスト)を取得
- プレイヤーの場にカードがある場合とない場合で処理を分岐させる
- カードがある場合は、0番目のカードを対象にバトルさせる
- カードがない場合は、リーダーに攻撃させる
コードの解説
今回追記していくコードで見ていくと、、
①まずはプレイヤー側のフィールドのカードのリストを取得
CardController[] playerFieldCardList = playerField.GetComponentsInChildren<CardController>();
②プレイヤーの場にカードがある場合とない場合で処理を分岐させる
if (playerFieldCardList.Length > 0) // プレイヤーの場にカードがある場合
{ プレイヤーのカードへの攻撃処理 }
else // プレイヤーの場にカードがない場合
{ プレイヤーのリーダーへの攻撃処理 }
リストの長さ(playerFieldCardList.Length)でカードが存在しているのかどうかを判断しているよ。
playerFieldCardListの要素の個数が0より多いなら、プレイヤー側のフィールドにカードが存在する。ということだね。
③カードがある場合は、0番目のカードを対象にバトルさせる
if (playerFieldCardList.Length > 0) // プレイヤーの場にカードがある場合
{ CardController defenceCard = playerFieldCardList[0]; CardBattle(attackCard, defenceCard); }
④カードがない場合は、リーダーに攻撃させる
else // プレイヤーの場にカードがない場合
{ AttackToLeader(attackCard, false); }
そして最後に、さっきリーダーに攻撃させるように実装した時のコードをコメントアウト
//AttackToLeader(attackCard, false);
という感じ!!
ではコードの意味が分かったところで、実際にコードの追記をしていこう!
コードの追記
GameManagerのEnemyTurnメソッドにオレンジ部分を追記、修正しよう↓
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 |
void EnemyTurn() { (敵のフィールドカードを攻撃可能にする処理 省略) CardController[] enemyFieldCardListSecond = enemyField.GetComponentsInChildren<CardController>(); while (Array.Exists(enemyFieldCardListSecond, card => card.model.canAttack)) { // 攻撃可能カードを取得 CardController[] enemyCanAttackCardList = Array.FindAll(enemyFieldCardListSecond, card => card.model.canAttack); CardController[] playerFieldCardList = playerField.GetComponentsInChildren<CardController>(); CardController attackCard = enemyCanAttackCardList[0]; //AttackToLeader(attackCard, false); // コメントアウトする if (playerFieldCardList.Length > 0) // プレイヤーの場にカードがある場合 { CardController defenceCard = playerFieldCardList[0]; CardBattle(attackCard, defenceCard); } else // プレイヤーの場にカードがない場合 { AttackToLeader(attackCard, false); } enemyFieldCardList = enemyField.GetComponentsInChildren<CardController>(); } ChangeTurn(); // ターンエンドする } |
エラーメッセージが出てないことを確認したらゲームを実行してみよう!
カードを出してない時はリーダーに攻撃をしてきて、
カードが出ている時はそのカードに攻撃してきたかな??
「ん、、???なんか変な動きしてない???」
って思ったひとは素晴らしい!
実はこのままの処理だと、
自分の場にカードが出てると、1番左の自分のカードに対して全て敵のカードが攻撃してきてしまっているよ!
(1番左のカードが破壊されてたとしても、そこに攻撃し続けてる。。)
なんでこうなってしまうのか?って原因を自分なりに見つけてから次に進もう
④処理待ち時間を作る
さて、なんで全ての敵のカードがPlayerFieldの一番左のカードに攻撃してきたのか分かったかな??
変な動きになっている原因としては、
「破壊されたはずのカードに攻撃してる」
ってところだよね?
じゃあなんで破壊されたカードに攻撃してしまうのか??
っていう理由としては、
カードが破壊される処理が、敵の攻撃の処理に追いついてない
ってのが原因になります!!
なので攻撃する処理にちょっとだけ待ち時間を作ってあげよう!!
その為には「コルーチン」ってものを使うよ!!
コルーチンってのはめっちゃ雑に言うと、「この処理のこの部分でちょっと止めたいな~」って時に使う関数のことね。
※ちゃんと知りたい人はこれを読むべし→コルーチン – Unity マニュアル
んで、コルーチンを使う時に気を付ける点が2つあって、
- メソッドの宣言で「IEnumerator」を使う
- メソッドを呼ぶときは「StartCoroutine()」を使う
この2つをちゃんと使わないとコルーチンは使えないから注意してね!!
上記の2点に沿ってコルーチンを使えるようになったら、
処理を止めたいところで、
yield return new WaitForSeconds(1);
と書くだけで、1秒処理を止めることが出来る様になるよ!!
コードの追記
では実際にコードの追記をしていこう!!
まずはGameManagerのTurnCalcメソッドでEnemyTurnメソッドを呼び出してる部分をStartCoroutineに書き換えよう↓
1 2 3 4 5 6 7 8 9 10 11 12 |
void TurnCalc() // ターンを管理する { if (isPlayerTurn) { PlayerTurn(); } else { //EnemyTurn(); // コメントアウトする StartCoroutine(EnemyTurn()); // StartCoroutineで呼び出す } } |
そしたらGameManagerのEnemyTurnメソッドのオレンジ部分を追記、修正して、攻撃前と攻撃後に1秒処理を止めるようにしていこう↓
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 |
//void EnemyTurn() // コメントアウトする IEnumerator EnemyTurn() // StartCoroutineで呼ばれたので、IEnumeratorに変更 { Debug.Log("Enemyのターン"); CardController[] enemyFieldCardList = enemyField.GetComponentsInChildren<CardController>(); yield return new WaitForSeconds(1f); /// 敵のフィールドのカードを攻撃可能にして、緑の枠を付ける /// SetAttackableFieldCard(enemyFieldCardList, true); yield return new WaitForSeconds(1f); ※ 省略(カードの召喚処理) CardController[] enemyFieldCardListSecond = enemyField.GetComponentsInChildren<CardController>(); yield return new WaitForSeconds(1f); while (Array.Exists(enemyFieldCardListSecond, card => card.model.canAttack)) { // 攻撃可能カードを取得 CardController[] enemyCanAttackCardList = Array.FindAll(enemyFieldCardListSecond, card => card.model.canAttack); CardController[] playerFieldCardList = playerField.GetComponentsInChildren<CardController>(); CardController attackCard = enemyCanAttackCardList[0]; //AttackToLeader(attackCard, false); if (playerFieldCardList.Length > 0) // プレイヤーの場にカードがある場合 { CardController defenceCard = playerFieldCardList[0]; CardBattle(attackCard, defenceCard); Debug.Log("プレイヤー側にカードがある"); } else { AttackToLeader(attackCard, false); Debug.Log("攻撃対象がいないのでリターン"); } yield return new WaitForSeconds(1f); enemyFieldCardList = enemyField.GetComponentsInChildren<CardController>(); } ChangeTurn(); // ターンエンドする } |
ではエラーが出ていないことを確認して、ゲームを実行してみよう!
ちゃんと敵の攻撃が実装出来てる!!!
【演習】敵の攻撃先を変更する
では最後に演習をやっていこう!!
今回の演習としては、
「敵の攻撃対象をランダムにする」
というのを実装していこう!
現状は、PlayerFieldの一番左(0番目)にいるカードをひたすら攻撃してくるよね?
それだけだとちょっと味気ないので、ランダムに攻撃してくるようにしてみよう!
今回の演習のポイントとしてはこの2つ↓
- 攻撃対象を決定してるコード
- ランダムの値を出すコード
ランダムの値を出すコードについては実際に調べてみて!
分からないことは実際に調べて実装出来る様にならないと、いつまでも自力でゲームを作れるようにはならないので頑張ってみよう!
攻撃対象を決定してるコードについては、今回の記事の内容を理解できていればすぐわかるはず!!
「全く見当も付かん!!」って人は、今回の内容をあんまり理解できてないかな~(-ω-)
その場合はもう一回読み直してみて!!
では自力で実装してみてから次に進んでね。
【解説】敵の攻撃対象をランダムにする
ではでは実装できたかな??
それでは答え合わせしていこう!
、、、って思ったけど、
今回は皆さんの理解度が知りたいのでまだ解説はしません!!
演習が出来た人は「出来たよー!」ってコメントで教えて下さい!
演習が出来なかった人も「出来ませんわ(´・ω・`)」ってコメントで教えて下さい!!(むしろこっちの意見の方がありがたかったりする。)
出来なかった人は「~の部分が分からなかった」って書いていただけると、その部分の解説をもうちょい分かりやすく書き直しますので!!
あと記事の構成としても、
- コードを実装してから、コードの解説(前回までの構成)
- コードの解説をしながら、コードの実装(今回の構成)
このどっちの方が読みやすい、分かりやすいって言う感想を教えて貰えるとものすごくありがたいです!!
という訳で今回の記事はここまで!!
演習の理解度がコメントで何人か確認出来たら、演習の解説も追記していきます!
そして一年以上掛けて書いてきたこの解説記事も、なんと次でいったん完結となります!!
ここまで一緒に作ってくれた皆さま、ありがとうございました!!!
次回は簡単なアニメーションを実装していくよ!
最後まで頑張っていきましょ!!
次の記事↓
前の記事↓
参考にさせて頂いた動画
[Unityゲーム開発講座] シャドバ風!?カードゲームの作り方 #1 UIの実装
いつもわかりやすい記事をありがとうございます。説明の仕方は今回のほうが分かりやすかったです。敵の攻撃対象はSystam.Randomを使ったらできました。
>サイさん
いつもコメントありがとうございます!
やっぱり作りながら説明があった方が分かりやすいですよね。
ありがとうございます、参考にさせて頂きます!
演習も出来たようで良かったです、次の更新はちょっと先になっちゃいますので、
他のゲームでも作成しつつ気長にお待ち下さい!!
一昨日このブログを見つけて一気にここまで走り抜けました!
とても分かりやすかったので、自分のタイプミス以外ではほとんどつまずきませんでした!
次の更新を楽しみにしながら自分で勉強したいと思います!
ところで、③章「フィールドに合わせて攻撃対象を変更する」の「コードの追記」のソースコードのハイライトは11行目と15~25行目にかかっていますが、12行目と16~26行目だと思うのですがどうでしょうか?
>ジェリーフィッシュさん
2,3日でここまで来るとは凄まじい進度ですね、、(笑)
分かりやすく伝わったみたいで良かったです(/・ω・)/
あー、、完全にこれはズレてますね。。
ご指摘ありがたいです、助かりました!!
修正しました!またミスあったら教えてください!
プログラミングは経験あるのでなんとか!
次の更新も楽しみにしてます!
スペルカードなどを実装はどうやるのでしょうか?モンスターと同じようにスペルのprefabを作成からのListを
List deck = new List() { M1, S2, S3, M1, S1, S2, M2, M3, M3, S1, S2, M3, S1, M2, S3, S1, S2, S3 };(Mはモンスターカード,Sはスペルカード)のように構成し場合分けによって生成するのでしょうか?
またそうした場合prefabにそれぞれ違う効果をつけるにはどうすればいいのでしょうか?
調べながら私もスペルカードを作ってみているのですが、一応それらしいものができたのでみじんこさんの返信が来るまでよかったら参考にしてみてください。
(結構難しいと思うので、調べてもわからなければ、記事の更新を待った方がいいと思います。この内容だけで1,2記事書けそうなくらい)
まずスペルカードを実現するには、カードに「出たとき効果」を作って、その最後に自身を破壊するなり墓地に送るなりする処理を書けば良いと思います。(この出たとき効果を作るのが難しいです)
生成は、List deck = new List() { C1, C1, S1, C2, C2, C2, C3, C3, C3, C4, C4, C4, C5, C5, C5, C6, C6, C6 }
という状態を作り、(Cはモンスターとスペルで共通のIDです)その後シャッフルすればできると思います。シャッフルはこのブログ内に記事があるので参考にしました。
カードごとに違う効果は、EntityにintのidとEffectクラスのeffectというフィールドを作って、id==1ならEffect1をid==2ならEffect2をeffectにつっこむことで実現できます。(Effect1,2はEffectを継承する。継承は知らないと理解できない概念なので調べてみてください)
以上です。私もゲーム作りは初心者なので間違っていたらごめんなさい。
待っている間にC#とかUnityの本買ってやってみたりするのもいいと思います。お互い頑張りましょう!
返事がくれてすみません!
大変参考になりました!
この方法であれば効果を使い回したりできそうですね!
CardentityにエフェクトのIDの配列を用意して
その対象がなくなるまでエフェクトを繰り返し発動することによってドロー効果など自由な枚数ドローできたりしそうです!
色々試してみます!
参考になったようで嬉しいです!
基本的な効果であれば、親クラス(Effect)に実装してしまった方が都合が良いかもしれないですね。
この実装が効率良いのかはわからないので、答え合わせの意味で更新が楽しみです。
最近見つけたのですがとても分かりやすくてプログラム初心者ですがここまで来れました!!
演習は結構大変でしたが何とか出来ました!次の記事も楽しみにしています。
作ってみたいカードゲーム!
作ってみてよ!