Unityゲームの作り方メモ

【Unity 2D】カードゲームの作り方【シャドウバース風】

Unity

Unityを使用して作成したシャドウバース風カードゲームの作り方のメモです。トグルボタンをクリックすると記事が表示されます。小さくて見ずらい画像はクリックで拡大できます。

シャドウバースについて


シャドウバースは、Cygamesによって開発された日本のトレーディングカードゲームです。

以下はゲームの基本ルールです。

  1. リーダーとデッキ:
    • プレイヤーはゲームを進めるための「リーダー」と呼ばれるキャラクターを選び、デッキを構築します。
  2. プレイヤーターン:
    • ターンは、自分と相手の2つのフェーズに分かれています。まず、自分のターンがあり、その後相手のターンが続きます。
  3. コストとマナ:
    • カードをプレイするには、コストと呼ばれるリソースが必要です。コストは、手札のカードに表示されている数字です。これに対応するリソースは「マナ」と呼ばれ、ターンごとにリフレッシュされます。
  4. 進化:
    • プレイヤーは自分のターン中に進化ポイントを使ってカードを進化させることができます。進化したカードは能力が向上し、進化前よりも強力になります。
  5. アタックとディフェンス:
    • プレイヤーは進化したカードや進化ポイントを使ったカードで相手のリーダーを攻撃します。相手のリーダーの体力をゼロにすると勝利となります。
  6. シャドウ:
    • フィールド上に破壊されたカードは「シャドウ」として墓地に送られます。一部のカードはシャドウを利用して強力な効果を発動させることができます。
  7. 勝利条件:
    • 相手のリーダーの体力をゼロにするか、相手がデッキを使い切った時点で勝利となります。



UIの作成

カードのベースになるオブジェクトを作成します。

Canvas→Imageを作成しました。名前はCardにしました。

サイズを調整してImageをremoveしました。

カードの背景部分を作成します。

子オブジェクトにImageを作成しました。名前はBackPanelにしました。

サイズを親オブジェクトに合わせました。

カードの画像を設定するオブジェクトを作成します。

Cardの子オブジェクトにImageを作成しました。名前はCharaImageにしました。

Spriteを設定してサイズを調整しました。

画像はこちらのアセットを使用しました。

カードの名前、攻撃力、HP、コストを表示するテキストを作成します。

テキストを4つ作成して画像のように配置しました。

位置の調整が終わったらCardをprefabにしておきます。

画像に alt 属性が指定されていません。ファイル名: 2024-01-10_025158.png

手札を表示する場所を作成します。

UI→Panelを作成しました。名前はPlayerHandにしました。

位置を調整してadd componentからHorizontalLayoutGroupをアタッチしました。

実際にカードを配置してHorizontalLayoutGroupを設定しました。

プレイヤーのフィールドを作成します。

PlayerHandをコピーして名前を変更してPlayerFieldを作成しました。

手札と同じようにカードを配置してサイズを調整しました。

敵の手札とフィールドを作成します。

プレイヤーのフィールドが完成したらPlayerFieldとPlayerHandをコピーして敵のフィールドを作成します。

PosYの+-を変更するだけで位置調整は完了します。

ヒーローの背景部分を作成します。

UI→Imageを作成しました。名前はPlayerHeroにしました。

ヒーローの画像部分を作成します。

PlayerHeroの子オブジェクトにImageを作成しました。名前はIconにしました。

IconにSpriteを設定して位置を調整しました。

ヒーローのHPを表示するテキストを作成します。

PlayerHeroの子オブジェクトにTextを作成しました。名前はHPにしました。

テキストの位置、色、サイズを調整しました。

敵のヒーローを作成します。

PlayerHeroをコピーして位置を調整しました。

ターンエンドボタン、制限時間、コストを表示するUI作成しました。

カードのデータについて

scriptableObjectでこのようなカードデータを作成します。

新しくC#スクリプトを作成しました。名前はCardEntityにしました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//カードデータ本体
[CreateAssetMenu(fileName ="CardEntity",menuName ="Create CardEntity")]
public class CardEntity : ScriptableObject
{
    public new string name;
    public int hp;
    public int at;
    public int cost;
    public Sprite icon;
}

プロジェクトビューの+ボタンをクリックします。

CreateCardEntityという項目ができているのでクリックします。

するとファイルが作成されます。名前をCard1に変更しました。

inspector画面からカードデータを設定します。3種類ほど作成しました。

このデータを読み込ませてカードに反映します。

Resourcesフォルダを作成してその中に新しくCardEntityListフォルダを作成しました。

CardEntityListフォルダにカードデータのファイルを入れておきました。

