カードゲームの作り方

【解説通りに作るだけ】Unityのデジタルカードゲーム作り方⑦ カードバトル編

 

前回までの記事で、ドローやターンエンドボタンなど
ターンフェーズについての実装が完了しましたね!

前回の記事を見てない人はそちらを参考↓

【解説通りに作るだけ】Unityのデジタルカードゲーム作り方講座⑥ ターンフェーズ作成編 前回までの記事で、実際にデータを持ったカードを自由に出せるようになりましたね!! 前回の記事を見てない人はそちらを参考↓ ...

 

今回の記事では、カードゲームなら外せない、
カード同士を戦わせるバトルの機能を実装していきます!!

完成形としてはこんな感じ↓

 

攻撃が出来ると、よりカードゲームっぽくなるね!

徐々にコードが増えて難しくなってきたけど、少しながら理解しながらやっていこう!!

 

全体の流れの説明

ではまず今回やる全体の作業の流れについて説明します!

ざっくりこんな感じに進めてくよ↓

  1. バトル関数の作成
    カードを重ねた時にバトルする処理の追加
  2. 攻撃に制限を付ける
    攻撃回数に制限を掛ける処理の追加
  3. 攻撃可能なカードに枠を付ける
    攻撃可能なのかわかりずらいので見える化する
  4. 枠を作る
    攻撃可能なカードに付ける枠を作る
  5. アタッチする
     コードとオブジェクトをアタッチする

今回はかなりコードのボリュームと難しさがあると思うけど、頑張っていこう!!

 

バトル関数の作成

ではここの章では、

カードをドラッグして、
他のカードの上でドロップした時に、
パワーが低い方を破壊する

という処理を作って、追加していくよ!

 

実際にやることはこんな感じ↓

  1. 「AttackedCard」クラスを作る
  2. attackedCardクラスに処理を書く
  3. GameManagerにカードバトルメソッドを作る
  4. CardControllerにDestroy処理を追記する
  5. GameManagerをどこでも使える化する
  6. Cardプレハブにアタッチ

では上から順番にやっていくよ!

①「AttackedCard」クラスを作る

まずは新しいクラス「AttackedCard」を作ろう。

こんな感じに作れたらOK↓

 

②AttackedCardクラスに処理を書く

ではまず、さっき作ったAttackedCardクラスに処理を書いていくよ!!

丸々コピペで大丈夫だよ↓

エラーが出てると思うけど、いったん気にしないで大丈夫。

 

コードの説明

先に進む前に、ざっくりコードの内容の説明をするよ!

ここで重要になるのは「OnDrop」メソッド

 

あれ?なんか聞いたことあるぞ??

って人は素晴らしい!

第4回目に解説した「DropPlace」クラスでも使ったメソッドだね。

「DropPlace」クラスは、
カードを“フィールドにドロップした時”
処理が動くようにしたけど、

今回の「AttackedCard」クラスは、
カードをカードの上にドロップした時
処理を動かすようにしていくよ!

 

では実際のコードの説明をしていくと、、

↑これでマウスの下にあるカード攻撃側(attackCard)に設定。

↑これでドロップされたカード守備側(defendCard)に設定。

↑これで(まだ作ってないけど)、GameManagerのCardBattleメソッドを使って攻撃側と守備側をバトルさせる

という処理をしているよ!!

 

③CardBattleメソッドを作る

では次は今回の主役。
CardBattleメソッドを作っていこう!

GameManagerに追記する(EnemyTurnメソッドの下とかでいいよ)↓

またエラーが出てると思うけど、気にしなくて大丈夫!

 

コードの説明

わりと見たまんまだけど、一応説明!

さっき、AttackedCardクラスのOnDropメソッドで、攻撃側(attackCard)と守備側(defndCard)が決まったよね?

↑それらのパワー(~.model.power)を比較して、攻撃側のパワーが高いなら、

↑守備側(defenceCard)のカードを破壊(destory)する

って処理を行ってるよ!!

 

他の2つ処理も、

  • パワーが同じなら両方のカードを破壊する。
  • 守備側のパワーが高いなら攻撃側のカードを破壊する。

ということなので、ほぼ一緒!!

 

④CardControllerにdestory処理を書く

今さっき出てきた、“カードを破壊する”って処理がまだ書かれてないので、CardControllerに追記していくよ。

CardControllerに追記しよう↓

