
前回で、カードをドラッグして使えるようになりました。でも、まだ使っても何も起きません・・・。今回は、1ターンに使えるエネルギーと、攻撃・防御・回復などのカードの効果を作ります。効果は、6-14 クラスを受け継ごう:継承とabstractの応用編と同じ形で、「効果の共通の形」を決めて、中身をクラスごとに分けて作ってみたいと思います!
- シリーズの目次:デッキ構築カードゲームの作り方
- 前回:#6 カードをドラッグして使おう
今回作るもの
- エネルギー:毎ターン3。カードを使うとコストの分だけ減る
- エネルギーが足りないカードは暗くなり、使おうとするとエネルギーの表示が震える
- スライムちゃんと敵のHPと防御
- カードの効果:ダメージ・防御・回復・カードを引く・エネルギーを増やす
- 新しいカード6枚(全部で9種類)
| スクリプト | 役割 | 付ける場所 |
|---|---|---|
| Energy | エネルギーの残りと表示。足りないときに震える | Energy |
| Fighter | HPと防御。ダメージ・防御・回復を受ける | PlayerArea・Enemy |
| CardEffect | 効果の共通の形(abstract) | (付けない。アセットを作る) |
| DamageEffect ほか4つ | 効果の中身 | (付けない。アセットを作る) |
| CardData(直す) | 効果のリストを持つ | - |
| CardView(直す) | エネルギーが足りないカードを暗くする | Card(プレハブ) |
| PlayZone(直す) | エネルギーを払って、効果を出す | Stage |
HPはまだ文字で出すだけです。HPのゲージと敵の行動の予告は#8、攻撃や回復の演出は#10・#11で作ります。
前回の続きから
前回の最後の状態から始めます。新しいカードの絵は、素材のzipを入れ直すと入ります(DeckBuilding/Sprites/Cardsに6枚増えます)。
- DeckBuildingAssets.zip
- 自分で入れた場合は、6枚を選んでSprite Mode:Singleにして Apply
エネルギーを作ろう
まずはエネルギーです。#1で左下に置いたオレンジの丸(Energy)に付けます。
- DeckBuilding/Scriptsに「Energy」スクリプトを作る
using System.Collections;
using TMPro;
using UnityEngine;
// 1ターンに使えるエネルギー。カードを使うとコストの分だけ減る
public class Energy : MonoBehaviour
{
[SerializeField] private int maxEnergy = 3;
[SerializeField] private TextMeshProUGUI energyText;
private int current;
private Vector2 basePosition;
public int Current => current;
private void Start()
{
basePosition = ((RectTransform)transform).anchoredPosition;
Refill();
}
// ターンの始めに、最大まで戻す
public void Refill()
{
current = maxEnergy;
UpdateText();
}
public bool CanPay(int cost)
{
return current >= cost;
}
public void Pay(int cost)
{
current -= cost;
UpdateText();
}
public void Gain(int amount)
{
current += amount;
UpdateText();
}
// 足りないときに、ブルッと震える
public void Shake()
{
StopAllCoroutines();
StartCoroutine(ShakeRoutine());
}
private IEnumerator ShakeRoutine()
{
RectTransform rt = (RectTransform)transform;
float time = 0f;
while (time < 0.3f)
{
time += Time.deltaTime;
rt.anchoredPosition = basePosition + new Vector2(Mathf.Sin(time * 80f) * 10f, 0f);
yield return null;
}
rt.anchoredPosition = basePosition;
}
private void UpdateText()
{
energyText.text = $"{current}/{maxEnergy}";
}
}
- CanPay・Pay・Gain
- 「払えるか」「払う」「増やす」を、それぞれメソッドにしておく。エネルギーの数字を直接さわるのはEnergyだけにしておくと、表示の更新を忘れない
- Shake
Mathf.Sinで左右に行ったり来たりさせて、0.3秒で元の位置に戻す- 震えている途中でもう一度呼ばれても、StopAllCoroutinesで前の震えを止めてからやり直すので、位置がずれない
エネルギーを付けます。
- HierarchyのHud>EnergyにEnergyを付ける
- Max Energy:3、Energy Text:Energyの子のText (TMP)
- EndTurnButtonのOn Click ()の+を押して、3つ目にEnergyをドラッグしてEnergy > Refill ()
- ターン終了を押すと、手札が入れかわって、エネルギーが3に戻る