prefabsフォルダもResourcesフォルダに入れておきました。

カード側にデータを保持させるスクリプトを作成します。

新しくC#スクリプトを作成しました。名前はCardModelにしました。

using UnityEngine;

//カードのデータ
public class CardModel
{
    public string name;
    public int hp;
    public int at;
    public int cost;
    public Sprite icon;

    public CardModel(int cardID) 
    {
        //リソースフォルダからカードデータを取得してカードデータを作成する
        CardEntity cardEntity = Resources.Load<CardEntity>("CardEntityList/Card"+cardID);
        name = cardEntity.name;
        hp = cardEntity.hp;
        at = cardEntity.at;
        cost = cardEntity.cost;
        icon = cardEntity.icon;
    
    }
}

オブジェクトにアタッチする必要はありません。フォルダの名前が違っているとエラーになります。

カードのデータを操作するスクリプトを作成します。

新しくC#スクリプトを作成しました。名前はCardControllerにしました。

using UnityEngine;


public class CardController : MonoBehaviour
{
    //カードデータを表示する
    CardView view;
    //カードデータに関する事を操作
    CardModel model;

    private void Awake()
    {
        view = GetComponent<CardView>();
    }
    public void Init(int cardID) 
    {
        model = new CardModel(cardID);
        view.Show(model);
    
    }
}

CardControllerをカードprefabを開いてアタッチしました。

カードの見た目を変更するスクリプトを作成します。

新しくC#スクリプトを作成しました。名前はCardViewにしました。

using UnityEngine;
using UnityEngine.UI;

public class CardView : MonoBehaviour
{
    [SerializeField] Text nameText;
    [SerializeField] Text hpText;
    [SerializeField] Text atText;
    [SerializeField] Text cosText;
    [SerializeField] Image iconImage;

    public void Show(CardModel cardModel)
    {
        nameText.text = cardModel.name;
        hpText.text = cardModel.hp.ToString();
        atText.text = cardModel.at.ToString();
        cosText.text = cardModel.cost.ToString();
        iconImage.sprite = cardModel.icon;
    }
}

CardViewスクリプトをカードprefabを開いてアタッチしてテキストとイメージを設定しました。



基本的な機能の実装

一度カード手札に生成してデータが反映されているかチェックします。

新しくC#スクリプトを作成しました。名前はGameManagerにしました。

using UnityEngine;

public class GameManager : MonoBehaviour
{
    //カードのプレファブを入れる
    [SerializeField] CardController cardPrefab;

    //手札のTransformを入れる
    [SerializeField] Transform PlayerHandTransform;

   
    void Start()
    {
        CreateCard(PlayerHandTransform);
    }

    void CreateCard(Transform hand) 
    {
        CardController card = Instantiate(cardPrefab, hand, false);

        //()にカードIDを入れる
        card.Init(1);
    }
}

この場合CardEntityでデータを入力したCard1が手札に生成されます。

空のオブジェクトを作成してアタッチしました。

カードのプレファブとPlayerHandをアタッチしました。

Unityを実行するとカード(Card1)が手札に生成されます。

ドラッグ&ドロップでカードをフィールドに出せるようにします。

カードのプレファブを開いてadd componentでCanvasGroupを追加しました。

カードをドラッグ&ドロップで移動させるにはCanvasGroupコンポーネントのBlockRaycastsをスクリプトから操作する必要があります。

新しくC#スクリプトを作成しました。名前CardMovementにしました。

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UIElements;

public class CardMovement : MonoBehaviour, IDragHandler, IBeginDragHandler, IEndDragHandler
{
    public Transform defaultParent;
    public void OnBeginDrag(PointerEventData eventData)
    {
        defaultParent = transform.parent;
        transform.SetParent(defaultParent.parent, false);
        GetComponent<CanvasGroup>().blocksRaycasts = false;
    }

    public void OnDrag(PointerEventData eventData)
    {
        //カード引っ張ったときに行う処理
        transform.position = eventData.position;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        //カードを離したときに行う処理
        transform.SetParent(defaultParent, false);
        GetComponent<CanvasGroup>().blocksRaycasts = true;
    }
}

作成したスクリプトをカードprefabを開いてアタッチしました。

新しくC#スクリプトを作成しました。名前はDropPlaceにしました。

using UnityEngine;
using UnityEngine.EventSystems;

public class DropPlace : MonoBehaviour, IDropHandler
{
    public void OnDrop(PointerEventData eventData)
    {
        //カードが重なった時に親を変更する

        CardMovement card = eventData.pointerDrag.GetComponent<CardMovement>();
        if (card != null) 
        {
            card.defaultParent = this.transform;
        
        }
    }
}