追記してもまだエラーが出るけど、まだ無視して!

 

コードの説明

これはめっちゃ単純で、

↑このDestroy関数オブジェクトを削除してくれる関数)で、指定したCardオブジェクトを破壊してるだけ!!

 

⑤GameManagerをどこからでも使えるようにする

では最後にエラーを解消しよう!

現状だとこんなエラーがずっと出てるよね↓

は??何言ってんの??

って気持ちはよくわかる。

でも大丈夫、僕もずっと思ってた。

てか最早、今でも「何言ってんの?」って思ってる。笑

 

ただ実は言ってることは単純で、

この場合は「このままじゃAttackedCardクラスから、GameManagerのメソッドは使えないよ!!」ってこと。

要は今回の場合で言うと、
他のクラスのメソッドは使えないよ!!
ってことだね。

※なんで他のメソッドを使えないのかって言うと、色々深い意味があるんだけど、今はまだ「基本そのままだと他のクラスのメソッドは使えない」って認識で」OK

 

でも「使えません、残念でした(‘ω’)」って話じゃないからご安心を。

使うためにはひと手間が必要ってことね。

今回はそのうちの一つの方法を使っていくよ!

 

GameManagerに追記しよう(Startメソッドの上でいいよ)↓

 

こっちはAttackedCardクラスのOnDropメソッドの一部を編集しよう↓

追記したらエラーは消えたかな??

 

詳しい説明は省くけど、

「他のクラスでも、このクラスのメソッドを使いたい!!」って時は、これを書いておけばすべてのクラスから使えるようになる!!って認識でひとまずOK。

んで、そのメソッドを使いたい時は

こんな風に、
《クラス名》.instance.《メソッド名》
と書けば他のクラスからも制限なく使えるよ!!

※ただし注意点として、参照元のメソッドの名前の前に”public”って書いてないメソッドは他のクラスからは使えないから注意ね!!

 

では最後に「AttackedCard」のコードを、Cardプレハブにアタッチしたら完成!!

 

では実際にやってみよう!

 

攻撃が出来るようになった!!!

 

でも

  • すぐ攻撃できちゃうし、
  • 何度でも攻撃できちゃう。

という問題があるので、次はここら辺を修正していこう!!

 

攻撃に制限を付ける

まずは修正したいことを洗い出そう↓

  • 出したターンは攻撃できないようにする
  • 1ターンに1度だけ攻撃出来るようにする

こんな感じかな!

 

これを実現するには、カード一枚一枚に
「いま攻撃を出来る状態なのか」という情報
を持たせる必要がある。

じゃあどうやるかというと、
canAttack“というbool型の変数を使って実装するよ!

bool型の変数って言うのは、”True”か”False”しか入れられない変数のことね。
int型の変数は、数字の整数しか入れられないじゃん?
それの「〇」か「×」しか入れられない版。って認識でOK

具体的に言うと、
canAttack=True  → 攻撃出来る
canAttack=False → 攻撃出来ない
っていう風に攻撃の制御をしていくよ!!

 

実装の為にやることはこんな感じ↓

  1. CardModelクラスに、変数”canAttack”を作る
  2. CardBattleメソッドに、攻撃可能か判断する処理を追記する
  3. PlyerTurnメソッドに、
    ターンの始めにフィールドのカードを攻撃可能にする処理を追記する

では上からやっていこう!

 

CardModelに追記↓

 

GameManagerにもいくつか追記しよう。

CardBattleメソッドにオレンジ部分を追記しよう↓

 

最後に”PlayerTurn”メソッドに追記しよう↓

 

では実際に動かしてみよう!!

出したターンに攻撃出来ないし、
連続で攻撃も出来なくなったかな??

そしたら無事バグの解消が出来たので、
コードの解説に入っていくよ!!

 

コードの説明

ではコードの説明をざっくりやってくよ!!

 

まず最初にCardModelクラスに下記のコードを追記したよね↓

これでカードは基本的に攻撃できないってことになる。

 

次にGameManagerに追記したコードについて。

CardBattleメソッドに、4~7行目と11行目の追記をしたよね↓

 

これらに解説を付けるとこんな感じ↓

【解説】攻撃できないカード(canAttackがFalseのカード)は、
if (attackCard.model.canAttack == false)
{
【解説】バトル処理まで行かずに終了する。
return;
}

(省略)~バトル処理~


【解説】攻撃を終えたカードを攻撃不可にする