HPと防御を持たせよう
次に、スライムちゃんと敵のHPと防御です。どちらも「ダメージを受ける」「防御を得る」「回復する」は同じなので、1つのスクリプト(Fighter)を両方に付けます。
- DeckBuilding/Scriptsに「Fighter」スクリプトを作る
using TMPro;
using UnityEngine;
// HP と防御を持って、ダメージ・防御・回復を受ける(スライムちゃんと敵の共通部分)
public class Fighter : MonoBehaviour
{
[SerializeField] private string fighterName = "スライムちゃん";
[SerializeField] private int maxHp = 60;
[SerializeField] private TextMeshProUGUI statusText; // 「HP 60/60 防御 5」の表示
private int hp;
private int block; // 防御:ダメージを先に受けとめてくれる
public string FighterName => fighterName;
public int Hp => hp;
private void Awake()
{
hp = maxHp;
}
private void Start()
{
UpdateText();
}
public void TakeDamage(int amount)
{
// 先に防御で受けとめて、残りだけ HP を減らす
int blocked = Mathf.Min(block, amount);
block -= blocked;
int damage = amount - blocked;
hp = Mathf.Max(0, hp - damage);
Debug.Log($"{fighterName}に{amount}ダメージ(防御で{blocked}防いだ) HP {hp}/{maxHp}");
UpdateText();
}
public void AddBlock(int amount)
{
block += amount;
Debug.Log($"{fighterName}の防御 +{amount} 防御 {block}");
UpdateText();
}
public void Heal(int amount)
{
hp = Mathf.Min(maxHp, hp + amount);
Debug.Log($"{fighterName}のHPが{amount}回復 HP {hp}/{maxHp}");
UpdateText();
}
private void UpdateText()
{
statusText.text = block > 0 ? $"HP {hp}/{maxHp} 防御 {block}" : $"HP {hp}/{maxHp}";
}
}
- TakeDamage
- ダメージは、先に防御で受けとめる。防御5で6ダメージを受けると、防御が0になって、HPは1だけ減る
Mathf.Max(0, …)で、HPが0より下にならないようにする
- Heal
Mathf.Min(maxHp, …)で、最大HPをこえないようにする
- statusText
- 防御が0のときは「HP 60/60」、あるときは「HP 60/60 防御 5」と出す

HPの表示を作って、Fighterを付けます。
- PlayerAreaの子にStatusText(Text – TextMeshPro)
- アンカー:bottom-center、Pos Y:10、Width:320、Height:50
- テキスト:「HP 60/60」(再生するとスクリプトが書きかえる)、Font Size:32、Alignment:中央、Color:#3C2F2F
- Enemyの子にStatusText(Text – TextMeshPro)
- アンカー:bottom-center、Pos Y:-64(名前の下)、Width:260、Height:40
- テキスト:「HP 28/28」、Font Size:28、Alignment:中央、Color:#3C2F2F
- PlayerAreaにFighterを付ける
- Fighter Name:スライムちゃん、Max Hp:60、Status Text:PlayerAreaの子のStatusText
- EnemyにFighterを付ける
- Fighter Name:ブリキロボ、Max Hp:28、Status Text:Enemyの子のStatusText