作成したスクリプトをPlayerHandとPlayerFieldにアタッチしました。

手札のカードをフィールドにドラッグ&ドロップすると移動できるようになりました。

ゲームの実装

両プレイヤーに3枚づつカードを配るようにします。

GameManagerスクリプトを開いてコードを追加します。



public class GameManager : MonoBehaviour
{
    //カードのプレファブを入れる
    [SerializeField] CardController cardPrefab;

    //両プレイヤーの手札のTransformを入れる
    [SerializeField] Transform PlayerHandTransform,EnemyHandTransform;


   
    void Start()
    {
        StartGame();
    }

    private void StartGame()
    {
        SettingHand();
    }

    void CreateCard(Transform hand) 
    {
        //手札にカードを生成する
        CardController card = Instantiate(cardPrefab, hand, false);

        //カードIDを入れる
        card.Init(1);
    }

    void SettingHand() 
    {
        //それぞれの手札に3枚カードを配る
        for (int i = 0; i < 3; i++)
        {
            CreateCard(PlayerHandTransform);
            CreateCard(EnemyHandTransform);

        }
    }
}

GameManagerのinspector画面からEnemyHandを設定しました。

ゲームを開始するとカードが配られるようになります。

ボタンを押してターンを切り替えられるようにします。

GameManagerスクリプトを開いてコードを追加しました。



public class GameManager : MonoBehaviour
{
    

    //プレイヤーのターンかの判定
    bool isPlayerTurn;


    void Start()
    {
        StartGame();
        isPlayerTurn= true;
        TurnCalc();
    }

    void TurnCalc() 
    {
        //ターン進行の処理をする
        if (isPlayerTurn)
        {
            PlayerTurn();
        }
        else 
        {
            EnemyTurn();
        }
    }

    void PlayerTurn() 
    {
        //プレイヤーターンの処理
        Debug.Log("プレイヤーのターンです");
    
    }

    void EnemyTurn()
    {
        //エネミーターンの処理
        Debug.Log("敵のターンです");

        //ターンを終了する
        ChangeTurn();
    }


    public void ChangeTurn() 
    {
        //ターンの切り替え処理
        isPlayerTurn = !isPlayerTurn;
        TurnCalc();
    }

    
}

ターンエンドボタンのinspector画面からChangeTurnを設定しました。

ゲームを実行するとプレイヤーのターンが開始します。ターンエンドボタンをクリックすると敵のターンなります。各ターンではデバックログが表示されます。敵は何もせずターンエンドします。

ターンの切り替え時にカードをドローするようにします。

GameManagerスクリプトのChangeTurnにコードを追加しました。

 public void ChangeTurn() 
    {
        //ターンの切り替え処理
        isPlayerTurn = !isPlayerTurn;

        if (isPlayerTurn)
        {
            //ドローする
            CreateCard(PlayerHandTransform);
        }
        else 
        {
            //ドローする
            CreateCard(EnemyHandTransform);
        }
        TurnCalc();
    }

ゲームを実行してターンが切り替わるときにカードをドローするようになりました。

敵のターンにカードを出せるようにします。

CardMovementに関数を作成しました。

//手札からフィールドにカードの位置を変更する
    public void SetCardTransform(Transform parentTransform) 
    {
        defaultParent = parentTransform;
        transform.SetParent(defaultParent, false);
    }

CardControllerにCardMovement変数を作成しました。

   //カードの移動を操作
    public CardMovement movement;

  private void Awake()
    {
        movement = GetComponent<CardMovement>();
    }

GameManagerのEnemyTurnに処理を書きます

 void EnemyTurn()
    {
        //エネミーターンの処理
        Debug.Log("敵のターンです");
        //手札のカードリストを取得
        CardController[] cardList= EnemyHandTransform.GetComponentsInChildren<CardController>();
        //場に出すカードを選択
        CardController card= cardList[0];
        //カードを移動する
        card.movement.SetCardTransform(EnemyFieldTransform);
        //ターンを終了する
        ChangeTurn();
    }

敵のターンになるとカードを出してターンエンドするようになりました。

おすすめのアセット

TCG Engineは、Unityで使えるカードゲーム作成キットです。このキットを使えば、一人用もしくはオンラインで遊べるカードゲームを自分で作ることができます。ログイン機能やユーザーデータの管理、カードの集め方やリーダーボードなど、ゲームに必要なさまざまな機能が揃っています。操作はパソコンやスマホで簡単にでき、自分だけのカードゲームをデザインすることが可能です。プログラミングの知識が必要ですが、コードはシンプルで初心者でも始めやすいです。質問やサポートが必要なら専用のDiscordで助けを求められます。