attackCard.model.canAttack = false;

要はバトル処理の前に、攻撃してきたカードが攻撃できる状態なのかを判断して、

バトル処理が終わったら、もう攻撃できなくする

ってことだね。

 

その後に追記したメソッドはこんな感じ↓

【解説】指定したリストのカードを全て攻撃可能か攻撃不能にするメソッド
void SetAttackableFieldCard(CardController[] cardList, bool canAttack)
{
    【解説】リストの中のカードに対して全て同じ処理を行う
foreach (CardController card in cardList)
{
        【解説】”canAttack”を引数に指定した方に変える
card.model.canAttack = canAttack;
}
}

このメソッドを使うことで、指定したフィールドのカードを全て攻撃可能にしたり、攻撃できなくしたり出来るよ!

 

最後に”PlayerTurn”メソッドに追記したのは、今説明したメソッドだね。

Playerフィールドのカードリストを取得(5行目)して、
それらを全て攻撃出来るようにする(6行目)って処理だね。

つまり、「ターンの始めにPlayerフィールドのカードを全部攻撃可能にしてる」ってことだね。

 

なかなか複雑になってきたので「よく分かんない!!」って人はコメントくださいな。

理解しながら進めてるよーって人もコメント頂けるとありがたいです!

 

【演習】バグの解決

ではでは攻撃処理も完成に近づいてきたので、演習やっていきましょう!

今回の演習はバグの解決です!!

さっきは、下のふたつのバグを解決したよね?

  • 場に出したターンに攻撃出来ること
  • 何度も攻撃出来ること

 

気づいてる人も居るかもしれないけど、実はまだバグが2つあります

※気づいてない人は探してみてね。バグを見つけるのも大事な力です。

 

一般的なカードゲームのルールを元にすると、

  • 味方同士でバトル出来ること
  • 手札に攻撃出来ること

この2つが出来るのはおかしいよね??

(まあ攻撃の選択肢が広がって、これはこれで面白そうだけど。(笑))

今回の演習ではこの2つのバグを直してもらいます!!

 

そして今回は新しい試みとして、まだ答えを教えません!!!

悩んで下さい!!(笑)

そして実装出来たらコメントで教えてね!

出来た人が出てきたら、答え合わせとして答えを載せようかと思ってます!

※出来なかったら「難しい!!」ってコメントください。
それはそれで答えのヒントや、解説を載せるので。

 

最後に

いつかの前の記事で「7月中にカードゲームの基本の作り方記事は完成させるよ!!」って言ったのに、完成できなくてごめんなさい!

思った以上に、1記事作るのに時間が掛かってしまってしまいました。。

8月中には完成する(はず)なので、のんびり演習やるか、
今までの知識を使って自分の作りたいカードゲームを作ってみて下さい。

恐らく今までの記事の知識を使えば、
あなたが作ってみたかったカードゲームを少し形に出来ると思います。

このカードゲーム作成講座の記事をやる前だったら、
「どこから手を付けて良いのかも分かんないよ!!」って状態だったかも知れません。

でも今なら、
カードはどうやって作るのか、
どうしたらカードを持って動かせるのか、
どうやってフィールドを作るのか、
全部わかるよね??

(「そんなのやったっけ??(^.^)」ってひとは復習なさい。(笑))

だから一旦、自分の作りたいゲームに手を付けてみる。ってのもアリかと思います。

 

ただぶっちゃけ、ここまで来れた方々は超ツワモノだと思います。

「カードゲームを作りたい!!」って思って、解説記事①を読んだ人の中でも、この記事まで来れた人は見た感じ全体の5%くらいです。

95%のひとは「難しそうだな~」とか「やり方はなんとなく分かった(作ってないけど)。」とか言って、カードゲームが作りたい気持ちはあっても行動に移さないで、ゲームやって遊んでますね。たぶん。

だからここまでゲームを作ってこれた人達は自信持っていいと思います。

「初心者でも出来る!!」ってタイトルのくせに、最近の解説難しいしね。(笑)

出来るだけ分かりやすく噛み砕いて教えるので、また見てくださいな!

※分かりやすく書いてるつもりが、逆に説明し過ぎて分かりずらいみたいなこともあるので、コメントはほんとにありがたいです。

そんな訳で今回は終わりっ!!

次はリーダーへの攻撃を実装してくよ!!

(そういえばカードの枠付けるの忘れてたので、次回解説します。。笑)