効果の共通の形を作ろう
いよいよカードの効果です。効果には「ダメージ」「防御」「回復」「カードを引く」「エネルギー」といろいろありますが、カードを使う側から見ると、どれも「効果を出す」だけです。そこで、効果の共通の形をabstractのクラスで決めておきます。
- DeckBuilding/Scriptsに「CardEffect」スクリプトを作る
using UnityEngine;
// カードの効果の共通の形。中身は子クラスで決める
public abstract class CardEffect : ScriptableObject
{
// player:スライムちゃん enemy:ねらった敵(攻撃のカードのとき。ほかは null)
public abstract void Apply(Fighter player, Fighter enemy, Deck deck, Energy energy);
}
- abstract class ・ ScriptableObject
- 6-14の応用編と同じく、「アセットにできる」「中身は子クラスにまかせる」の2つを合わせた形
- Apply の4つの引数
- 効果によって、さわる相手がちがう(ダメージは敵、防御と回復はスライムちゃん、カードを引くのはDeck、エネルギーはEnergy)。必要になりそうなものを全部渡しておき、それぞれの効果は、使うものだけ使う
5つの効果を作ろう
子クラスを5つ作ります。どれも短いスクリプトです。1つのファイルに1つのクラスで作り、ファイル名とクラス名をそろえましょう。
using UnityEngine;
// 敵にダメージを与える(hits 回くり返す)
[CreateAssetMenu(fileName = "DamageEffect", menuName = "DeckBuilding/Effect/Damage")]
public class DamageEffect : CardEffect
{
[SerializeField] private int amount = 6;
[SerializeField] private int hits = 1;
public override void Apply(Fighter player, Fighter enemy, Deck deck, Energy energy)
{
if (enemy == null)
{
return;
}
for (int i = 0; i < hits; i++)
{
enemy.TakeDamage(amount);
}
}
}
using UnityEngine;
// スライムちゃんが防御を得る
[CreateAssetMenu(fileName = "BlockEffect", menuName = "DeckBuilding/Effect/Block")]
public class BlockEffect : CardEffect
{
[SerializeField] private int amount = 5;
public override void Apply(Fighter player, Fighter enemy, Deck deck, Energy energy)
{
player.AddBlock(amount);
}
}
using UnityEngine;
// スライムちゃんの HP を回復する
[CreateAssetMenu(fileName = "HealEffect", menuName = "DeckBuilding/Effect/Heal")]
public class HealEffect : CardEffect
{
[SerializeField] private int amount = 5;
public override void Apply(Fighter player, Fighter enemy, Deck deck, Energy energy)
{
player.Heal(amount);
}
}
using UnityEngine;
// 山札からカードを引く
[CreateAssetMenu(fileName = "DrawEffect", menuName = "DeckBuilding/Effect/Draw")]
public class DrawEffect : CardEffect
{
[SerializeField] private int count = 2;
public override void Apply(Fighter player, Fighter enemy, Deck deck, Energy energy)
{
deck.Draw(count);
}
}
using UnityEngine;
// エネルギーを増やす
[CreateAssetMenu(fileName = "EnergyEffect", menuName = "DeckBuilding/Effect/Energy")]
public class EnergyEffect : CardEffect
{
[SerializeField] private int amount = 1;
public override void Apply(Fighter player, Fighter enemy, Deck deck, Energy energy)
{
energy.Gain(amount);
}
}
- どれも
: CardEffectを受け継いで、Applyをoverrideしているだけ - DamageEffectのhits
- 同じダメージを何回与えるか。「ハサミでチョキチョキ」の「3ダメージを2回」に使う。1回ずつTakeDamageを呼ぶので、防御は1回目で削られて、2回目はそのままHPに届く
スライム同じ形のスクリプトが5つも並ぶと、ちょっと多く感じますね・・・



でも、あとで新しい効果を足すときは「1つ増やすだけ」で、ほかは何も直さなくていいんだ。#13で特殊なカードを足すときに、このありがたさが分かるよ!
カードに効果を持たせよう
CardDataに効果のリストを足す
#2で作ったCardDataに、効果のリストを1行足します。
using System.Collections.Generic;
using UnityEngine;
// カードの種類(枠の色や、使う相手を決めるときに使う)
public enum CardType
{
Attack, // 攻撃
Defense, // 防御
Heal, // 回復
Special // 特殊
}
// カード1枚分のデータ。Project ウインドの Create>DeckBuilding>Card Data でアセットを作れる
[CreateAssetMenu(fileName = "NewCard", menuName = "DeckBuilding/Card Data")]
public class CardData : ScriptableObject
{
public string cardName; // 画面に出す名前
[Min(0)] public int cost = 1; // 使うのに要るエネルギー
public CardType cardType; // 種類
[TextArea] public string description; // 説明
public Sprite artwork; // カードの絵
public List<CardEffect> effects = new List<CardEffect>(); // ★#7 使ったときの効果(上から順に出す)
}
効果のアセットを作る
- DeckBuilding>Dataの中に「Effects」フォルダを作る
- 右クリック>Create>DeckBuilding>Effect から、次の9つを作る
| アセットの名前 | 作るメニュー | 値 |
|---|---|---|
| Damage6 | Damage | Amount:6、Hits:1 |
| Damage3x2 | Damage | Amount:3、Hits:2 |
| Damage4 | Damage | Amount:4、Hits:1 |
| Damage12 | Damage | Amount:12、Hits:1 |
| Block5 | Block | Amount:5 |
| Block12 | Block | Amount:12 |
| Heal5 | Heal | Amount:5 |
| Draw2 | Draw | Count:2 |
| Energy1 | Energy | Amount:1 |


カードに効果を入れる・カードを増やす
今までの3枚に効果を入れて、新しいカードを6枚作ります(Data>Cardsで右クリック>Create>DeckBuilding>Card Data)。Effectsの「+」で欄を足して、効果のアセットをドラッグ&ドロップします。
| アセットの名前 | Card Name | Cost | Card Type | Description | Artwork | Effects |
|---|---|---|---|---|---|---|
| Ruler | 定規でペシッ | 1 | Attack | 敵に6ダメージ | card_ruler | Damage6 |
| Scissors | ハサミでチョキチョキ | 1 | Attack | 3ダメージを2回 | card_scissors | Damage3x2 |
| Compass | コンパスでチクッ | 0 | Attack | 敵に4ダメージ | card_compass | Damage4 |
| Stapler | ホチキスでパチン | 2 | Attack | 敵に12ダメージ | card_stapler | Damage12 |
| PencilBoard | 下じきガード | 1 | Defense | 防御を5得る | card_pencilboard | Block5 |
| PencilCase | ふでばこの壁 | 2 | Defense | 防御を12得る | card_pencilcase | Block12 |
| Bandage | ばんそうこう | 1 | Heal | HPを5回復する | card_bandage | Heal5 |
| StickyNote | 付せんメモ | 1 | Special | カードを2枚引く | card_stickynote | Draw2 |
| ColorPencils | 色えんぴつセット | 0 | Special | エネルギーを1増やす | card_colorpencils | Energy1 |