では演習頑張って!!!

 

次の記事↓

【解説通りに作るだけ】Unityのデジタルカードゲーム作り方⑧ リーダーへの攻撃編 前回の記事でモンスターへの攻撃が実装出来ましたね! やってない人は前回の記事参照↓ http://yuus01.in...

前の記事↓

【解説通りに作るだけ】Unityのデジタルカードゲーム作り方講座⑥ ターンフェーズ作成編 前回までの記事で、実際にデータを持ったカードを自由に出せるようになりましたね!! 前回の記事を見てない人はそちらを参考↓ ...

 

Unityの初心者向けのチュートリアルすら難しくて心がめげた話【ゲーム制作 2日目】 昨日は Unityって神?? 何も知らない初心者がカードゲームアプリを作ろうとした結果www とか言ったけど前言撤回、...
恋活パーティーってものに行ってみた -エクシオ編-【男三人脱童貞奮闘期①】 これは大学生活の約2年間を脱童貞の為に奮闘した非モテ男3人の物語である。 第1話:R男から届いた謎のURL 始まりは...
大学生よ、ぼっちであるな。ソロプレイヤーであれ。【経験者は語る】 大学に友達がいない? いつもひとりでお昼食べてる? ペアを組む授業が辛い? 完全にぼっちじゃんwww ...
ABOUT ME
みじんこ
【名前】みじんこ(ここのブログ書いてるひと) ・大学2年の時に「ゲームが作りたいー!!」って思ったのに、ゲームの作り方を解説してるサイトがことごとく何言ってんのか分かんなくて挫折した。 ・数年な時を経て「だったら俺が完全初心者にも超わかりやすいサイトを作ってやんよ!」って事で、初めてゲームを作ろうとしてる方向けに解説記事を書いてるよ。

POSTED COMMENT

  1. ナエニア より:

    こんにちは。
    解説が分かりやすく、ここまで一気にやることができました。
    演習は、コードの書き方がいまいちわからないですけど親を取得してif文を使うとかですかね?
    それと、自分の場には何体でもモンスターを出せる(これはすぐ解決できた)と相手のモンスターを自分の場に出せる(直し方は演習のとおなじかな?)というバグを発見しました。できればこちらも教えてほしいです。
    次回の記事も楽しみにしています。

    • みじんこ より:

      >ナエニアさん
      ありがとうございます!!
      そう言っていただけると、頑張って解説書いてよかったです、、(泣)

      演習はナエニアさんの言ったように親を取得でも出来ないことはないと思いますが、
      解答例として出そうとしてたのは、カードに変数isPlayer(プレイヤーのカードなのかどうか)を持たせて、ifで味方同士ならバトルしない。という分岐にする予定でした。これなら手札にも攻撃できなくなりますからね。
      あとで解答を追記しますが、このヒントを使ってやってみてください!
      出来なかったとしても、めっちゃ力付くと思いますので!

      バグについても自己解決出来ていて素晴らしいですね。
      理解しながら進めているのが分かって、すごく嬉しいです!
      他のバグのところも今後解説していきますね。

      コメントほんとにありがとうございました!
      次回の記事は少々お待ちください。(笑)

  2. もっけ より:

    毎回楽しみにしています!!

    今回の演習からぐっと難易度が上がりましたね
    半日くらい悩み続けて、やっとできました(汗
    いや~これは難しかったです
    コメント欄のみじんこさんとナエニアさんのヒントを参考にして
    ギリギリできたって感じです

    以下、追記した箇所です

    public class CardModel
    {
    (省略)
    public bool isPlayer = false; // true=Player, false=Enemy

    (省略)

    }
    public class GameManager : MonoBehaviour

    void PlayerTurn()
    {
    UnityEngine.Debug.Log(“Playerのターン”);

    CardController[] playerHandCardList = playerHand.GetComponentsInChildren();
    CardController[] playerFieldCardList = playerField.GetComponentsInChildren();

    // ターン開始時はPlayerフィールドのカードをすべて攻撃可能にする
    SetAttackableFieldCard(playerFieldCardList, true);

    // ターン開始時はPlayerHandのカードをすべてプレイヤー属性にする
    SetPlayerableCard(playerHandCardList, true);

    // ターン開始時はPlayerFieldのカードをすべてプレイヤー属性にする
    SetPlayerableCard(playerFieldCardList, true);

    DrowCard(playerHand,0); // 手札を一枚加える
    }

    public void CardBattle(CardController attackCard, CardController defenceCard)
    {
    (省略)

    if (attackCard.model.isPlayer != defenceCard.model.isPlayer)
    {
    (省略 アタック動作の処理)

    //攻撃を終えたカードは追加で攻撃不可にする
    attackCard.model.canAttack = false;
    }

    //プレイヤーフィールドのカードをすべて isPlayer=ture or false にする
    void SetPlayerableCard(CardController[] cardList, bool isPlayer)
    {
    // リストの中のカードに対してすべて同じ処理を行う
    foreach (CardController card in cardList)
    {
    card.model.isPlayer = isPlayer;
    }
    }
    }

    • みじんこ より:

      >もっけさん
      これは素晴らしすぎますね、、!!
      というかこんなにしっかり書けるってことは、もっけさん初心者ではないですね。。笑
      失礼しました。素晴らしいです!

      ではちょっとだけコードの添削しますね!
      CardModelのコードについては文句無しです。
      GamaManagerのコードについても、
      ① Playerのカードに対してPlayer属性を付与
      ② 同じPlayer同士なら攻撃しないようにする
      の2点がしっかり実装されていていて素晴らしいです!!
      (if (attackCard.model.isPlayer != defenceCard.model.isPlayer){} の条件分岐の中にreturnが書かれてないのは、抜けですかね??)

      ただ1点気になったのが、DrowCardのメソッドをちょっと変更しました??
      (例えば、2つ目の引数が0なら、Playerのカードにするとか?)
      そうじゃないと、ドロー前に手札のカードを全てPlayerの属性を付与しても、ドローしたカードにPlayer属性が付かなくなっちゃうので!

      このままのコードでもほぼ大丈夫ですが、あとで(次の記事がもう少しなので、それを上げたら、、、)僕のコード例を出すのでちょっと参考にしてみてくださいね!

      「ちょっと難しすぎたかなぁ。。」と反省してたので答えていただけて助かりました。
      もっけさんには簡単になっちゃうかも知れないですけど、次からちょっとだけ簡単にしてみます!
      コメントと回答、ありがとうございましたー!!

      • もっけ より:

        添削ありがとうございます!
        おっしゃる通り条件分岐内でreturnを書いた方がいいですね
        そのほうが予期せぬ動作を防げそうです

        DrowCardメソッドでは前回の課題でデッキを2つ作っていて
        答えどおりに戻し忘れたまま進めていました(汗
        そのため、2つ目の引数が0だとPlayerのデッキからカードを引き
        1だとEnemyのデッキからカードを引くようになっていました
        忘れずに課題⑥の答えに戻して進めてみたところ
        たしかにドローにPlayer属性がついていませんね(汗
        プレイヤー属性を付与した後に手札を加えるというのが原因でした
        逆にすると良いと思い
        PlayerTrunメソッド内のDrowCard呼び出しを、
        playerHandCardListの定義前に持っていくことで解消できました!

        8回目ももうすぐということなので楽しみです!!

        • みじんこ より:

          >もっけさん
          DrowCardメソッドはそういう事だったんですね!
          「なるほど。そんなやり方もあるな!」って思ってました。笑

          8回目も投稿したのでやってみてくださいねー!(‘ω’)

  3. トキムネ より:

    すいません、バグが出たので相談したいです。
    戦闘のコードを試していたのですが、攻撃している側のカードが勝負に勝った際に負けカードと一緒に消えてしまうという現象が起きています。

    攻撃側が負けの場合は問題なく動いており、いろいろ試してみたところ「攻撃時にドラッグしたカードが攻撃対象カードの子図番に移動してしまう」という現象がおきているみたいです。。。

    おそらくCardMovementのどこかでなにかミスをしたんだと思うのですが自分で見ても問題があわかりません。。。

    何か「ここが間違っているんじゃないか」って思い当たることがあれば教えていただきたいです。

  4. トキムネ より:

    すいません、自己解決しました。
    Cardプレハブに何故かDropPlaceを貼り付けていたみたいです。DropPlaceを削除したら無事に動きました。
    お手数おかけしました

    • みじんこ より:

      >トキムネさん
      返信出来ず、すみません、、!
      なるほど、そんな動きするんですね。笑
      解決出来たみたいで良かったです!

COMMENT

メールアドレスが公開されることはありません。