「3ダメージを2回」は、新しいスクリプトを書かずに、DamageEffectのアセットの数字を変えるだけで作れました。効果を1つ作っておくと、数字を変えたアセットを増やすだけで、いろいろなカードが作れます。
カードを使ったら効果を出そう
最後に、#6のPlayZoneを直します。カードを使う前にエネルギーを確かめて、使ったら効果を順番に出します。
using UnityEngine;
using UnityEngine.EventSystems;
// Stage でカードを離したときに、使うかどうかを決める
public class PlayZone : MonoBehaviour, IDropHandler
{
[SerializeField] private Deck deck;
[SerializeField] private Energy energy; // ★#7
[SerializeField] private Fighter player; // ★#7 スライムちゃん
public void OnDrop(PointerEventData eventData)
{
if (eventData.pointerDrag == null)
{
return;
}
CardDrag card = eventData.pointerDrag.GetComponent<CardDrag>();
if (card == null)
{
return;
}
CardData data = card.View.Data;
// ★#7 エネルギーが足りないカードは使えない
if (!energy.CanPay(data.cost))
{
energy.Shake();
return;
}
Fighter enemy = null;
string targetName = player.FighterName;
if (card.IsAttack)
{
// 攻撃のカードは、敵の上で離したときだけ使う
GameObject over = eventData.pointerCurrentRaycast.gameObject;
EnemyTarget target = over != null ? over.GetComponentInParent<EnemyTarget>() : null;
if (target == null)
{
return; // 敵の上ではないので使わない(手札に戻る)
}
enemy = target.GetComponent<Fighter>();
targetName = target.EnemyName;
}
card.MarkUsed();
energy.Pay(data.cost); // ★#7 エネルギーを払う
deck.UseCard(card.View, targetName);
// ★#7 カードの効果を、上から順番に出す
foreach (CardEffect effect in data.effects)
{
effect.Apply(player, enemy, deck, energy);
}
}
}
- energy.CanPay → Shake
- 足りなければ、エネルギーを震わせて何もしない。MarkUsedを呼ばないので、カードは手札に戻る
- foreach (CardEffect effect in data.effects)
- ここが今回のいちばんのポイント。PlayZoneは、効果が「ダメージ」なのか「カードを引く」なのかを知らない。CardEffectとしてApplyを呼ぶだけで、それぞれの効果が自分の仕事をしてくれる
- UseCardのあとにApply
- 先に手札から捨て札へ送ってから効果を出す。「付せんメモ」で2枚引くとき、使った付せんメモが手札に残っていると、引いた数がずれてしまうため
StageのPlay Zoneに、EnergyとPlayerを入れます。
- Energy:Hud>Energy、Player:PlayerArea
カードのEffectsが空のままだと、エネルギーは減るのに何も起きません。「使ったのに効果が出ない」ときは、まずそのカードのEffectsに効果のアセットが入っているかを確かめましょう。
使えないカードを暗くしよう
エネルギーが足りないカードは、見ただけで分かるように暗くします。#3のCardViewに足します。
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// カードのデータ(CardData)を、見た目に反映する
public class CardView : MonoBehaviour
{
[SerializeField] private CardData cardData; // 表示するカード
[Header("表示する部品")]
[SerializeField] private Image frameImage;
[SerializeField] private Image artworkImage;
[SerializeField] private TextMeshProUGUI nameText;
[SerializeField] private TextMeshProUGUI costText;
[SerializeField] private TextMeshProUGUI descriptionText;
[Header("種類ごとの枠の色")]
[SerializeField] private Color attackColor = new Color(0.91f, 0.42f, 0.36f); // 攻撃:赤
[SerializeField] private Color defenseColor = new Color(0.36f, 0.61f, 0.88f); // 防御:青
[SerializeField] private Color healColor = new Color(0.42f, 0.77f, 0.48f); // 回復:緑
[SerializeField] private Color specialColor = new Color(0.65f, 0.48f, 0.84f); // 特殊:紫
private CanvasGroup canvasGroup; // ★#7 暗くするのに使う
private Energy energy; // ★#7 エネルギーが足りるか見る
public CardData Data => cardData;
private void Awake()
{
canvasGroup = GetComponent<CanvasGroup>(); // ★#7
}
private void Start()
{
energy = FindFirstObjectByType<Energy>(); // ★#7
// Inspector でデータを入れておいたときは、そのカードを表示する
if (cardData != null)
{
Setup(cardData);
}
}
// ★#7 エネルギーが足りないカードは、半分すける
private void Update()
{
if (canvasGroup == null || energy == null || cardData == null)
{
return;
}
canvasGroup.alpha = energy.CanPay(cardData.cost) ? 1f : 0.5f;
}
// データを受け取って、見た目を書き換える
public void Setup(CardData data)
{
cardData = data;
nameText.text = data.cardName;
costText.text = data.cost.ToString();
descriptionText.text = data.description;
artworkImage.sprite = data.artwork;
frameImage.color = FrameColor(data.cardType);
}
// 種類ごとの枠の色
private Color FrameColor(CardType type)
{
switch (type)
{
case CardType.Attack:
return attackColor;
case CardType.Defense:
return defenseColor;
case CardType.Heal:
return healColor;
case CardType.Special:
return specialColor;
default:
return Color.gray;
}
}
}
- canvasGroup.alpha
- Canvas Groupのアルファは、子のImageやテキストも全部まとめてすけさせる。カードのプレハブには、#6でCardDragを付けたときにCanvas Groupが付いています
- FindFirstObjectByType
() - シーンの中からEnergyを探す。カードはスクリプトから作るプレハブなので、Inspectorでシーンのものを入れておけないため、Startで探しています
動かして確認!
新しいカードは、まだデッキに入っていません(報酬でもらえるのは#12から)。確かめるときは、DeckのStarting Deckに1枚ずつ足してみましょう。
- 例:Starting Deckを、Ruler・PencilBoard・Bandage・Scissors・Compass・Stapler・PencilCase・StickyNote・ColorPencilsの1枚ずつ(9行・Countは全部1)にする
- 確かめ終わったら、元の3行(Ruler 5・PencilBoard 4・Bandage 1)に戻す




#7の動作確認
- 再生すると、エネルギーが3/3、スライムちゃんがHP 60/60、ブリキロボがHP 28/28
- カードを使うと、コストの分だけエネルギーが減る
- エネルギーが足りないカードは暗くなり、使おうとするとエネルギーの丸が震えて、手札に戻る
- 攻撃のカードで敵のHPが減る。ハサミでチョキチョキは2回に分けて減る
- 下じきガードで「防御 5」が付き、ばんそうこうでHPが回復する(最大HPはこえない)
- 付せんメモで2枚引ける、色えんぴつセットでエネルギーが1増える
- ターン終了で、エネルギーが3に戻る
今はまだ、防御がターンをまたいで残ります。「ターンの始めに防御を0に戻す」のは、ターンの流れを作る#9で入れます。
あとがき
今回は、エネルギーと、カードの効果を作りました。効果を「共通の形+中身のクラス」に分けておいたので、カードが増えても、PlayZoneは「Applyを呼ぶ」だけのままです。次回は、敵のHPのゲージと、敵が次にすることを予告する表示を作りたいと思います!
コメント
コメント一覧 (1件)
[…] 前回:#7 エネルギーとカードの効果を作ろう […]